mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 09:02:11 +03:00
Validated in a combined 5-PR batch worktree off release/v3.8.51 tip (opencode.ts overlapped with already-merged #11409 — git auto-merged cleanly, no manual resolution needed). - Focused test: opencode-tools-no-truncation.test.ts — part of batch's 115/115 node:test run - typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK - Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff Thanks for catching the leftover duplicate truncation from #6193 — a silent 128-tool cap breaking subagent execution on oc/* models is a nasty one to trace back.
This commit is contained in:
@@ -885,12 +885,6 @@ export class OpencodeExecutor extends BaseExecutor {
|
||||
) {
|
||||
delete (modifiedBody as Record<string, unknown>).client_metadata;
|
||||
}
|
||||
if (modifiedBody && typeof modifiedBody === "object" && !Array.isArray(modifiedBody)) {
|
||||
const mb = modifiedBody as Record<string, unknown>;
|
||||
if (Array.isArray(mb.tools) && mb.tools.length > 128) {
|
||||
mb.tools = mb.tools.slice(0, 128);
|
||||
}
|
||||
}
|
||||
if (modifiedBody && typeof modifiedBody === "object" && !Array.isArray(modifiedBody)) {
|
||||
const mb = modifiedBody as Record<string, unknown>;
|
||||
const parsed = parseEffortLevel(model);
|
||||
|
||||
122
tests/unit/opencode-tools-no-truncation.test.ts
Normal file
122
tests/unit/opencode-tools-no-truncation.test.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { OpencodeExecutor } = await import("../../open-sse/executors/opencode.ts");
|
||||
|
||||
/**
|
||||
* Regression test for decolua/9router#11444:
|
||||
*
|
||||
* OpencodeExecutor.transformRequest contained a second hardcoded tool-list
|
||||
* truncation (`mb.tools.slice(0, 128)`) on top of the one PR #6193 already
|
||||
* removed from the chatCore layer. When a client (opencode/claude-code) sends
|
||||
* >128 tools, the opencode executor silently dropped every tool after the
|
||||
* 128th (e.g. task, skill, write, read), leaving subagent tasks paralyzed.
|
||||
*
|
||||
* Tool-list limiting is the chatCore upstreamBody.truncateToolList()'s job
|
||||
* (per-provider known/effective limits via toolLimitDetector), NOT the
|
||||
* executor's. This test pins that OpencodeExecutor.transformRequest forwards
|
||||
* a 150+ tool array intact.
|
||||
*/
|
||||
describe("OpencodeExecutor — no hardcoded 128-tool truncation (#11444)", () => {
|
||||
const executor = new OpencodeExecutor("opencode-go");
|
||||
const CREDENTIALS = { apiKey: "test-key" } as Record<string, unknown>;
|
||||
|
||||
function buildTools(count: number) {
|
||||
return Array.from({ length: count }, (_, i) => ({
|
||||
type: "function",
|
||||
function: {
|
||||
name: `tool_${i}`,
|
||||
description: `tool number ${i}`,
|
||||
parameters: { type: "object", properties: {} },
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
function bodyWithTools(tools: unknown[]) {
|
||||
return {
|
||||
model: "oc/kimi-k2.6",
|
||||
stream: true,
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
tools,
|
||||
};
|
||||
}
|
||||
|
||||
it("preserves all 150 tools (no slice(0,128))", () => {
|
||||
const tools = buildTools(150);
|
||||
const out = executor.transformRequest(
|
||||
"oc/kimi-k2.6",
|
||||
bodyWithTools(tools),
|
||||
true,
|
||||
CREDENTIALS
|
||||
) as { tools?: unknown[] };
|
||||
|
||||
assert.ok(Array.isArray(out.tools), "tools array preserved");
|
||||
assert.equal(out.tools.length, 150, "all 150 tools must survive transformRequest");
|
||||
});
|
||||
|
||||
it("preserves all 300 tools (well beyond the old hardcoded 128 cap)", () => {
|
||||
const tools = buildTools(300);
|
||||
const out = executor.transformRequest(
|
||||
"oc/kimi-k2.6",
|
||||
bodyWithTools(tools),
|
||||
true,
|
||||
CREDENTIALS
|
||||
) as { tools?: unknown[] };
|
||||
|
||||
assert.ok(Array.isArray(out.tools));
|
||||
assert.equal(out.tools.length, 300, "all 300 tools must survive transformRequest");
|
||||
});
|
||||
|
||||
it("preserves tool order (first AND last tool intact)", () => {
|
||||
const tools = buildTools(200);
|
||||
const out = executor.transformRequest(
|
||||
"oc/kimi-k2.6",
|
||||
bodyWithTools(tools),
|
||||
true,
|
||||
CREDENTIALS
|
||||
) as { tools?: Array<{ function: { name: string } }> };
|
||||
|
||||
assert.equal(out.tools![0].function.name, "tool_0");
|
||||
assert.equal(
|
||||
out.tools![199].function.name,
|
||||
"tool_199",
|
||||
"the 200th tool (dropped by the old 128-cap) must still be present"
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves empty tools array intact", () => {
|
||||
const out = executor.transformRequest(
|
||||
"oc/kimi-k2.6",
|
||||
{ model: "oc/kimi-k2.6", messages: [{ role: "user", content: "hi" }], tools: [] },
|
||||
true,
|
||||
CREDENTIALS
|
||||
) as { tools?: unknown[] };
|
||||
|
||||
assert.equal(out.tools?.length, 0, "empty tools array must be preserved intact");
|
||||
});
|
||||
|
||||
it("preserves tool objects content exactly without mutation", () => {
|
||||
const tools = [
|
||||
{ type: "function", function: { name: "custom_tool", description: "desc" } },
|
||||
];
|
||||
const out = executor.transformRequest(
|
||||
"oc/kimi-k2.6",
|
||||
{ model: "oc/kimi-k2.6", messages: [{ role: "user", content: "hi" }], tools },
|
||||
true,
|
||||
CREDENTIALS
|
||||
) as { tools?: typeof tools };
|
||||
|
||||
assert.deepEqual(out.tools, tools, "tool objects must survive transform unchanged");
|
||||
});
|
||||
|
||||
it("is a no-op when tools is absent", () => {
|
||||
const out = executor.transformRequest(
|
||||
"oc/kimi-k2.6",
|
||||
{ model: "oc/kimi-k2.6", messages: [{ role: "user", content: "hi" }] },
|
||||
true,
|
||||
CREDENTIALS
|
||||
) as { tools?: unknown[] };
|
||||
|
||||
assert.equal(out.tools, undefined, "no tools key should be introduced");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user