fix: prevent raw toolCall blocks from bypassing argument normalization#52
Open
rivetphilbot wants to merge 2 commits intoMartian-Engineering:mainfrom
Open
fix: prevent raw toolCall blocks from bypassing argument normalization#52rivetphilbot wants to merge 2 commits intoMartian-Engineering:mainfrom
rivetphilbot wants to merge 2 commits intoMartian-Engineering:mainfrom
Conversation
blockFromPart() has an early return path that returns metadata.raw verbatim when it exists as an object. This bypasses toolCallBlockFromPart() and toolResultBlockFromPart(), which are responsible for normalizing the arguments field. When tool call arguments are stored as a JavaScript object (which is the common case for OpenClaw-originated tool calls), the raw block passes them through as-is. Chat Completions providers (xAI, OpenAI, etc.) require arguments to be a JSON string, not an object, and reject the request with HTTP 422: 'invalid type: map, expected a string' The Responses API has a safety net that stringifies arguments if needed, but Chat Completions does not — so any provider using that path is affected. The fix checks the raw block's type field before the early return. Tool-related types (toolCall, tool_use, functionCall, etc.) now fall through to the proper normalization functions, which read from the dedicated part columns and let OpenClaw's provider adapters handle wire format conversion. Found while testing Martian-Engineering#44 (PostgreSQL backend) — tool calls accumulated during heartbeat sessions triggered the 422 on xAI's Chat Completions API. Reproduced by sending arguments as an object vs JSON string to the xAI API directly.
toolCallBlockFromPart emitted tool call input under the 'input' key for toolCall-typed blocks. OpenClaw's provider layer (extractToolCalls for Chat Completions, Responses API path) reads 'arguments', not 'input'. This caused xAI/OpenAI to reject assembled context with HTTP 422: 'missing field arguments'. The fix: - toolCall and functionCall types now emit 'arguments' (consumed by OpenAI/xAI Chat Completions and Responses API) - tool_use, tool-use, and toolUse types continue to emit 'input' (Anthropic native format, separate provider path) - Export toolCallBlockFromPart, toolResultBlockFromPart, and blockFromPart for direct unit testing New test file (assembler-blocks.test.ts) with 22 tests covering: - toolCallBlockFromPart: arguments vs input for all rawType variants, string input, null input, empty toolCallId - toolResultBlockFromPart: default/function_call_output types, fallback chain (toolOutput → textContent → empty) - blockFromPart: raw bypass guard for tool types, OpenAI reasoning restoration, tool result routing, text fallback Updated engine test: 'counts large raw metadata blocks' renamed to 'normalizes tool_result blocks instead of returning raw metadata verbatim' — the old test was asserting buggy behavior (raw metadata blobs leaking into assembled payload). Found via xAI 422 errors on Grok heartbeat sessions (conv 326) where LCM-assembled tool call history was rejected by the Chat Completions API.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Two related bugs in
assembler.tscaused xAI/OpenAI Chat Completions API to reject LCM-assembled tool call history with HTTP 422:Bug 1: Raw block bypass (fixed in initial commit)
blockFromPart()had an early return path that returnedmetadata.rawverbatim for all block types. This bypassedtoolCallBlockFromPart()/toolResultBlockFromPart(), which normalize theargumentsfield. When tool call arguments were stored as a JavaScript object (the common case), the raw block passed them as-is — but Chat Completions requiresargumentsto be a JSON string.Bug 2: Wrong field name for toolCall type (fixed in this update)
Even after routing through
toolCallBlockFromPart(), the function emitted tool call input under theinputkey fortoolCall-typed blocks:OpenClaw's provider layer (
extractToolCallsfor Chat Completions, Responses API path) readsblock.arguments, notblock.input. The mismatch causedarguments: undefinedin the serialized payload, which xAI rejects withmissing field "arguments".Fix
toolCallandfunctionCall→arguments(consumed by Chat Completions + Responses API)tool_use,toolUse,tool-use→input(Anthropic native format, separate provider path)Blast radius
extractToolCallsreadsargumentsblock.argumentstool_useformat withinput, different provider path entirelyrawType: "toolCall". The fix applies at assembly time, no data migration needed.Reproduction
Test coverage
New
assembler-blocks.test.tswith 22 tests:argumentsvsinputfor all rawType variants (toolCall,functionCall,function_call,tool_use,toolUse,tool-use), string input, null input, empty toolCallIdfunction_call_outputtypes, fallback chain (toolOutput → textContent → empty)Updated engine test for the raw metadata normalization behavior.
269 tests passing, 0 failures.
Context
Found while debugging persistent xAI 422 errors on Grok's heartbeat session (conversation 326). Every 30-minute heartbeat cycle failed because the conversation history contained tool calls from the first successful run, and subsequent replays hit both bugs.