fix(providers): cap grok-cli tools at 200 for cli-chat-proxy (#6986)

* fix(providers): cap grok-cli tools at 200 for cli-chat-proxy

xAI's cli-chat-proxy enforces a hard limit of 200 tools per request and
returns a 400 above that ceiling. A client fanning a large MCP toolset
through Grok Build/Composer (e.g. Claude Code with many registered
tools) can exceed it. transformRequest() now caps the tools array
defensively before forwarding, and the grok-cli registry entries are
annotated supportsReasoning:false to document the existing (already
unconditional) reasoning_effort/reasoning strip for these two models.

Co-authored-by: Joseph Yaksich <294273268+gitcommit90@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/2534

* chore(changelog): fragment for #6986

---------

Co-authored-by: Joseph Yaksich <294273268+gitcommit90@users.noreply.github.com>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-17 10:52:26 -03:00
committed by GitHub
parent 32dd3a8249
commit d06151bd86
4 changed files with 54 additions and 0 deletions

View File

@@ -0,0 +1 @@
- **fix(providers):** cap grok-cli tools at 200 per request, matching xAI's cli-chat-proxy limit, and document the non-reasoning capability of grok-build/grok-composer-2.5-fast in the registry (#6986, thanks @gitcommit90)

View File

@@ -15,6 +15,9 @@ export const grok_cliProvider: RegistryEntry = {
id: "grok-build",
name: "Grok Build",
contextLength: 256000,
// cli-chat-proxy rejects reasoning_effort/reasoning outright (see grok-cli.ts
// executor's transformRequest, which strips them unconditionally for this model).
supportsReasoning: false,
unsupportedParams: [
"presencePenalty",
"frequencyPenalty",
@@ -27,6 +30,9 @@ export const grok_cliProvider: RegistryEntry = {
id: "grok-composer-2.5-fast",
name: "Grok Composer 2.5 Fast",
contextLength: 200000,
// cli-chat-proxy rejects reasoning_effort/reasoning outright (see grok-cli.ts
// executor's transformRequest, which strips them unconditionally for this model).
supportsReasoning: false,
unsupportedParams: [
"presencePenalty",
"frequencyPenalty",

View File

@@ -21,6 +21,8 @@ import { HttpsProxyAgent } from "https-proxy-agent";
const GROK_TOKEN_URL = "https://auth.x.ai/oauth2/token";
const REQUEST_TIMEOUT_MS = 60_000;
// xAI cli-chat-proxy hard limit on tools per request.
const MAX_TOOLS = 200;
type ProxyResolution = { source: string; proxyUrl: string | null };
type GrokRequestDispatch = { agent?: https.Agent; family?: 4 };
@@ -310,6 +312,14 @@ export class GrokCliExecutor extends BaseExecutor {
}
}
// xAI's cli-chat-proxy enforces a maximum of 200 tools per request and
// 400s above that ceiling. Clients that fan a large MCP toolset through
// Grok Build/Composer (e.g. Claude Code with many registered tools) can
// exceed it — cap defensively rather than let the request fail upstream.
if (Array.isArray(transformed.tools) && transformed.tools.length > MAX_TOOLS) {
transformed.tools = transformed.tools.slice(0, MAX_TOOLS);
}
return transformed;
}
}

View File

@@ -51,3 +51,40 @@ test("#5273 grok-cli transformRequest leaves a clean body unchanged (no false st
assert.equal(out.model, "grok-composer-2.5-fast");
assert.equal(out.stream, true);
});
// Ported from decolua/9router#2534 (@gitcommit90): xAI's cli-chat-proxy enforces a
// hard cap of 200 tools per request and 400s above it. Clients that fan a large MCP
// toolset through Grok Build/Composer can exceed that ceiling — transformRequest()
// must cap defensively instead of forwarding an oversized array upstream.
test("2534 grok-cli transformRequest caps tools at 200", () => {
const executor = new GrokCliExecutor();
const tools = Array.from({ length: 250 }, (_, i) => ({
type: "function",
function: { name: `tool_${i}` },
}));
const out = executor.transformRequest(
"grok-build",
{ messages: [{ role: "user", content: "hi" }], tools },
false,
{} as never
) as Record<string, unknown>;
assert.equal((out.tools as unknown[]).length, 200);
assert.deepEqual(out.tools, tools.slice(0, 200));
});
test("2534 grok-cli transformRequest leaves a tools array under the cap untouched", () => {
const executor = new GrokCliExecutor();
const tools = Array.from({ length: 10 }, (_, i) => ({
type: "function",
function: { name: `tool_${i}` },
}));
const out = executor.transformRequest(
"grok-composer-2.5-fast",
{ messages: [{ role: "user", content: "hi" }], tools },
false,
{} as never
) as Record<string, unknown>;
assert.deepEqual(out.tools, tools);
});