From e8dbd276fe72c71b8d04f3e798869b35adfe07a1 Mon Sep 17 00:00:00 2001 From: Choti Wongbussakorn <126886556+Chewji9875@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:55:38 +0700 Subject: [PATCH] fix: truncate tool list when provider limit exceeds MAX_TOOLS_LIMIT (grok-cli 200) - Add proactive PROVIDER_TOOL_LIMITS map with grok-cli: 200 - Fix regex to capture 'maximum is 200' (not '427 tools provided') - Remove broken truncation gate that skipped limits >= MAX_TOOLS_LIMIT (128) - Add tests for Grok regex, proactive limits, and limits above threshold Refs #5563 --- open-sse/handlers/chatCore/upstreamBody.ts | 16 ++++++-------- open-sse/services/toolLimitDetector.ts | 9 ++++++++ tests/unit/tool-limit-detector.test.ts | 25 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/open-sse/handlers/chatCore/upstreamBody.ts b/open-sse/handlers/chatCore/upstreamBody.ts index 51d6c74c11..1a302552d8 100644 --- a/open-sse/handlers/chatCore/upstreamBody.ts +++ b/open-sse/handlers/chatCore/upstreamBody.ts @@ -16,7 +16,6 @@ import { } from "../../services/payloadRules.ts"; import { getEffectiveToolLimit } from "../../services/toolLimitDetector.ts"; import { providerSupportsCaching } from "../../utils/cacheControlPolicy.ts"; -import { MAX_TOOLS_LIMIT } from "../../config/constants.ts"; import { FORMATS } from "../../translator/formats.ts"; type LoggerLike = { debug?: (...args: unknown[]) => void } | null | undefined; @@ -39,13 +38,13 @@ function buildAppliedRulesSummary( .join(", "); } -function truncateToolList(bodyToSend: Body, provider: string | null | undefined, log?: LoggerLike): Body { +function truncateToolList( + bodyToSend: Body, + provider: string | null | undefined, + log?: LoggerLike +): Body { const effectiveToolLimit = getEffectiveToolLimit(provider); - if ( - effectiveToolLimit < MAX_TOOLS_LIMIT && - Array.isArray(bodyToSend.tools) && - bodyToSend.tools.length > effectiveToolLimit - ) { + if (Array.isArray(bodyToSend.tools) && bodyToSend.tools.length > effectiveToolLimit) { const truncatedTools = bodyToSend.tools.slice(0, effectiveToolLimit); bodyToSend = { ...bodyToSend, tools: truncatedTools }; log?.debug?.( @@ -64,8 +63,7 @@ function backfillQwenOAuthUser( credentials: CredentialsLike, log?: LoggerLike ): Body { - const hasValidQwenUser = - typeof bodyToSend.user === "string" && bodyToSend.user.trim().length > 0; + const hasValidQwenUser = typeof bodyToSend.user === "string" && bodyToSend.user.trim().length > 0; const isQwenOAuthRequest = provider === "qwen" && !credentials?.apiKey && diff --git a/open-sse/services/toolLimitDetector.ts b/open-sse/services/toolLimitDetector.ts index 035003004a..48969fcb12 100644 --- a/open-sse/services/toolLimitDetector.ts +++ b/open-sse/services/toolLimitDetector.ts @@ -4,6 +4,10 @@ const DETECTED_LIMITS = new Map(); const TTL_MS = 24 * 60 * 60 * 1000; const DEFAULT_LIMIT = MAX_TOOLS_LIMIT; +const PROVIDER_TOOL_LIMITS: Record = { + "grok-cli": 200, +}; + const _detectedLimitsSweep = setInterval(() => { const now = Date.now(); for (const [key, entry] of DETECTED_LIMITS) { @@ -15,6 +19,10 @@ if (typeof _detectedLimitsSweep === "object" && "unref" in _detectedLimitsSweep) } export function getEffectiveToolLimit(provider: string): number { + const proactiveLimit = PROVIDER_TOOL_LIMITS[provider]; + if (proactiveLimit !== undefined) { + return proactiveLimit; + } const cached = DETECTED_LIMITS.get(provider); if (cached && Date.now() - cached.timestamp < TTL_MS) { return cached.limit; @@ -33,6 +41,7 @@ const TOOL_LIMIT_PATTERNS = [ /'tools':\s*maximum\s+number\s+of\s+items\s+is\s+(\d+)/i, /Maximum\s+number\s+of\s+tools\s+(?:allowed\s+)?(?:is\s+)?(\d+)/i, /Too\s+many\s+tools\.?\s*(?:Maximum\s+)?(\d+)/i, + /\d+\s+tools\s+have\s+been\s+provided\s+but\s+(?:the\s+)?maximum\s+is\s+(\d+)/i, /tool.*limit.*(\d+)/i, /tools.*exceeded.*(\d+)/i, ]; diff --git a/tests/unit/tool-limit-detector.test.ts b/tests/unit/tool-limit-detector.test.ts index ad13102789..d1ce4a88fd 100644 --- a/tests/unit/tool-limit-detector.test.ts +++ b/tests/unit/tool-limit-detector.test.ts @@ -48,6 +48,31 @@ describe("toolLimitDetector", () => { 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(getEffectiveToolLimit("grok-cli"), 200); + }); + + it("should not override proactive limit with setDetectedToolLimit", () => { + setDetectedToolLimit("grok-cli", 150); + assert.strictEqual(getEffectiveToolLimit("grok-cli"), 200); + }); + + 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);