Skip to content

fix(ai): wrap invalid tool call input in JSON object to prevent API rejection#15863

Open
chatman-media wants to merge 1 commit into
vercel:mainfrom
chatman-media:fix/invalid-tool-call-input-object
Open

fix(ai): wrap invalid tool call input in JSON object to prevent API rejection#15863
chatman-media wants to merge 1 commit into
vercel:mainfrom
chatman-media:fix/invalid-tool-call-input-object

Conversation

@chatman-media
Copy link
Copy Markdown

Closes #14442

Root cause

In packages/ai/src/generate-text/parse-tool-call.ts, the outer catch block that handles all tool-call parse errors contains:

const parsedInput = await safeParseJSON({ text: toolCall.input });
const input = parsedInput.success ? parsedInput.value : toolCall.input;
//                                                       ^^^^^^^^^^^^^^
//                                              raw string, not a JSON object

When a model emits malformed JSON for a tool call's input and safeParseJSON fails, the raw string is stored as input on the invalid tool-call result. This raw string then flows through to-response-messages.ts and convert-to-language-model-prompt.ts into the assistant message for the next API step. Providers like Amazon Bedrock require toolUse.input to be a JSON object (the Bedrock provider casts input: part.input as JSONObject), so the API rejects the request with:

"The format of the value at messages.X.content.Y.toolUse.input is invalid. Provide a json object for the field and try again."

The model never sees the tool-error result and has no opportunity to retry.

Fix

Wrap the fallback in a valid JSON object so conversation history stays API-valid:

const input = parsedInput.success
  ? parsedInput.value
  : { rawInvalidInput: toolCall.input };

The invalid: true flag and the original error are still surfaced on the tool-call result, so the model can observe the failure and retry.

Tests

  • Updated two existing inline snapshot tests that previously asserted "input": "invalid json" (raw string) — they now correctly assert "input": { "rawInvalidInput": "invalid json" }.
  • Added a new regression test: 'should wrap unparseable input in a JSON object so API stays valid' — it fails without the fix (raw string returned) and passes with it (wrapped object returned, invalid and error still set).

Verification: pnpm --filter ai test:node src/generate-text/parse-tool-call.test.ts — 22/22 pass with fix, 3/22 fail without it.

🤖 Generated with Claude Code

…ejection

When safeParseJSON fails to parse a tool call's input, the fallback
previously stored the raw string as `input`. Providers like Amazon Bedrock
require toolUse.input to be a JSON object and reject requests containing a
raw string, so the model never sees the tool-error and cannot retry.

Now the fallback wraps the unparseable value in `{ rawInvalidInput: <string> }`
so conversation history stays structurally valid for all providers.

Closes vercel#14442
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid tool call input stored as raw string causes API rejection on next step

1 participant