Files
diegosouzapw--omniroute/tests/unit/opencode-deepseek-reasoning-injected.test.ts
2026-07-13 13:39:12 +08:00

95 lines
3.4 KiB
TypeScript

import { describe, it } from "node:test";
import assert from "node:assert/strict";
const { OpencodeExecutor } = await import("../../open-sse/executors/opencode.ts");
/**
* Regression test for upstream decolua/9router#1099 (issue #1543):
*
* OpenCode is a meta-provider that proxies multi-turn chats to thinking-mode
* upstreams (DeepSeek V4 Flash, Kimi, MiniMax, ...). Those upstreams require
* `reasoning_content` to be echoed back on every assistant message in the
* conversation history; otherwise they return:
*
* 400 Bad Request — reasoning_content must be passed back
*
* Standard OpenAI clients do not preserve `reasoning_content` across turns, so
* OpencodeExecutor.transformRequest must inject a non-empty placeholder on any
* assistant message that lacks it when routing to a thinking-mode model.
*/
describe("OpencodeExecutor — DeepSeek reasoning_content injection (#1099)", () => {
const executor = new OpencodeExecutor("opencode-zen");
function buildBody(extraAssistant: Record<string, unknown>) {
return {
model: "oc/deepseek-v4-flash-free",
stream: true,
messages: [
{ role: "user", content: "hi" },
{ role: "assistant", content: "previous answer", ...extraAssistant },
{ role: "user", content: "follow up" },
],
};
}
it("injects reasoning_content on an assistant message that lacks it (deepseek model)", () => {
const out = executor.transformRequest(
"oc/deepseek-v4-flash-free",
buildBody({}),
true,
{ apiKey: "test" } as never
);
const assistant = out.messages.find((m: { role: string }) => m.role === "assistant");
assert.equal(typeof assistant.reasoning_content, "string");
assert.ok(
assistant.reasoning_content.length > 0,
"expected non-empty reasoning_content placeholder injected"
);
});
it("preserves an existing non-empty reasoning_content instead of overwriting", () => {
const original = "prior chain of thought";
const out = executor.transformRequest(
"oc/deepseek-v4-flash-free",
buildBody({ reasoning_content: original }),
true,
{ apiKey: "test" } as never
);
const assistant = out.messages.find((m: { role: string }) => m.role === "assistant");
assert.equal(assistant.reasoning_content, original);
});
it("injects for kimi-family models routed through opencode", () => {
const body = {
model: "oc/kimi-k2-thinking",
stream: false,
messages: [
{ role: "user", content: "hi" },
{ role: "assistant", content: "answer" },
],
};
const out = executor.transformRequest("oc/kimi-k2-thinking", body, false, {
apiKey: "test",
} as never);
const assistant = out.messages.find((m: { role: string }) => m.role === "assistant");
assert.equal(typeof assistant.reasoning_content, "string");
assert.ok(assistant.reasoning_content.length > 0);
});
it("does not inject for non-thinking opencode models (no false positives)", () => {
const body = {
model: "oc/gpt-5",
stream: false,
messages: [
{ role: "user", content: "hi" },
{ role: "assistant", content: "answer" },
],
};
const out = executor.transformRequest("oc/gpt-5", body, false, {
apiKey: "test",
} as never);
const assistant = out.messages.find((m: { role: string }) => m.role === "assistant");
assert.equal(assistant.reasoning_content, undefined);
});
});