Skip to content

Commit ff11aee

Browse files
Backport: fix(ai): fix providerExecuted tool approvals being passed to language model twice (#14319)
This is an automated backport of #14289 to the release-v6.0 branch. FYI @felixarntz ~~This backport has conflicts that need to be resolved manually.~~ Conflicts resolved. ### `git cherry-pick` output ``` Auto-merging packages/ai/src/generate-text/generate-text.test.ts Auto-merging packages/ai/src/generate-text/generate-text.ts CONFLICT (content): Merge conflict in packages/ai/src/generate-text/generate-text.ts Auto-merging packages/ai/src/generate-text/stream-text.test.ts Auto-merging packages/ai/src/generate-text/stream-text.ts CONFLICT (content): Merge conflict in packages/ai/src/generate-text/stream-text.ts error: could not apply f372547... fix(ai): fix `providerExecuted` tool approvals being passed to language model twice (#14289) 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: Felix Arntz <felix.arntz@vercel.com>
1 parent 9a8d276 commit ff11aee

File tree

5 files changed

+10
-80
lines changed

5 files changed

+10
-80
lines changed

.changeset/wise-dodos-help.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): fix `providerExecuted` tool approvals being passed to language model twice

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

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8404,7 +8404,7 @@ describe('generateText', () => {
84048404
});
84058405
});
84068406

8407-
it('should send tool-approval-response to the model', async () => {
8407+
it('should send tool-approval-response to the model exactly once', async () => {
84088408
expect(prompts[0]).toMatchInlineSnapshot(`
84098409
[
84108410
{
@@ -8441,12 +8441,6 @@ describe('generateText', () => {
84418441
"reason": undefined,
84428442
"type": "tool-approval-response",
84438443
},
8444-
{
8445-
"approvalId": "mcp-approval-1",
8446-
"approved": true,
8447-
"reason": undefined,
8448-
"type": "tool-approval-response",
8449-
},
84508444
],
84518445
"providerOptions": undefined,
84528446
"role": "tool",
@@ -8564,7 +8558,7 @@ describe('generateText', () => {
85648558
});
85658559
});
85668560

8567-
it('should send denied tool-approval-response to the model', async () => {
8561+
it('should send denied tool-approval-response to the model exactly once', async () => {
85688562
expect(prompts[0]).toMatchInlineSnapshot(`
85698563
[
85708564
{
@@ -8616,12 +8610,6 @@ describe('generateText', () => {
86168610
"toolName": "mcp_tool",
86178611
"type": "tool-result",
86188612
},
8619-
{
8620-
"approvalId": "mcp-approval-1",
8621-
"approved": false,
8622-
"reason": "User denied the request",
8623-
"type": "tool-approval-response",
8624-
},
86258613
],
86268614
"providerOptions": undefined,
86278615
"role": "tool",

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

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@ import {
22
LanguageModelV3,
33
LanguageModelV3Content,
44
LanguageModelV3ToolCall,
5-
LanguageModelV3ToolChoice,
65
} from '@ai-sdk/provider';
76
import {
87
createIdGenerator,
98
getErrorMessage,
109
IdGenerator,
1110
ProviderOptions,
12-
SystemModelMessage,
13-
ToolApprovalResponse,
1411
withUserAgentSuffix,
1512
} from '@ai-sdk/provider-utils';
1613
import { Tracer } from '@opentelemetry/api';
@@ -633,28 +630,6 @@ export async function generateText<
633630
});
634631
}
635632

636-
// Forward provider-executed approval responses to the provider
637-
const providerExecutedToolApprovals = [
638-
...approvedToolApprovals,
639-
...deniedToolApprovals,
640-
].filter(toolApproval => toolApproval.toolCall.providerExecuted);
641-
642-
if (providerExecutedToolApprovals.length > 0) {
643-
responseMessages.push({
644-
role: 'tool',
645-
content: providerExecutedToolApprovals.map(
646-
toolApproval =>
647-
({
648-
type: 'tool-approval-response',
649-
approvalId: toolApproval.approvalResponse.approvalId,
650-
approved: toolApproval.approvalResponse.approved,
651-
reason: toolApproval.approvalResponse.reason,
652-
providerExecuted: true,
653-
}) satisfies ToolApprovalResponse,
654-
),
655-
});
656-
}
657-
658633
const callSettings = prepareCallSettings(settings);
659634

660635
let currentModelResponse: Awaited<

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

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22338,7 +22338,7 @@ describe('streamText', () => {
2233822338
});
2233922339
});
2234022340

22341-
it('should send tool-approval-response to the model', async () => {
22341+
it('should send tool-approval-response to the model exactly once', async () => {
2234222342
await result.consumeStream();
2234322343
expect(prompts[0]).toMatchInlineSnapshot(`
2234422344
[
@@ -22376,12 +22376,6 @@ describe('streamText', () => {
2237622376
"reason": undefined,
2237722377
"type": "tool-approval-response",
2237822378
},
22379-
{
22380-
"approvalId": "mcp-approval-1",
22381-
"approved": true,
22382-
"reason": undefined,
22383-
"type": "tool-approval-response",
22384-
},
2238522379
],
2238622380
"providerOptions": undefined,
2238722381
"role": "tool",
@@ -22513,7 +22507,7 @@ describe('streamText', () => {
2251322507
});
2251422508
});
2251522509

22516-
it('should send denied tool-approval-response to the model', async () => {
22510+
it('should send denied tool-approval-response to the model exactly once', async () => {
2251722511
await result.consumeStream();
2251822512
expect(prompts[0]).toMatchInlineSnapshot(`
2251922513
[
@@ -22551,12 +22545,6 @@ describe('streamText', () => {
2255122545
"reason": "User denied the request",
2255222546
"type": "tool-approval-response",
2255322547
},
22554-
{
22555-
"approvalId": "mcp-approval-1",
22556-
"approved": false,
22557-
"reason": "User denied the request",
22558-
"type": "tool-approval-response",
22559-
},
2256022548
],
2256122549
"providerOptions": undefined,
2256222550
"role": "tool",

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

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {
22
getErrorMessage,
33
LanguageModelV3,
4-
LanguageModelV3ToolChoice,
54
SharedV3Warning,
65
UnsupportedFunctionalityError,
76
} from '@ai-sdk/provider';
@@ -10,16 +9,13 @@ import {
109
DelayedPromise,
1110
IdGenerator,
1211
isAbortError,
13-
ModelMessage,
1412
ProviderOptions,
15-
SystemModelMessage,
16-
ToolApprovalResponse,
1713
ToolContent,
1814
} from '@ai-sdk/provider-utils';
1915
import { Span } from '@opentelemetry/api';
2016
import { ServerResponse } from 'node:http';
2117
import { NoOutputGeneratedError } from '../error';
22-
import { Listener, notify } from '../util/notify';
18+
import { notify } from '../util/notify';
2319
import { logWarnings } from '../logger/log-warnings';
2420
import { resolveLanguageModel } from '../model/resolve-model';
2521
import {
@@ -1360,11 +1356,6 @@ class DefaultStreamTextResult<
13601356
deniedToolApprovals.length > 0 ||
13611357
approvedToolApprovals.length > 0
13621358
) {
1363-
const providerExecutedToolApprovals = [
1364-
...approvedToolApprovals,
1365-
...deniedToolApprovals,
1366-
].filter(toolApproval => toolApproval.toolCall.providerExecuted);
1367-
13681359
const localApprovedToolApprovals = approvedToolApprovals.filter(
13691360
toolApproval => !toolApproval.toolCall.providerExecuted,
13701361
);
@@ -1438,23 +1429,6 @@ class DefaultStreamTextResult<
14381429
}),
14391430
);
14401431

1441-
// forward provider-executed approval responses to the provider (do not execute locally):
1442-
if (providerExecutedToolApprovals.length > 0) {
1443-
initialResponseMessages.push({
1444-
role: 'tool',
1445-
content: providerExecutedToolApprovals.map(
1446-
toolApproval =>
1447-
({
1448-
type: 'tool-approval-response',
1449-
approvalId: toolApproval.approvalResponse.approvalId,
1450-
approved: toolApproval.approvalResponse.approved,
1451-
reason: toolApproval.approvalResponse.reason,
1452-
providerExecuted: true,
1453-
}) satisfies ToolApprovalResponse,
1454-
),
1455-
});
1456-
}
1457-
14581432
// Local tool results (approved + denied) are sent as tool results:
14591433
if (toolOutputs.length > 0 || localDeniedToolApprovals.length > 0) {
14601434
const localToolContent: ToolContent = [];

0 commit comments

Comments
 (0)