Skip to content
Merged
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/nervous-gorillas-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ai": patch
---

fix(ai): skip passing invalid JSON inputs to response messages
126 changes: 126 additions & 0 deletions packages/ai/src/generate-text/to-response-messages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,132 @@ describe('toResponseMessages', () => {
});
});

it('should sanitize invalid tool call with non-object input to empty object', async () => {
const result = await toResponseMessages({
content: [
{
type: 'tool-call',
toolCallId: 'call-1',
toolName: 'weather',
input: '{ city: San Francisco, }',
dynamic: true,
invalid: true,
error: new Error('JSON parsing failed'),
},
{
type: 'tool-error',
toolCallId: 'call-1',
toolName: 'weather',
input: '{ city: San Francisco, }',
error: 'Invalid input for tool weather: JSON parsing failed',
dynamic: true,
},
],
tools: {
weather: tool({
description: 'Get weather',
inputSchema: z.object({ city: z.string() }),
}),
},
});

expect(result).toMatchInlineSnapshot(`
[
{
"content": [
{
"input": {},
"providerExecuted": undefined,
"providerOptions": undefined,
"toolCallId": "call-1",
"toolName": "weather",
"type": "tool-call",
},
],
"role": "assistant",
},
{
"content": [
{
"output": {
"type": "error-text",
"value": "Invalid input for tool weather: JSON parsing failed",
},
"toolCallId": "call-1",
"toolName": "weather",
"type": "tool-result",
},
],
"role": "tool",
},
]
`);
});

it('should preserve valid object input on invalid tool call', async () => {
const result = await toResponseMessages({
content: [
{
type: 'tool-call',
toolCallId: 'call-1',
toolName: 'weather',
input: { cities: 'San Francisco' },
dynamic: true,
invalid: true,
error: new Error('Type validation failed'),
},
{
type: 'tool-error',
toolCallId: 'call-1',
toolName: 'weather',
input: { cities: 'San Francisco' },
error: 'Invalid input for tool weather: Type validation failed',
dynamic: true,
},
],
tools: {
weather: tool({
description: 'Get weather',
inputSchema: z.object({ city: z.string() }),
}),
},
});

expect(result).toMatchInlineSnapshot(`
[
{
"content": [
{
"input": {
"cities": "San Francisco",
},
"providerExecuted": undefined,
"providerOptions": undefined,
"toolCallId": "call-1",
"toolName": "weather",
"type": "tool-call",
},
],
"role": "assistant",
},
{
"content": [
{
"output": {
"type": "error-text",
"value": "Invalid input for tool weather: Type validation failed",
},
"toolCallId": "call-1",
"toolName": "weather",
"type": "tool-result",
},
],
"role": "tool",
},
]
`);
});

it('should include provider metadata in the text parts', async () => {
const result = await toResponseMessages({
content: [
Expand Down
3 changes: 2 additions & 1 deletion packages/ai/src/generate-text/to-response-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ export async function toResponseMessages<TOOLS extends ToolSet>({
type: 'tool-call',
toolCallId: part.toolCallId,
toolName: part.toolName,
input: part.input,
input:
part.invalid && typeof part.input !== 'object' ? {} : part.input,
providerExecuted: part.providerExecuted,
providerOptions: part.providerMetadata,
});
Expand Down
Loading