Skip to content

Commit 49fe268

Browse files
committed
Merge remote-tracking branch 'origin/main' into jahooma/deepseek-v4-provider
# Conflicts: # common/src/constants/free-agents.ts # web/src/app/api/v1/chat/completions/__tests__/completions.test.ts # web/src/server/free-session/config.ts
2 parents e4a97a6 + 7f1131a commit 49fe268

19 files changed

Lines changed: 522 additions & 204 deletions

File tree

agents/base2/base2-gemini-no-editor-evals.ts renamed to agents/base2/base2-gemini-evals.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ const definition = {
66
model: 'google/gemini-3.1-pro-preview',
77
providerOptions: {},
88
}),
9-
id: 'base2-gemini-no-editor-evals',
10-
displayName: 'Buffy the Gemini Evals Orchestrator',
9+
id: 'base2-gemini-evals',
10+
displayName: 'Buffy the Gemini Orchestrator',
1111
}
1212

1313
export default definition

agents/base2/base2.ts

Lines changed: 18 additions & 1 deletion
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,
@@ -70,7 +78,6 @@ export function createBase2(
7078
'read_subtree',
7179
!isFast && 'write_todos',
7280
!isFast && !noAskUser && 'suggest_followups',
73-
!isFast && 'render_ui',
7481
'str_replace',
7582
'write_file',
7683
!isFree && 'propose_str_replace',
@@ -98,6 +105,7 @@ export function createBase2(
98105
isFree && 'code-reviewer-lite',
99106
isDefault && 'code-reviewer',
100107
isMax && 'code-reviewer-multi-prompt',
108+
hasFreeGeminiThinker && FREEBUFF_GEMINI_THINKER_AGENT_ID,
101109
'thinker-gpt',
102110
'context-pruner',
103111
),
@@ -155,6 +163,7 @@ Use the spawn_agents tool to spawn specialized agents to help you complete the u
155163
'- 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.',
156164
isFree &&
157165
'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,
158167
isDefault &&
159168
'- Spawn the editor agent to implement the changes after you have gathered all the context you need.',
160169
(isDefault || isMax) &&
@@ -281,6 +290,7 @@ ${PLACEHOLDER.GIT_CHANGES_PROMPT}
281290
isDefault,
282291
isMax,
283292
isFree,
293+
hasFreeGeminiThinker,
284294
hasNoValidation,
285295
noAskUser,
286296
}),
@@ -293,6 +303,7 @@ ${PLACEHOLDER.GIT_CHANGES_PROMPT}
293303
hasNoValidation,
294304
isSonnet,
295305
isFree,
306+
hasFreeGeminiThinker,
296307
noAskUser,
297308
}),
298309

@@ -341,6 +352,7 @@ function buildImplementationInstructionsPrompt({
341352
isDefault,
342353
isMax,
343354
isFree,
355+
hasFreeGeminiThinker,
344356
hasNoValidation,
345357
noAskUser,
346358
}: {
@@ -349,6 +361,7 @@ function buildImplementationInstructionsPrompt({
349361
isDefault: boolean
350362
isMax: boolean
351363
isFree: boolean
364+
hasFreeGeminiThinker: boolean
352365
hasNoValidation: boolean
353366
noAskUser: boolean
354367
}) {
@@ -366,6 +379,7 @@ ${buildArray(
366379
'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.',
367380
(isDefault || isMax || isFree) &&
368381
`- 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,
369383
(isDefault || isMax) &&
370384
`- 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)`,
371385
isDefault &&
@@ -396,6 +410,7 @@ function buildImplementationStepPrompt({
396410
hasNoValidation,
397411
isSonnet,
398412
isFree,
413+
hasFreeGeminiThinker,
399414
noAskUser,
400415
}: {
401416
isDefault: boolean
@@ -404,12 +419,14 @@ function buildImplementationStepPrompt({
404419
hasNoValidation: boolean
405420
isSonnet: boolean
406421
isFree: boolean
422+
hasFreeGeminiThinker: boolean
407423
noAskUser: boolean
408424
}) {
409425
return buildArray(
410426
isMax &&
411427
`Keep working until the user's request is completely satisfied${!hasNoValidation ? ' and validated' : ''}, or until you require more information from the user.`,
412428
'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,
413430
isMax &&
414431
`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.`,
415432
(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)