fix(ai, ai-openai): normalize null tool input to empty object#430
fix(ai, ai-openai): normalize null tool input to empty object#430AlemTuzlak wants to merge 3 commits intomainfrom
Conversation
When a model produces a tool_use block with no input (or literal null),
JSON.parse('null') returns null, which fails Zod schema validation and
silently kills the agent loop.
Normalize null/non-object parsed tool input to {} in four locations:
- executeToolCalls(): after JSON.parse of arguments string
- ToolCallManager.completeToolCall(): before JSON.stringify of event input
- ToolCallManager.executeTools(): replace fragile string comparison
- OpenAI adapter: in TOOL_CALL_END emission (matching existing Anthropic fix)
Fixes #265
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughNormalize non-object or Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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 |
🚀 Changeset Version Preview2 package(s) bumped directly, 25 bumped as dependents. 🟩 Patch bumps
|
|
View your CI Pipeline Execution ↗ for commit 80fb196
☁️ Nx Cloud last updated this comment at |
@tanstack/ai
@tanstack/ai-anthropic
@tanstack/ai-client
@tanstack/ai-code-mode
@tanstack/ai-code-mode-skills
@tanstack/ai-devtools-core
@tanstack/ai-elevenlabs
@tanstack/ai-event-client
@tanstack/ai-fal
@tanstack/ai-gemini
@tanstack/ai-grok
@tanstack/ai-groq
@tanstack/ai-isolate-cloudflare
@tanstack/ai-isolate-node
@tanstack/ai-isolate-quickjs
@tanstack/ai-ollama
@tanstack/ai-openai
@tanstack/ai-openrouter
@tanstack/ai-preact
@tanstack/ai-react
@tanstack/ai-react-ui
@tanstack/ai-solid
@tanstack/ai-solid-ui
@tanstack/ai-svelte
@tanstack/ai-vue
@tanstack/ai-vue-ui
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
commit: |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/typescript/ai/tests/tool-calls-null-input.test.ts`:
- Around line 108-136: The tests fail to compile because the ToolCallManager
constructor currently accepts only one parameter (tools: ReadonlyArray<Tool>)
but tests call new ToolCallManager(..., mockFinishedEvent); update the
ToolCallManager constructor signature to accept an optional second parameter
(e.g., finishedEvent or initialFinishedEvent) with the same type as
mockFinishedEvent, adjust the constructor implementation to handle when that
second argument is provided (store or process it the same way tests expect), and
ensure all usages and exports of ToolCallManager are updated to the new
signature so the tests compiling calls to ToolCallManager(...,
mockFinishedEvent) succeed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: be0d9dc8-a6e1-48a3-81fe-d6e79d0c38d0
📒 Files selected for processing (3)
packages/typescript/ai-openai/src/adapters/text.tspackages/typescript/ai/src/activities/chat/tools/tool-calls.tspackages/typescript/ai/tests/tool-calls-null-input.test.ts
| describe('ToolCallManager.completeToolCall', () => { | ||
| it('should normalize null input to empty object', () => { | ||
| const manager = new ToolCallManager([], mockFinishedEvent) | ||
|
|
||
| // Register a tool call | ||
| manager.addToolCallStartEvent({ | ||
| type: 'TOOL_CALL_START', | ||
| toolCallId: 'tc-1', | ||
| toolName: 'test_tool', | ||
| model: 'test', | ||
| timestamp: Date.now(), | ||
| index: 0, | ||
| }) | ||
|
|
||
| // Complete with null input (simulating Anthropic empty tool_use) | ||
| manager.completeToolCall({ | ||
| type: 'TOOL_CALL_END', | ||
| toolCallId: 'tc-1', | ||
| toolName: 'test_tool', | ||
| model: 'test', | ||
| timestamp: Date.now(), | ||
| input: null as unknown, | ||
| }) | ||
|
|
||
| const toolCalls = manager.getToolCalls() | ||
| expect(toolCalls).toHaveLength(1) | ||
| // Should be "{}" not "null" | ||
| expect(toolCalls[0]!.function.arguments).toBe('{}') | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify ToolCallManager constructor signature
ast-grep --pattern 'class ToolCallManager {
$$$
constructor($PARAMS) {
$$$
}
$$$
}'Repository: TanStack/ai
Length of output: 17852
🏁 Script executed:
cat -n packages/typescript/ai/tests/tool-calls-null-input.test.tsRepository: TanStack/ai
Length of output: 5661
Constructor signature mismatch — tests will not compile.
ToolCallManager constructor accepts only one parameter (tools: ReadonlyArray<Tool>), but the tests at lines 110 and 139 pass a second argument (mockFinishedEvent). This causes TypeScript compilation errors.
🐛 Proposed fix
describe('ToolCallManager.completeToolCall', () => {
it('should normalize null input to empty object', () => {
- const manager = new ToolCallManager([], mockFinishedEvent)
+ const manager = new ToolCallManager([])
// Register a tool call
manager.addToolCallStartEvent({ it('should preserve valid object input', () => {
- const manager = new ToolCallManager([], mockFinishedEvent)
+ const manager = new ToolCallManager([])
manager.addToolCallStartEvent({📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| describe('ToolCallManager.completeToolCall', () => { | |
| it('should normalize null input to empty object', () => { | |
| const manager = new ToolCallManager([], mockFinishedEvent) | |
| // Register a tool call | |
| manager.addToolCallStartEvent({ | |
| type: 'TOOL_CALL_START', | |
| toolCallId: 'tc-1', | |
| toolName: 'test_tool', | |
| model: 'test', | |
| timestamp: Date.now(), | |
| index: 0, | |
| }) | |
| // Complete with null input (simulating Anthropic empty tool_use) | |
| manager.completeToolCall({ | |
| type: 'TOOL_CALL_END', | |
| toolCallId: 'tc-1', | |
| toolName: 'test_tool', | |
| model: 'test', | |
| timestamp: Date.now(), | |
| input: null as unknown, | |
| }) | |
| const toolCalls = manager.getToolCalls() | |
| expect(toolCalls).toHaveLength(1) | |
| // Should be "{}" not "null" | |
| expect(toolCalls[0]!.function.arguments).toBe('{}') | |
| }) | |
| describe('ToolCallManager.completeToolCall', () => { | |
| it('should normalize null input to empty object', () => { | |
| const manager = new ToolCallManager([]) | |
| // Register a tool call | |
| manager.addToolCallStartEvent({ | |
| type: 'TOOL_CALL_START', | |
| toolCallId: 'tc-1', | |
| toolName: 'test_tool', | |
| model: 'test', | |
| timestamp: Date.now(), | |
| index: 0, | |
| }) | |
| // Complete with null input (simulating Anthropic empty tool_use) | |
| manager.completeToolCall({ | |
| type: 'TOOL_CALL_END', | |
| toolCallId: 'tc-1', | |
| toolName: 'test_tool', | |
| model: 'test', | |
| timestamp: Date.now(), | |
| input: null as unknown, | |
| }) | |
| const toolCalls = manager.getToolCalls() | |
| expect(toolCalls).toHaveLength(1) | |
| // Should be "{}" not "null" | |
| expect(toolCalls[0]!.function.arguments).toBe('{}') | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/typescript/ai/tests/tool-calls-null-input.test.ts` around lines 108
- 136, The tests fail to compile because the ToolCallManager constructor
currently accepts only one parameter (tools: ReadonlyArray<Tool>) but tests call
new ToolCallManager(..., mockFinishedEvent); update the ToolCallManager
constructor signature to accept an optional second parameter (e.g.,
finishedEvent or initialFinishedEvent) with the same type as mockFinishedEvent,
adjust the constructor implementation to handle when that second argument is
provided (store or process it the same way tests expect), and ensure all usages
and exports of ToolCallManager are updated to the new signature so the tests
compiling calls to ToolCallManager(..., mockFinishedEvent) succeed.
Summary
tool_useblock with no input (or literalnull),JSON.parse('null')returnsnull, which fails Zod schema validation and silently kills the agent loopnull/non-object parsed tool input to{}in four locations:executeToolCalls(): afterJSON.parseof arguments stringToolCallManager.completeToolCall(): beforeJSON.stringifyof event inputToolCallManager.executeTools(): replace fragileargsString === 'null'string comparison with robust type checkTOOL_CALL_ENDemission (matching existing Anthropic adapter fix)Fixes #265
Test plan
null, empty, and valid arguments for bothexecuteToolCallsandToolCallManager@tanstack/aitests pass@tanstack/ai-openaitests passSummary by CodeRabbit
Bug Fixes
Tests
Chores