Skip to content

Commit 77e0bd3

Browse files
committed
Restore Gemini thinker for Kimi freebuff
1 parent f85cf87 commit 77e0bd3

16 files changed

Lines changed: 480 additions & 187 deletions

File tree

agents/base2/base2.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
import { buildArray } from '@codebuff/common/util/array'
2+
import { FREEBUFF_KIMI_MODEL_ID } from '@codebuff/common/constants/freebuff-models'
3+
import {
4+
FREEBUFF_GEMINI_THINKER_AGENT_ID,
5+
FREEBUFF_GEMINI_THINKER_INSTRUCTIONS_PROMPT,
6+
FREEBUFF_GEMINI_THINKER_STEP_PROMPT,
7+
FREEBUFF_GEMINI_THINKER_SYSTEM_INSTRUCTION,
8+
} from '@codebuff/common/constants/freebuff-gemini-thinker'
29

310
import { publisher } from '../constants'
411
import {
@@ -32,6 +39,7 @@ export function createBase2(
3239
const model =
3340
modelOverride ??
3441
(isFree ? 'moonshotai/kimi-k2.6' : 'anthropic/claude-opus-4.7')
42+
const hasFreeGeminiThinker = isFree && model === FREEBUFF_KIMI_MODEL_ID
3543
const defaultProviderOptions = isFree
3644
? {
3745
data_collection: 'deny' as const,
@@ -97,6 +105,7 @@ export function createBase2(
97105
isFree && 'code-reviewer-lite',
98106
isDefault && 'code-reviewer',
99107
isMax && 'code-reviewer-multi-prompt',
108+
hasFreeGeminiThinker && FREEBUFF_GEMINI_THINKER_AGENT_ID,
100109
'thinker-gpt',
101110
'context-pruner',
102111
),
@@ -154,6 +163,7 @@ Use the spawn_agents tool to spawn specialized agents to help you complete the u
154163
'- Spawn context-gathering agents (file pickers, code searchers, and web/docs researchers) before making edits. Use the list_directory and glob tools directly for searching and exploring the codebase.',
155164
isFree &&
156165
'Do not spawn the thinker-gpt agent, unless the user asks. Not everyone has connected their ChatGPT subscription to Codebuff to allow for it.',
166+
hasFreeGeminiThinker && FREEBUFF_GEMINI_THINKER_SYSTEM_INSTRUCTION,
157167
isDefault &&
158168
'- Spawn the editor agent to implement the changes after you have gathered all the context you need.',
159169
(isDefault || isMax) &&
@@ -280,6 +290,7 @@ ${PLACEHOLDER.GIT_CHANGES_PROMPT}
280290
isDefault,
281291
isMax,
282292
isFree,
293+
hasFreeGeminiThinker,
283294
hasNoValidation,
284295
noAskUser,
285296
}),
@@ -292,6 +303,7 @@ ${PLACEHOLDER.GIT_CHANGES_PROMPT}
292303
hasNoValidation,
293304
isSonnet,
294305
isFree,
306+
hasFreeGeminiThinker,
295307
noAskUser,
296308
}),
297309

@@ -340,6 +352,7 @@ function buildImplementationInstructionsPrompt({
340352
isDefault,
341353
isMax,
342354
isFree,
355+
hasFreeGeminiThinker,
343356
hasNoValidation,
344357
noAskUser,
345358
}: {
@@ -348,6 +361,7 @@ function buildImplementationInstructionsPrompt({
348361
isDefault: boolean
349362
isMax: boolean
350363
isFree: boolean
364+
hasFreeGeminiThinker: boolean
351365
hasNoValidation: boolean
352366
noAskUser: boolean
353367
}) {
@@ -365,6 +379,7 @@ ${buildArray(
365379
'After getting context on the user request from the codebase or from research, use the ask_user tool to ask the user for important clarifications on their request or alternate implementation strategies. You should skip this step if the choice is obvious -- only ask the user if you need their help making the best choice.',
366380
(isDefault || isMax || isFree) &&
367381
`- For any task requiring 3+ steps, use the write_todos tool to write out your step-by-step implementation plan. Include ALL of the applicable tasks in the list.${isFast ? '' : ' You should include a step to review the changes after you have implemented the changes.'}:${hasNoValidation ? '' : ' You should include at least one step to validate/test your changes: be specific about whether to typecheck, run tests, run lints, etc.'} You may be able to do reviewing and validation in parallel in the same step. Skip write_todos for simple tasks like quick edits or answering questions.`,
382+
hasFreeGeminiThinker && FREEBUFF_GEMINI_THINKER_INSTRUCTIONS_PROMPT,
368383
(isDefault || isMax) &&
369384
`- For quick problems, briefly explain your reasoning to the user. If you need to think longer, write your thoughts within the <think> tags. Finally, for complex problems, spawn the thinker agent to help find the best solution. (gpt-5-agent is a last resort for complex problems)`,
370385
isDefault &&
@@ -395,6 +410,7 @@ function buildImplementationStepPrompt({
395410
hasNoValidation,
396411
isSonnet,
397412
isFree,
413+
hasFreeGeminiThinker,
398414
noAskUser,
399415
}: {
400416
isDefault: boolean
@@ -403,12 +419,14 @@ function buildImplementationStepPrompt({
403419
hasNoValidation: boolean
404420
isSonnet: boolean
405421
isFree: boolean
422+
hasFreeGeminiThinker: boolean
406423
noAskUser: boolean
407424
}) {
408425
return buildArray(
409426
isMax &&
410427
`Keep working until the user's request is completely satisfied${!hasNoValidation ? ' and validated' : ''}, or until you require more information from the user.`,
411428
'Consider loading relevant skills with the skill tool if they might help with the current task. Do not reload skills that were already loaded earlier in this conversation.',
429+
hasFreeGeminiThinker && FREEBUFF_GEMINI_THINKER_STEP_PROMPT,
412430
isMax &&
413431
`You must spawn the 'editor-multi-prompt' agent to implement code changes rather than using the str_replace or write_file tools, since it will generate the best code changes.`,
414432
(isDefault || isMax) &&

cli/src/__tests__/integration/local-agents.test.ts

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ import path from 'path'
44

55
import { validateAgents } from '@codebuff/sdk'
66
import {
7-
describe,
8-
test,
9-
expect,
10-
beforeEach,
11-
afterEach,
12-
mock,
13-
} from 'bun:test'
7+
FREEBUFF_GEMINI_THINKER_AGENT_ID,
8+
FREEBUFF_GEMINI_THINKER_INSTRUCTIONS_PROMPT,
9+
FREEBUFF_GEMINI_THINKER_STEP_PROMPT,
10+
FREEBUFF_GEMINI_THINKER_SYSTEM_INSTRUCTION,
11+
} from '@codebuff/common/constants/freebuff-gemini-thinker'
12+
import {
13+
FREEBUFF_KIMI_MODEL_ID,
14+
FREEBUFF_MINIMAX_MODEL_ID,
15+
} from '@codebuff/common/constants/freebuff-models'
16+
import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'
1417

1518
// Mock the logger to prevent analytics initialization errors in tests
1619
mock.module('../../utils/logger', () => ({
@@ -27,6 +30,7 @@ import { setProjectRoot, getProjectRoot } from '../../project-files'
2730
import {
2831
loadAgentDefinitions,
2932
loadLocalAgents,
33+
configureFreebuffBaseAgentForModel,
3034
initializeAgentRegistry,
3135
findAgentsDirectory,
3236
getLoadedAgentsData,
@@ -37,6 +41,67 @@ import {
3741

3842
const MODEL_NAME = 'anthropic/claude-sonnet-4'
3943

44+
describe('configureFreebuffBaseAgentForModel', () => {
45+
const makeBase2Free = () => ({
46+
id: 'base2-free',
47+
spawnableAgents: ['file-picker', FREEBUFF_GEMINI_THINKER_AGENT_ID],
48+
systemPrompt: [
49+
'before',
50+
FREEBUFF_GEMINI_THINKER_SYSTEM_INSTRUCTION,
51+
'after',
52+
].join('\n'),
53+
instructionsPrompt: [
54+
'before',
55+
FREEBUFF_GEMINI_THINKER_INSTRUCTIONS_PROMPT,
56+
'after',
57+
].join('\n'),
58+
stepPrompt: ['before', FREEBUFF_GEMINI_THINKER_STEP_PROMPT, 'after'].join(
59+
'\n',
60+
),
61+
})
62+
63+
test('keeps the Gemini thinker and prompt guidance for Kimi', () => {
64+
const definition = makeBase2Free()
65+
66+
configureFreebuffBaseAgentForModel(definition, FREEBUFF_KIMI_MODEL_ID)
67+
68+
expect(definition.spawnableAgents).toContain(
69+
FREEBUFF_GEMINI_THINKER_AGENT_ID,
70+
)
71+
expect(definition.systemPrompt).toContain(
72+
FREEBUFF_GEMINI_THINKER_SYSTEM_INSTRUCTION,
73+
)
74+
expect(definition.instructionsPrompt).toContain(
75+
FREEBUFF_GEMINI_THINKER_INSTRUCTIONS_PROMPT,
76+
)
77+
expect(definition.stepPrompt).toContain(FREEBUFF_GEMINI_THINKER_STEP_PROMPT)
78+
})
79+
80+
test('removes only exact Gemini thinker prompt guidance for MiniMax', () => {
81+
const definition = makeBase2Free()
82+
definition.systemPrompt +=
83+
'\nUser text mentioning thinker-with-files-gemini should stay.'
84+
85+
configureFreebuffBaseAgentForModel(definition, FREEBUFF_MINIMAX_MODEL_ID)
86+
87+
expect(definition.spawnableAgents).not.toContain(
88+
FREEBUFF_GEMINI_THINKER_AGENT_ID,
89+
)
90+
expect(definition.systemPrompt).not.toContain(
91+
FREEBUFF_GEMINI_THINKER_SYSTEM_INSTRUCTION,
92+
)
93+
expect(definition.instructionsPrompt).not.toContain(
94+
FREEBUFF_GEMINI_THINKER_INSTRUCTIONS_PROMPT,
95+
)
96+
expect(definition.stepPrompt).not.toContain(
97+
FREEBUFF_GEMINI_THINKER_STEP_PROMPT,
98+
)
99+
expect(definition.systemPrompt).toContain(
100+
'User text mentioning thinker-with-files-gemini should stay.',
101+
)
102+
})
103+
})
104+
40105
const writeAgentFile = (
41106
agentsDir: string,
42107
fileName: string,
@@ -408,7 +473,9 @@ describe('Local Agent Integration', () => {
408473
expect(uiAgent!.id).toBe('test-ui-agent')
409474
// File path should be populated for "Open file" UI links
410475
// Use realpathSync to normalize paths (on macOS, /var is a symlink to /private/var)
411-
expect(realpathSync(uiAgent!.filePath!)).toBe(realpathSync(path.join(agentsDir, 'ui-agent.ts')))
476+
expect(realpathSync(uiAgent!.filePath!)).toBe(
477+
realpathSync(path.join(agentsDir, 'ui-agent.ts')),
478+
)
412479
})
413480

414481
test('loadLocalAgents sorts agents alphabetically by displayName', async () => {
@@ -735,7 +802,9 @@ describe('Local Agent Integration', () => {
735802
const data = getLoadedAgentsData()
736803
expect(data).not.toBeNull()
737804
expect(data!.agents.some((a) => a.id === 'test-announce-agent')).toBe(true)
738-
expect(data!.agents.some((a) => a.displayName === 'Announce Test Agent')).toBe(true)
805+
expect(
806+
data!.agents.some((a) => a.displayName === 'Announce Test Agent'),
807+
).toBe(true)
739808
})
740809

741810
// ============================================================================

cli/src/components/freebuff-model-selector.tsx

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'
55
import { Button } from './button'
66
import {
77
FALLBACK_FREEBUFF_MODEL_ID,
8-
FREEBUFF_GEMINI_PRO_MODEL_ID,
98
FREEBUFF_KIMI_MODEL_ID,
109
FREEBUFF_MODELS,
1110
getFreebuffDeploymentAvailabilityLabel,
@@ -23,15 +22,8 @@ import { nextFreebuffModelId } from '../utils/freebuff-model-navigation'
2322
import type { KeyEvent } from '@opentui/core'
2423

2524
const FREEBUFF_MODEL_SELECTOR_MODELS = [
26-
...FREEBUFF_MODELS.filter(
27-
(model) => model.id === FREEBUFF_GEMINI_PRO_MODEL_ID,
28-
),
2925
...FREEBUFF_MODELS.filter((model) => model.id === FREEBUFF_KIMI_MODEL_ID),
30-
...FREEBUFF_MODELS.filter(
31-
(model) =>
32-
model.id !== FREEBUFF_GEMINI_PRO_MODEL_ID &&
33-
model.id !== FREEBUFF_KIMI_MODEL_ID,
34-
),
26+
...FREEBUFF_MODELS.filter((model) => model.id !== FREEBUFF_KIMI_MODEL_ID),
3527
]
3628

3729
/**
@@ -121,13 +113,7 @@ export const FreebuffModelSelector: React.FC = () => {
121113
// when the user's selection moves between queues. The tagline is shown
122114
// inline with the name now, so it's no longer part of this slot.
123115
const hintWidth = useMemo(
124-
() =>
125-
Math.max(
126-
'No wait'.length,
127-
'999 ahead'.length,
128-
'Used today'.length,
129-
'Limit used'.length,
130-
),
116+
() => Math.max('No wait'.length, '999 ahead'.length, 'Limit used'.length),
131117
[],
132118
)
133119

@@ -267,9 +253,7 @@ export const FreebuffModelSelector: React.FC = () => {
267253
const hint = !isAvailable
268254
? 'Closed'
269255
: isQuotaExhausted
270-
? model.id === FREEBUFF_GEMINI_PRO_MODEL_ID
271-
? 'Used today'
272-
: 'Limit used'
256+
? 'Limit used'
273257
: ahead === undefined
274258
? ''
275259
: ahead === 0

cli/src/hooks/use-send-message.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import { createStreamController } from './stream-state'
55
import { useChatStore } from '../state/chat-store'
66
import { getFreebuffInstanceId } from './use-freebuff-session'
77
import { getCodebuffClient } from '../utils/codebuff-client'
8-
import { AGENT_MODE_TO_ID, AGENT_MODE_TO_COST_MODE, IS_FREEBUFF } from '../utils/constants'
8+
import {
9+
AGENT_MODE_TO_ID,
10+
AGENT_MODE_TO_COST_MODE,
11+
IS_FREEBUFF,
12+
} from '../utils/constants'
913
import { createEventHandlerState } from '../utils/create-event-handler-state'
1014
import { createRunConfig } from '../utils/create-run-config'
1115
import { loadAgentDefinitions } from '../utils/local-agent-registry'
@@ -108,7 +112,7 @@ export const useSendMessage = ({
108112
onBeforeMessageSend,
109113
mainAgentTimer,
110114
scrollToLatest,
111-
onTimerEvent = () => { },
115+
onTimerEvent = () => {},
112116
isQueuePausedRef,
113117
isProcessingQueueRef,
114118
resumeQueue,
@@ -295,13 +299,13 @@ export const useSendMessage = ({
295299
const errorsToAttach =
296300
validationResult.errors.length === 0
297301
? [
298-
// Hide this for now, as validate endpoint may be flaky and we don't want to bother users.
299-
// {
300-
// id: NETWORK_ERROR_ID,
301-
// message:
302-
// 'Agent validation failed. This may be due to a network issue or temporary server problem. Please try again.',
303-
// },
304-
]
302+
// Hide this for now, as validate endpoint may be flaky and we don't want to bother users.
303+
// {
304+
// id: NETWORK_ERROR_ID,
305+
// message:
306+
// 'Agent validation failed. This may be due to a network issue or temporary server problem. Please try again.',
307+
// },
308+
]
305309
: validationResult.errors
306310

307311
setMessages((prev) =>
@@ -457,12 +461,16 @@ export const useSendMessage = ({
457461
eventHandlerState,
458462
signal: abortController.signal,
459463
costMode: AGENT_MODE_TO_COST_MODE[agentMode],
460-
extraCodebuffMetadata: freebuffInstanceId
461-
? { freebuff_instance_id: freebuffInstanceId }
462-
: undefined,
464+
extraCodebuffMetadata:
465+
IS_FREEBUFF && freebuffInstanceId
466+
? { freebuff_instance_id: freebuffInstanceId }
467+
: undefined,
463468
})
464469

465-
logger.info({ runConfig }, '[send-message] Sending message with sdk run config')
470+
logger.info(
471+
{ runConfig },
472+
'[send-message] Sending message with sdk run config',
473+
)
466474
const runState = await client.run(runConfig)
467475

468476
// Finalize: persist state and mark complete

0 commit comments

Comments
 (0)