fix(openai-base): preserve malformed tool arguments for error recovery - #1602
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (1)
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review. 📝 WalkthroughWalkthroughThe Chat Completions adapter leaves parsed input undefined when tool arguments are malformed. Unit and end-to-end tests cover streams with and without a tool-call finish reason. ChangesMalformed tool arguments
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix · Severity of issue fixed: Medium Merge Risk: ⚪ Minimal · up to Malformed arguments now produce a tool error instead of executing the tool, allowing recovery in both stream-ending paths. No actionable merge risk is identified; test-run results were not supplied. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The change prevents malformed, nonempty tool arguments from being replaced with executable empty input. The new test endpoint uses simulated responses and a local tool without external side effects. No introduced security concern was identified, but deployed exposure and runtime test results were not established. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Thanks for the PR, @L-1ngg! 🙌 @AlemTuzlak will take a look. Automated pre-review checks
Automated triage — a human review follows. |
🦋 Changeset detectedLatest commit: 8c3fc5e The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
View your CI Pipeline Execution ↗ for commit 8c3fc5e
☁️ Nx Cloud last updated this comment at |
@tanstack/ai
@tanstack/ai-acp
@tanstack/ai-angular
@tanstack/ai-anthropic
@tanstack/ai-bedrock
@tanstack/ai-byteplus
@tanstack/ai-claude-code
@tanstack/ai-client
@tanstack/ai-cloudflare
@tanstack/ai-code-mode
@tanstack/ai-code-mode-snippets
@tanstack/ai-codex
@tanstack/ai-cohere
@tanstack/ai-compaction
@tanstack/ai-devtools-core
@tanstack/ai-durable-stream
@tanstack/ai-elevenlabs
@tanstack/ai-event-client
@tanstack/ai-fal
@tanstack/ai-gemini
@tanstack/ai-grok
@tanstack/ai-grok-build
@tanstack/ai-groq
@tanstack/ai-isolate-cloudflare
@tanstack/ai-isolate-daytona
@tanstack/ai-isolate-node
@tanstack/ai-isolate-quickjs
@tanstack/ai-isolate-quickjs-bun
@tanstack/ai-llmgateway
@tanstack/ai-lovable
@tanstack/ai-mcp
@tanstack/ai-memory
@tanstack/ai-mistral
@tanstack/ai-octane
@tanstack/ai-ollama
@tanstack/ai-ollaya
@tanstack/ai-openai
@tanstack/ai-opencode
@tanstack/ai-openrouter
@tanstack/ai-perplexity
@tanstack/ai-persistence
@tanstack/ai-preact
@tanstack/ai-react
@tanstack/ai-react-ui
@tanstack/ai-reactor
@tanstack/ai-remix
@tanstack/ai-sandbox
@tanstack/ai-sandbox-blaxel
@tanstack/ai-sandbox-boxd
@tanstack/ai-sandbox-cloudflare
@tanstack/ai-sandbox-daytona
@tanstack/ai-sandbox-docker
@tanstack/ai-sandbox-e2b
@tanstack/ai-sandbox-local-process
@tanstack/ai-sandbox-sprites
@tanstack/ai-sandbox-upstash-box
@tanstack/ai-sandbox-vercel
@tanstack/ai-skills
@tanstack/ai-solid
@tanstack/ai-solid-ui
@tanstack/ai-svelte
@tanstack/ai-typesafe
@tanstack/ai-utils
@tanstack/ai-vercel-gateway
@tanstack/ai-vertex
@tanstack/ai-vue
@tanstack/ai-vue-ui
@tanstack/ai-worldlabs
@tanstack/openai-base
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
@tanstack/svelte-ai-devtools
commit: |
…tool-arguments # Conflicts: # testing/e2e/src/routeTree.gen.ts
|
Thank you for this! |
…ai-malformed-tool-arguments
Chat Completions can run a tool after streamed arguments fail JSON parsing. The adapter replaces those arguments with
{}. This PR keeps the original argument string. The core then returns a tool error, and the tool does not run.🎯 Changes
processStreamChunkssetsparsedInputtoundefinedwhen JSON parsing fails. This applies on thefinish_reasonpath and on the end-of-body drain path.completeToolCallkeeps the raw argument string when that input isundefined. The core parses the original string, returns a tool error, and does not call the tool. Valid JSON still usesnormalizeToolInput.The docs page states that this also applies to Chat Completions tools with no required input fields. The changeset is a patch for
@tanstack/openai-base.✅ Checklist
pnpm run test:pr, or these tests do not apply to this pull request.docs/for this change, or this change is not user-facing.pnpm changeset), or this PR does not change a published package.🚀 Release Impact
Root cause
Issue. A tool with
z.object({ path: z.string().optional() })runs with{}when the model streams{"path":. This happens whenfinish_reasonistool_calls. It also happens when the body ends with no finish reason.Cause.
OpenAIBaseChatCompletionsTextAdapter.processStreamChunksassigns{}in the JSON parse catch.completeToolCallwritesJSON.stringifyof that value over the raw argument string.executeToolCallsthen parses"{}". The optional schema accepts that object, so the tool runs.Fix. Both catch paths assign
undefined.completeToolCallreturns early wheninputisundefined, so the raw string stays in place. The core returnsFailed to parse tool arguments as JSONand does not call the tool. The agent loop can continue.Possible alternatives
RUN_ERRORor a thrown error stops the tool. It also stops the agent loop, so the model cannot repair the call.{}at the source.Testing
Commands run. I ran one agent-written
chat()command on clean mainf35fec4d4and on this branch8c3fc5e52. Main exited 1. This branch exited 0. The command calls the Chat Completions adapter with arguments{"path":.pnpm test:prdid not run after merge commit8c3fc5e52. This branch includesorigin/mainatf35fec4d4. That commit adds the afterModel continuation fixture.Clean main:
This branch:
Manual test.
chat()with tool schemaz.object({ path: z.string().optional() })and arguments{"path":. The tool runs with{}.finish_reason: "tool_calls".Failed to parse tool arguments as JSON: {"path":.pnpm --filter @tanstack/ai-e2e test:e2e -- tests/openai-malformed-tool-arguments.spec.ts.How this PR makes testing easy.
testing/e2e/tests/openai-malformed-tool-arguments.spec.tscovers both endings through the real adapter and the core tool loop.packages/openai-base/tests/chat-completions-text.test.tsasserts thatTOOL_CALL_END.inputstaysundefined. These tests need no provider key.Linked issues
Closes #1601
Risk / rollback
Callers that treated a broken JSON tool call as an empty-object call now get a tool error. Revert this PR to restore the previous
{}fallback. The Responses adapter still assigns{}on a parse failure. That path is outside this PR.Summary by CodeRabbit
Bug Fixes
Documentation