fix(xai): normalize nested tool schemas - #124
Conversation
|
Tested locally per your request (Windows, on top of the installed 2.7.16-equivalent source, verified against the live xAI endpoint through the running proxy, which forwards What works
One functional regression: the normalized union tool can no longer emit arguments
Forced-call probe against
This matches the xAI docs: their accepted root-union example is a bare Suggested change In the multi-variant branch of return { ...metadata, oneOf: variants };and flip the test assertion accordingly ( With that one-line change this looks like the right fix for #122 to me. |
|
Addressed review feedback in commit 7e449d3. Multi-variant xAI unions now omit the root ype wrapper, preserving xAI's argument grammar so forced tool calls can emit branch properties instead of {}. Updated regression assertion; focused suite remains 54 passed, with typecheck and diff check clean. |
|
Reviewed and merged. The recursive root oneOf/anyOf flattening, scoped to api.x.ai and cli-chat-proxy.grok.com, resolves #122, including the historical tool_search_output replay path where src/responses/parser.ts rebuilds historical tools each turn. One follow-up remains: when normalization omits an unsafe tool, an explicit tool_choice naming that tool can still be forwarded. The tool list is normalized in src/adapters/openai-chat.ts around lines 180-193, while tool_choice is computed from the original list around lines 196-253. That can produce the same 400 response. A follow-up should reconcile tool_choice with the normalized tool list. Thanks for the fix. |
Summary
Fixes #122.
xAI/Grok rejects some function-tool parameter schemas after Codex
tool_searchloads a deferred app or MCP tool. The failing shape is a rootoneOf/anyOfwhose branches include another root union. xAI requires every root union branch to resolve directly to an object schema.Root cause
Responses parsing correctly replays tool definitions from historical
tool_search_outputitems into the active tool list. Theopenai-chatadapter previously forwarded everyt.parametersschema unchanged. Once the incompatible schema entered the thread history, every later request resent it and received HTTP 400.Changes
api.x.aiand the Grok CLI proxy) so other OpenAI-compatible providers retain their original schemas.oneOf/anyOfobject branches into direct object branches.$defswhile rebuilding the normalized union.tool_search_outputhistory replay path.Verification
bun test --isolate ./tests/xai-transport.test.ts ./tests/adapter-usage.test.ts ./tests/responses-parser.test.tsbun x tsc --noEmitgit diff --checkRisk and compatibility
This changes only tool schemas sent to xAI-compatible endpoints. Providers using other base URLs are unchanged. The normalization intentionally does not rewrite
allOf, and unsafe non-object union branches are omitted instead of being silently coerced. A remaining limitation is that a tool omitted for an unsafe schema will not be callable for that request; this is preferable to making the entire conversation fail repeatedly with HTTP 400.Maintainer review focus
Please verify the xAI schema contract for nested root unions and, if possible, retest with Codex Desktop using
tool_searchfollowed by an automation-related operation on Grok.