Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export type ConfigModel = {

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

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

function checkIsAnthropic(provider: string): boolean {
return provider === "anthropic";
function usesAnthropicApi(provider: string): boolean {
return provider === "anthropic" || provider === "deepseek";
}

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

const anthropic = checkIsAnthropic(provider);
const anthropic = usesAnthropicApi(provider);

for (const [modelId, hubModel] of Object.entries(providerData.models)) {
const entry = resolveEntry(provider, modelId, byFullId);
Expand Down
85 changes: 85 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,43 @@ const MODELS_DEV_DATA = {
},
},
},
deepseek: {
models: {
"deepseek-v4-pro": {
id: "deepseek-v4-pro",
name: "DeepSeek V4 Pro",
limit: { context: 128000, output: 32000 },
attachment: true,
reasoning: true,
temperature: true,
tool_call: true,
modalities: {
input: ["text"],
output: ["text"],
},
cost: { input: 1.5, output: 6, cache_read: 0.15, cache_write: 1.5 },
},
"deepseek-v4-flash": {
id: "deepseek-v4-flash",
name: "DeepSeek V4 Flash",
limit: { context: 128000, output: 32000 },
attachment: true,
reasoning: true,
temperature: true,
tool_call: true,
modalities: {
input: ["text"],
output: ["text"],
},
cost: {
input: 0.15,
output: 0.6,
cache_read: 0.015,
cache_write: 0.15,
},
},
},
},
};

function hubResponse() {
Expand All @@ -80,6 +117,12 @@ function hubResponse() {
"claude-sonnet-4-20250514": { display_name: "Claude Sonnet 4" },
},
},
deepseek: {
models: {
"deepseek-v4-pro": { display_name: "DeepSeek V4 Pro" },
"deepseek-v4-flash": { display_name: "DeepSeek V4 Flash" },
},
},
},
};
}
Expand Down Expand Up @@ -169,6 +212,48 @@ describe("config hook", () => {
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
},
});

expect(models["deepseek-v4-pro"]).toEqual({
id: "deepseek-v4-pro",
name: "DeepSeek V4 Pro",
provider: {
api: "https://hub.coreinfra.ai/claude/api/v1",
npm: "@ai-sdk/anthropic",
},
attachment: true,
reasoning: true,
temperature: true,
tool_call: true,
modalities: { input: ["text"], output: ["text"] },
cost: { input: 1.5, output: 6, cache_read: 0.15, cache_write: 1.5 },
limit: { context: 128000, output: 32000 },
interleaved: { field: "reasoning_content" },
headers: {
"anthropic-beta":
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
},
});

expect(models["deepseek-v4-flash"]).toEqual({
id: "deepseek-v4-flash",
name: "DeepSeek V4 Flash",
provider: {
api: "https://hub.coreinfra.ai/claude/api/v1",
npm: "@ai-sdk/anthropic",
},
attachment: true,
reasoning: true,
temperature: true,
tool_call: true,
modalities: { input: ["text"], output: ["text"] },
cost: { input: 0.15, output: 0.6, cache_read: 0.015, cache_write: 0.15 },
limit: { context: 128000, output: 32000 },
interleaved: { field: "reasoning_content" },
headers: {
"anthropic-beta":
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
},
});
});

it("uses defaults when models.dev has no matching model", async () => {
Expand Down
97 changes: 95 additions & 2 deletions test/models.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,43 @@ const MODELS_DEV_FIXTURE = {
},
},
},
deepseek: {
models: {
"deepseek-v4-pro": {
id: "deepseek-v4-pro",
name: "DeepSeek V4 Pro",
limit: { context: 128000, output: 32000 },
attachment: true,
reasoning: true,
temperature: true,
tool_call: true,
modalities: {
input: ["text"],
output: ["text"],
},
cost: { input: 1.5, output: 6, cache_read: 0.15, cache_write: 1.5 },
},
"deepseek-v4-flash": {
id: "deepseek-v4-flash",
name: "DeepSeek V4 Flash",
limit: { context: 128000, output: 32000 },
attachment: true,
reasoning: true,
temperature: true,
tool_call: true,
modalities: {
input: ["text"],
output: ["text"],
},
cost: {
input: 0.15,
output: 0.6,
cache_read: 0.015,
cache_write: 0.15,
},
},
},
},
};

const HUB_FIXTURE = {
Expand All @@ -66,6 +103,12 @@ const HUB_FIXTURE = {
"claude-sonnet-4-20250514": { display_name: "Claude Sonnet 4" },
},
},
deepseek: {
models: {
"deepseek-v4-pro": { display_name: "DeepSeek V4 Pro" },
"deepseek-v4-flash": { display_name: "DeepSeek V4 Flash" },
},
},
},
};

Expand All @@ -77,7 +120,7 @@ describe("buildConfigModels", () => {
);

expect(warnings).toEqual([]);
expect(Object.keys(models)).toHaveLength(2);
expect(Object.keys(models)).toHaveLength(4);

const gpt = models["gpt-5.4-nano"];
expect(gpt).toEqual({
Expand Down Expand Up @@ -125,6 +168,56 @@ describe("buildConfigModels", () => {
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
},
});

const dsPro = models["deepseek-v4-pro"];
expect(dsPro).toEqual({
id: "deepseek-v4-pro",
name: "DeepSeek V4 Pro",
provider: {
api: "https://hub.coreinfra.ai/claude/api/v1",
npm: "@ai-sdk/anthropic",
},
attachment: true,
reasoning: true,
temperature: true,
tool_call: true,
modalities: {
input: ["text"],
output: ["text"],
},
cost: { input: 1.5, output: 6, cache_read: 0.15, cache_write: 1.5 },
limit: { context: 128000, output: 32000 },
interleaved: { field: "reasoning_content" },
headers: {
"anthropic-beta":
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
},
});

const dsFlash = models["deepseek-v4-flash"];
expect(dsFlash).toEqual({
id: "deepseek-v4-flash",
name: "DeepSeek V4 Flash",
provider: {
api: "https://hub.coreinfra.ai/claude/api/v1",
npm: "@ai-sdk/anthropic",
},
attachment: true,
reasoning: true,
temperature: true,
tool_call: true,
modalities: {
input: ["text"],
output: ["text"],
},
cost: { input: 0.15, output: 0.6, cache_read: 0.015, cache_write: 0.15 },
limit: { context: 128000, output: 32000 },
interleaved: { field: "reasoning_content" },
headers: {
"anthropic-beta":
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
},
});
});

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

it("does not resolve from non-openai/anthropic providers in models.dev", () => {
it("does not resolve from non-allowed providers in models.dev", () => {
const modelsDevData = {
"provider-a": {
models: {
Expand Down