Skip to content

Commit 9d6c886

Browse files
committed
feat: support deepseek models
1 parent 9ff933c commit 9d6c886

3 files changed

Lines changed: 174 additions & 6 deletions

File tree

src/models.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export type ConfigModel = {
9797

9898
function buildLookupMap(modelsDevData: ModelsDevData) {
9999
const byFullId = new Map<string, ModelDevEntry>();
100-
const allowedProviders = new Set(["openai", "anthropic"]);
100+
const allowedProviders = new Set(["openai", "anthropic", "deepseek"]);
101101

102102
for (const [provider, providerData] of Object.entries(modelsDevData)) {
103103
if (!allowedProviders.has(provider) || !providerData?.models) continue;
@@ -156,8 +156,8 @@ export async function fetchHubModels(): Promise<HubResponse> {
156156
return res.json() as Promise<HubResponse>;
157157
}
158158

159-
function checkIsAnthropic(provider: string): boolean {
160-
return provider === "anthropic";
159+
function usesAnthropicApi(provider: string): boolean {
160+
return provider === "anthropic" || provider === "deepseek";
161161
}
162162

163163
export function buildConfigModels(
@@ -171,7 +171,7 @@ export function buildConfigModels(
171171
for (const [provider, providerData] of Object.entries(hubData.providers)) {
172172
if (!providerData?.models) continue;
173173

174-
const anthropic = checkIsAnthropic(provider);
174+
const anthropic = usesAnthropicApi(provider);
175175

176176
for (const [modelId, hubModel] of Object.entries(providerData.models)) {
177177
const entry = resolveEntry(provider, modelId, byFullId);

test/index.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,38 @@ const MODELS_DEV_DATA = {
6565
},
6666
},
6767
},
68+
deepseek: {
69+
models: {
70+
"deepseek-v4-pro": {
71+
id: "deepseek-v4-pro",
72+
name: "DeepSeek V4 Pro",
73+
limit: { context: 128000, output: 32000 },
74+
attachment: true,
75+
reasoning: true,
76+
temperature: true,
77+
tool_call: true,
78+
modalities: {
79+
input: ["text"],
80+
output: ["text"],
81+
},
82+
cost: { input: 1.5, output: 6, cache_read: 0.15, cache_write: 1.5 },
83+
},
84+
"deepseek-v4-flash": {
85+
id: "deepseek-v4-flash",
86+
name: "DeepSeek V4 Flash",
87+
limit: { context: 128000, output: 32000 },
88+
attachment: true,
89+
reasoning: true,
90+
temperature: true,
91+
tool_call: true,
92+
modalities: {
93+
input: ["text"],
94+
output: ["text"],
95+
},
96+
cost: { input: 0.15, output: 0.6, cache_read: 0.015, cache_write: 0.15 },
97+
},
98+
},
99+
},
68100
};
69101

70102
function hubResponse() {
@@ -80,6 +112,12 @@ function hubResponse() {
80112
"claude-sonnet-4-20250514": { display_name: "Claude Sonnet 4" },
81113
},
82114
},
115+
deepseek: {
116+
models: {
117+
"deepseek-v4-pro": { display_name: "DeepSeek V4 Pro" },
118+
"deepseek-v4-flash": { display_name: "DeepSeek V4 Flash" },
119+
},
120+
},
83121
},
84122
};
85123
}
@@ -169,6 +207,48 @@ describe("config hook", () => {
169207
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
170208
},
171209
});
210+
211+
expect(models["deepseek-v4-pro"]).toEqual({
212+
id: "deepseek-v4-pro",
213+
name: "DeepSeek V4 Pro",
214+
provider: {
215+
api: "https://hub.coreinfra.ai/claude/api/v1",
216+
npm: "@ai-sdk/anthropic",
217+
},
218+
attachment: true,
219+
reasoning: true,
220+
temperature: true,
221+
tool_call: true,
222+
modalities: { input: ["text"], output: ["text"] },
223+
cost: { input: 1.5, output: 6, cache_read: 0.15, cache_write: 1.5 },
224+
limit: { context: 128000, output: 32000 },
225+
interleaved: { field: "reasoning_content" },
226+
headers: {
227+
"anthropic-beta":
228+
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
229+
},
230+
});
231+
232+
expect(models["deepseek-v4-flash"]).toEqual({
233+
id: "deepseek-v4-flash",
234+
name: "DeepSeek V4 Flash",
235+
provider: {
236+
api: "https://hub.coreinfra.ai/claude/api/v1",
237+
npm: "@ai-sdk/anthropic",
238+
},
239+
attachment: true,
240+
reasoning: true,
241+
temperature: true,
242+
tool_call: true,
243+
modalities: { input: ["text"], output: ["text"] },
244+
cost: { input: 0.15, output: 0.6, cache_read: 0.015, cache_write: 0.15 },
245+
limit: { context: 128000, output: 32000 },
246+
interleaved: { field: "reasoning_content" },
247+
headers: {
248+
"anthropic-beta":
249+
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
250+
},
251+
});
172252
});
173253

174254
it("uses defaults when models.dev has no matching model", async () => {

test/models.test.ts

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,38 @@ const MODELS_DEV_FIXTURE = {
5252
},
5353
},
5454
},
55+
deepseek: {
56+
models: {
57+
"deepseek-v4-pro": {
58+
id: "deepseek-v4-pro",
59+
name: "DeepSeek V4 Pro",
60+
limit: { context: 128000, output: 32000 },
61+
attachment: true,
62+
reasoning: true,
63+
temperature: true,
64+
tool_call: true,
65+
modalities: {
66+
input: ["text"],
67+
output: ["text"],
68+
},
69+
cost: { input: 1.5, output: 6, cache_read: 0.15, cache_write: 1.5 },
70+
},
71+
"deepseek-v4-flash": {
72+
id: "deepseek-v4-flash",
73+
name: "DeepSeek V4 Flash",
74+
limit: { context: 128000, output: 32000 },
75+
attachment: true,
76+
reasoning: true,
77+
temperature: true,
78+
tool_call: true,
79+
modalities: {
80+
input: ["text"],
81+
output: ["text"],
82+
},
83+
cost: { input: 0.15, output: 0.6, cache_read: 0.015, cache_write: 0.15 },
84+
},
85+
},
86+
},
5587
};
5688

5789
const HUB_FIXTURE = {
@@ -66,6 +98,12 @@ const HUB_FIXTURE = {
6698
"claude-sonnet-4-20250514": { display_name: "Claude Sonnet 4" },
6799
},
68100
},
101+
deepseek: {
102+
models: {
103+
"deepseek-v4-pro": { display_name: "DeepSeek V4 Pro" },
104+
"deepseek-v4-flash": { display_name: "DeepSeek V4 Flash" },
105+
},
106+
},
69107
},
70108
};
71109

@@ -77,7 +115,7 @@ describe("buildConfigModels", () => {
77115
);
78116

79117
expect(warnings).toEqual([]);
80-
expect(Object.keys(models)).toHaveLength(2);
118+
expect(Object.keys(models)).toHaveLength(4);
81119

82120
const gpt = models["gpt-5.4-nano"];
83121
expect(gpt).toEqual({
@@ -125,6 +163,56 @@ describe("buildConfigModels", () => {
125163
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
126164
},
127165
});
166+
167+
const dsPro = models["deepseek-v4-pro"];
168+
expect(dsPro).toEqual({
169+
id: "deepseek-v4-pro",
170+
name: "DeepSeek V4 Pro",
171+
provider: {
172+
api: "https://hub.coreinfra.ai/claude/api/v1",
173+
npm: "@ai-sdk/anthropic",
174+
},
175+
attachment: true,
176+
reasoning: true,
177+
temperature: true,
178+
tool_call: true,
179+
modalities: {
180+
input: ["text"],
181+
output: ["text"],
182+
},
183+
cost: { input: 1.5, output: 6, cache_read: 0.15, cache_write: 1.5 },
184+
limit: { context: 128000, output: 32000 },
185+
interleaved: { field: "reasoning_content" },
186+
headers: {
187+
"anthropic-beta":
188+
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
189+
},
190+
});
191+
192+
const dsFlash = models["deepseek-v4-flash"];
193+
expect(dsFlash).toEqual({
194+
id: "deepseek-v4-flash",
195+
name: "DeepSeek V4 Flash",
196+
provider: {
197+
api: "https://hub.coreinfra.ai/claude/api/v1",
198+
npm: "@ai-sdk/anthropic",
199+
},
200+
attachment: true,
201+
reasoning: true,
202+
temperature: true,
203+
tool_call: true,
204+
modalities: {
205+
input: ["text"],
206+
output: ["text"],
207+
},
208+
cost: { input: 0.15, output: 0.6, cache_read: 0.015, cache_write: 0.15 },
209+
limit: { context: 128000, output: 32000 },
210+
interleaved: { field: "reasoning_content" },
211+
headers: {
212+
"anthropic-beta":
213+
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
214+
},
215+
});
128216
});
129217

130218
it("uses defaults and emits warning when model not in models.dev", () => {
@@ -197,7 +285,7 @@ describe("buildConfigModels", () => {
197285
expect(models["shared-id"].limit.context).toBe(100000);
198286
});
199287

200-
it("does not resolve from non-openai/anthropic providers in models.dev", () => {
288+
it("does not resolve from non-allowed providers in models.dev", () => {
201289
const modelsDevData = {
202290
"provider-a": {
203291
models: {

0 commit comments

Comments
 (0)