Files
diegosouzapw--omniroute/tests/unit/translator-thinking-provider-compat-2043.test.ts
2026-07-13 13:39:12 +08:00

188 lines
7.1 KiB
TypeScript

/**
* Tests for provider thinking compatibility fixes ported from decolua/9router#2043.
*
* Covers:
* (a) DeepSeek unsigned thinking placeholders — no `signature` field in injected blocks.
* (b) DeepSeek keeps existing thinking blocks as-is (not overwritten by cache/placeholder).
* (c) Gemini `reasoning_effort: "auto"` maps to high budget (not silent fallback to default).
* (d) Gemini `reasoning_effort: "max"` / `"xhigh"` clamp to high budget.
*/
import test from "node:test";
import assert from "node:assert/strict";
const { prepareClaudeRequest } = await import("../../open-sse/translator/helpers/claudeHelper.ts");
const reasoningCache = await import("../../open-sse/services/reasoningCache.ts");
const { openaiToGeminiRequest } =
await import("../../open-sse/translator/request/openai-to-gemini.ts");
const { capThinkingBudget } = await import("../../src/lib/modelCapabilities.ts");
// ──────────────── Fix (a): DeepSeek placeholder has NO signature field ────────────────
test("deepseek: injected thinking placeholder has no signature field", () => {
reasoningCache.clearReasoningCacheAll();
const body = {
model: "deepseek-v4-pro",
thinking: { type: "enabled", budget_tokens: 4096 },
messages: [
{ role: "user", content: [{ type: "text", text: "q" }] },
{
role: "assistant",
content: [{ type: "tool_use", id: "toolu_1", name: "Read", input: { file_path: "x" } }],
},
{
role: "user",
content: [{ type: "tool_result", tool_use_id: "toolu_1", content: "ok" }],
},
],
};
const out = prepareClaudeRequest(body as any, "deepseek");
const assistant = (out as any).messages.find((m: any) => m.role === "assistant");
assert.ok(assistant, "assistant message should exist");
assert.equal(assistant.content[0].type, "thinking", "injected block should be type=thinking");
assert.equal(
assistant.content[0].signature,
undefined,
"DeepSeek placeholder must NOT have a signature field"
);
assert.equal(assistant.content[1].type, "tool_use", "tool_use should follow the thinking block");
});
// ──────────────── Fix (b): DeepSeek keeps existing thinking blocks as-is ────────────────
test("deepseek: existing thinking blocks are preserved as-is (text and type unchanged)", () => {
reasoningCache.clearReasoningCacheAll();
const body = {
model: "deepseek-v4-pro",
thinking: { type: "enabled", budget_tokens: 4096 },
messages: [
{ role: "user", content: [{ type: "text", text: "q" }] },
{
role: "assistant",
content: [
{ type: "thinking", thinking: "my actual reasoning", signature: "ds-sig" },
{ type: "tool_use", id: "toolu_2", name: "Read", input: {} },
],
},
{
role: "user",
content: [{ type: "tool_result", tool_use_id: "toolu_2", content: "ok" }],
},
],
};
const out = prepareClaudeRequest(body as any, "deepseek");
const assistant = (out as any).messages.find((m: any) => m.role === "assistant");
assert.equal(assistant.content[0].type, "thinking");
assert.equal(
assistant.content[0].thinking,
"my actual reasoning",
"DeepSeek must keep existing thinking text verbatim"
);
// Signature stripping is acceptable (DeepSeek doesn't need it), but text must not
// be overwritten with placeholder or cache content.
assert.ok(
assistant.content.length >= 2,
"should not drop blocks: thinking + tool_use both present"
);
assert.equal(assistant.content[1].type, "tool_use");
});
test("deepseek: injected placeholder thinking text is non-empty dot sentinel", () => {
// The upstream uses "." as placeholder text for DeepSeek (not the long NON_ANTHROPIC placeholder)
reasoningCache.clearReasoningCacheAll();
const body = {
model: "deepseek-v4-pro",
thinking: { type: "enabled", budget_tokens: 4096 },
messages: [
{ role: "user", content: [{ type: "text", text: "q" }] },
{
role: "assistant",
content: [{ type: "tool_use", id: "toolu_3", name: "Write", input: {} }],
},
{
role: "user",
content: [{ type: "tool_result", tool_use_id: "toolu_3", content: "done" }],
},
],
};
const out = prepareClaudeRequest(body as any, "deepseek");
const assistant = (out as any).messages.find((m: any) => m.role === "assistant");
assert.equal(assistant.content[0].type, "thinking");
// Placeholder must be non-empty (DeepSeek rejects empty thinking text).
// The value is either "." (upstream canonical) or another non-empty fallback.
assert.ok(
typeof assistant.content[0].thinking === "string" && assistant.content[0].thinking.length > 0,
`thinking placeholder must be non-empty, got: ${JSON.stringify(assistant.content[0].thinking)}`
);
assert.equal(
assistant.content[0].signature,
undefined,
"no signature on injected DeepSeek block"
);
});
// ──────────────── Fix (c): Gemini reasoning_effort "auto" → high budget ────────────────
test("Gemini: reasoning_effort 'auto' maps to a defined (non-zero) thinking budget", () => {
const out = openaiToGeminiRequest(
"gemini-3-pro",
{
messages: [{ role: "user", content: "hello" }],
reasoning_effort: "auto",
},
false
) as any;
const thinkingBudget = out.generationConfig?.thinkingConfig?.thinkingBudget;
assert.ok(
typeof thinkingBudget === "number" && thinkingBudget > 0,
`reasoning_effort 'auto' should produce a positive thinkingBudget, got: ${thinkingBudget}`
);
// Specifically should be at least the high-tier budget level (clamped to model max)
const highBudget = capThinkingBudget("gemini-3-pro", 32768);
assert.equal(
thinkingBudget,
highBudget,
`reasoning_effort 'auto' should map to high budget (${highBudget})`
);
});
// ──────────────── Fix (d): Gemini reasoning_effort "max"/"xhigh" → high budget ────────────────
test("Gemini: reasoning_effort 'max' clamps to high budget (not default fallback)", () => {
const out = openaiToGeminiRequest(
"gemini-3-pro",
{
messages: [{ role: "user", content: "hello" }],
reasoning_effort: "max",
},
false
) as any;
const thinkingBudget = out.generationConfig?.thinkingConfig?.thinkingBudget;
const highBudget = capThinkingBudget("gemini-3-pro", 32768);
assert.equal(
thinkingBudget,
highBudget,
`reasoning_effort 'max' should clamp to high budget (${highBudget})`
);
});
test("Gemini: reasoning_effort 'xhigh' clamps to high budget (not default fallback)", () => {
const out = openaiToGeminiRequest(
"gemini-3-pro",
{
messages: [{ role: "user", content: "hello" }],
reasoning_effort: "xhigh",
},
false
) as any;
const thinkingBudget = out.generationConfig?.thinkingConfig?.thinkingBudget;
const highBudget = capThinkingBudget("gemini-3-pro", 32768);
assert.equal(
thinkingBudget,
highBudget,
`reasoning_effort 'xhigh' should clamp to high budget (${highBudget})`
);
});