


























@@ -0,0 +1,235 @@
1+import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-types";
2+import { describe, expect, it } from "vitest";
3+import { normalizeConfig } from "./provider-policy-api.js";
4+5+describe("deepseek provider-policy-api", () => {
6+it("hydrates contextWindow and cost from catalog for known models", () => {
7+const providerConfig: ModelProviderConfig = {
8+baseUrl: "https://api.deepseek.com",
9+api: "openai-completions",
10+models: [
11+{
12+id: "deepseek-v4-flash",
13+name: "DeepSeek V4 Flash",
14+reasoning: true,
15+input: ["text"],
16+} as never,
17+],
18+};
19+20+const result = normalizeConfig({ provider: "deepseek", providerConfig });
21+22+expect(result).not.toBe(providerConfig);
23+const model = result.models[0];
24+expect(model.contextWindow).toBe(1_000_000);
25+expect(model.maxTokens).toBe(384_000);
26+expect(model.cost).toEqual({
27+input: 0.14,
28+output: 0.28,
29+cacheRead: 0.028,
30+cacheWrite: 0,
31+});
32+});
33+34+it("hydrates deepseek-v4-pro with correct metadata", () => {
35+const providerConfig: ModelProviderConfig = {
36+baseUrl: "https://api.deepseek.com",
37+api: "openai-completions",
38+models: [
39+{
40+id: "deepseek-v4-pro",
41+name: "DeepSeek V4 Pro",
42+reasoning: true,
43+input: ["text"],
44+} as never,
45+],
46+};
47+48+const result = normalizeConfig({ provider: "deepseek", providerConfig });
49+const model = result.models[0];
50+expect(model.contextWindow).toBe(1_000_000);
51+expect(model.maxTokens).toBe(384_000);
52+expect(model.cost).toEqual({
53+input: 1.74,
54+output: 3.48,
55+cacheRead: 0.145,
56+cacheWrite: 0,
57+});
58+});
59+60+it("hydrates deepseek-chat with 131k context", () => {
61+const providerConfig: ModelProviderConfig = {
62+baseUrl: "https://api.deepseek.com",
63+api: "openai-completions",
64+models: [
65+{
66+id: "deepseek-chat",
67+name: "DeepSeek Chat",
68+reasoning: false,
69+input: ["text"],
70+} as never,
71+],
72+};
73+74+const result = normalizeConfig({ provider: "deepseek", providerConfig });
75+const model = result.models[0];
76+expect(model.contextWindow).toBe(131_072);
77+});
78+79+it("preserves explicit user contextWindow override", () => {
80+const providerConfig: ModelProviderConfig = {
81+baseUrl: "https://api.deepseek.com",
82+api: "openai-completions",
83+models: [
84+{
85+id: "deepseek-v4-flash",
86+name: "DeepSeek V4 Flash",
87+reasoning: true,
88+input: ["text"],
89+contextWindow: 500_000,
90+} as never,
91+],
92+};
93+94+const result = normalizeConfig({ provider: "deepseek", providerConfig });
95+const model = result.models[0];
96+expect(model.contextWindow).toBe(500_000);
97+// cost should still be hydrated since it was missing
98+expect(model.cost).toEqual({
99+input: 0.14,
100+output: 0.28,
101+cacheRead: 0.028,
102+cacheWrite: 0,
103+});
104+});
105+106+it("preserves explicit user cost override", () => {
107+const userCost = { input: 99, output: 99, cacheRead: 99, cacheWrite: 99 };
108+const providerConfig: ModelProviderConfig = {
109+baseUrl: "https://api.deepseek.com",
110+api: "openai-completions",
111+models: [
112+{
113+id: "deepseek-v4-flash",
114+name: "DeepSeek V4 Flash",
115+reasoning: true,
116+input: ["text"],
117+cost: userCost,
118+} as never,
119+],
120+};
121+122+const result = normalizeConfig({ provider: "deepseek", providerConfig });
123+const model = result.models[0];
124+expect(model.cost).toEqual(userCost);
125+// contextWindow should still be hydrated since it was missing
126+expect(model.contextWindow).toBe(1_000_000);
127+});
128+129+it("preserves explicit user maxTokens override", () => {
130+const providerConfig: ModelProviderConfig = {
131+baseUrl: "https://api.deepseek.com",
132+api: "openai-completions",
133+models: [
134+{
135+id: "deepseek-v4-flash",
136+name: "DeepSeek V4 Flash",
137+reasoning: true,
138+input: ["text"],
139+maxTokens: 100_000,
140+} as never,
141+],
142+};
143+144+const result = normalizeConfig({ provider: "deepseek", providerConfig });
145+const model = result.models[0];
146+expect(model.maxTokens).toBe(100_000);
147+});
148+149+it("returns providerConfig unchanged when all models already have metadata", () => {
150+const providerConfig: ModelProviderConfig = {
151+baseUrl: "https://api.deepseek.com",
152+api: "openai-completions",
153+models: [
154+{
155+id: "deepseek-v4-flash",
156+name: "DeepSeek V4 Flash",
157+reasoning: true,
158+input: ["text"],
159+contextWindow: 1_000_000,
160+maxTokens: 384_000,
161+cost: { input: 0.14, output: 0.28, cacheRead: 0.028, cacheWrite: 0 },
162+} as never,
163+],
164+};
165+166+const result = normalizeConfig({ provider: "deepseek", providerConfig });
167+expect(result).toBe(providerConfig);
168+});
169+170+it("passes through unknown model ids unchanged", () => {
171+const providerConfig: ModelProviderConfig = {
172+baseUrl: "https://api.deepseek.com",
173+api: "openai-completions",
174+models: [
175+{
176+id: "deepseek-custom-finetune",
177+name: "Custom Fine-tune",
178+reasoning: false,
179+input: ["text"],
180+} as never,
181+],
182+};
183+184+const result = normalizeConfig({ provider: "deepseek", providerConfig });
185+expect(result).toBe(providerConfig);
186+});
187+188+it("returns providerConfig unchanged when models array is empty", () => {
189+const providerConfig: ModelProviderConfig = {
190+baseUrl: "https://api.deepseek.com",
191+api: "openai-completions",
192+models: [],
193+};
194+195+const result = normalizeConfig({ provider: "deepseek", providerConfig });
196+expect(result).toBe(providerConfig);
197+});
198+199+it("hydrates only the models that need it in a mixed list", () => {
200+const providerConfig: ModelProviderConfig = {
201+baseUrl: "https://api.deepseek.com",
202+api: "openai-completions",
203+models: [
204+{
205+id: "deepseek-v4-flash",
206+name: "DeepSeek V4 Flash",
207+reasoning: true,
208+input: ["text"],
209+contextWindow: 1_000_000,
210+maxTokens: 384_000,
211+cost: { input: 0.14, output: 0.28, cacheRead: 0.028, cacheWrite: 0 },
212+} as never,
213+{
214+id: "deepseek-v4-pro",
215+name: "DeepSeek V4 Pro",
216+reasoning: true,
217+input: ["text"],
218+} as never,
219+],
220+};
221+222+const result = normalizeConfig({ provider: "deepseek", providerConfig });
223+expect(result).not.toBe(providerConfig);
224+// First model should be unchanged (same reference)
225+expect(result.models[0]).toBe(providerConfig.models[0]);
226+// Second model should be hydrated
227+expect(result.models[1].contextWindow).toBe(1_000_000);
228+expect(result.models[1].cost).toEqual({
229+input: 1.74,
230+output: 3.48,
231+cacheRead: 0.145,
232+cacheWrite: 0,
233+});
234+});
235+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。