Files
diegosouzapw--omniroute/tests/unit/translator-helper-branches.test.ts
2026-07-13 13:39:12 +08:00

870 lines
30 KiB
TypeScript

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("fallbackToolCallId returns the right id shape with and without an index", () => {
const noIndex = toolCallHelper.fallbackToolCallId();
assert.match(
noIndex,
/^call_\d+$/,
"no-index form must be `call_<ts>` (matches kiro/openai-responses fallback shape)"
);
const withIndex = toolCallHelper.fallbackToolCallId(2);
assert.match(
withIndex,
/^call_2_\d+$/,
"index form must be `call_<i>_<ts>` (matches indexed fallback shape)"
);
// index 0 is falsy but defined — must still produce the indexed form, not the no-index form.
const zeroIndex = toolCallHelper.fallbackToolCallId(0);
assert.match(zeroIndex, /^call_0_\d+$/, "index 0 must use the indexed form, not the bare form");
});
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();
});