Skip to content

Commit 06764c5

Browse files
Backport: fix(ai): skip passing invalid JSON inputs to response messages (#14281)
This is an automated backport of #14280 to the release-v6.0 branch. FYI @aayush-kapoor Co-authored-by: Aayush Kapoor <83492835+aayush-kapoor@users.noreply.github.com>
1 parent d372a96 commit 06764c5

File tree

3 files changed

+133
-1
lines changed

3 files changed

+133
-1
lines changed

.changeset/nervous-gorillas-own.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"ai": patch
3+
---
4+
5+
fix(ai): skip passing invalid JSON inputs to response messages

packages/ai/src/generate-text/to-response-messages.test.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,132 @@ describe('toResponseMessages', () => {
888888
});
889889
});
890890

891+
it('should sanitize invalid tool call with non-object input to empty object', async () => {
892+
const result = await toResponseMessages({
893+
content: [
894+
{
895+
type: 'tool-call',
896+
toolCallId: 'call-1',
897+
toolName: 'weather',
898+
input: '{ city: San Francisco, }',
899+
dynamic: true,
900+
invalid: true,
901+
error: new Error('JSON parsing failed'),
902+
},
903+
{
904+
type: 'tool-error',
905+
toolCallId: 'call-1',
906+
toolName: 'weather',
907+
input: '{ city: San Francisco, }',
908+
error: 'Invalid input for tool weather: JSON parsing failed',
909+
dynamic: true,
910+
},
911+
],
912+
tools: {
913+
weather: tool({
914+
description: 'Get weather',
915+
inputSchema: z.object({ city: z.string() }),
916+
}),
917+
},
918+
});
919+
920+
expect(result).toMatchInlineSnapshot(`
921+
[
922+
{
923+
"content": [
924+
{
925+
"input": {},
926+
"providerExecuted": undefined,
927+
"providerOptions": undefined,
928+
"toolCallId": "call-1",
929+
"toolName": "weather",
930+
"type": "tool-call",
931+
},
932+
],
933+
"role": "assistant",
934+
},
935+
{
936+
"content": [
937+
{
938+
"output": {
939+
"type": "error-text",
940+
"value": "Invalid input for tool weather: JSON parsing failed",
941+
},
942+
"toolCallId": "call-1",
943+
"toolName": "weather",
944+
"type": "tool-result",
945+
},
946+
],
947+
"role": "tool",
948+
},
949+
]
950+
`);
951+
});
952+
953+
it('should preserve valid object input on invalid tool call', async () => {
954+
const result = await toResponseMessages({
955+
content: [
956+
{
957+
type: 'tool-call',
958+
toolCallId: 'call-1',
959+
toolName: 'weather',
960+
input: { cities: 'San Francisco' },
961+
dynamic: true,
962+
invalid: true,
963+
error: new Error('Type validation failed'),
964+
},
965+
{
966+
type: 'tool-error',
967+
toolCallId: 'call-1',
968+
toolName: 'weather',
969+
input: { cities: 'San Francisco' },
970+
error: 'Invalid input for tool weather: Type validation failed',
971+
dynamic: true,
972+
},
973+
],
974+
tools: {
975+
weather: tool({
976+
description: 'Get weather',
977+
inputSchema: z.object({ city: z.string() }),
978+
}),
979+
},
980+
});
981+
982+
expect(result).toMatchInlineSnapshot(`
983+
[
984+
{
985+
"content": [
986+
{
987+
"input": {
988+
"cities": "San Francisco",
989+
},
990+
"providerExecuted": undefined,
991+
"providerOptions": undefined,
992+
"toolCallId": "call-1",
993+
"toolName": "weather",
994+
"type": "tool-call",
995+
},
996+
],
997+
"role": "assistant",
998+
},
999+
{
1000+
"content": [
1001+
{
1002+
"output": {
1003+
"type": "error-text",
1004+
"value": "Invalid input for tool weather: Type validation failed",
1005+
},
1006+
"toolCallId": "call-1",
1007+
"toolName": "weather",
1008+
"type": "tool-result",
1009+
},
1010+
],
1011+
"role": "tool",
1012+
},
1013+
]
1014+
`);
1015+
});
1016+
8911017
it('should include provider metadata in the text parts', async () => {
8921018
const result = await toResponseMessages({
8931019
content: [

packages/ai/src/generate-text/to-response-messages.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ export async function toResponseMessages<TOOLS extends ToolSet>({
6868
type: 'tool-call',
6969
toolCallId: part.toolCallId,
7070
toolName: part.toolName,
71-
input: part.input,
71+
input:
72+
part.invalid && typeof part.input !== 'object' ? {} : part.input,
7273
providerExecuted: part.providerExecuted,
7374
providerOptions: part.providerMetadata,
7475
});

0 commit comments

Comments
 (0)