// Deepseek tests cover index plugin behavior. import type { Context, Model } from "openclaw/plugin-sdk/llm"; import { createAssistantMessageEventStream } from "openclaw/plugin-sdk/llm"; import { registerSingleProviderPlugin, resolveProviderPluginChoice, } from "openclaw/plugin-sdk/plugin-test-runtime"; import { buildOpenAICompletionsParams } from "openclaw/plugin-sdk/provider-transport-runtime"; import { createProviderUsageFetch, makeResponse } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it } from "vitest"; import { runSingleProviderCatalog } from "../test-support/provider-model-test-helpers.js"; import deepseekPlugin from "./index.js"; import { createDeepSeekV4ThinkingWrapper } from "./stream.js"; type OpenAICompletionsModel = Model<"openai-completions">; type PayloadCapture = { payload?: Record; }; type ThinkingPayload = { type?: unknown; }; type ReplayToolCall = { id?: unknown; type?: unknown; function?: { name?: unknown; arguments?: unknown; }; }; type RegisteredProvider = Awaited>; const emptyUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, }; function requireThinkingProfileResolver( provider: RegisteredProvider, ): NonNullable { if (!provider.resolveThinkingProfile) { throw new Error("DeepSeek provider did not register a thinking profile resolver"); } return provider.resolveThinkingProfile; } const readToolCall = { type: "toolCall", id: "call_1", name: "read", arguments: {} }; const readToolResult = { role: "toolResult", toolCallId: "call_1", toolName: "read", content: [{ type: "text", text: "ok" }], isError: false, timestamp: 3, }; const readTool = { name: "read", description: "Read data", parameters: { type: "object", properties: {}, required: [], additionalProperties: false }, }; function deepSeekV4Model(id: "deepseek-v4-flash" | "deepseek-v4-pro"): OpenAICompletionsModel { return { provider: "deepseek", id, name: id === "deepseek-v4-flash" ? "DeepSeek V4 Flash" : "DeepSeek V4 Pro", api: "openai-completions", baseUrl: "https://api.deepseek.com", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1_000_000, maxTokens: 384_000, compat: { supportsUsageInStreaming: true, supportsReasoningEffort: true, maxTokensField: "max_tokens", }, } as OpenAICompletionsModel; } function replayAssistantMessage(params: { provider: string; model: string; content: Array>; stopReason: "stop" | "toolUse"; }) { return { role: "assistant", api: "openai-completions", provider: params.provider, model: params.model, content: params.content, usage: emptyUsage, stopReason: params.stopReason, timestamp: 2, }; } function readToolReplayContext(assistantMessage: ReturnType) { return { messages: [{ role: "user", content: "hi", timestamp: 1 }, assistantMessage, readToolResult], tools: [readTool], } as Context; } function deepSeekReasoningToolReplayContext() { return readToolReplayContext( replayAssistantMessage({ provider: "deepseek", model: "deepseek-v4-flash", content: [ { type: "thinking", thinking: "call reasoning", thinkingSignature: "reasoning_content", }, readToolCall, ], stopReason: "toolUse", }), ); } function createPayloadCapturingStream(capture: PayloadCapture) { return ( streamModel: OpenAICompletionsModel, streamContext: Context, options?: { onPayload?: (payload: unknown, model: unknown) => unknown }, ) => { capture.payload = buildOpenAICompletionsParams(streamModel, streamContext, { reasoning: "high", } as never); options?.onPayload?.(capture.payload, streamModel); const stream = createAssistantMessageEventStream(); queueMicrotask(() => stream.end()); return stream; }; } function requireThinkingWrapper( wrapper: ReturnType, label: string, ): NonNullable> { if (!wrapper) { throw new Error(`expected DeepSeek thinking wrapper for ${label}`); } return wrapper; } function readThinking(payload: Record | undefined): ThinkingPayload | undefined { return payload?.thinking as ThinkingPayload | undefined; } function readPayloadMessage( capture: PayloadCapture, index: number, ): Record | undefined { return (capture.payload?.messages as Array> | undefined)?.[index]; } function readFirstToolCall( message: Record | undefined, ): ReplayToolCall | undefined { return (message?.tool_calls as ReplayToolCall[] | undefined)?.[0]; } describe("deepseek provider plugin", () => { it("registers DeepSeek with api-key auth wizard metadata", async () => { const provider = await registerSingleProviderPlugin(deepseekPlugin); const resolved = resolveProviderPluginChoice({ providers: [provider], choice: "deepseek-api-key", }); expect(provider.id).toBe("deepseek"); expect(provider.label).toBe("DeepSeek"); expect(provider.envVars).toEqual(["DEEPSEEK_API_KEY"]); expect(provider.auth).toHaveLength(1); if (!resolved) { throw new Error("expected DeepSeek api-key auth choice"); } expect(resolved.provider.id).toBe("deepseek"); expect(resolved.method.id).toBe("api-key"); }); it("builds the static DeepSeek model catalog", async () => { const provider = await registerSingleProviderPlugin(deepseekPlugin); const catalogProvider = await runSingleProviderCatalog(provider); expect(catalogProvider.api).toBe("openai-completions"); expect(catalogProvider.baseUrl).toBe("https://api.deepseek.com"); expect(catalogProvider.models?.map((model) => model.id)).toEqual([ "deepseek-v4-flash", "deepseek-v4-pro", "deepseek-chat", "deepseek-reasoner", ]); const flashModel = catalogProvider.models?.find((model) => model.id === "deepseek-v4-flash"); expect(flashModel?.reasoning).toBe(true); expect(flashModel?.contextWindow).toBe(1_000_000); expect(flashModel?.maxTokens).toBe(384_000); expect(flashModel?.compat?.supportsReasoningEffort).toBe(true); expect(flashModel?.compat?.maxTokensField).toBe("max_tokens"); expect( catalogProvider.models?.find((model) => model.id === "deepseek-reasoner")?.reasoning, ).toBe(true); expect( Object.fromEntries( (catalogProvider.models ?? []).map((model) => [ model.id, { contextWindow: model.contextWindow, maxTokens: model.maxTokens, cost: model.cost, }, ]), ), ).toEqual({ "deepseek-v4-flash": { contextWindow: 1_000_000, maxTokens: 384_000, cost: { input: 0.14, output: 0.28, cacheRead: 0.0028, cacheWrite: 0 }, }, "deepseek-v4-pro": { contextWindow: 1_000_000, maxTokens: 384_000, cost: { input: 0.435, output: 0.87, cacheRead: 0.003625, cacheWrite: 0 }, }, "deepseek-chat": { contextWindow: 1_000_000, maxTokens: 384_000, cost: { input: 0.14, output: 0.28, cacheRead: 0.0028, cacheWrite: 0 }, }, "deepseek-reasoner": { contextWindow: 1_000_000, maxTokens: 384_000, cost: { input: 0.14, output: 0.28, cacheRead: 0.0028, cacheWrite: 0 }, }, }); }); it("resolves API-key usage auth from DeepSeek config sources", async () => { const provider = await registerSingleProviderPlugin(deepseekPlugin); await expect( provider.resolveUsageAuth?.({ env: {}, resolveApiKeyFromConfigAndStore: (options?: { envDirect?: Array }) => { expect(options?.envDirect).toEqual([undefined]); return "config-deepseek-key"; }, } as never), ).resolves.toEqual({ token: "config-deepseek-key" }); }); it("fetches DeepSeek usage balance through the provider hook", async () => { const provider = await registerSingleProviderPlugin(deepseekPlugin); const mockFetch = createProviderUsageFetch(async () => makeResponse(200, { is_available: true, balance_infos: [{ currency: "CNY", total_balance: "8.88", granted_balance: "1.00" }], }), ); await expect( provider.fetchUsageSnapshot?.({ token: "deepseek-key", timeoutMs: 5000, fetchFn: mockFetch, } as never), ).resolves.toMatchObject({ provider: "deepseek", displayName: "DeepSeek", windows: [], summary: "Balance ¥8.88 · Granted ¥1.00", }); }); it("owns OpenAI-compatible replay policy", async () => { const provider = await registerSingleProviderPlugin(deepseekPlugin); const replayPolicy = provider.buildReplayPolicy?.({ modelApi: "openai-completions" } as never); expect(replayPolicy?.sanitizeToolCallIds).toBe(true); expect(replayPolicy?.toolCallIdMode).toBe("strict"); expect(replayPolicy?.validateGeminiTurns).toBe(true); expect(replayPolicy?.validateAnthropicTurns).toBe(true); }); it("owns DeepSeek tool schema compatibility for MCP union schemas", async () => { const provider = await registerSingleProviderPlugin(deepseekPlugin); const mcpTool = { name: "unusual-whales__get_balance_sheet_screener", description: "", parameters: { type: "object", properties: { date: { anyOf: [{ type: "string" }, { type: "integer" }], }, period: { oneOf: [{ type: "string" }, { type: "null" }], }, }, }, execute: () => undefined, } as never; const normalized = provider.normalizeToolSchemas?.({ provider: "deepseek", modelId: "deepseek-v4-pro", modelApi: "openai-completions", model: deepSeekV4Model("deepseek-v4-pro"), tools: [mcpTool], } as never); expect(normalized?.[0]?.parameters).toEqual({ type: "object", properties: { date: { type: "string" }, period: { type: "string", nullable: true }, }, }); expect( provider.inspectToolSchemas?.({ provider: "deepseek", modelId: "deepseek-v4-pro", modelApi: "openai-completions", model: deepSeekV4Model("deepseek-v4-pro"), tools: normalized ?? [], } as never), ).toStrictEqual([]); }); it("advertises max thinking levels for DeepSeek V4 models only", async () => { const provider = await registerSingleProviderPlugin(deepseekPlugin); const resolveThinkingProfile = requireThinkingProfileResolver(provider); const expectedV4Levels = ["off", "minimal", "low", "medium", "high", "xhigh", "max"]; expect( resolveThinkingProfile({ provider: "deepseek", modelId: "deepseek-v4-pro", } as never)?.levels.map((level) => level.id), ).toEqual(expectedV4Levels); expect( resolveThinkingProfile({ provider: "deepseek", modelId: "deepseek-v4-flash", } as never)?.defaultLevel, ).toBe("high"); expect( resolveThinkingProfile({ provider: "deepseek", modelId: "deepseek-v4-flash", } as never)?.levels.map((level) => level.id), ).toEqual(expectedV4Levels); expect( resolveThinkingProfile({ provider: "deepseek", modelId: "deepseek-chat" } as never), ).toBe(undefined); expect( resolveThinkingProfile({ provider: "deepseek", modelId: "deepseek-reasoner" } as never), ).toBe(undefined); }); it("maps thinking levels to DeepSeek V4 payload controls", async () => { let capturedPayload: Record | undefined; const baseStreamFn = ( _model: Model<"openai-completions">, _context: Context, options?: { onPayload?: (payload: unknown) => unknown }, ) => { capturedPayload = { model: "deepseek-v4-pro", reasoning_effort: "high", }; options?.onPayload?.(capturedPayload); const stream = createAssistantMessageEventStream(); queueMicrotask(() => stream.end()); return stream; }; const wrapThinkingOff = requireThinkingWrapper( createDeepSeekV4ThinkingWrapper(baseStreamFn as never, "off"), "off", ); await wrapThinkingOff( { provider: "deepseek", id: "deepseek-v4-pro", api: "openai-completions", } as never, { messages: [] } as never, {}, ); expect(readThinking(capturedPayload)?.type).toBe("disabled"); expect(capturedPayload).not.toHaveProperty("reasoning_effort"); const wrapThinkingXhigh = requireThinkingWrapper( createDeepSeekV4ThinkingWrapper(baseStreamFn as never, "xhigh"), "xhigh", ); await wrapThinkingXhigh( { provider: "deepseek", id: "deepseek-v4-pro", api: "openai-completions", } as never, { messages: [] } as never, {}, ); expect(readThinking(capturedPayload)?.type).toBe("enabled"); expect(capturedPayload?.reasoning_effort).toBe("max"); }); it("preserves replayed reasoning_content when DeepSeek V4 thinking is enabled", async () => { const capture: PayloadCapture = {}; const model = deepSeekV4Model("deepseek-v4-flash"); const context = deepSeekReasoningToolReplayContext(); const baseStreamFn = createPayloadCapturingStream(capture); const wrapThinkingHigh = requireThinkingWrapper( createDeepSeekV4ThinkingWrapper(baseStreamFn as never, "high"), "high", ); await wrapThinkingHigh(model, context, {}); expect(readThinking(capture.payload)?.type).toBe("enabled"); expect(capture.payload?.reasoning_effort).toBe("high"); const assistantMessage = readPayloadMessage(capture, 1); expect(assistantMessage?.role).toBe("assistant"); expect(assistantMessage?.reasoning_content).toBe("call reasoning"); const toolCall = readFirstToolCall(assistantMessage); expect(toolCall?.id).toBe("call_1"); expect(toolCall?.type).toBe("function"); expect(toolCall?.function?.name).toBe("read"); expect(toolCall?.function?.arguments).toBe("{}"); }); it("adds blank reasoning_content for replayed tool calls from non-DeepSeek turns", async () => { const capture: PayloadCapture = {}; const model = deepSeekV4Model("deepseek-v4-pro"); const context = readToolReplayContext( replayAssistantMessage({ provider: "openai", model: "gpt-5.4", content: [readToolCall], stopReason: "toolUse", }), ); const baseStreamFn = createPayloadCapturingStream(capture); const wrapThinkingHigh = requireThinkingWrapper( createDeepSeekV4ThinkingWrapper(baseStreamFn as never, "high"), "high", ); await wrapThinkingHigh(model, context, {}); const assistantMessage = readPayloadMessage(capture, 1); expect(assistantMessage?.role).toBe("assistant"); expect(assistantMessage?.reasoning_content).toBe(""); const toolCall = readFirstToolCall(assistantMessage); expect(toolCall?.id).toBe("call_1"); expect(toolCall?.type).toBe("function"); expect(toolCall?.function?.name).toBe("read"); expect(toolCall?.function?.arguments).toBe("{}"); }); it("adds blank reasoning_content for replayed plain assistant messages", async () => { const capture: PayloadCapture = {}; const model = deepSeekV4Model("deepseek-v4-pro"); const context = { messages: [ { role: "user", content: "hi", timestamp: 1 }, replayAssistantMessage({ provider: "openai", model: "gpt-5.4", content: [{ type: "text", text: "Hello." }], stopReason: "stop", }), { role: "user", content: "next", timestamp: 3 }, ], } as Context; const baseStreamFn = createPayloadCapturingStream(capture); const wrapThinkingHigh = requireThinkingWrapper( createDeepSeekV4ThinkingWrapper(baseStreamFn as never, "high"), "high", ); await wrapThinkingHigh(model, context, {}); const assistantMessage = readPayloadMessage(capture, 1); expect(assistantMessage?.role).toBe("assistant"); expect(assistantMessage?.content).toBe("Hello."); expect(assistantMessage?.reasoning_content).toBe(""); }); it("strips replayed reasoning_content when DeepSeek V4 thinking is disabled", async () => { const capture: PayloadCapture = {}; const model = deepSeekV4Model("deepseek-v4-flash"); const context = deepSeekReasoningToolReplayContext(); const baseStreamFn = createPayloadCapturingStream(capture); const wrapThinkingNone = requireThinkingWrapper( createDeepSeekV4ThinkingWrapper(baseStreamFn as never, "none" as never), "none", ); await wrapThinkingNone(model, context, {}); expect(readThinking(capture.payload)?.type).toBe("disabled"); expect(capture.payload).not.toHaveProperty("reasoning_effort"); expect((capture.payload!.messages as Array>)[1]).not.toHaveProperty( "reasoning_content", ); }); it("publishes configured DeepSeek models through plugin-owned catalog augmentation", async () => { const provider = await registerSingleProviderPlugin(deepseekPlugin); expect( provider.augmentModelCatalog?.({ config: { models: { providers: { deepseek: { models: [ { id: "deepseek-chat", name: "DeepSeek Chat", input: ["text"], reasoning: false, contextWindow: 65536, }, ], }, }, }, }, } as never), ).toEqual([ { provider: "deepseek", id: "deepseek-chat", name: "DeepSeek Chat", input: ["text"], reasoning: false, contextWindow: 65536, }, ]); }); });