@ai-sdk/workflow 1.0.0-canary.85 → 1.0.0-canary.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/index.mjs +109 -99
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/workflow-agent.ts +90 -112
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/workflow",
|
|
3
|
-
"version": "1.0.0-canary.
|
|
3
|
+
"version": "1.0.0-canary.87",
|
|
4
4
|
"description": "WorkflowAgent for building AI agents with AI SDK",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"ajv": "^8.20.0",
|
|
30
30
|
"@ai-sdk/provider": "4.0.0-canary.18",
|
|
31
|
-
"@ai-sdk/provider-utils": "5.0.0-canary.
|
|
32
|
-
"ai": "7.0.0-canary.
|
|
31
|
+
"@ai-sdk/provider-utils": "5.0.0-canary.47",
|
|
32
|
+
"ai": "7.0.0-canary.170"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "22.19.19",
|
package/src/workflow-agent.ts
CHANGED
|
@@ -35,10 +35,12 @@ import {
|
|
|
35
35
|
} from 'ai';
|
|
36
36
|
import {
|
|
37
37
|
createRestrictedTelemetryDispatcher,
|
|
38
|
+
collectToolApprovals,
|
|
38
39
|
convertToLanguageModelPrompt,
|
|
39
40
|
mergeAbortSignals,
|
|
40
41
|
mergeCallbacks,
|
|
41
42
|
standardizePrompt,
|
|
43
|
+
validateApprovedToolApprovals,
|
|
42
44
|
} from 'ai/internal';
|
|
43
45
|
import { createLanguageModelToolResultOutput } from './create-language-model-tool-result-output.js';
|
|
44
46
|
import { streamTextIterator } from './stream-text-iterator.js';
|
|
@@ -1351,8 +1353,30 @@ export class WorkflowAgent<
|
|
|
1351
1353
|
// This mirrors how stream-text.ts handles tool-approval-response parts:
|
|
1352
1354
|
// approved tools are executed, denied tools get denial results, and
|
|
1353
1355
|
// approval parts are stripped from the messages.
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
+
// Use the AI SDK core collector so this path cannot drift from the
|
|
1357
|
+
// hardened generateText/streamText implementation. The collected approvals
|
|
1358
|
+
// are mapped to the flat shape used below; the original (nested) approval
|
|
1359
|
+
// is carried on `collected` for re-validation.
|
|
1360
|
+
const collectedApprovals = collectToolApprovals<ToolSet>({
|
|
1361
|
+
messages: prompt.messages,
|
|
1362
|
+
});
|
|
1363
|
+
const approvedToolApprovals = collectedApprovals.approvedToolApprovals.map(
|
|
1364
|
+
collected => ({
|
|
1365
|
+
toolCallId: collected.toolCall.toolCallId,
|
|
1366
|
+
toolName: collected.toolCall.toolName,
|
|
1367
|
+
input: collected.toolCall.input,
|
|
1368
|
+
reason: collected.approvalResponse.reason,
|
|
1369
|
+
collected,
|
|
1370
|
+
}),
|
|
1371
|
+
);
|
|
1372
|
+
const deniedToolApprovals = collectedApprovals.deniedToolApprovals.map(
|
|
1373
|
+
collected => ({
|
|
1374
|
+
toolCallId: collected.toolCall.toolCallId,
|
|
1375
|
+
toolName: collected.toolCall.toolName,
|
|
1376
|
+
input: collected.toolCall.input,
|
|
1377
|
+
reason: collected.approvalResponse.reason,
|
|
1378
|
+
}),
|
|
1379
|
+
);
|
|
1356
1380
|
|
|
1357
1381
|
if (approvedToolApprovals.length > 0 || deniedToolApprovals.length > 0) {
|
|
1358
1382
|
const _toolResultMessages: ModelMessage[] = [];
|
|
@@ -1368,6 +1392,70 @@ export class WorkflowAgent<
|
|
|
1368
1392
|
for (const approval of approvedToolApprovals) {
|
|
1369
1393
|
const tool = (this.tools as ToolSet)[approval.toolName];
|
|
1370
1394
|
if (tool && typeof tool.execute === 'function') {
|
|
1395
|
+
if (!tool.needsApproval) {
|
|
1396
|
+
const reason = `Tool "${approval.toolName}" does not require approval`;
|
|
1397
|
+
toolResultContent.push({
|
|
1398
|
+
type: 'tool-result' as const,
|
|
1399
|
+
toolCallId: approval.toolCallId,
|
|
1400
|
+
toolName: approval.toolName,
|
|
1401
|
+
output: await createLanguageModelToolResultOutput({
|
|
1402
|
+
toolCallId: approval.toolCallId,
|
|
1403
|
+
toolName: approval.toolName,
|
|
1404
|
+
input: approval.input,
|
|
1405
|
+
output: reason,
|
|
1406
|
+
tool,
|
|
1407
|
+
errorMode: 'text',
|
|
1408
|
+
supportedUrls: {},
|
|
1409
|
+
download,
|
|
1410
|
+
}),
|
|
1411
|
+
});
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// Re-validate through the shared core implementation: input schema,
|
|
1416
|
+
// HMAC signature (when configured), and approval policy. It throws on
|
|
1417
|
+
// invalid input/signature; convert that to a denial result so the
|
|
1418
|
+
// agent loop can continue gracefully.
|
|
1419
|
+
let revalidationReason: string | undefined;
|
|
1420
|
+
try {
|
|
1421
|
+
const { deniedToolApprovals: policyDenied } =
|
|
1422
|
+
await validateApprovedToolApprovals({
|
|
1423
|
+
approvedToolApprovals: [approval.collected],
|
|
1424
|
+
tools: this.tools as ToolSet,
|
|
1425
|
+
toolApproval: undefined,
|
|
1426
|
+
messages: prompt.messages,
|
|
1427
|
+
toolsContext:
|
|
1428
|
+
effectiveToolsContext as InferToolSetContext<ToolSet>,
|
|
1429
|
+
runtimeContext: effectiveRuntimeContext,
|
|
1430
|
+
});
|
|
1431
|
+
if (policyDenied.length > 0) {
|
|
1432
|
+
revalidationReason =
|
|
1433
|
+
policyDenied[0].approvalResponse.reason ??
|
|
1434
|
+
'Tool approval denied';
|
|
1435
|
+
}
|
|
1436
|
+
} catch (error) {
|
|
1437
|
+
revalidationReason = getErrorMessage(error);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
if (revalidationReason != null) {
|
|
1441
|
+
toolResultContent.push({
|
|
1442
|
+
type: 'tool-result' as const,
|
|
1443
|
+
toolCallId: approval.toolCallId,
|
|
1444
|
+
toolName: approval.toolName,
|
|
1445
|
+
output: await createLanguageModelToolResultOutput({
|
|
1446
|
+
toolCallId: approval.toolCallId,
|
|
1447
|
+
toolName: approval.toolName,
|
|
1448
|
+
input: approval.input,
|
|
1449
|
+
output: revalidationReason,
|
|
1450
|
+
tool,
|
|
1451
|
+
errorMode: 'text',
|
|
1452
|
+
supportedUrls: {},
|
|
1453
|
+
download,
|
|
1454
|
+
}),
|
|
1455
|
+
});
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1371
1459
|
try {
|
|
1372
1460
|
const { execute } = tool;
|
|
1373
1461
|
const resolvedContext = await resolveToolContext({
|
|
@@ -2689,113 +2777,3 @@ async function executeTool(
|
|
|
2689
2777
|
isError: false,
|
|
2690
2778
|
};
|
|
2691
2779
|
}
|
|
2692
|
-
|
|
2693
|
-
/**
|
|
2694
|
-
* Collected tool approval information for a single tool call.
|
|
2695
|
-
*/
|
|
2696
|
-
interface CollectedApproval {
|
|
2697
|
-
toolCallId: string;
|
|
2698
|
-
toolName: string;
|
|
2699
|
-
input: unknown;
|
|
2700
|
-
approvalId: string;
|
|
2701
|
-
reason?: string;
|
|
2702
|
-
}
|
|
2703
|
-
|
|
2704
|
-
/**
|
|
2705
|
-
* Collect tool approval responses from model messages.
|
|
2706
|
-
* Mirrors the logic from `collectToolApprovals` in the AI SDK core
|
|
2707
|
-
* (`packages/ai/src/generate-text/collect-tool-approvals.ts`).
|
|
2708
|
-
*
|
|
2709
|
-
* Scans the last tool message for `tool-approval-response` parts,
|
|
2710
|
-
* matches them with `tool-approval-request` parts in assistant messages
|
|
2711
|
-
* and the corresponding `tool-call` parts.
|
|
2712
|
-
*/
|
|
2713
|
-
function collectToolApprovalsFromMessages(messages: ModelMessage[]): {
|
|
2714
|
-
approvedToolApprovals: CollectedApproval[];
|
|
2715
|
-
deniedToolApprovals: CollectedApproval[];
|
|
2716
|
-
} {
|
|
2717
|
-
const lastMessage = messages.at(-1);
|
|
2718
|
-
|
|
2719
|
-
if (lastMessage?.role !== 'tool') {
|
|
2720
|
-
return { approvedToolApprovals: [], deniedToolApprovals: [] };
|
|
2721
|
-
}
|
|
2722
|
-
|
|
2723
|
-
// Gather tool calls from assistant messages
|
|
2724
|
-
const toolCallsByToolCallId: Record<
|
|
2725
|
-
string,
|
|
2726
|
-
{ toolName: string; input: unknown }
|
|
2727
|
-
> = {};
|
|
2728
|
-
for (const message of messages) {
|
|
2729
|
-
if (message.role === 'assistant' && Array.isArray(message.content)) {
|
|
2730
|
-
for (const part of message.content as any[]) {
|
|
2731
|
-
if (part.type === 'tool-call') {
|
|
2732
|
-
toolCallsByToolCallId[part.toolCallId] = {
|
|
2733
|
-
toolName: part.toolName,
|
|
2734
|
-
input: part.input ?? part.args,
|
|
2735
|
-
};
|
|
2736
|
-
}
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
}
|
|
2740
|
-
|
|
2741
|
-
// Gather approval requests from assistant messages
|
|
2742
|
-
const approvalRequestsByApprovalId: Record<
|
|
2743
|
-
string,
|
|
2744
|
-
{ approvalId: string; toolCallId: string }
|
|
2745
|
-
> = {};
|
|
2746
|
-
for (const message of messages) {
|
|
2747
|
-
if (message.role === 'assistant' && Array.isArray(message.content)) {
|
|
2748
|
-
for (const part of message.content as any[]) {
|
|
2749
|
-
if (part.type === 'tool-approval-request') {
|
|
2750
|
-
approvalRequestsByApprovalId[part.approvalId] = {
|
|
2751
|
-
approvalId: part.approvalId,
|
|
2752
|
-
toolCallId: part.toolCallId,
|
|
2753
|
-
};
|
|
2754
|
-
}
|
|
2755
|
-
}
|
|
2756
|
-
}
|
|
2757
|
-
}
|
|
2758
|
-
|
|
2759
|
-
// Gather existing tool results to avoid re-executing
|
|
2760
|
-
const existingToolResults = new Set<string>();
|
|
2761
|
-
for (const part of lastMessage.content as any[]) {
|
|
2762
|
-
if (part.type === 'tool-result') {
|
|
2763
|
-
existingToolResults.add(part.toolCallId);
|
|
2764
|
-
}
|
|
2765
|
-
}
|
|
2766
|
-
|
|
2767
|
-
const approvedToolApprovals: CollectedApproval[] = [];
|
|
2768
|
-
const deniedToolApprovals: CollectedApproval[] = [];
|
|
2769
|
-
|
|
2770
|
-
// Collect approval responses from the last tool message
|
|
2771
|
-
const approvalResponses = (lastMessage.content as any[]).filter(
|
|
2772
|
-
(part: any) => part.type === 'tool-approval-response',
|
|
2773
|
-
);
|
|
2774
|
-
|
|
2775
|
-
for (const response of approvalResponses) {
|
|
2776
|
-
const approvalRequest = approvalRequestsByApprovalId[response.approvalId];
|
|
2777
|
-
if (approvalRequest == null) continue;
|
|
2778
|
-
|
|
2779
|
-
// Skip if there's already a tool result for this tool call
|
|
2780
|
-
if (existingToolResults.has(approvalRequest.toolCallId)) continue;
|
|
2781
|
-
|
|
2782
|
-
const toolCall = toolCallsByToolCallId[approvalRequest.toolCallId];
|
|
2783
|
-
if (toolCall == null) continue;
|
|
2784
|
-
|
|
2785
|
-
const approval: CollectedApproval = {
|
|
2786
|
-
toolCallId: approvalRequest.toolCallId,
|
|
2787
|
-
toolName: toolCall.toolName,
|
|
2788
|
-
input: toolCall.input,
|
|
2789
|
-
approvalId: response.approvalId,
|
|
2790
|
-
reason: response.reason,
|
|
2791
|
-
};
|
|
2792
|
-
|
|
2793
|
-
if (response.approved) {
|
|
2794
|
-
approvedToolApprovals.push(approval);
|
|
2795
|
-
} else {
|
|
2796
|
-
deniedToolApprovals.push(approval);
|
|
2797
|
-
}
|
|
2798
|
-
}
|
|
2799
|
-
|
|
2800
|
-
return { approvedToolApprovals, deniedToolApprovals };
|
|
2801
|
-
}
|