Files
OmniRoute/tests/unit/tool-limit-detector.test.ts
Denis Kotsyuba b1e27258c0 fix(chatcore): exempt opencode client from the default 128-tool truncation (#6193)
* fix(chatcore): exempt opencode client from the default 128-tool truncation

The default MAX_TOOLS_LIMIT (128) cap made truncateToolList blind-slice
tools.slice(0, 128), dropping opencode's built-in task tool and part of
its MCP tools when the inbound list exceeded 128 — so models routed
through OmniRoute could not launch subagents or reach all their tools.

Detect the opencode client (any x-opencode-* header, or 'opencode' in
the user-agent) and bypass ONLY the speculative 128 default. A known
provider ceiling (proactive PROVIDER_TOOL_LIMITS or a detected limit)
always wins and still truncates, even for opencode, so upstreams with
real hard limits (e.g. grok-cli 200) keep their 400-avoidance guard.
Non-opencode clients are unchanged.

- requestFormat.ts: add isOpencodeClient(headers, userAgent) + expose it
  on resolveChatCoreRequestFormat.
- toolLimitDetector.ts: add getKnownToolLimit(); getEffectiveToolLimit
  becomes getKnownToolLimit(provider) ?? DEFAULT_LIMIT (byte-identical
  for existing callers).
- upstreamBody.ts: truncateToolList takes bypassDefaultToolLimit and
  encodes the precedence; fix cosmetic debug-log count.
- chatCore.ts: thread the flag into prepareUpstreamBody.
- tests: extend tool-limit-detector unit tests.

* refactor(tools): accept nullable provider in tool-limit resolvers

Address PR review: widen getKnownToolLimit / getEffectiveToolLimit to
(provider: string | null | undefined) to match the call sites in
truncateToolList, and add unit assertions covering null/undefined
providers (getKnownToolLimit -> null, getEffectiveToolLimit -> 128).

---------

Co-authored-by: DKotsyuba <16292493+DKotsyuba@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-07-05 02:30:30 -03:00

131 lines
4.8 KiB
TypeScript

/**
* Unit tests for the tool limit detector.
*/
import { describe, it, beforeEach } from "node:test";
import assert from "node:assert/strict";
import {
getEffectiveToolLimit,
getKnownToolLimit,
setDetectedToolLimit,
parseToolLimitFromError,
shouldDetectLimit,
clearDetectedLimits,
} from "../../open-sse/services/toolLimitDetector.ts";
describe("toolLimitDetector", () => {
beforeEach(() => {
clearDetectedLimits();
});
it("should return null from getKnownToolLimit when no proactive or detected limit exists", () => {
assert.strictEqual(getKnownToolLimit("openai"), null);
});
it("should return null from getKnownToolLimit for null/undefined provider", () => {
assert.strictEqual(getKnownToolLimit("openai"), null);
assert.strictEqual(getKnownToolLimit(null), null);
assert.strictEqual(getKnownToolLimit(undefined), null);
});
it("should return default limit when no cached value", () => {
assert.strictEqual(getEffectiveToolLimit("openai"), 128);
assert.strictEqual(getEffectiveToolLimit(null), 128);
assert.strictEqual(getEffectiveToolLimit(undefined), 128);
});
it("should return proactive known limit for grok-cli", () => {
assert.strictEqual(getKnownToolLimit("grok-cli"), 200);
});
it("should return detected known limit when available", () => {
setDetectedToolLimit("openai", 100);
assert.strictEqual(getKnownToolLimit("openai"), 100);
assert.strictEqual(getEffectiveToolLimit("openai"), 100);
});
it("should keep getEffectiveToolLimit contract for default, proactive, and detected limits", () => {
assert.strictEqual(getEffectiveToolLimit("openai"), 128);
assert.strictEqual(getEffectiveToolLimit("grok-cli"), 200);
setDetectedToolLimit("openai", 100);
assert.strictEqual(getEffectiveToolLimit("openai"), 100);
});
it("should return cached limit when available", () => {
setDetectedToolLimit("openai", 100);
assert.strictEqual(getEffectiveToolLimit("openai"), 100);
});
it("should only update cache when limit is lower", () => {
setDetectedToolLimit("openai", 100);
setDetectedToolLimit("openai", 120);
assert.strictEqual(getEffectiveToolLimit("openai"), 100);
});
it("should parse tool limit from OpenAI error message", () => {
const result = parseToolLimitFromError("'tools': maximum number of items is 128");
assert.strictEqual(result, 128);
});
it("should parse tool limit from alternative format", () => {
const result = parseToolLimitFromError("Maximum number of tools allowed is 64");
assert.strictEqual(result, 64);
});
it("should return null for non-tool errors", () => {
const result = parseToolLimitFromError("Invalid API key");
assert.strictEqual(result, null);
});
it("should parse Grok-style error capturing the maximum (200), not the provided count (427)", () => {
const result = parseToolLimitFromError(
"Maximum tools limit reached. 427 tools have been provided but the maximum is 200."
);
assert.strictEqual(result, 200);
});
it("should parse Grok-style error without 'the' before maximum", () => {
const result = parseToolLimitFromError("427 tools have been provided but maximum is 150");
assert.strictEqual(result, 150);
});
it("should return proactive limit for grok-cli (200) without any detection", () => {
assert.strictEqual(getKnownToolLimit("grok-cli"), 200);
assert.strictEqual(getEffectiveToolLimit("grok-cli"), 200);
});
it("should document grok-cli known limit precedence for opencode bypass truncation", () => {
assert.strictEqual(getKnownToolLimit("grok-cli"), 200);
});
it("should not override proactive limit with setDetectedToolLimit", () => {
setDetectedToolLimit("grok-cli", 150);
assert.strictEqual(getEffectiveToolLimit("grok-cli"), 200);
});
it("should return proactive limit for nvidia (1536) without any detection", () => {
assert.strictEqual(getEffectiveToolLimit("nvidia"), 1536);
});
it("should not override nvidia proactive limit with reactive detection", () => {
setDetectedToolLimit("nvidia", 100);
assert.strictEqual(getEffectiveToolLimit("nvidia"), 1536);
});
it("should still return default (128) for unknown providers", () => {
assert.strictEqual(getEffectiveToolLimit("some-new-provider"), 128);
});
it("should detect tool limit errors for 400 status", () => {
assert.strictEqual(shouldDetectLimit("Maximum number of tools is 128", 400), true);
assert.strictEqual(shouldDetectLimit("Too many tools provided", 400), true);
assert.strictEqual(shouldDetectLimit("Invalid API key", 400), false);
});
it("should not detect for non-400 errors", () => {
assert.strictEqual(shouldDetectLimit("Maximum number of tools is 128", 500), false);
assert.strictEqual(shouldDetectLimit("Maximum number of tools is 128", 429), false);
});
});