Skip to content

Commit 5414a55

Browse files
committed
Configure freebuff agents by model
1 parent e7de867 commit 5414a55

18 files changed

Lines changed: 204 additions & 229 deletions

agents/base2/base2-free-deepseek-v4.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
import { createBase2 } from './base2'
1+
import base2FreeDeepseek from './base2-free-deepseek'
22

33
const definition = {
4-
...createBase2('free', {
5-
noAskUser: true,
6-
model: 'deepseek/deepseek-v4-pro',
7-
}),
4+
...base2FreeDeepseek,
85
id: 'base2-free-deepseek-v4',
96
displayName: 'Buffy the DeepSeek V4 Free Orchestrator',
107
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID } from '@codebuff/common/constants/freebuff-models'
2+
3+
import { createBase2 } from './base2'
4+
5+
const definition = {
6+
...createBase2('free', {
7+
noAskUser: true,
8+
model: FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
9+
freeCodeReviewerAgentId: 'code-reviewer-deepseek',
10+
}),
11+
id: 'base2-free-deepseek',
12+
displayName: 'Buffy the DeepSeek Free Orchestrator',
13+
}
14+
15+
export default definition

agents/base2/base2-free-kimi.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { FREEBUFF_KIMI_MODEL_ID } from '@codebuff/common/constants/freebuff-models'
2+
3+
import { createBase2 } from './base2'
4+
5+
const definition = {
6+
...createBase2('free', {
7+
model: FREEBUFF_KIMI_MODEL_ID,
8+
freeCodeReviewerAgentId: 'code-reviewer-kimi',
9+
}),
10+
id: 'base2-free-kimi',
11+
displayName: 'Buffy the Kimi Free Orchestrator',
12+
}
13+
14+
export default definition

agents/base2/base2-free.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { createBase2 } from './base2'
22

33
const definition = {
4-
...createBase2('free'),
4+
...createBase2('free', {
5+
freeCodeReviewerAgentId: 'code-reviewer-lite',
6+
}),
57
id: 'base2-free',
68
displayName: 'Buffy the Free Orchestrator',
79
}

agents/base2/base2.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import {
55
FREEBUFF_GEMINI_THINKER_STEP_PROMPT,
66
FREEBUFF_GEMINI_THINKER_SYSTEM_INSTRUCTION,
77
} from '@codebuff/common/constants/freebuff-gemini-thinker'
8+
import {
9+
canFreebuffModelSpawnGeminiThinker,
10+
FREEBUFF_MINIMAX_MODEL_ID,
11+
} from '@codebuff/common/constants/freebuff-models'
812

913
import { publisher } from '../constants'
1014
import {
@@ -20,6 +24,7 @@ export function createBase2(
2024
noAskUser?: boolean
2125
model?: SecretAgentDefinition['model']
2226
providerOptions?: SecretAgentDefinition['providerOptions']
27+
freeCodeReviewerAgentId?: string
2328
},
2429
): Omit<SecretAgentDefinition, 'id'> {
2530
const {
@@ -28,6 +33,7 @@ export function createBase2(
2833
noAskUser = false,
2934
model: modelOverride,
3035
providerOptions,
36+
freeCodeReviewerAgentId = 'code-reviewer-lite',
3137
} = options ?? {}
3238
const isDefault = mode === 'default'
3339
const isFast = mode === 'fast'
@@ -38,20 +44,18 @@ export function createBase2(
3844
// Lite (paid Codebuff) defaults to Kimi: no data-retention surface in the
3945
// CLI today, so we don't want to silently route Codebuff prompts through a
4046
// model whose provider trains on user data. Free (freebuff) defaults to
41-
// DeepSeek and surfaces the data-collection caveat in the picker; the CLI
42-
// overrides the model anyway based on the user's freebuff selection.
47+
// MiniMax M2.7; Kimi and DeepSeek are separate free agent variants.
4348
const model =
4449
modelOverride ??
4550
(mode === 'lite'
4651
? 'moonshotai/kimi-k2.6'
4752
: mode === 'free'
48-
? 'deepseek/deepseek-v4-pro'
53+
? FREEBUFF_MINIMAX_MODEL_ID
4954
: 'anthropic/claude-opus-4.7')
50-
// Bundled free-mode definitions ship with the gemini-thinker spawnable +
51-
// prompts; the CLI strips them at runtime if the user picks a fast model
52-
// that doesn't benefit (e.g. MiniMax). Smart freebuff models (Kimi,
53-
// DeepSeek) keep it so they can offload deeper reasoning.
54-
const hasFreeGeminiThinker = isFree
55+
// Smart freebuff model variants (Kimi, DeepSeek) can offload deeper
56+
// reasoning. Fast MiniMax omits the extra round trip by construction.
57+
const hasFreeGeminiThinker =
58+
isFree && canFreebuffModelSpawnGeminiThinker(model)
5559
const defaultProviderOptions = isFree
5660
? {
5761
data_collection: 'deny' as const,
@@ -114,7 +118,7 @@ export function createBase2(
114118
isMax && 'editor-multi-prompt',
115119
'tmux-cli',
116120
'browser-use',
117-
isFree && 'code-reviewer-lite',
121+
isFree && freeCodeReviewerAgentId,
118122
isDefault && 'code-reviewer',
119123
isMax && 'code-reviewer-multi-prompt',
120124
hasFreeGeminiThinker && FREEBUFF_GEMINI_THINKER_AGENT_ID,
@@ -183,7 +187,7 @@ Use the spawn_agents tool to spawn specialized agents to help you complete the u
183187
isMax &&
184188
`- IMPORTANT: You must spawn the editor-multi-prompt agent to implement the changes after you have gathered all the context you need. You must spawn this agent for non-trivial changes, since it writes much better code than you would with the str_replace or write_file tools. Don't spawn the editor in parallel with context-gathering agents.`,
185189
isFree &&
186-
'- Spawn a code-reviewer-lite to review the changes after you have implemented the changes.',
190+
`- Spawn a ${freeCodeReviewerAgentId} to review the changes after you have implemented the changes.`,
187191
'- Spawn bashers sequentially if the second command depends on the the first.',
188192
isDefault &&
189193
'- Spawn a code-reviewer to review the changes after you have implemented the changes.',
@@ -252,7 +256,7 @@ ${
252256
isDefault
253257
? `[ You spawn a code-reviewer, a basher to typecheck the changes, and another basher to run tests, all in parallel ]`
254258
: isFree
255-
? `[ You spawn a code-reviewer-lite to review the changes, a basher to typecheck the local changes, a basher to typecheck the whole project, and another basher to run tests, all in parallel ]`
259+
? `[ You spawn a ${freeCodeReviewerAgentId} to review the changes, a basher to typecheck the local changes, a basher to typecheck the whole project, and another basher to run tests, all in parallel ]`
256260
: isMax
257261
? `[ You spawn a basher to typecheck the changes, and another basher to run tests, in parallel. Then, you spawn a code-reviewer-multi-prompt to review the changes. ]`
258262
: '[ You spawn a basher to typecheck the changes and another basher to run tests, all in parallel ]'
@@ -262,7 +266,7 @@ ${
262266
isDefault
263267
? `[ You fix the issues found by the code-reviewer and type/test errors ]`
264268
: isFree
265-
? `[ You fix the issues found by the code-reviewer-lite and type/test errors ]`
269+
? `[ You fix the issues found by the ${freeCodeReviewerAgentId} and type/test errors ]`
266270
: isMax
267271
? `[ You fix the issues found by the code-reviewer-multi-prompt and type/test errors ]`
268272
: '[ You fix the issues found by the type/test errors and spawn more bashers to confirm ]'
@@ -305,6 +309,7 @@ ${PLACEHOLDER.GIT_CHANGES_PROMPT}
305309
hasFreeGeminiThinker,
306310
hasNoValidation,
307311
noAskUser,
312+
freeCodeReviewerAgentId,
308313
}),
309314
stepPrompt: planOnly
310315
? buildPlanOnlyStepPrompt({})
@@ -317,6 +322,7 @@ ${PLACEHOLDER.GIT_CHANGES_PROMPT}
317322
isFree,
318323
hasFreeGeminiThinker,
319324
noAskUser,
325+
freeCodeReviewerAgentId,
320326
}),
321327

322328
// handleSteps is serialized via .toString() and re-eval'd, so closure
@@ -367,6 +373,7 @@ function buildImplementationInstructionsPrompt({
367373
hasFreeGeminiThinker,
368374
hasNoValidation,
369375
noAskUser,
376+
freeCodeReviewerAgentId,
370377
}: {
371378
isSonnet: boolean
372379
isFast: boolean
@@ -376,6 +383,7 @@ function buildImplementationInstructionsPrompt({
376383
hasFreeGeminiThinker: boolean
377384
hasNoValidation: boolean
378385
noAskUser: boolean
386+
freeCodeReviewerAgentId: string
379387
}) {
380388
return `Act as a helpful assistant and freely respond to the user's request however would be most helpful to the user. Use your judgement to orchestrate the completion of the user's request using your specialized sub-agents and tools as needed. Take your time and be comprehensive. Don't surprise the user. For example, don't modify files if the user has not asked you to do so at least implicitly.
381389
@@ -407,7 +415,7 @@ ${buildArray(
407415
(isDefault || isMax) &&
408416
`- Spawn a ${isDefault ? 'code-reviewer' : 'code-reviewer-multi-prompt'} to review the changes after you have implemented changes. (Skip this step only if the change is extremely straightforward and obvious.)`,
409417
isFree &&
410-
`- Spawn a code-reviewer-lite to review the changes after you have implemented changes. (Skip this step only if the change is extremely straightforward and obvious.)`,
418+
`- Spawn a ${freeCodeReviewerAgentId} to review the changes after you have implemented changes. (Skip this step only if the change is extremely straightforward and obvious.)`,
411419
`- Inform the user that you have completed the task in one sentence or a few short bullet points.${isSonnet ? " Don't create any markdown summary files or example documentation files, unless asked by the user." : ''}`,
412420
!isFast &&
413421
!noAskUser &&
@@ -424,6 +432,7 @@ function buildImplementationStepPrompt({
424432
isFree,
425433
hasFreeGeminiThinker,
426434
noAskUser,
435+
freeCodeReviewerAgentId,
427436
}: {
428437
isDefault: boolean
429438
isFast: boolean
@@ -433,6 +442,7 @@ function buildImplementationStepPrompt({
433442
isFree: boolean
434443
hasFreeGeminiThinker: boolean
435444
noAskUser: boolean
445+
freeCodeReviewerAgentId: string
436446
}) {
437447
return buildArray(
438448
isMax &&
@@ -444,7 +454,7 @@ function buildImplementationStepPrompt({
444454
(isDefault || isMax) &&
445455
`You must spawn a ${isDefault ? 'code-reviewer' : 'code-reviewer-multi-prompt'} to review the changes after you have implemented the changes and in parallel with typechecking or testing.`,
446456
isFree &&
447-
`You must spawn a code-reviewer-lite to review the changes after you have implemented the changes and in parallel with typechecking or testing.`,
457+
`You must spawn a ${freeCodeReviewerAgentId} to review the changes after you have implemented the changes and in parallel with typechecking or testing.`,
448458
`After completing the user request, summarize your changes in a sentence${isFast ? '' : ' or a few short bullet points'}.${isSonnet ? " Don't create any summary markdown files or example documentation files, unless asked by the user." : ''}.`,
449459
!isFast &&
450460
!noAskUser &&
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { publisher } from '../constants'
2+
import type { SecretAgentDefinition } from '../types/secret-agent-definition'
3+
import { createReviewer } from './code-reviewer'
4+
5+
const definition: SecretAgentDefinition = {
6+
id: 'code-reviewer-deepseek',
7+
publisher,
8+
...createReviewer('deepseek/deepseek-v4-pro'),
9+
}
10+
11+
export default definition
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { publisher } from '../constants'
2+
import type { SecretAgentDefinition } from '../types/secret-agent-definition'
3+
import { createReviewer } from './code-reviewer'
4+
5+
const definition: SecretAgentDefinition = {
6+
id: 'code-reviewer-kimi',
7+
publisher,
8+
...createReviewer('moonshotai/kimi-k2.6'),
9+
}
10+
11+
export default definition

agents/reviewer/code-reviewer-lite.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { createReviewer } from './code-reviewer'
55
const definition: SecretAgentDefinition = {
66
id: 'code-reviewer-lite',
77
publisher,
8-
...createReviewer('moonshotai/kimi-k2.6'),
8+
...createReviewer('minimax/minimax-m2.7'),
99
}
1010

1111
export default definition

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

Lines changed: 0 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,6 @@ import os from 'os'
33
import path from 'path'
44

55
import { validateAgents } from '@codebuff/sdk'
6-
import {
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_DEEPSEEK_V4_PRO_MODEL_ID,
14-
FREEBUFF_KIMI_MODEL_ID,
15-
FREEBUFF_MINIMAX_MODEL_ID,
16-
} from '@codebuff/common/constants/freebuff-models'
176
import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'
187

198
// Mock the logger to prevent analytics initialization errors in tests
@@ -31,7 +20,6 @@ import { setProjectRoot, getProjectRoot } from '../../project-files'
3120
import {
3221
loadAgentDefinitions,
3322
loadLocalAgents,
34-
configureFreebuffBaseAgentForModel,
3523
initializeAgentRegistry,
3624
findAgentsDirectory,
3725
getLoadedAgentsData,
@@ -42,87 +30,6 @@ import {
4230

4331
const MODEL_NAME = 'anthropic/claude-sonnet-4'
4432

45-
describe('configureFreebuffBaseAgentForModel', () => {
46-
const makeBase2Free = () => ({
47-
id: 'base2-free',
48-
spawnableAgents: ['file-picker', FREEBUFF_GEMINI_THINKER_AGENT_ID],
49-
systemPrompt: [
50-
'before',
51-
FREEBUFF_GEMINI_THINKER_SYSTEM_INSTRUCTION,
52-
'after',
53-
].join('\n'),
54-
instructionsPrompt: [
55-
'before',
56-
FREEBUFF_GEMINI_THINKER_INSTRUCTIONS_PROMPT,
57-
'after',
58-
].join('\n'),
59-
stepPrompt: ['before', FREEBUFF_GEMINI_THINKER_STEP_PROMPT, 'after'].join(
60-
'\n',
61-
),
62-
})
63-
64-
test('keeps the Gemini thinker and prompt guidance for Kimi', () => {
65-
const definition = makeBase2Free()
66-
67-
configureFreebuffBaseAgentForModel(definition, FREEBUFF_KIMI_MODEL_ID)
68-
69-
expect(definition.spawnableAgents).toContain(
70-
FREEBUFF_GEMINI_THINKER_AGENT_ID,
71-
)
72-
expect(definition.systemPrompt).toContain(
73-
FREEBUFF_GEMINI_THINKER_SYSTEM_INSTRUCTION,
74-
)
75-
expect(definition.instructionsPrompt).toContain(
76-
FREEBUFF_GEMINI_THINKER_INSTRUCTIONS_PROMPT,
77-
)
78-
expect(definition.stepPrompt).toContain(FREEBUFF_GEMINI_THINKER_STEP_PROMPT)
79-
})
80-
81-
test('keeps the Gemini thinker and prompt guidance for DeepSeek', () => {
82-
const definition = makeBase2Free()
83-
84-
configureFreebuffBaseAgentForModel(
85-
definition,
86-
FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
87-
)
88-
89-
expect(definition.spawnableAgents).toContain(
90-
FREEBUFF_GEMINI_THINKER_AGENT_ID,
91-
)
92-
expect(definition.systemPrompt).toContain(
93-
FREEBUFF_GEMINI_THINKER_SYSTEM_INSTRUCTION,
94-
)
95-
expect(definition.instructionsPrompt).toContain(
96-
FREEBUFF_GEMINI_THINKER_INSTRUCTIONS_PROMPT,
97-
)
98-
expect(definition.stepPrompt).toContain(FREEBUFF_GEMINI_THINKER_STEP_PROMPT)
99-
})
100-
101-
test('removes only exact Gemini thinker prompt guidance for MiniMax', () => {
102-
const definition = makeBase2Free()
103-
definition.systemPrompt +=
104-
'\nUser text mentioning thinker-with-files-gemini should stay.'
105-
106-
configureFreebuffBaseAgentForModel(definition, FREEBUFF_MINIMAX_MODEL_ID)
107-
108-
expect(definition.spawnableAgents).not.toContain(
109-
FREEBUFF_GEMINI_THINKER_AGENT_ID,
110-
)
111-
expect(definition.systemPrompt).not.toContain(
112-
FREEBUFF_GEMINI_THINKER_SYSTEM_INSTRUCTION,
113-
)
114-
expect(definition.instructionsPrompt).not.toContain(
115-
FREEBUFF_GEMINI_THINKER_INSTRUCTIONS_PROMPT,
116-
)
117-
expect(definition.stepPrompt).not.toContain(
118-
FREEBUFF_GEMINI_THINKER_STEP_PROMPT,
119-
)
120-
expect(definition.systemPrompt).toContain(
121-
'User text mentioning thinker-with-files-gemini should stay.',
122-
)
123-
})
124-
})
125-
12633
const writeAgentFile = (
12734
agentsDir: string,
12835
fileName: string,

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@ 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 {
9-
AGENT_MODE_TO_ID,
10-
AGENT_MODE_TO_COST_MODE,
11-
IS_FREEBUFF,
12-
} from '../utils/constants'
8+
import { AGENT_MODE_TO_COST_MODE, IS_FREEBUFF } from '../utils/constants'
139
import { createEventHandlerState } from '../utils/create-event-handler-state'
1410
import { createRunConfig } from '../utils/create-run-config'
11+
import { getAgentIdForMode } from '../utils/freebuff-agent-selection'
1512
import { loadAgentDefinitions } from '../utils/local-agent-registry'
1613
import { logger } from '../utils/logger'
1714
import {
@@ -81,7 +78,7 @@ const resolveAgent = (
8178
? agentDefinitions.find((definition) => definition.id === agentId)
8279
: undefined
8380

84-
return selectedAgentDefinition ?? agentId ?? AGENT_MODE_TO_ID[agentMode]
81+
return selectedAgentDefinition ?? agentId ?? getAgentIdForMode(agentMode)
8582
}
8683

8784
// Respect bash context, but avoid sending empty prompts when only images are attached.

0 commit comments

Comments
 (0)