Skip to content

Commit d5aed7b

Browse files
committed
Add DeepSeek V4 provider
1 parent 7015b88 commit d5aed7b

13 files changed

Lines changed: 1004 additions & 40 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { createBase2 } from './base2'
2+
3+
const definition = {
4+
...createBase2('free', {
5+
noAskUser: true,
6+
model: 'deepseek/deepseek-v4-pro',
7+
}),
8+
id: 'base2-free-deepseek-v4',
9+
displayName: 'Buffy the DeepSeek V4 Free Orchestrator',
10+
}
11+
export default definition

agents/types/agent-definition.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,8 @@ export type ModelName =
415415
| 'qwen/qwen3-30b-a3b:nitro'
416416

417417
// DeepSeek
418+
| 'deepseek/deepseek-v4-pro'
419+
| 'deepseek-v4-pro'
418420
| 'deepseek/deepseek-chat-v3-0324'
419421
| 'deepseek/deepseek-chat-v3-0324:nitro'
420422
| 'deepseek/deepseek-r1-0528'

common/src/constants/free-agents.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { parseAgentId } from '../util/agent-id-parsing'
22

3-
import { SUPPORTED_FREEBUFF_MODELS } from './freebuff-models'
3+
import {
4+
FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
5+
SUPPORTED_FREEBUFF_MODELS,
6+
} from './freebuff-models'
47

58
import type { CostMode } from './model-config'
69

@@ -16,7 +19,10 @@ export const FREE_COST_MODE = 'free' as const
1619
* excluded — they're spawned by the root, so counting them would inflate
1720
* every user's apparent activity.
1821
*/
19-
export const FREEBUFF_ROOT_AGENT_IDS = ['base2-free'] as const
22+
export const FREEBUFF_ROOT_AGENT_IDS = [
23+
'base2-free',
24+
'base2-free-deepseek-v4',
25+
] as const
2026
const FREEBUFF_ROOT_AGENT_ID_SET: ReadonlySet<string> = new Set(
2127
FREEBUFF_ROOT_AGENT_IDS,
2228
)
@@ -35,6 +41,7 @@ const FREEBUFF_ALLOWED_MODEL_IDS = SUPPORTED_FREEBUFF_MODELS.map(
3541
export const FREE_MODE_AGENT_MODELS: Record<string, Set<string>> = {
3642
// Root orchestrator
3743
'base2-free': new Set(FREEBUFF_ALLOWED_MODEL_IDS),
44+
'base2-free-deepseek-v4': new Set([FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID]),
3845

3946
// File exploration agents
4047
'file-picker': new Set(['google/gemini-2.5-flash-lite']),

common/src/constants/freebuff-models.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface FreebuffModelOption {
2222
* `getFreebuffDeploymentAvailabilityLabel()` instead. */
2323
export const FREEBUFF_DEPLOYMENT_HOURS_LABEL = '9am ET-5pm PT every day'
2424
export const FREEBUFF_GEMINI_PRO_MODEL_ID = 'google/gemini-3.1-pro-preview'
25+
export const FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID = 'deepseek/deepseek-v4-pro'
2526
export const FREEBUFF_GLM_MODEL_ID = 'z-ai/glm-5.1'
2627
export const FREEBUFF_KIMI_MODEL_ID = 'moonshotai/kimi-k2.6'
2728
export const FREEBUFF_MINIMAX_MODEL_ID = 'minimax/minimax-m2.7'
@@ -48,6 +49,12 @@ export const FREEBUFF_MODELS = [
4849
tagline: 'Deepest, 1/day',
4950
availability: 'always',
5051
},
52+
{
53+
id: FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
54+
displayName: 'DeepSeek V4 Pro',
55+
tagline: 'Experimental',
56+
availability: 'always',
57+
},
5158
{
5259
id: FREEBUFF_MINIMAX_MODEL_ID,
5360
displayName: 'MiniMax M2.7',

common/src/constants/model-config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const ALLOWED_MODEL_PREFIXES = [
66
'openai',
77
'google',
88
'x-ai',
9+
'deepseek',
910
] as const
1011

1112
export const costModes = [
@@ -55,6 +56,8 @@ export type openrouterModel =
5556
export const deepseekModels = {
5657
deepseekChat: 'deepseek-chat',
5758
deepseekReasoner: 'deepseek-reasoner',
59+
deepseekV4ProDirect: 'deepseek-v4-pro',
60+
deepseekV4Pro: 'deepseek/deepseek-v4-pro',
5861
} as const
5962
export type DeepseekModel = (typeof deepseekModels)[keyof typeof deepseekModels]
6063

common/src/templates/initial-agents-dir/types/agent-definition.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,8 @@ export type ModelName =
415415
| 'qwen/qwen3-30b-a3b:nitro'
416416

417417
// DeepSeek
418+
| 'deepseek/deepseek-v4-pro'
419+
| 'deepseek-v4-pro'
418420
| 'deepseek/deepseek-chat-v3-0324'
419421
| 'deepseek/deepseek-chat-v3-0324:nitro'
420422
| 'deepseek/deepseek-r1-0528'

evals/buffbench/main-single-eval.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ async function main() {
77

88
await runBuffBench({
99
evalDataPaths: [path.join(__dirname, 'eval-codebuff.json')],
10-
agents: ['base2-free-evals'],
10+
agents: ['base2-free-deepseek-v4'],
1111
taskIds: ['server-agent-validation'],
1212
saveTraces,
1313
})

packages/internal/src/env-schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const serverEnvSchema = clientEnvSchema.extend({
88
ANTHROPIC_API_KEY: z.string().min(1),
99
FIREWORKS_API_KEY: z.string().min(1),
1010
CANOPYWAVE_API_KEY: z.string().min(1).optional(),
11+
DEEPSEEK_API_KEY: z.string().min(1).optional(),
1112
SILICONFLOW_API_KEY: z.string().min(1).optional(),
1213
LINKUP_API_KEY: z.string().min(1),
1314
CONTEXT7_API_KEY: z.string().optional(),
@@ -92,6 +93,7 @@ export const serverProcessEnv: ServerInput = {
9293
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
9394
FIREWORKS_API_KEY: process.env.FIREWORKS_API_KEY,
9495
CANOPYWAVE_API_KEY: process.env.CANOPYWAVE_API_KEY,
96+
DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY,
9597
SILICONFLOW_API_KEY: process.env.SILICONFLOW_API_KEY,
9698
LINKUP_API_KEY: process.env.LINKUP_API_KEY,
9799
CONTEXT7_API_KEY: process.env.CONTEXT7_API_KEY,

packages/internal/src/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ if (isCI) {
1818
ensureEnvDefault('ANTHROPIC_API_KEY', 'test')
1919
ensureEnvDefault('FIREWORKS_API_KEY', 'test')
2020
ensureEnvDefault('CANOPYWAVE_API_KEY', 'test')
21+
ensureEnvDefault('DEEPSEEK_API_KEY', 'test')
2122
ensureEnvDefault('LINKUP_API_KEY', 'test')
2223
ensureEnvDefault('GRAVITY_API_KEY', 'test')
2324
ensureEnvDefault('IPINFO_TOKEN', 'test')

web/src/app/api/v1/chat/completions/__tests__/completions.test.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, mock, it } from 'bun:test'
22
import { NextRequest } from 'next/server'
33

44
import {
5+
FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
56
FREEBUFF_GEMINI_PRO_MODEL_ID,
67
FREEBUFF_GLM_MODEL_ID,
78
isFreebuffDeploymentHours,
@@ -147,6 +148,13 @@ describe('/api/v1/chat/completions POST endpoint', () => {
147148
status: 'running',
148149
}
149150
}
151+
if (runId === 'run-free-deepseek-v4') {
152+
return {
153+
agent_id: 'base2-free-deepseek-v4',
154+
ancestor_run_ids: [],
155+
status: 'running',
156+
}
157+
}
150158
if (runId === 'run-reviewer-direct') {
151159
return {
152160
agent_id: 'code-reviewer-lite',
@@ -823,6 +831,111 @@ describe('/api/v1/chat/completions POST endpoint', () => {
823831
FETCH_PATH_TEST_TIMEOUT_MS,
824832
)
825833

834+
it(
835+
'lets the DeepSeek V4 free agent use the direct DeepSeek provider',
836+
async () => {
837+
const fetchedBodies: Record<string, unknown>[] = []
838+
const fetchedUrls: string[] = []
839+
const fetchViaDeepSeek = mock(
840+
async (url: string | URL | Request, init?: RequestInit) => {
841+
fetchedUrls.push(String(url))
842+
fetchedBodies.push(JSON.parse(init?.body as string))
843+
return new Response(
844+
JSON.stringify({
845+
id: 'test-id',
846+
model: 'deepseek-v4-pro',
847+
choices: [{ message: { content: 'test response' } }],
848+
usage: {
849+
prompt_tokens: 10,
850+
prompt_cache_hit_tokens: 4,
851+
completion_tokens: 20,
852+
total_tokens: 30,
853+
},
854+
}),
855+
{
856+
status: 200,
857+
headers: { 'Content-Type': 'application/json' },
858+
},
859+
)
860+
},
861+
) as unknown as typeof globalThis.fetch
862+
863+
const req = new NextRequest(
864+
'http://localhost:3000/api/v1/chat/completions',
865+
{
866+
method: 'POST',
867+
headers: allowedFreeModeHeaders('test-api-key-new-free'),
868+
body: JSON.stringify({
869+
model: FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
870+
stream: false,
871+
codebuff_metadata: {
872+
run_id: 'run-free-deepseek-v4',
873+
client_id: 'test-client-id-123',
874+
cost_mode: 'free',
875+
},
876+
}),
877+
},
878+
)
879+
880+
const response = await postChatCompletions({
881+
req,
882+
getUserInfoFromApiKey: mockGetUserInfoFromApiKey,
883+
logger: mockLogger,
884+
trackEvent: mockTrackEvent,
885+
getUserUsageData: mockGetUserUsageData,
886+
getAgentRunFromId: mockGetAgentRunFromId,
887+
fetch: fetchViaDeepSeek,
888+
insertMessageBigquery: mockInsertMessageBigquery,
889+
loggerWithContext: mockLoggerWithContext,
890+
checkSessionAdmissible: mockCheckSessionAdmissibleAllow,
891+
})
892+
893+
const body = await response.json()
894+
expect(response.status).toBe(200)
895+
expect(fetchedUrls[0]).toBe('https://api.deepseek.com/chat/completions')
896+
expect(fetchedBodies[0].model).toBe('deepseek-v4-pro')
897+
expect(body.model).toBe(FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID)
898+
expect(body.provider).toBe('DeepSeek')
899+
},
900+
FETCH_PATH_TEST_TIMEOUT_MS,
901+
)
902+
903+
it('rejects the DeepSeek V4 free agent when it requests another free model', async () => {
904+
const req = new NextRequest(
905+
'http://localhost:3000/api/v1/chat/completions',
906+
{
907+
method: 'POST',
908+
headers: allowedFreeModeHeaders('test-api-key-new-free'),
909+
body: JSON.stringify({
910+
model: FREEBUFF_GEMINI_PRO_MODEL_ID,
911+
stream: false,
912+
codebuff_metadata: {
913+
run_id: 'run-free-deepseek-v4',
914+
client_id: 'test-client-id-123',
915+
cost_mode: 'free',
916+
},
917+
}),
918+
},
919+
)
920+
921+
const response = await postChatCompletions({
922+
req,
923+
getUserInfoFromApiKey: mockGetUserInfoFromApiKey,
924+
logger: mockLogger,
925+
trackEvent: mockTrackEvent,
926+
getUserUsageData: mockGetUserUsageData,
927+
getAgentRunFromId: mockGetAgentRunFromId,
928+
fetch: mockFetch,
929+
insertMessageBigquery: mockInsertMessageBigquery,
930+
loggerWithContext: mockLoggerWithContext,
931+
checkSessionAdmissible: mockCheckSessionAdmissibleAllow,
932+
})
933+
934+
const body = await response.json()
935+
expect(response.status).toBe(403)
936+
expect(body.error).toBe('free_mode_invalid_agent_model')
937+
})
938+
826939
it('lets freebuff use Gemini 3.1 Pro through the free-mode allowlist', async () => {
827940
const req = new NextRequest(
828941
'http://localhost:3000/api/v1/chat/completions',

0 commit comments

Comments
 (0)