Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-invalid-tool-call-input-object.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'ai': patch
---

fix(ai): wrap unparseable tool call input in a JSON object instead of storing the raw string, preventing API rejections (e.g. Amazon Bedrock) on the next step
37 changes: 35 additions & 2 deletions packages/ai/src/generate-text/parse-tool-call.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,35 @@ describe('parseToolCall', () => {
`);
});

it('should wrap unparseable input in a JSON object so API stays valid', async () => {
// Regression: previously the raw string was stored as `input`, which causes
// providers like Amazon Bedrock to reject the next API call with
// "toolUse.input must be a JSON object".
const result = await parseToolCall({
toolCall: {
type: 'tool-call',
toolName: 'testTool',
toolCallId: '123',
input: 'this is not json',
},
tools: {
testTool: tool({
inputSchema: z.object({ param1: z.string() }),
}),
} as const,
repairToolCall: undefined,
messages: [],
instructions: undefined,
});

expect(result.invalid).toBe(true);
// input must be a plain object, not a raw string
expect(typeof result.input).toBe('object');
expect(result.input).toEqual({ rawInvalidInput: 'this is not json' });
// error is still surfaced so the model can react
expect(result.error).toBeDefined();
});

describe('tool call repair', () => {
it('should invoke repairTool when provided and use its result', async () => {
const repairToolCall = vi.fn().mockResolvedValue({
Expand Down Expand Up @@ -471,7 +500,9 @@ describe('parseToolCall', () => {
"dynamic": true,
"error": [AI_InvalidToolInputError: Invalid input for tool testTool: AI_JSONParseError: JSON parsing failed: Text: invalid json.
Error message: SyntaxError: Unexpected token 'i', "invalid json" is not valid JSON],
"input": "invalid json",
"input": {
"rawInvalidInput": "invalid json",
},
"invalid": true,
"providerExecuted": undefined,
"providerMetadata": undefined,
Expand Down Expand Up @@ -510,7 +541,9 @@ describe('parseToolCall', () => {
{
"dynamic": true,
"error": [AI_ToolCallRepairError: Error repairing tool call: Error: test error],
"input": "invalid json",
"input": {
"rawInvalidInput": "invalid json",
},
"invalid": true,
"providerExecuted": undefined,
"providerMetadata": undefined,
Expand Down
7 changes: 5 additions & 2 deletions packages/ai/src/generate-text/parse-tool-call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,12 @@ export async function parseToolCall<TOOLS extends ToolSet>({
});
}
} catch (error) {
// use parsed input when possible
// use parsed input when possible; fall back to a JSON object so the
// conversation history remains API-valid (e.g. Bedrock rejects raw strings)
const parsedInput = await safeParseJSON({ text: toolCall.input });
const input = parsedInput.success ? parsedInput.value : toolCall.input;
const input = parsedInput.success
? parsedInput.value
: { rawInvalidInput: toolCall.input };
const tool = tools?.[toolCall.toolName];

// TODO AI SDK 6: special invalid tool call parts
Expand Down