v0.12.215 — recover GPT-5.4 plain-text tool calls (#193)
v0.12.215 — June 30, 2026
Recover tool calls that GPT 5.4 emits as plain JSON / function-call-looking text instead of structured tool_calls (#193, thanks @0xCheetah1).
GPT-5.4 plain-text tool calls now become real tool calls
- Symptom: through the OpenAI-compatible path, GPT 5.4 sometimes emits a tool call as assistant text instead of structured
tool_calls— e.g.{"type":"function","name":"terminal","parameters":{"cmd":"ls -alh /home/Blockrun"}},{"name":"session_search","parameters":{…}},read_file(parameters={"path":"…"}), or aterminal\nCOMMAND\n[/terminal]block. Downstream chat surfaces (seen in Chermes/Telegram) then displayed the raw text to the user instead of dispatching the tool — the same class of failure as the Gemini #189/#190 fix, but with GPT-specific shapes. - Fix: added a fourth recognizer to
src/textual-tool-calls.ts(which already synthesizes structured calls from OpenClaw<tool_call>, Anthropic<function_calls>, and Gemini[Called function …]text shapes). The GPT extractor recovers: whole-content{"name":…,"parameters":…}, whole-content{"type":"function",…}(incl. pretty-printed), whole-contentNAME(parameters={…}), a trailing JSON object after prose only when"type":"function"is explicit, and theterminal\n…\n[/terminal]block. Guardrails: prose JSON examples only fire when the whole content is a call; an incomplete terminal block or a non-functiontype(e.g. a JSON schema"type":"object") is rejected; the terminalcmdarg is mirrored tocommand(preservingcmd) for OpenClaw's terminal tool. It runs only when the tag-based extractors find nothing, so it can't interfere with tagged content. Both call sites — streaming and non-streaming inproxy.ts— share this function. - Tests: 14 new cases in
src/textual-tool-calls.test.ts(all five shapes plus no-mis-fire on prose examples, missingparameters, incomplete terminal block, non-functiontype, andcmd→commandnormalization). Full suite 645 passed, lint + typecheck + prettier + build clean.