






















@@ -1,8 +1,12 @@
1+import type { Context, Model } from "@mariozechner/pi-ai";
2+import { createAssistantMessageEventStream } from "@mariozechner/pi-ai";
13import { describe, expect, it } from "vitest";
4+import { buildOpenAICompletionsParams } from "../../src/agents/openai-transport-stream.js";
25import { resolveProviderPluginChoice } from "../../src/plugins/provider-auth-choice.runtime.js";
36import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js";
47import { runSingleProviderCatalog } from "../test-support/provider-model-test-helpers.js";
58import deepseekPlugin from "./index.js";
9+import { createDeepSeekV4ThinkingWrapper } from "./stream.js";
610711describe("deepseek provider plugin", () => {
812it("registers DeepSeek with api-key auth wizard metadata", async () => {
@@ -28,14 +32,181 @@ describe("deepseek provider plugin", () => {
2832expect(catalogProvider.api).toBe("openai-completions");
2933expect(catalogProvider.baseUrl).toBe("https://api.deepseek.com");
3034expect(catalogProvider.models?.map((model) => model.id)).toEqual([
35+"deepseek-v4-flash",
36+"deepseek-v4-pro",
3137"deepseek-chat",
3238"deepseek-reasoner",
3339]);
40+expect(catalogProvider.models?.find((model) => model.id === "deepseek-v4-flash")).toMatchObject(
41+{
42+reasoning: true,
43+contextWindow: 1_000_000,
44+maxTokens: 384_000,
45+compat: expect.objectContaining({
46+supportsReasoningEffort: true,
47+maxTokensField: "max_tokens",
48+}),
49+},
50+);
3451expect(
3552catalogProvider.models?.find((model) => model.id === "deepseek-reasoner")?.reasoning,
3653).toBe(true);
3754});
385556+it("owns OpenAI-compatible replay policy", async () => {
57+const provider = await registerSingleProviderPlugin(deepseekPlugin);
58+59+expect(provider.buildReplayPolicy?.({ modelApi: "openai-completions" } as never)).toMatchObject(
60+{
61+sanitizeToolCallIds: true,
62+toolCallIdMode: "strict",
63+validateGeminiTurns: true,
64+validateAnthropicTurns: true,
65+},
66+);
67+});
68+69+it("maps thinking levels to DeepSeek V4 payload controls", async () => {
70+let capturedPayload: Record<string, unknown> | undefined;
71+const baseStreamFn = (
72+_model: Model<"openai-completions">,
73+_context: Context,
74+options?: { onPayload?: (payload: unknown) => unknown },
75+) => {
76+capturedPayload = {
77+model: "deepseek-v4-pro",
78+reasoning_effort: "high",
79+};
80+options?.onPayload?.(capturedPayload);
81+const stream = createAssistantMessageEventStream();
82+queueMicrotask(() => stream.end());
83+return stream;
84+};
85+86+const wrapThinkingOff = createDeepSeekV4ThinkingWrapper(baseStreamFn as never, "off");
87+expect(wrapThinkingOff).toBeDefined();
88+wrapThinkingOff?.(
89+{
90+provider: "deepseek",
91+id: "deepseek-v4-pro",
92+api: "openai-completions",
93+} as never,
94+{ messages: [] } as never,
95+{},
96+);
97+98+expect(capturedPayload).toMatchObject({ thinking: { type: "disabled" } });
99+expect(capturedPayload).not.toHaveProperty("reasoning_effort");
100+101+const wrapThinkingXhigh = createDeepSeekV4ThinkingWrapper(baseStreamFn as never, "xhigh");
102+expect(wrapThinkingXhigh).toBeDefined();
103+wrapThinkingXhigh?.(
104+{
105+provider: "deepseek",
106+id: "deepseek-v4-pro",
107+api: "openai-completions",
108+} as never,
109+{ messages: [] } as never,
110+{},
111+);
112+113+expect(capturedPayload).toMatchObject({
114+thinking: { type: "enabled" },
115+reasoning_effort: "max",
116+});
117+});
118+119+it("strips replayed reasoning_content when DeepSeek V4 thinking is disabled", async () => {
120+let capturedPayload: Record<string, unknown> | undefined;
121+const model = {
122+provider: "deepseek",
123+id: "deepseek-v4-flash",
124+name: "DeepSeek V4 Flash",
125+api: "openai-completions",
126+baseUrl: "https://api.deepseek.com",
127+reasoning: true,
128+input: ["text"],
129+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
130+contextWindow: 1_000_000,
131+maxTokens: 384_000,
132+compat: {
133+supportsUsageInStreaming: true,
134+supportsReasoningEffort: true,
135+maxTokensField: "max_tokens",
136+},
137+} as Model<"openai-completions">;
138+const context = {
139+messages: [
140+{ role: "user", content: "hi", timestamp: 1 },
141+{
142+role: "assistant",
143+api: "openai-completions",
144+provider: "deepseek",
145+model: "deepseek-v4-flash",
146+content: [
147+{
148+type: "thinking",
149+thinking: "call reasoning",
150+thinkingSignature: "reasoning_content",
151+},
152+{ type: "toolCall", id: "call_1", name: "read", arguments: {} },
153+],
154+usage: {
155+input: 0,
156+output: 0,
157+cacheRead: 0,
158+cacheWrite: 0,
159+totalTokens: 0,
160+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
161+},
162+stopReason: "toolUse",
163+timestamp: 2,
164+},
165+{
166+role: "toolResult",
167+toolCallId: "call_1",
168+toolName: "read",
169+content: [{ type: "text", text: "ok" }],
170+isError: false,
171+timestamp: 3,
172+},
173+],
174+tools: [
175+{
176+name: "read",
177+description: "Read data",
178+parameters: { type: "object", properties: {}, required: [], additionalProperties: false },
179+},
180+],
181+} as Context;
182+const baseStreamFn = (
183+streamModel: Model<"openai-completions">,
184+streamContext: Context,
185+options?: { onPayload?: (payload: unknown, model: unknown) => unknown },
186+) => {
187+capturedPayload = buildOpenAICompletionsParams(streamModel, streamContext, {
188+reasoning: "high",
189+} as never);
190+options?.onPayload?.(capturedPayload, streamModel);
191+const stream = createAssistantMessageEventStream();
192+queueMicrotask(() => stream.end());
193+return stream;
194+};
195+196+const wrapThinkingNone = createDeepSeekV4ThinkingWrapper(
197+baseStreamFn as never,
198+"none" as never,
199+);
200+expect(wrapThinkingNone).toBeDefined();
201+wrapThinkingNone?.(model, context, {});
202+203+expect(capturedPayload).toMatchObject({ thinking: { type: "disabled" } });
204+expect(capturedPayload).not.toHaveProperty("reasoning_effort");
205+expect((capturedPayload?.messages as Array<Record<string, unknown>>)[1]).not.toHaveProperty(
206+"reasoning_content",
207+);
208+});
209+39210it("publishes configured DeepSeek models through plugin-owned catalog augmentation", async () => {
40211const provider = await registerSingleProviderPlugin(deepseekPlugin);
41212此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。