import type { StreamFn } from "openclaw/plugin-sdk/agent-core"; import { describe, expect, it } from "vitest"; import { createDeepSeekV4OpenAICompatibleThinkingWrapper, createAnthropicThinkingPrefillPayloadWrapper, createPayloadPatchStreamWrapper, defaultToolStreamExtraParams, isOpenAICompatibleThinkingEnabled, stripTrailingAnthropicAssistantPrefillWhenThinking, } from "./provider-stream-shared.js"; describe("defaultToolStreamExtraParams", () => { it("defaults tool_stream on when absent", () => { expect(defaultToolStreamExtraParams()).toEqual({ tool_stream: true }); expect(defaultToolStreamExtraParams({ fastMode: true })).toEqual({ fastMode: true, tool_stream: true, }); }); it("preserves explicit tool_stream values", () => { const enabled = { tool_stream: true, fastMode: true }; const disabled = { tool_stream: false, fastMode: true }; expect(defaultToolStreamExtraParams(enabled)).toBe(enabled); expect(defaultToolStreamExtraParams(disabled)).toBe(disabled); }); }); describe("isOpenAICompatibleThinkingEnabled", () => { it("uses explicit request reasoning before session thinking level", () => { expect( isOpenAICompatibleThinkingEnabled({ thinkingLevel: "high", options: { reasoning: "none" } as never, }), ).toBe(false); expect( isOpenAICompatibleThinkingEnabled({ thinkingLevel: "off", options: { reasoningEffort: "medium" } as never, }), ).toBe(true); }); it("treats off and none as disabled", () => { expect(isOpenAICompatibleThinkingEnabled({ thinkingLevel: "off", options: {} })).toBe(false); expect( isOpenAICompatibleThinkingEnabled({ thinkingLevel: "high", options: { reasoning: "none" } as never, }), ).toBe(false); }); it("defaults to enabled for missing or non-string values", () => { expect(isOpenAICompatibleThinkingEnabled({ thinkingLevel: undefined, options: {} })).toBe(true); expect( isOpenAICompatibleThinkingEnabled({ thinkingLevel: "off", options: { reasoning: { effort: "off" } } as never, }), ).toBe(true); }); }); describe("createDeepSeekV4OpenAICompatibleThinkingWrapper", () => { it("backfills reasoning_content on every replayed assistant message when thinking is enabled", () => { const payload = { messages: [ { role: "user", content: "read file" }, { role: "assistant", tool_calls: [{ id: "call_1", name: "read" }] }, { role: "tool", content: "ok" }, { role: "assistant", content: "done" }, { role: "assistant", content: "kept", reasoning_content: "native reasoning" }, ], }; const baseStreamFn: StreamFn = (_model, _context, options) => { options?.onPayload?.(payload as never, _model as never); return {} as ReturnType; }; const wrapped = createDeepSeekV4OpenAICompatibleThinkingWrapper({ baseStreamFn, thinkingLevel: "high", shouldPatchModel: () => true, }); void wrapped?.({} as never, {} as never, {}); expect(payload.messages[0]).not.toHaveProperty("reasoning_content"); expect(payload.messages[1]).toHaveProperty("reasoning_content", ""); expect(payload.messages[2]).not.toHaveProperty("reasoning_content"); expect(payload.messages[3]).toHaveProperty("reasoning_content", ""); expect(payload.messages[4]).toHaveProperty("reasoning_content", "native reasoning"); }); }); describe("createPayloadPatchStreamWrapper", () => { it("passes stream call options to payload patches", () => { let captured: Record = {}; const baseStreamFn: StreamFn = (_model, _context, options) => { const payload: Record = {}; options?.onPayload?.(payload, _model); captured = payload; return {} as ReturnType; }; const wrapped = createPayloadPatchStreamWrapper(baseStreamFn, ({ payload, options }) => { payload.reasoning = (options as { reasoning?: unknown } | undefined)?.reasoning; }); void wrapped( { id: "model" } as never, { messages: [] } as never, { reasoning: "medium", } as never, ); expect(captured).toEqual({ reasoning: "medium" }); }); it("calls the underlying stream directly when shouldPatch rejects the model", () => { let onPayloadWasInstalled = false; const baseStreamFn: StreamFn = (_model, _context, options) => { onPayloadWasInstalled = typeof options?.onPayload === "function"; return {} as ReturnType; }; const wrapped = createPayloadPatchStreamWrapper( baseStreamFn, ({ payload }) => { payload.unexpected = true; }, { shouldPatch: () => false }, ); void wrapped({ id: "model" } as never, { messages: [] } as never, {}); expect(onPayloadWasInstalled).toBe(false); }); }); describe("stripTrailingAnthropicAssistantPrefillWhenThinking", () => { it("removes trailing assistant text turns when Anthropic thinking is enabled", () => { const payload = { thinking: { type: "enabled", budget_tokens: 1024 }, messages: [ { role: "user", content: "Return JSON." }, { role: "assistant", content: "{" }, { role: "assistant", content: '"status"' }, ], }; expect(stripTrailingAnthropicAssistantPrefillWhenThinking(payload)).toBe(2); expect(payload.messages).toEqual([{ role: "user", content: "Return JSON." }]); }); it("preserves assistant tool-use turns across Anthropic and OpenAI-shaped payloads", () => { const anthropicPayload = { thinking: { type: "adaptive" }, messages: [ { role: "user", content: "Read a file." }, { role: "assistant", content: [{ type: "tool_use", id: "toolu_1", name: "Read" }] }, ], }; const openAiPayload = { thinking: { type: "adaptive" }, messages: [ { role: "user", content: "Read a file." }, { role: "assistant", content: [{ type: "toolCall", id: "call_1", name: "Read" }] }, ], }; const toolCallsPayload = { thinking: { type: "adaptive" }, messages: [{ role: "assistant", tool_calls: [{ id: "call_1", name: "Read" }] }], }; expect(stripTrailingAnthropicAssistantPrefillWhenThinking(anthropicPayload)).toBe(0); expect(stripTrailingAnthropicAssistantPrefillWhenThinking(openAiPayload)).toBe(0); expect(stripTrailingAnthropicAssistantPrefillWhenThinking(toolCallsPayload)).toBe(0); }); it("keeps assistant prefill when Anthropic thinking is disabled", () => { const payload = { thinking: { type: "disabled" }, messages: [ { role: "user", content: "Return JSON." }, { role: "assistant", content: "{" }, ], }; expect(stripTrailingAnthropicAssistantPrefillWhenThinking(payload)).toBe(0); expect(payload.messages).toHaveLength(2); }); }); describe("createAnthropicThinkingPrefillPayloadWrapper", () => { it("reports stripped assistant prefill count", () => { const payload = { thinking: { type: "enabled" }, messages: [ { role: "user", content: "Return JSON." }, { role: "assistant", content: "{" }, ], }; let strippedCount = 0; const baseStreamFn: StreamFn = (_model, _context, options) => { options?.onPayload?.(payload as never, _model as never); return {} as ReturnType; }; const wrapped = createAnthropicThinkingPrefillPayloadWrapper( baseStreamFn, (stripped) => { strippedCount = stripped; }, { shouldPatch: ({ model }) => model.api === "anthropic-messages" }, ); void wrapped({ api: "anthropic-messages" } as never, {} as never, {}); expect(payload.messages).toEqual([{ role: "user", content: "Return JSON." }]); expect(strippedCount).toBe(1); }); });