Skip to content

fix: gateway tool constraints — Muse schema depth, Beam name length, LongCat 402 hint - #270

Merged
ltmoerdani merged 4 commits into
mainfrom
fix/tool-schema-depth-limit
Oct 26, 2026
Merged

ltmoerdani merged 4 commits into
mainfrom
fix/tool-schema-depth-limit

Conversation

@ltmoerdani

Copy link
Copy Markdown
Owner

Gateway-quirk fixes: Muse schema depth, Beam tool names, LongCat 402 hint

Three user-reported gateway failures, each verified with live probes before fixing. All three share one theme: protocol constraints enforced upstream that our request layer never modeled (schema nesting depth, function-name length, upstream serving availability).

What's fixed

1. Muse Spark — HTTP 400 JSON schema exceeds the maximum nesting depth of 10 levels

Muse routes to the Responses API, where the gateway rejects any tool schema nested deeper than 10 levels — and deeply nested MCP tools (Postman's createCollection tree) crossed that. sanitizeToolSchema resolved $ref/$defs and guarded cycles but had no depth cap.

Fix: MAX_TOOL_SCHEMA_DEPTH = 10 in src/config.ts; the sanitizer tracks depth through properties/items/additionalProperties/anyOf/oneOf/allOf and resolved $ref targets, emitting a childless node with leaf keywords only at the ceiling (an empty child would still count as a level upstream). Shared, so all four request builders inherit it. Closes nothing tracked — reported directly.

2. Beam Free — HTTP 400 Invalid value for 'tools[7].function.name': maximum string length is 64

Beam's upstream enforces the OpenAI 64-char function-name limit, but the #165 truncation only ran on the Responses API path while beam-free routes to chat-completions. Adds a beam registry family, gates truncation with needsToolNameTruncation() (muse + beam) in both tool mappers, and forwards the truncated→original toolNameMap through the chat-completions extractor so tool calls round-trip.

3. LongCat Free — HTTP 402 Endpoint is unavailable surfaced raw

longcat-2.5-preview-free 402s with raw upstream text (misleading "Payment Required" for Go subscribers). Live probes showed the model passes gateway validation (dummy-key → 401 at the credential check), so the failure is upstream serving time. Web research confirmed the reading: Endpoint is unavailable is OpenCode's generic per-model incident error (anomalyco/opencode #55607 for this very model — closed fixed; #56796; #57613), tracked on status.opencode.ai — a Free-inference incident ran Oct 21 05:54–19:28 UTC and the reported failures sit in its aftermath. Temporary incident, not a dead model — the model stays selectable; describeEndpointUnavailable() maps the error to an actionable hint pointing at the status page. Side-fix: describeRouterUnavailable() returned the fallback string on mismatch, silently short-circuiting the whole ?? hint chain — now returns undefined.

Verification

  • npm test — 559/559 pass (11 new regression tests across the three fixes)
  • npm run lint — all 7 checks pass (pre-commit gate also ran on every commit)
  • Muse/Beam/LongCat error paths exercised via live gateway probes with real keys

Muse Spark routes to the Responses API, whose gateway rejects tool schemas nested deeper than 10 levels (HTTP 400). Deep MCP tools such as Postman's createCollection exceed that, while chat-completions models accepted the same schemas.

Track depth through properties/items/additionalProperties/composition and resolved $ref targets; at MAX_TOOL_SCHEMA_DEPTH emit a childless node with leaf keywords only (an empty child still counts as a level upstream). Covered by three regression tests.
…letions

Beam's upstream enforces the OpenAI 64-char function-name limit, but the issue #165 truncation only ran on the Responses API path while beam-free routes to chat-completions — long MCP tool names 400ed the whole request.

Add a beam registry row, gate truncation with needsToolNameTruncation (muse + beam) in both tool mappers, rename buildResponsesToolNameMap to buildToolNameMap, and forward the truncated-to-original map through the chat-completions extractor so tool calls round-trip.
longcat-2.5-preview-free 402s with raw 'Upstream request failed: Endpoint is unavailable.' Live probes show the model still passes gateway validation (dummy-key 401), so the failure is at upstream serving time - the Router.Unavailable family via a different status, not the #204 removed-model class, and the model is deliberately not blocklisted.

Add describeEndpointUnavailable() to the hint chain and make describeRouterUnavailable() return undefined on mismatch so later detectors actually run (it previously always returned the fallback string, short-circuiting the chain).
Upstream research settled the diagnosis: 'Endpoint is unavailable' is OpenCode's generic per-model incident error (402/429/503 across models - anomalyco/opencode #55607 for this very model, closed fixed; #56796; #57613), tracked on status.opencode.ai. A Free-inference upstream incident ran Oct 21 05:54-19:28 UTC and the reported failures (Oct 21 19:49-50 UTC, one ~1-minute burst, all payload sizes) sit in its aftermath window - a temporary OpenCode-side incident, not a dead model.

A dead-model blocklist was drafted and then rejected by that evidence (squashed away here); the model stays selectable. describeEndpointUnavailable() maps the raw upstream text to an actionable hint that points at the status page, in both message and userMessage (the #258 chat-render contract). describeRouterUnavailable() now returns undefined on mismatch so later detectors in the hint chain actually run.
@ltmoerdani
ltmoerdani merged commit 54bf9c7 into main Oct 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant