Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f591416

Browse files
Backport: feat(ai): add toolMetadata for tool specific metdata (#15053)
This is an automated backport of #15021 to the release-v6.0 branch. FYI @aayush-kapoor This backport has conflicts that need to be resolved manually. ### `git cherry-pick` output ``` Auto-merging content/docs/07-reference/01-ai-sdk-core/20-tool.mdx Auto-merging content/docs/07-reference/01-ai-sdk-core/22-dynamic-tool.mdx Auto-merging examples/ai-e2e-next/app/chat/mcp/page.tsx CONFLICT (content): Merge conflict in examples/ai-e2e-next/app/chat/mcp/page.tsx Auto-merging packages/ai/src/generate-text/execute-tool-call.test.ts CONFLICT (content): Merge conflict in packages/ai/src/generate-text/execute-tool-call.test.ts Auto-merging packages/ai/src/generate-text/execute-tool-call.ts CONFLICT (content): Merge conflict in packages/ai/src/generate-text/execute-tool-call.ts Auto-merging packages/ai/src/generate-text/generate-text.ts Auto-merging packages/ai/src/generate-text/parse-tool-call.test.ts Auto-merging packages/ai/src/generate-text/parse-tool-call.ts CONFLICT (modify/delete): packages/ai/src/generate-text/stream-language-model-call.ts deleted in HEAD and modified in 329a01b (feat(ai): add toolMetadata for tool specific metdata (#15021)). Version 329a01b (feat(ai): add toolMetadata for tool specific metdata (#15021)) of packages/ai/src/generate-text/stream-language-model-call.ts left in tree. Auto-merging packages/ai/src/generate-text/stream-text-result.ts CONFLICT (content): Merge conflict in packages/ai/src/generate-text/stream-text-result.ts Auto-merging packages/ai/src/generate-text/stream-text.test.ts Auto-merging packages/ai/src/generate-text/stream-text.ts Auto-merging packages/ai/src/generate-text/tool-call.ts CONFLICT (content): Merge conflict in packages/ai/src/generate-text/tool-call.ts Auto-merging packages/ai/src/generate-text/tool-error.ts CONFLICT (content): Merge conflict in packages/ai/src/generate-text/tool-error.ts Auto-merging packages/ai/src/generate-text/tool-result.ts CONFLICT (content): Merge conflict in packages/ai/src/generate-text/tool-result.ts Auto-merging packages/ai/src/ui-message-stream/ui-message-chunks.ts Auto-merging packages/ai/src/ui/process-ui-message-stream.test.ts Auto-merging packages/ai/src/ui/process-ui-message-stream.ts CONFLICT (content): Merge conflict in packages/ai/src/ui/process-ui-message-stream.ts Auto-merging packages/ai/src/ui/ui-messages.ts Auto-merging packages/ai/src/ui/validate-ui-messages.ts CONFLICT (content): Merge conflict in packages/ai/src/ui/validate-ui-messages.ts Auto-merging packages/mcp/src/tool/mcp-client.test.ts Auto-merging packages/mcp/src/tool/mcp-client.ts Auto-merging packages/provider-utils/src/types/tool.ts CONFLICT (content): Merge conflict in packages/provider-utils/src/types/tool.ts error: could not apply 329a01b... feat(ai): add toolMetadata for tool specific metdata (#15021) hint: After resolving the conflicts, mark them with hint: "git add/rm <pathspec>", then run hint: "git cherry-pick --continue". hint: You can instead skip this commit with "git cherry-pick --skip". hint: To abort and get back to the state before "git cherry-pick", hint: run "git cherry-pick --abort". hint: Disable this message with "git config set advice.mergeConflict false" ``` --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Aayush Kapoor <[email protected]> Co-authored-by: Aayush Kapoor <[email protected]>
1 parent 3a6aef7 commit f591416

24 files changed

Lines changed: 400 additions & 88 deletions

.changeset/famous-socks-peel.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@ai-sdk/provider-utils": patch
3+
"@ai-sdk/mcp": patch
4+
"ai": patch
5+
---
6+
7+
feat(ai): add toolMetadata for tool specific metdata

content/docs/07-reference/01-ai-sdk-core/20-tool.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,11 @@ export const weatherTool = tool({
170170
'Additional provider-specific metadata. They are passed through to the provider from the AI SDK and enable provider-specific functionality that can be fully encapsulated in the provider.',
171171
},
172172
{
173-
name: 'providerMetadata',
173+
name: 'metadata',
174174
isOptional: true,
175-
type: 'ProviderMetadata',
175+
type: 'JSONObject',
176176
description:
177-
"Optional metadata about the tool itself (e.g. its source). It is propagated onto the resulting tool call's providerMetadata so consumers can read it from tool call/result parts and UI message parts. Useful for sources of dynamic tools (e.g. an MCP server) to identify themselves.",
177+
"Optional metadata about the tool itself (e.g. its source). It is propagated onto the resulting tool call's toolMetadata so consumers can read it from tool call/result parts and UI message parts. Useful for sources of dynamic tools (e.g. an MCP server) to identify themselves.",
178178
},
179179
{
180180
name: 'type',

content/docs/07-reference/01-ai-sdk-core/22-dynamic-tool.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,11 @@ export const customTool = dynamicTool({
154154
description: 'Additional provider-specific metadata.'
155155
},
156156
{
157-
name: 'providerMetadata',
157+
name: 'metadata',
158158
isOptional: true,
159-
type: 'ProviderMetadata',
159+
type: 'JSONObject',
160160
description:
161-
"Optional metadata about the tool itself (e.g. its source). It is propagated onto the resulting tool call's providerMetadata so consumers can read it from tool call/result parts and UI message parts. Useful for sources of dynamic tools (e.g. an MCP server) to identify themselves."
161+
"Optional metadata about the tool itself (e.g. its source). It is propagated onto the resulting tool call's toolMetadata so consumers can read it from tool call/result parts and UI message parts. Useful for sources of dynamic tools (e.g. an MCP server) to identify themselves."
162162
}
163163
]
164164
}

examples/ai-e2e-next/app/chat/mcp/page.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,8 @@ export default function Chat() {
4949
const displayName = toolPart.title || toolName;
5050
const mcpServerName =
5151
toolPart.type === 'dynamic-tool' &&
52-
typeof toolPart.callProviderMetadata?.mcp?.clientName ===
53-
'string'
54-
? toolPart.callProviderMetadata.mcp.clientName
52+
typeof toolPart.toolMetadata?.clientName === 'string'
53+
? toolPart.toolMetadata.clientName
5554
: undefined;
5655

5756
return (

packages/ai/src/generate-text/execute-tool-call.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,30 @@ describe('executeToolCall', () => {
108108
providerMetadata: { custom: { key: 'value' } },
109109
});
110110
});
111+
112+
it('should preserve toolMetadata from toolCall', async () => {
113+
const result = await executeToolCall({
114+
toolCall: createToolCall({
115+
toolMetadata: { clientName: 'MyMCPClient' },
116+
}),
117+
tools: {
118+
testTool: tool({
119+
inputSchema: z.object({ value: z.string() }),
120+
execute: async ({ value }) => `${value}-result`,
121+
}),
122+
},
123+
tracer,
124+
telemetry: undefined,
125+
messages: [],
126+
abortSignal: undefined,
127+
experimental_context: undefined,
128+
});
129+
130+
expect(result).toMatchObject({
131+
type: 'tool-result',
132+
toolMetadata: { clientName: 'MyMCPClient' },
133+
});
134+
});
111135
});
112136

113137
describe('when tool execution fails', () => {
@@ -166,6 +190,32 @@ describe('executeToolCall', () => {
166190
providerMetadata: { custom: { key: 'value' } },
167191
});
168192
});
193+
194+
it('should preserve toolMetadata from toolCall on error', async () => {
195+
const result = await executeToolCall({
196+
toolCall: createToolCall({
197+
toolMetadata: { clientName: 'MyMCPClient' },
198+
}),
199+
tools: {
200+
testTool: tool({
201+
inputSchema: z.object({ value: z.string() }),
202+
execute: async (): Promise<string> => {
203+
throw new Error('execution failed');
204+
},
205+
}),
206+
},
207+
tracer,
208+
telemetry: undefined,
209+
messages: [],
210+
abortSignal: undefined,
211+
experimental_context: undefined,
212+
});
213+
214+
expect(result).toMatchObject({
215+
type: 'tool-error',
216+
toolMetadata: { clientName: 'MyMCPClient' },
217+
});
218+
});
169219
});
170220

171221
describe('onToolCallStart callback', () => {

packages/ai/src/generate-text/execute-tool-call.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ export async function executeToolCall<TOOLS extends ToolSet>({
148148
...(toolCall.providerMetadata != null
149149
? { providerMetadata: toolCall.providerMetadata }
150150
: {}),
151+
...(toolCall.toolMetadata != null
152+
? { toolMetadata: toolCall.toolMetadata }
153+
: {}),
151154
} as TypedToolError<TOOLS>;
152155
}
153156

@@ -191,6 +194,9 @@ export async function executeToolCall<TOOLS extends ToolSet>({
191194
...(toolCall.providerMetadata != null
192195
? { providerMetadata: toolCall.providerMetadata }
193196
: {}),
197+
...(toolCall.toolMetadata != null
198+
? { toolMetadata: toolCall.toolMetadata }
199+
: {}),
194200
} as TypedToolResult<TOOLS>;
195201
},
196202
});

packages/ai/src/generate-text/generate-text.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1486,6 +1486,9 @@ function asContent<TOOLS extends ToolSet>({
14861486
...(part.providerMetadata != null
14871487
? { providerMetadata: part.providerMetadata }
14881488
: {}),
1489+
...(tool?.metadata != null
1490+
? { toolMetadata: tool.metadata }
1491+
: {}),
14891492
} as TypedToolError<TOOLS>);
14901493
} else {
14911494
contentParts.push({
@@ -1499,6 +1502,9 @@ function asContent<TOOLS extends ToolSet>({
14991502
...(part.providerMetadata != null
15001503
? { providerMetadata: part.providerMetadata }
15011504
: {}),
1505+
...(tool?.metadata != null
1506+
? { toolMetadata: tool.metadata }
1507+
: {}),
15021508
} as TypedToolResult<TOOLS>);
15031509
}
15041510
break;
@@ -1516,6 +1522,9 @@ function asContent<TOOLS extends ToolSet>({
15161522
...(part.providerMetadata != null
15171523
? { providerMetadata: part.providerMetadata }
15181524
: {}),
1525+
...(toolCall.toolMetadata != null
1526+
? { toolMetadata: toolCall.toolMetadata }
1527+
: {}),
15191528
} as TypedToolError<TOOLS>);
15201529
} else {
15211530
contentParts.push({
@@ -1529,6 +1538,9 @@ function asContent<TOOLS extends ToolSet>({
15291538
...(part.providerMetadata != null
15301539
? { providerMetadata: part.providerMetadata }
15311540
: {}),
1541+
...(toolCall.toolMetadata != null
1542+
? { toolMetadata: toolCall.toolMetadata }
1543+
: {}),
15321544
} as TypedToolResult<TOOLS>);
15331545
}
15341546
break;

packages/ai/src/generate-text/parse-tool-call.test.ts

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -568,8 +568,8 @@ describe('parseToolCall', () => {
568568
});
569569
});
570570

571-
describe('tool providerMetadata propagation', () => {
572-
it('should propagate tool providerMetadata onto a parsed dynamic tool call', async () => {
571+
describe('tool metadata propagation', () => {
572+
it('should propagate tool metadata onto a parsed dynamic tool call', async () => {
573573
const result = await parseToolCall({
574574
toolCall: {
575575
type: 'tool-call',
@@ -580,7 +580,7 @@ describe('parseToolCall', () => {
580580
tools: {
581581
weather: dynamicTool({
582582
description: 'Get weather',
583-
providerMetadata: { mcp: { clientName: 'MyMCPClient' } },
583+
metadata: { clientName: 'MyMCPClient' },
584584
inputSchema: jsonSchema({
585585
type: 'object',
586586
properties: { location: { type: 'string' } },
@@ -594,12 +594,26 @@ describe('parseToolCall', () => {
594594
messages: [],
595595
});
596596

597-
expect(result.providerMetadata).toEqual({
598-
mcp: { clientName: 'MyMCPClient' },
599-
});
597+
expect(result).toMatchInlineSnapshot(`
598+
{
599+
"dynamic": true,
600+
"input": {
601+
"location": "Paris",
602+
},
603+
"providerExecuted": undefined,
604+
"providerMetadata": undefined,
605+
"title": undefined,
606+
"toolCallId": "call-1",
607+
"toolMetadata": {
608+
"clientName": "MyMCPClient",
609+
},
610+
"toolName": "weather",
611+
"type": "tool-call",
612+
}
613+
`);
600614
});
601615

602-
it('should propagate tool providerMetadata onto a parsed static tool call', async () => {
616+
it('should propagate tool metadata onto a parsed static tool call', async () => {
603617
const result = await parseToolCall({
604618
toolCall: {
605619
type: 'tool-call',
@@ -610,7 +624,7 @@ describe('parseToolCall', () => {
610624
tools: {
611625
calculator: tool({
612626
description: 'Calculate',
613-
providerMetadata: { mcp: { clientName: 'MyMCPClient' } },
627+
metadata: { clientName: 'MyMCPClient' },
614628
inputSchema: z.object({ a: z.number(), b: z.number() }),
615629
execute: async ({ a, b }) => a + b,
616630
}),
@@ -620,27 +634,40 @@ describe('parseToolCall', () => {
620634
messages: [],
621635
});
622636

623-
expect(result.providerMetadata).toEqual({
624-
mcp: { clientName: 'MyMCPClient' },
625-
});
637+
expect(result).toMatchInlineSnapshot(`
638+
{
639+
"input": {
640+
"a": 5,
641+
"b": 3,
642+
},
643+
"providerExecuted": undefined,
644+
"providerMetadata": undefined,
645+
"title": undefined,
646+
"toolCallId": "call-2",
647+
"toolMetadata": {
648+
"clientName": "MyMCPClient",
649+
},
650+
"toolName": "calculator",
651+
"type": "tool-call",
652+
}
653+
`);
626654
});
627655

628-
it('should merge tool providerMetadata with model-supplied providerMetadata (model wins on conflicts)', async () => {
656+
it('should keep tool metadata separate from model-supplied providerMetadata', async () => {
629657
const result = await parseToolCall({
630658
toolCall: {
631659
type: 'tool-call',
632660
toolCallId: 'call-3',
633661
toolName: 'weather',
634662
input: '{"location":"Paris"}',
635663
providerMetadata: {
636-
mcp: { clientName: 'OverriddenByModel' },
637664
anthropic: { cacheControl: { type: 'ephemeral' } },
638665
},
639666
},
640667
tools: {
641668
weather: dynamicTool({
642669
description: 'Get weather',
643-
providerMetadata: { mcp: { clientName: 'MyMCPClient' } },
670+
metadata: { clientName: 'MyMCPClient' },
644671
inputSchema: jsonSchema({
645672
type: 'object',
646673
properties: { location: { type: 'string' } },
@@ -654,13 +681,26 @@ describe('parseToolCall', () => {
654681
messages: [],
655682
});
656683

657-
expect(result.providerMetadata).toEqual({
658-
mcp: { clientName: 'OverriddenByModel' },
659-
anthropic: { cacheControl: { type: 'ephemeral' } },
660-
});
684+
expect({
685+
providerMetadata: result.providerMetadata,
686+
toolMetadata: result.toolMetadata,
687+
}).toMatchInlineSnapshot(`
688+
{
689+
"providerMetadata": {
690+
"anthropic": {
691+
"cacheControl": {
692+
"type": "ephemeral",
693+
},
694+
},
695+
},
696+
"toolMetadata": {
697+
"clientName": "MyMCPClient",
698+
},
699+
}
700+
`);
661701
});
662702

663-
it('should propagate tool providerMetadata onto an invalid tool call', async () => {
703+
it('should propagate tool metadata onto an invalid tool call', async () => {
664704
const result = await parseToolCall({
665705
toolCall: {
666706
type: 'tool-call',
@@ -671,7 +711,7 @@ describe('parseToolCall', () => {
671711
tools: {
672712
weather: dynamicTool({
673713
description: 'Get weather',
674-
providerMetadata: { mcp: { clientName: 'MyMCPClient' } },
714+
metadata: { clientName: 'MyMCPClient' },
675715
inputSchema: jsonSchema({
676716
type: 'object',
677717
properties: { location: { type: 'string' } },
@@ -686,10 +726,17 @@ describe('parseToolCall', () => {
686726
messages: [],
687727
});
688728

689-
expect(result.invalid).toBe(true);
690-
expect(result.providerMetadata).toEqual({
691-
mcp: { clientName: 'MyMCPClient' },
692-
});
729+
expect({
730+
invalid: result.invalid,
731+
toolMetadata: result.toolMetadata,
732+
}).toMatchInlineSnapshot(`
733+
{
734+
"invalid": true,
735+
"toolMetadata": {
736+
"clientName": "MyMCPClient",
737+
},
738+
}
739+
`);
693740
});
694741
});
695742
});

0 commit comments

Comments
 (0)