Files
openclaw/extensions/deepseek/provider-policy-api.test.ts
Peter Steinberger 9ad38e7739 fix(deepseek): align catalog costs and legacy aliases (#103241)
* fix(deepseek): align catalog with current contract

* fix(deepseek): preserve customized catalog rows

* docs(changelog): group DeepSeek catalog metadata

* fix(deepseek): preserve customized catalog rows
2026-07-10 03:41:33 +01:00

479 lines
14 KiB
TypeScript

// Deepseek tests cover provider policy api plugin behavior.
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-types";
import { describe, expect, it } from "vitest";
import { normalizeConfig, resolveThinkingProfile } from "./provider-policy-api.js";
describe("deepseek provider-policy-api", () => {
it("advertises max thinking levels for DeepSeek V4 models", () => {
const expectedV4Levels = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
expect(
resolveThinkingProfile({
provider: "deepseek",
modelId: "deepseek-v4-pro",
})?.levels.map((level) => level.id),
).toEqual(expectedV4Levels);
expect(
resolveThinkingProfile({
provider: "deepseek",
modelId: "deepseek-v4-flash",
})?.defaultLevel,
).toBe("high");
expect(
resolveThinkingProfile({
provider: "deepseek",
modelId: "deepseek-chat",
}),
).toBe(undefined);
expect(
resolveThinkingProfile({
provider: "openrouter",
modelId: "deepseek-v4-pro",
}),
).toBe(null);
});
it("hydrates contextWindow and cost from catalog for known models", () => {
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.deepseek.com",
api: "openai-completions",
models: [
{
id: "deepseek-v4-flash",
name: "DeepSeek V4 Flash",
reasoning: true,
input: ["text"],
} as never,
],
};
const result = normalizeConfig({ provider: "deepseek", providerConfig });
expect(result).not.toBe(providerConfig);
const model = result.models[0];
expect(model.contextWindow).toBe(1_000_000);
expect(model.maxTokens).toBe(384_000);
expect(model.cost).toEqual({
input: 0.14,
output: 0.28,
cacheRead: 0.0028,
cacheWrite: 0,
});
});
it("hydrates deepseek-v4-pro with correct metadata", () => {
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.deepseek.com",
api: "openai-completions",
models: [
{
id: "deepseek-v4-pro",
name: "DeepSeek V4 Pro",
reasoning: true,
input: ["text"],
} as never,
],
};
const result = normalizeConfig({ provider: "deepseek", providerConfig });
const model = result.models[0];
expect(model.contextWindow).toBe(1_000_000);
expect(model.maxTokens).toBe(384_000);
expect(model.cost).toEqual({
input: 0.435,
output: 0.87,
cacheRead: 0.003625,
cacheWrite: 0,
});
});
it("hydrates the legacy chat alias with current V4 Flash metadata", () => {
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.deepseek.com",
api: "openai-completions",
models: [
{
id: "deepseek-chat",
name: "DeepSeek Chat",
reasoning: false,
input: ["text"],
} as never,
],
};
const result = normalizeConfig({ provider: "deepseek", providerConfig });
const model = result.models[0];
expect(model.contextWindow).toBe(1_000_000);
expect(model.maxTokens).toBe(384_000);
expect(model.cost).toEqual({
input: 0.14,
output: 0.28,
cacheRead: 0.0028,
cacheWrite: 0,
});
});
it("refreshes exact catalog metadata snapshots written by prior releases", () => {
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.deepseek.com",
api: "openai-completions",
models: [
{
id: "deepseek-v4-flash",
name: "DeepSeek V4 Flash",
reasoning: true,
input: ["text"],
contextWindow: 1_000_000,
maxTokens: 384_000,
cost: { input: 0.14, output: 0.28, cacheRead: 0.028, cacheWrite: 0 },
},
{
id: "deepseek-v4-pro",
name: "DeepSeek V4 Pro",
reasoning: true,
input: ["text"],
contextWindow: 1_000_000,
maxTokens: 384_000,
cost: { input: 1.74, output: 3.48, cacheRead: 0.145, cacheWrite: 0 },
},
{
id: "deepseek-chat",
name: "DeepSeek Chat",
reasoning: false,
input: ["text"],
contextWindow: 131_072,
maxTokens: 8_192,
cost: { input: 0.28, output: 0.42, cacheRead: 0.028, cacheWrite: 0 },
},
{
id: "deepseek-reasoner",
name: "DeepSeek Reasoner",
reasoning: true,
input: ["text"],
contextWindow: 131_072,
maxTokens: 65_536,
cost: { input: 0.28, output: 0.42, cacheRead: 0.028, cacheWrite: 0 },
},
],
};
const result = normalizeConfig({ provider: "deepseek", providerConfig });
expect(
result.models.map(({ id, contextWindow, maxTokens, cost }) => ({
id,
contextWindow,
maxTokens,
cost,
})),
).toEqual([
{
id: "deepseek-v4-flash",
contextWindow: 1_000_000,
maxTokens: 384_000,
cost: { input: 0.14, output: 0.28, cacheRead: 0.0028, cacheWrite: 0 },
},
{
id: "deepseek-v4-pro",
contextWindow: 1_000_000,
maxTokens: 384_000,
cost: { input: 0.435, output: 0.87, cacheRead: 0.003625, cacheWrite: 0 },
},
{
id: "deepseek-chat",
contextWindow: 1_000_000,
maxTokens: 384_000,
cost: { input: 0.14, output: 0.28, cacheRead: 0.0028, cacheWrite: 0 },
},
{
id: "deepseek-reasoner",
contextWindow: 1_000_000,
maxTokens: 384_000,
cost: { input: 0.14, output: 0.28, cacheRead: 0.0028, cacheWrite: 0 },
},
]);
});
it("refreshes exact zero-cost legacy alias rows written by tagged releases", () => {
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.deepseek.com",
api: "openai-completions",
models: [
{
id: "deepseek-chat",
name: "DeepSeek Chat",
reasoning: false,
input: ["text"],
contextWindow: 131_072,
maxTokens: 8_192,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
},
{
id: "deepseek-reasoner",
name: "DeepSeek Reasoner",
reasoning: true,
input: ["text"],
contextWindow: 131_072,
maxTokens: 65_536,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
},
],
};
const result = normalizeConfig({ provider: "deepseek", providerConfig });
for (const model of result.models) {
expect(model.contextWindow).toBe(1_000_000);
expect(model.maxTokens).toBe(384_000);
expect(model.cost).toEqual({
input: 0.14,
output: 0.28,
cacheRead: 0.0028,
cacheWrite: 0,
});
}
});
it("preserves legacy alias metadata when any catalog-owned field is customized", () => {
const userCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.deepseek.com",
api: "openai-completions",
models: [
{
id: "deepseek-chat",
name: "DeepSeek Chat",
reasoning: false,
input: ["text"],
contextWindow: 500_000,
maxTokens: 8_192,
cost: userCost,
} as never,
],
};
const result = normalizeConfig({ provider: "deepseek", providerConfig });
expect(result.models[0].contextWindow).toBe(500_000);
expect(result.models[0].maxTokens).toBe(8_192);
expect(result.models[0].cost).toBe(userCost);
});
it("preserves an old maxTokens value when another field makes the row user-owned", () => {
const userCost = { input: 0.28, output: 0.42, cacheRead: 0.028, cacheWrite: 0 };
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.deepseek.com",
api: "openai-completions",
models: [
{
id: "deepseek-chat",
name: "DeepSeek Chat",
reasoning: false,
input: ["text"],
contextWindow: 500_000,
maxTokens: 8_192,
cost: userCost,
} as never,
],
};
const result = normalizeConfig({ provider: "deepseek", providerConfig });
expect(result).toBe(providerConfig);
expect(result.models[0]).toMatchObject({ contextWindow: 500_000, maxTokens: 8_192 });
expect(result.models[0].cost).toBe(userCost);
});
it("preserves explicit user contextWindow override", () => {
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.deepseek.com",
api: "openai-completions",
models: [
{
id: "deepseek-v4-flash",
name: "DeepSeek V4 Flash",
reasoning: true,
input: ["text"],
contextWindow: 500_000,
} as never,
],
};
const result = normalizeConfig({ provider: "deepseek", providerConfig });
const model = result.models[0];
expect(model.contextWindow).toBe(500_000);
// cost should still be hydrated since it was missing
expect(model.cost).toEqual({
input: 0.14,
output: 0.28,
cacheRead: 0.0028,
cacheWrite: 0,
});
});
it("preserves explicit user cost override", () => {
const userCost = { input: 99, output: 99, cacheRead: 99, cacheWrite: 99 };
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.deepseek.com",
api: "openai-completions",
models: [
{
id: "deepseek-v4-flash",
name: "DeepSeek V4 Flash",
reasoning: true,
input: ["text"],
cost: userCost,
} as never,
],
};
const result = normalizeConfig({ provider: "deepseek", providerConfig });
const model = result.models[0];
expect(model.cost).toEqual(userCost);
// contextWindow should still be hydrated since it was missing
expect(model.contextWindow).toBe(1_000_000);
});
it("preserves tiered pricing layered onto an older flat catalog snapshot", () => {
const userCost = {
input: 1.74,
output: 3.48,
cacheRead: 0.145,
cacheWrite: 0,
tieredPricing: [
{
upTo: 200_000,
input: 1,
output: 2,
cacheRead: 0.1,
cacheWrite: 0,
},
],
};
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.deepseek.com",
api: "openai-completions",
models: [
{
id: "deepseek-v4-pro",
name: "DeepSeek V4 Pro",
reasoning: true,
input: ["text"],
cost: userCost,
} as never,
],
};
const result = normalizeConfig({ provider: "deepseek", providerConfig });
expect(result.models[0].cost).toBe(userCost);
});
it("preserves explicit user maxTokens override", () => {
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.deepseek.com",
api: "openai-completions",
models: [
{
id: "deepseek-v4-flash",
name: "DeepSeek V4 Flash",
reasoning: true,
input: ["text"],
maxTokens: 100_000,
} as never,
],
};
const result = normalizeConfig({ provider: "deepseek", providerConfig });
const model = result.models[0];
expect(model.maxTokens).toBe(100_000);
});
it("returns providerConfig unchanged when all models already have metadata", () => {
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.deepseek.com",
api: "openai-completions",
models: [
{
id: "deepseek-v4-flash",
name: "DeepSeek V4 Flash",
reasoning: true,
input: ["text"],
contextWindow: 1_000_000,
maxTokens: 384_000,
cost: { input: 0.14, output: 0.28, cacheRead: 0.0028, cacheWrite: 0 },
} as never,
],
};
const result = normalizeConfig({ provider: "deepseek", providerConfig });
expect(result).toBe(providerConfig);
});
it("passes through unknown model ids unchanged", () => {
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.deepseek.com",
api: "openai-completions",
models: [
{
id: "deepseek-custom-finetune",
name: "Custom Fine-tune",
reasoning: false,
input: ["text"],
} as never,
],
};
const result = normalizeConfig({ provider: "deepseek", providerConfig });
expect(result).toBe(providerConfig);
});
it("returns providerConfig unchanged when models array is empty", () => {
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.deepseek.com",
api: "openai-completions",
models: [],
};
const result = normalizeConfig({ provider: "deepseek", providerConfig });
expect(result).toBe(providerConfig);
});
it("hydrates only the models that need it in a mixed list", () => {
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.deepseek.com",
api: "openai-completions",
models: [
{
id: "deepseek-v4-flash",
name: "DeepSeek V4 Flash",
reasoning: true,
input: ["text"],
contextWindow: 1_000_000,
maxTokens: 384_000,
cost: { input: 0.14, output: 0.28, cacheRead: 0.0028, cacheWrite: 0 },
} as never,
{
id: "deepseek-v4-pro",
name: "DeepSeek V4 Pro",
reasoning: true,
input: ["text"],
} as never,
],
};
const result = normalizeConfig({ provider: "deepseek", providerConfig });
expect(result).not.toBe(providerConfig);
// First model should be unchanged (same reference)
expect(result.models[0]).toBe(providerConfig.models[0]);
// Second model should be hydrated
expect(result.models[1].contextWindow).toBe(1_000_000);
expect(result.models[1].cost).toEqual({
input: 0.435,
output: 0.87,
cacheRead: 0.003625,
cacheWrite: 0,
});
});
});