import test from "node:test"; import assert from "node:assert/strict"; const schemaCoercion = await import("../../open-sse/translator/helpers/schemaCoercion.ts"); const openaiHelper = await import("../../open-sse/translator/helpers/openaiHelper.ts"); const claudeHelper = await import("../../open-sse/translator/helpers/claudeHelper.ts"); const geminiHelper = await import("../../open-sse/translator/helpers/geminiHelper.ts"); const toolCallHelper = await import("../../open-sse/translator/helpers/toolCallHelper.ts"); const { FORMATS } = await import("../../open-sse/translator/formats.ts"); const { translateRequest } = await import("../../open-sse/translator/index.ts"); const { cacheReasoningByKey, clearReasoningCacheAll, getReasoningCacheServiceStats } = await import("../../open-sse/services/reasoningCache.ts"); const { clearModelsDevCapabilities, saveModelsDevCapabilities } = await import("../../src/lib/modelsDevSync.ts"); function buildCapability(overrides = {}) { return { tool_call: null, reasoning: null, attachment: null, structured_output: null, temperature: null, modalities_input: "[]", modalities_output: "[]", knowledge_cutoff: null, release_date: null, last_updated: null, status: null, family: null, open_weights: null, limit_context: null, limit_input: null, limit_output: null, interleaved_field: null, ...overrides, }; } const originalMathRandom = Math.random; test.afterEach(() => { Math.random = originalMathRandom; }); test("schemaCoercion recursively coerces schema numeric fields across object variants", () => { const result = schemaCoercion.coerceSchemaNumericFields({ minimum: "1", maxItems: "5", properties: { nested: { minLength: "2", items: { maximum: "7" }, }, }, patternProperties: { "^x-": { minProperties: "1" }, }, definitions: { one: { exclusiveMaximum: "9" }, }, $defs: { two: { minItems: "3" }, }, dependentSchemas: { dep: { maxProperties: "4" }, }, additionalProperties: { maximum: "8" }, unevaluatedProperties: { minimum: "0" }, prefixItems: [{ minimum: "11" }], anyOf: [{ maximum: "12" }], oneOf: [{ minimum: "13" }], allOf: [{ maxLength: "14" }], not: { minimum: "15" }, if: { minimum: "16" }, then: { maximum: "17" }, else: { minItems: "18" }, }); assert.equal((result as any).minimum, 1); (assert as any).equal((result as any).maxItems, 5); (assert as any).equal((result as any).properties.nested.minLength, 2); assert.equal((result as any).properties.nested.items.maximum, 7); assert.equal((result as any).patternProperties["^x-"].minProperties, 1); assert.equal((result as any).definitions.one.exclusiveMaximum, 9); assert.equal((result as any).$defs.two.minItems, 3); assert.equal((result as any).dependentSchemas.dep.maxProperties, 4); assert.equal((result as any).additionalProperties.maximum, 8); assert.equal((result as any).unevaluatedProperties.minimum, 0); assert.equal((result as any).prefixItems[0].minimum, 11); assert.equal((result as any).anyOf[0].maximum, 12); assert.equal((result as any).oneOf[0].minimum, 13); assert.equal((result as any).allOf[0].maxLength, 14); assert.equal((result as any).not.minimum, 15); assert.equal((result as any).if.minimum, 16); assert.equal((result as any).then.maximum, 17); assert.equal((result as any).else.minItems, 18); assert.equal(schemaCoercion.coerceSchemaNumericFields("unchanged"), "unchanged"); assert.deepEqual(schemaCoercion.coerceSchemaNumericFields(["2", { minimum: "3" }]), [ "2", { minimum: 3 }, ]); }); test("schemaCoercion sanitizes descriptions, tool schemas, tool ids and deepseek reasoning placeholders", () => { const sanitizedOpenAI = schemaCoercion.sanitizeToolDescription({ type: "function", function: { name: "weather", description: 42 }, }); (assert as any).equal((sanitizedOpenAI as any).function.description, "42"); const sanitizedClaude = schemaCoercion.sanitizeToolDescription({ name: "weather", description: null, }); assert.equal((sanitizedClaude as any).description, ""); const sanitizedGemini = schemaCoercion.sanitizeToolDescription({ functionDeclarations: [{ name: "one", description: 12 }, { name: "two" }], }); assert.equal((sanitizedGemini as any).functionDeclarations[0].description, "12"); assert.equal((sanitizedGemini as any).functionDeclarations[1].name, "two"); assert.equal(schemaCoercion.sanitizeToolDescription("plain"), "plain"); const coercedTools = schemaCoercion.coerceToolSchemas([ { type: "function", function: { parameters: { minimum: "4" } }, }, { name: "claude-style", input_schema: { minItems: "2" }, }, { parameters: { maximum: "9" }, }, { functionDeclarations: [{ parameters: { minLength: "1" } }], }, "untouched", ]); assert.equal(coercedTools[0].function.parameters.minimum, 4); assert.equal(coercedTools[1].input_schema.minItems, 2); assert.equal(coercedTools[2].parameters.maximum, 9); assert.equal(coercedTools[3].functionDeclarations[0].parameters.minLength, 1); assert.equal(coercedTools[4], "untouched"); assert.equal(schemaCoercion.coerceToolSchemas("not-array"), "not-array"); const descriptionList = schemaCoercion.sanitizeToolDescriptions([{ description: 7 }, "raw"]); assert.equal(descriptionList[0].description, "7"); assert.equal(descriptionList[1], "raw"); assert.equal(schemaCoercion.sanitizeToolDescriptions("raw"), "raw"); assert.equal(schemaCoercion.sanitizeToolId("call.abc:123"), "call_abc_123"); assert.match(schemaCoercion.sanitizeToolId(""), /^tool_[a-z0-9_]+$/); assert.match(schemaCoercion.sanitizeToolId(undefined), /^tool_[a-z0-9_]+$/); const injected = schemaCoercion.injectEmptyReasoningContentForToolCalls( [ { role: "assistant", tool_calls: [{ id: "call_1" }] }, { role: "assistant", tool_calls: [{ id: "call_2" }], reasoning_content: "keep" }, { role: "user", tool_calls: [{ id: "call_3" }] }, ], "deepseek", "deepseek-v4-flash" ); assert.equal(injected[0].reasoning_content, ""); assert.equal(injected[1].reasoning_content, "keep"); assert.equal(injected[2].reasoning_content, undefined); assert.equal( schemaCoercion.injectEmptyReasoningContentForToolCalls( [{ role: "assistant" }], "openai", "gpt-4o" )[0].reasoning_content, undefined ); }); test("openaiHelper filters content, normalizes tools and removes OpenAI-incompatible fields", () => { const body = { messages: [ { role: "tool", content: "" }, { role: "assistant", tool_calls: [{ id: "call_1" }], content: "" }, { role: "assistant", content: [ { type: "thinking", thinking: "plan first" }, { type: "redacted_thinking", text: "skip" }, { type: "text", text: "visible text" }, { type: "image_url", image_url: { url: "https://example.com/a.png" }, signature: "x" }, { type: "tool_use", id: "call_1" }, { type: "tool_result", tool_use_id: "call_1", text: "done", cache_control: "drop" }, ], }, { role: "user", content: [{ type: "text", text: " " }] }, { role: "assistant", content: [{ type: "tool_result", tool_use_id: "call_2" }] }, ], tools: [ { name: "claude-tool", description: "Claude style", input_schema: { type: "object" }, }, { functionDeclarations: [ { name: "gemini-tool", description: "Gemini style", parameters: { type: "object" } }, ], }, { type: "function", function: { name: "openai-tool", parameters: { type: "object" } }, }, ], tool_choice: { type: "tool", name: "forced_tool" }, metadata: { remove: true }, anthropic_version: "2023-06-01", }; const result = openaiHelper.filterToOpenAIFormat(body); assert.equal(result.messages.length, 3); assert.equal(result.messages[2].reasoning_content, "plan first"); assert.deepEqual(result.messages[2].content, [ { type: "text", text: "visible text" }, { type: "image_url", image_url: { url: "https://example.com/a.png" } }, { type: "text", text: "[Tool Result: call_1]\ndone" }, ]); assert.equal(result.tools.length, 3); assert.equal(result.tools[0].function.name, "claude-tool"); assert.equal(result.tools[1].function.name, "gemini-tool"); assert.equal(result.tools[2].function.name, "openai-tool"); assert.deepEqual(result.tool_choice, { type: "function", function: { name: "forced_tool" } }); assert.equal("metadata" in result, false); assert.equal("anthropic_version" in result, false); }); test("openaiHelper keeps unmatched tool choices and deletes empty tools arrays", () => { const autoChoice = openaiHelper.filterToOpenAIFormat({ messages: [{ role: "assistant", content: "" }], tools: [], tool_choice: { type: "auto" }, }); assert.equal(autoChoice.tool_choice, "auto"); assert.equal("tools" in autoChoice, false); const requiredChoice = openaiHelper.filterToOpenAIFormat({ messages: [{ role: "assistant", content: "" }], tool_choice: { type: "any" }, }); assert.equal(requiredChoice.tool_choice, "required"); const untouched = { metadata: { keep: false } }; assert.deepEqual(openaiHelper.filterToOpenAIFormat(untouched), { metadata: { keep: false }, }); }); test("claudeHelper validates content, ordering and request preparation branches", () => { assert.equal(claudeHelper.hasValidContent({ content: " hello " }), true); assert.equal(claudeHelper.hasValidContent({ content: [{ type: "tool_use", id: "call" }] }), true); assert.equal(claudeHelper.hasValidContent({ content: [{ type: "text", text: " " }] }), false); assert.deepEqual(claudeHelper.fixToolUseOrdering([{ role: "user", content: "single" }]), [ { role: "user", content: "single" }, ]); const reordered = claudeHelper.fixToolUseOrdering([ { role: "assistant", content: [ { type: "text", text: "before" }, { type: "tool_use", id: "call_1", name: "lookup", input: {} }, { type: "text", text: "after" }, ], }, { role: "assistant", content: [{ type: "tool_result", tool_use_id: "call_1", content: [] }] }, ]); assert.deepEqual(reordered[0].content, [ { type: "tool_result", tool_use_id: "call_1", content: [] }, { type: "text", text: "before" }, { type: "tool_use", id: "call_1", name: "lookup", input: {} }, ]); // splitMisplacedToolResults: a tool_result whose tool_use_id was already // emitted by an earlier assistant turn is moved into the preceding user // message. The trailing tool_use survives on the assistant side. (#2815) const split = claudeHelper.splitMisplacedToolResults([ { role: "user", content: [{ type: "text", text: "q" }] }, { role: "assistant", content: [{ type: "tool_use", id: "call_x", name: "Read", input: {} }] }, { role: "assistant", content: [ { type: "tool_result", tool_use_id: "call_x", content: "ok" }, { type: "tool_use", id: "call_y", name: "Read", input: {} }, ], }, ]); assert.deepEqual(split, [ { role: "user", content: [{ type: "text", text: "q" }] }, { role: "assistant", content: [{ type: "tool_use", id: "call_x", name: "Read", input: {} }] }, { role: "user", content: [{ type: "tool_result", tool_use_id: "call_x", content: "ok" }] }, { role: "assistant", content: [{ type: "tool_use", id: "call_y", name: "Read", input: {} }] }, ]); // tool_result whose id has not been seen earlier is dropped — moving it // would just shift the 400 to "unexpected tool_use_id". const droppedOrphan = claudeHelper.splitMisplacedToolResults([ { role: "user", content: [{ type: "text", text: "q" }] }, { role: "assistant", content: [ { type: "tool_result", tool_use_id: "self-ref", content: "Skill not found" }, { type: "tool_use", id: "self-ref", name: "Read", input: {} }, ], }, ]); assert.deepEqual(droppedOrphan, [ { role: "user", content: [{ type: "text", text: "q" }] }, { role: "assistant", content: [{ type: "tool_use", id: "self-ref", name: "Read", input: {} }] }, ]); const prepared = claudeHelper.prepareClaudeRequest( { system: [ { type: "text", text: "one", cache_control: { type: "ephemeral" } }, { type: "text", text: "two", cache_control: { type: "ephemeral" } }, ], messages: [ { role: "user", content: [{ type: "text", text: "first question" }] }, { role: "assistant", content: "first answer" }, { role: "user", content: [{ type: "text", text: "follow up" }] }, { role: "assistant", content: [ { type: "text", text: "before tool" }, { type: "tool_use", id: "call_1", name: "lookup", input: {} }, { type: "text", text: "drop after" }, { type: "redacted_thinking", text: "keep" }, ], }, { role: "user", content: [ { type: "tool_result", tool_use_id: "call_1", content: "ok" }, { type: "tool_result", content: "drop missing id" }, ], }, { role: "assistant", content: [ { type: "thinking", thinking: "old", signature: "replace" }, { type: "tool_use", id: "call_2", name: " ", input: {} }, { type: "text", text: "" }, ], }, ], thinking: { type: "enabled" }, tools: [ { name: "", description: "drop me" }, { name: "deferred", defer_loading: true, cache_control: { type: "ephemeral" } }, { name: "kept-tool", cache_control: { type: "ephemeral" } }, ], }, "claude", false ); assert.deepEqual(prepared.system[0], { type: "text", text: "one" }); assert.deepEqual(prepared.system[1], { type: "text", text: "two", cache_control: { type: "ephemeral", ttl: "1h" }, }); assert.equal(prepared.messages.length, 6); assert.equal(prepared.messages[2].content.at(-1).cache_control.type, "ephemeral"); assert.equal(prepared.messages[4].content[0].type, "tool_result"); // messages[5] is the latest (and last) assistant message; Anthropic enforces // that its thinking blocks must remain verbatim — not rewritten to // redacted_thinking. The guard in prepareClaudeRequest preserves them. assert.deepEqual( prepared.messages[5].content.map((block) => block.type), ["thinking", "text"] ); assert.equal(prepared.messages[5].content[0].thinking, "old", "thinking text preserved verbatim"); assert.equal( prepared.messages[5].content[0].signature, "replace", "signature preserved verbatim" ); assert.equal( prepared.messages[5].content[0].data, undefined, "no data field on verbatim thinking" ); assert.equal(prepared.tools.length, 2); assert.equal(prepared.tools[0].cache_control, undefined); assert.deepEqual(prepared.tools[1].cache_control, { type: "ephemeral", ttl: "1h" }); const preserved = claudeHelper.prepareClaudeRequest( { messages: [ { role: "assistant", content: [{ type: "text", text: "keep cache", cache_control: { type: "ephemeral" } }], }, ], tools: [{ name: "kept", cache_control: { type: "ephemeral" } }], }, "openai", true ); assert.deepEqual(preserved.messages[0].content[0].cache_control, { type: "ephemeral" }); assert.deepEqual(preserved.tools[0].cache_control, { type: "ephemeral" }); }); test("geminiHelper converts content, safely parses JSON and cleans complex schemas", () => { assert.deepEqual(geminiHelper.convertOpenAIContentToParts("hello"), [{ text: "hello" }]); assert.deepEqual( geminiHelper.convertOpenAIContentToParts([ { type: "text", text: "hello" }, { type: "image_url", image_url: { url: "data:image/png;base64,abc" } }, { type: "file_url", file_url: { url: "not-a-data-url" } }, ]), [{ text: "hello" }, { inlineData: { mimeType: "image/png", data: "abc" } }] ); assert.equal( geminiHelper.extractTextContent([ { type: "text", text: "A" }, { type: "image_url", image_url: { url: "https://example.com" } }, { type: "text", text: "B" }, ]), "AB" ); assert.equal(geminiHelper.extractTextContent({ no: "text" }), ""); assert.deepEqual(geminiHelper.tryParseJSON('{"ok":true}'), { ok: true }); assert.equal(geminiHelper.tryParseJSON("{broken"), null); assert.equal(geminiHelper.tryParseJSON(42), 42); assert.match(geminiHelper.generateRequestId(), /^agent-/); assert.match(geminiHelper.generateSessionId(), /^-/); const schema = { type: ["null", "object"], properties: {}, required: ["missing"], anyOf: [{ type: "null" }, { type: "string", enum: [1, 2] }], oneOf: [{ type: "null" }, { type: "array", items: { type: "integer", enum: [1, 2] } }], allOf: [ { properties: { a: { type: "string", minLength: 2 } }, required: ["a"] }, { properties: { b: { const: "fixed" } }, required: ["b"] }, ], additionalProperties: false, patternProperties: { "^x-": { type: "number" } }, if: { type: "string" }, then: { type: "string" }, else: { type: "string" }, default: "remove", examples: ["remove"], }; const cleaned = geminiHelper.cleanJSONSchemaForAntigravity(schema); assert.equal(cleaned.type, "array"); assert.deepEqual(cleaned.required.sort(), ["a", "b"]); assert.equal(cleaned.properties.a.minLength, undefined); assert.deepEqual(cleaned.properties.b.enum, ["fixed"]); assert.deepEqual(cleaned.enum, ["1", "2"]); assert.equal(cleaned.items.type, "integer"); assert.equal(cleaned.additionalProperties, undefined); assert.equal(cleaned.patternProperties, undefined); assert.equal(cleaned.if, undefined); assert.equal(cleaned.default, undefined); assert.equal(cleaned.examples, undefined); const placeholder = geminiHelper.cleanJSONSchemaForAntigravity({ type: "object", properties: {}, }); assert.deepEqual(placeholder.required, ["reason"]); assert.equal(placeholder.properties.reason.type, "string"); }); test("toolCallHelper normalizes ids, links tool responses and inserts missing tool results", () => { let randomCalls = 0; Math.random = () => ((randomCalls++ % 50) + 1) / 100; const body = toolCallHelper.ensureToolCallIds( { messages: [ { role: "assistant", tool_calls: [ { function: { name: "first", arguments: { city: "Tokyo" } } }, { id: " ", function: { name: "second", arguments: "{}" } }, ], }, { role: "tool", content: "first result" }, { role: "tool", content: "second result" }, ], }, { use9CharId: true } ); assert.equal(body.messages[0].tool_calls[0].type, "function"); assert.equal(typeof body.messages[0].tool_calls[0].function.arguments, "string"); assert.match(body.messages[0].tool_calls[0].id, /^[a-zA-Z0-9]{9}$/); assert.match(body.messages[1].tool_call_id, /^[a-zA-Z0-9]{9}$/); assert.match(body.messages[2].tool_call_id, /^[a-zA-Z0-9]{9}$/); const missingResponseFixed = toolCallHelper.fixMissingToolResponses({ messages: [ { role: "assistant", tool_calls: [{ id: "call_a", function: { name: "lookup", arguments: "{}" } }], }, { role: "user", content: "no tool result here" }, { role: "assistant", content: [{ type: "tool_use", id: "call_b", name: "search", input: {} }], }, { role: "user", content: [{ type: "tool_result", tool_use_id: "call_b", content: "done" }], }, ], }); assert.equal(missingResponseFixed.messages[1].role, "tool"); assert.equal(missingResponseFixed.messages[1].tool_call_id, "call_a"); assert.equal(missingResponseFixed.messages[1].content, ""); assert.deepEqual( toolCallHelper.getToolCallIds({ role: "assistant", tool_calls: [{ id: "call_a" }], content: [{ type: "tool_use", id: "call_b" }], }), ["call_a", "call_b"] ); assert.equal(toolCallHelper.getToolCallIds({ role: "user" }).length, 0); assert.equal( toolCallHelper.hasToolResults({ role: "tool", tool_call_id: "call_a" }, ["call_a"]), true ); assert.equal( toolCallHelper.hasToolResults( { role: "user", content: [{ type: "tool_result", tool_use_id: "call_b" }] }, ["call_b"] ), true ); assert.equal(toolCallHelper.hasToolResults({ role: "user", content: [] }, []), false); assert.deepEqual(toolCallHelper.fixMissingToolResponses({ messages: null }), { messages: null }); }); test("fixMissingToolResponses inserts Claude tool_result block when assistant uses Claude shape", () => { const fixed = toolCallHelper.fixMissingToolResponses({ messages: [ { role: "user", content: [{ type: "text", text: "do it" }] }, { role: "assistant", content: [ { type: "tool_use", id: "tool_a", name: "bash", input: { cmd: "ls" } }, { type: "tool_use", id: "tool_b", name: "bash", input: { cmd: "pwd" } }, ], }, { role: "user", content: [{ type: "text", text: "continue" }] }, ], }); assert.equal(fixed.messages.length, 4); const inserted = fixed.messages[2]; assert.equal(inserted.role, "user"); assert.ok(Array.isArray(inserted.content)); assert.equal(inserted.content.length, 2); assert.equal(inserted.content[0].type, "tool_result"); assert.equal(inserted.content[0].tool_use_id, "tool_a"); assert.equal(inserted.content[0].content, ""); assert.equal(inserted.content[1].tool_use_id, "tool_b"); }); test("fixMissingToolResponses keeps OpenAI role:tool when assistant uses OpenAI tool_calls", () => { const fixed = toolCallHelper.fixMissingToolResponses({ messages: [ { role: "assistant", tool_calls: [ { id: "call_a", type: "function", function: { name: "lookup", arguments: "{}" } }, { id: "call_b", type: "function", function: { name: "search", arguments: "{}" } }, ], }, { role: "user", content: "no tool result here" }, ], }); assert.equal(fixed.messages.length, 4); assert.equal(fixed.messages[1].role, "tool"); assert.equal(fixed.messages[1].tool_call_id, "call_a"); assert.equal(fixed.messages[2].role, "tool"); assert.equal(fixed.messages[2].tool_call_id, "call_b"); }); test("translateRequest replays cached reasoning-only messages when interleaved field is reasoning_content", () => { clearReasoningCacheAll(); clearModelsDevCapabilities(); saveModelsDevCapabilities({ deepseek: { "deepseek-v4-flash": buildCapability({ interleaved_field: "reasoning_content", reasoning: true, tool_call: true, }), }, }); cacheReasoningByKey( "request:req_reasoning_only:message:0", "deepseek", "deepseek-v4-flash", "cached reasoning only" ); const result = translateRequest( FORMATS.OPENAI, FORMATS.OPENAI, "deepseek-v4-flash", { _reasoningCacheRequestId: "req_reasoning_only", messages: [ { role: "user", content: "solve this" }, { role: "assistant", content: "answer", reasoning_content: "" }, ], }, false, null, "deepseek" ); assert.equal(result.messages[1].reasoning_content, "cached reasoning only"); assert.equal(getReasoningCacheServiceStats().replays, 1); clearModelsDevCapabilities(); clearReasoningCacheAll(); }); test("translateRequest does not replay reasoning-only messages for non-DeepSeek models", () => { clearReasoningCacheAll(); cacheReasoningByKey( "request:req_kimi_reasoning_only:message:0", "kimi", "kimi-k2.5", "cached kimi reasoning" ); const result = translateRequest( FORMATS.OPENAI, FORMATS.OPENAI, "kimi-k2.5", { _reasoningCacheRequestId: "req_kimi_reasoning_only", messages: [ { role: "user", content: "solve this" }, { role: "assistant", content: "answer", reasoning_content: "" }, ], }, false, null, "kimi" ); assert.equal(result.messages[1].reasoning_content, undefined); assert.equal(getReasoningCacheServiceStats().replays, 0); clearReasoningCacheAll(); }); test("translateRequest injects thinking block into Claude-format messages for Kimi K2 reasoning models", () => { clearReasoningCacheAll(); clearModelsDevCapabilities(); saveModelsDevCapabilities({ "kimi-coding": { "kimi-k2.5": buildCapability({ interleaved_field: "reasoning_content", reasoning: true, tool_call: true, }), }, }); cacheReasoningByKey( "toolu_kimi_claude", "kimi-coding", "kimi-k2.5", "cached thinking for Kimi tool call" ); // Claude-format request: assistant has tool_use in content[] but NO thinking block // This simulates the scenario that causes infinite loops const result = translateRequest( FORMATS.OPENAI, FORMATS.CLAUDE, "kimi-k2.5", { thinking: { type: "enabled", budget_tokens: 2000 }, messages: [ { role: "user", content: "read the file" }, { role: "assistant", content: [ { type: "tool_use", id: "toolu_kimi_claude", name: "read_file", input: { path: "test.ts" }, }, ], }, { role: "tool", tool_call_id: "toolu_kimi_claude", content: "file data" }, ], }, false, null, "kimi-coding" ); const assistantMsg = result.messages.find((m) => m.role === "assistant"); assert.ok(assistantMsg, "assistant message should exist"); assert.ok(Array.isArray(assistantMsg.content), "content should be array"); // Should have a thinking block injected before tool_use const thinkingBlock = assistantMsg.content.find((b) => b?.type === "thinking"); assert.ok(thinkingBlock, "thinking block should be injected"); assert.equal( thinkingBlock.thinking, "cached thinking for Kimi tool call", "should use cached reasoning" ); // Thinking block should appear before tool_use const thinkingIdx = assistantMsg.content.indexOf(thinkingBlock); const toolUseIdx = assistantMsg.content.findIndex((b) => b?.type === "tool_use"); assert.ok(thinkingIdx < toolUseIdx, "thinking block should be before tool_use"); assert.equal(getReasoningCacheServiceStats().replays, 1); clearModelsDevCapabilities(); clearReasoningCacheAll(); }); test("translateRequest injects placeholder thinking block for Claude-format Kimi K2 on cache miss", () => { clearReasoningCacheAll(); clearModelsDevCapabilities(); saveModelsDevCapabilities({ "kimi-coding": { "kimi-k2.6": buildCapability({ interleaved_field: "reasoning_content", reasoning: true, tool_call: true, }), }, }); // No cache seeded - should fall back to placeholder const result = translateRequest( FORMATS.OPENAI, FORMATS.CLAUDE, "kimi-k2.6", { thinking: { type: "enabled", budget_tokens: 2000 }, messages: [ { role: "user", content: "do it" }, { role: "assistant", content: [ { type: "tool_use", id: "toolu_miss", name: "bash", input: { command: "ls" } }, ], }, { role: "tool", tool_call_id: "toolu_miss", content: "output" }, ], }, false, null, "kimi-coding" ); const assistantMsg = result.messages.find((m) => m.role === "assistant"); assert.ok(assistantMsg, "assistant message should exist"); const thinkingBlock = Array.isArray(assistantMsg.content) && assistantMsg.content.find((b) => b?.type === "thinking"); assert.ok(thinkingBlock, "thinking block should be injected on cache miss"); // Must be non-empty for kimi-coding assert.ok( thinkingBlock.thinking && thinkingBlock.thinking.length > 0, "placeholder must be non-empty" ); clearModelsDevCapabilities(); clearReasoningCacheAll(); }); test("translateRequest does NOT inject duplicate thinking for Claude-format messages with existing thinking block", () => { clearReasoningCacheAll(); clearModelsDevCapabilities(); saveModelsDevCapabilities({ "kimi-coding": { "kimi-k2.5": buildCapability({ interleaved_field: "reasoning_content", reasoning: true, tool_call: true, }), }, }); const result = translateRequest( FORMATS.OPENAI, FORMATS.CLAUDE, "kimi-k2.5", { messages: [ { role: "user", content: "hi" }, { role: "assistant", content: [ { type: "thinking", thinking: "I already have this" }, { type: "tool_use", id: "toolu_existing", name: "read", input: {} }, ], }, { role: "tool", tool_call_id: "toolu_existing", content: "data" }, ], }, false, null, "kimi-coding" ); const assistantMsg = result.messages.find((m) => m.role === "assistant"); const thinkingBlocks = Array.isArray(assistantMsg.content) && assistantMsg.content.filter((b) => b?.type === "thinking"); assert.equal( thinkingBlocks?.length, 1, "should have exactly one thinking block (no duplicate)" ); assert.equal( thinkingBlocks[0].thinking, "I already have this", "original thinking should be preserved" ); clearModelsDevCapabilities(); clearReasoningCacheAll(); });