@ai-sdk/workflow 2.0.14 → 2.0.16
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 +20 -0
- package/dist/index.d.ts +39 -1
- package/dist/index.js +170 -57
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/do-stream-step.ts +16 -1
- package/src/index.ts +1 -0
- package/src/test/agent-e2e-workflows.ts +97 -0
- package/src/to-ui-message-chunk.ts +14 -12
- package/src/workflow-agent.ts +199 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/workflow",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "WorkflowAgent for building AI agents with AI SDK",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@ai-sdk/provider": "4.0.
|
|
35
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
36
|
-
"ai": "7.0.
|
|
34
|
+
"@ai-sdk/provider": "4.0.9",
|
|
35
|
+
"@ai-sdk/provider-utils": "5.0.34",
|
|
36
|
+
"ai": "7.0.86",
|
|
37
37
|
"ajv": "^8.20.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
package/src/do-stream-step.ts
CHANGED
|
@@ -25,6 +25,12 @@ import {
|
|
|
25
25
|
|
|
26
26
|
export type ModelCallStreamPart<TTools extends ToolSet = ToolSet> =
|
|
27
27
|
| Experimental_LanguageModelStreamPart<TTools>
|
|
28
|
+
| {
|
|
29
|
+
type: 'tool-approval-request';
|
|
30
|
+
approvalId: string;
|
|
31
|
+
toolCallId: string;
|
|
32
|
+
signature?: string;
|
|
33
|
+
}
|
|
28
34
|
| { type: 'reset-step' };
|
|
29
35
|
|
|
30
36
|
export type ModelStopCondition = StopCondition<NoInfer<ToolSet>, any>;
|
|
@@ -326,7 +332,16 @@ export async function doStreamStep(
|
|
|
326
332
|
|
|
327
333
|
// Write to writable in real-time
|
|
328
334
|
if (writer) {
|
|
329
|
-
|
|
335
|
+
if (part.type === 'tool-approval-request') {
|
|
336
|
+
await writer.write({
|
|
337
|
+
type: 'tool-approval-request',
|
|
338
|
+
approvalId: part.approvalId,
|
|
339
|
+
toolCallId: part.toolCall.toolCallId,
|
|
340
|
+
...(part.signature != null ? { signature: part.signature } : {}),
|
|
341
|
+
});
|
|
342
|
+
} else {
|
|
343
|
+
await writer.write(part);
|
|
344
|
+
}
|
|
330
345
|
}
|
|
331
346
|
|
|
332
347
|
if (part.type === 'error' && !hasTerminalError) {
|
package/src/index.ts
CHANGED
|
@@ -28,6 +28,13 @@ async function throwingStep(): Promise<string> {
|
|
|
28
28
|
throw new FatalError('Tool execution failed fatally');
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
async function executeApprovedAction(input: {
|
|
32
|
+
action: string;
|
|
33
|
+
}): Promise<string> {
|
|
34
|
+
'use step';
|
|
35
|
+
return `executed:${input.action}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
// ============================================================================
|
|
32
39
|
// Core agent tests
|
|
33
40
|
// ============================================================================
|
|
@@ -514,6 +521,96 @@ export async function agentToolApprovalE2e() {
|
|
|
514
521
|
};
|
|
515
522
|
}
|
|
516
523
|
|
|
524
|
+
const signedToolApprovalSecret = {
|
|
525
|
+
environmentVariable: 'WORKFLOW_TOOL_APPROVAL_SECRET',
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
export async function agentSignedToolApprovalIssueE2e() {
|
|
529
|
+
'use workflow';
|
|
530
|
+
const agent = new WorkflowAgent({
|
|
531
|
+
model: mockSequenceModel([
|
|
532
|
+
{
|
|
533
|
+
type: 'tool-call',
|
|
534
|
+
toolName: 'riskyTool',
|
|
535
|
+
input: JSON.stringify({ action: 'delete' }),
|
|
536
|
+
},
|
|
537
|
+
]),
|
|
538
|
+
tools: {
|
|
539
|
+
riskyTool: tool({
|
|
540
|
+
description: 'A dangerous tool that needs approval',
|
|
541
|
+
inputSchema: z.object({ action: z.string() }),
|
|
542
|
+
execute: executeApprovedAction,
|
|
543
|
+
needsApproval: true,
|
|
544
|
+
}),
|
|
545
|
+
},
|
|
546
|
+
experimental_toolApprovalSecret: signedToolApprovalSecret,
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
const result = await agent.stream({
|
|
550
|
+
messages: [{ role: 'user', content: 'do something risky' }],
|
|
551
|
+
writable: getWritable(),
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
return { toolCallsCount: result.toolCalls.length };
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export async function agentSignedToolApprovalResumeE2e(signature: string) {
|
|
558
|
+
'use workflow';
|
|
559
|
+
const agent = new WorkflowAgent({
|
|
560
|
+
model: mockSequenceModel([{ type: 'text', text: 'approved action done' }]),
|
|
561
|
+
tools: {
|
|
562
|
+
riskyTool: tool({
|
|
563
|
+
description: 'A dangerous tool that needs approval',
|
|
564
|
+
inputSchema: z.object({ action: z.string() }),
|
|
565
|
+
execute: executeApprovedAction,
|
|
566
|
+
needsApproval: true,
|
|
567
|
+
}),
|
|
568
|
+
},
|
|
569
|
+
experimental_toolApprovalSecret: signedToolApprovalSecret,
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
const result = await agent.stream({
|
|
573
|
+
messages: [
|
|
574
|
+
{ role: 'user', content: 'do something risky' },
|
|
575
|
+
{
|
|
576
|
+
role: 'assistant',
|
|
577
|
+
content: [
|
|
578
|
+
{
|
|
579
|
+
type: 'tool-call',
|
|
580
|
+
toolCallId: 'call-1',
|
|
581
|
+
toolName: 'riskyTool',
|
|
582
|
+
input: { action: 'delete' },
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
type: 'tool-approval-request',
|
|
586
|
+
approvalId: 'approval-call-1',
|
|
587
|
+
toolCallId: 'call-1',
|
|
588
|
+
signature,
|
|
589
|
+
},
|
|
590
|
+
],
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
role: 'tool',
|
|
594
|
+
content: [
|
|
595
|
+
{
|
|
596
|
+
type: 'tool-approval-response',
|
|
597
|
+
approvalId: 'approval-call-1',
|
|
598
|
+
approved: true,
|
|
599
|
+
},
|
|
600
|
+
],
|
|
601
|
+
},
|
|
602
|
+
],
|
|
603
|
+
writable: getWritable(),
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
return {
|
|
607
|
+
lastStepText: result.steps.at(-1)?.text,
|
|
608
|
+
containsApprovedToolResult: JSON.stringify(result.messages).includes(
|
|
609
|
+
'executed:delete',
|
|
610
|
+
),
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
517
614
|
// ============================================================================
|
|
518
615
|
// Tool with input schema (tests serialization across step boundary)
|
|
519
616
|
// ============================================================================
|
|
@@ -185,20 +185,22 @@ export function toUIMessageChunk(
|
|
|
185
185
|
case 'raw':
|
|
186
186
|
return undefined;
|
|
187
187
|
|
|
188
|
+
case 'tool-approval-request':
|
|
189
|
+
return {
|
|
190
|
+
type: 'tool-approval-request',
|
|
191
|
+
approvalId: part.approvalId,
|
|
192
|
+
toolCallId:
|
|
193
|
+
'toolCallId' in part ? part.toolCallId : part.toolCall.toolCallId,
|
|
194
|
+
...(part.signature != null ? { signature: part.signature } : {}),
|
|
195
|
+
};
|
|
196
|
+
|
|
188
197
|
default: {
|
|
189
|
-
// Pass through
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
198
|
+
// Pass through step boundaries and other chunks as-is. Step boundaries
|
|
199
|
+
// (finish-step/start-step) are not standard ModelCallStreamPart types
|
|
200
|
+
// but are written by the WorkflowAgent between tool execution and the
|
|
201
|
+
// next model step to ensure proper message splitting in
|
|
202
|
+
// convertToModelMessages.
|
|
194
203
|
const passthroughPart = part as any;
|
|
195
|
-
if (passthroughPart.type === 'tool-approval-request') {
|
|
196
|
-
return {
|
|
197
|
-
type: 'tool-approval-request',
|
|
198
|
-
approvalId: passthroughPart.approvalId,
|
|
199
|
-
toolCallId: passthroughPart.toolCallId,
|
|
200
|
-
} as UIMessageChunk;
|
|
201
|
-
}
|
|
202
204
|
if (
|
|
203
205
|
passthroughPart.type === 'finish-step' ||
|
|
204
206
|
passthroughPart.type === 'start-step' ||
|
package/src/workflow-agent.ts
CHANGED
|
@@ -34,14 +34,17 @@ import {
|
|
|
34
34
|
type TelemetryOptions as CoreTelemetryOptions,
|
|
35
35
|
type Instructions,
|
|
36
36
|
type Experimental_SandboxSession as SandboxSession,
|
|
37
|
+
InvalidToolApprovalSignatureError,
|
|
37
38
|
} from 'ai';
|
|
38
39
|
import {
|
|
39
40
|
createRestrictedTelemetryDispatcher,
|
|
40
41
|
collectToolApprovals,
|
|
41
42
|
convertToLanguageModelPrompt,
|
|
42
43
|
mergeCallbacks,
|
|
44
|
+
signToolApproval,
|
|
43
45
|
standardizePrompt,
|
|
44
46
|
validateApprovedToolApprovals,
|
|
47
|
+
verifyToolApprovalSignature,
|
|
45
48
|
} from 'ai/internal';
|
|
46
49
|
import { createLanguageModelToolResultOutput } from './create-language-model-tool-result-output.js';
|
|
47
50
|
import type { ModelCallStreamPart } from './do-stream-step.js';
|
|
@@ -116,6 +119,17 @@ export interface OutputSpecification<OUTPUT, PARTIAL> {
|
|
|
116
119
|
*/
|
|
117
120
|
export type ProviderOptions = SharedV4ProviderOptions;
|
|
118
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Workflow-safe reference to the environment variable that contains the
|
|
124
|
+
* secret used to sign and verify tool approvals.
|
|
125
|
+
*/
|
|
126
|
+
export type WorkflowToolApprovalSecret = {
|
|
127
|
+
/**
|
|
128
|
+
* Name of an environment variable containing a high-entropy secret.
|
|
129
|
+
*/
|
|
130
|
+
environmentVariable: string;
|
|
131
|
+
};
|
|
132
|
+
|
|
119
133
|
type WorkflowAgentToolsContextParameter<TTools extends ToolSet> =
|
|
120
134
|
HasRequiredKey<InferToolSetContext<TTools>> extends true
|
|
121
135
|
? { toolsContext: InferToolSetContext<TTools> }
|
|
@@ -543,6 +557,18 @@ export type WorkflowAgentOptions<
|
|
|
543
557
|
*/
|
|
544
558
|
experimental_sandbox?: SandboxSession;
|
|
545
559
|
|
|
560
|
+
/**
|
|
561
|
+
* Workflow-safe reference to the environment variable containing the
|
|
562
|
+
* secret for HMAC-signing tool approval requests. When set, the agent signs
|
|
563
|
+
* each approval request and verifies the signature before executing an
|
|
564
|
+
* approved tool replayed from client-supplied message history.
|
|
565
|
+
*
|
|
566
|
+
* Only the environment variable name crosses workflow boundaries. The
|
|
567
|
+
* secret value is read inside signing and verification steps and is never
|
|
568
|
+
* serialized. Per-stream values override this default.
|
|
569
|
+
*/
|
|
570
|
+
experimental_toolApprovalSecret?: WorkflowToolApprovalSecret;
|
|
571
|
+
|
|
546
572
|
/**
|
|
547
573
|
* Default callback function called before each step in the agent loop.
|
|
548
574
|
* Use this to modify settings, manage context, or inject messages dynamically
|
|
@@ -1002,6 +1028,18 @@ export type WorkflowAgentStreamOptions<
|
|
|
1002
1028
|
*/
|
|
1003
1029
|
experimental_sandbox?: SandboxSession;
|
|
1004
1030
|
|
|
1031
|
+
/**
|
|
1032
|
+
* Workflow-safe reference to the environment variable containing the
|
|
1033
|
+
* secret for HMAC-signing tool approval requests. When set, the agent signs
|
|
1034
|
+
* each approval request and verifies the signature before executing an
|
|
1035
|
+
* approved tool replayed from client-supplied message history.
|
|
1036
|
+
*
|
|
1037
|
+
* Only the environment variable name crosses workflow boundaries. The
|
|
1038
|
+
* secret value is read inside signing and verification steps and is never
|
|
1039
|
+
* serialized. Overrides the constructor-level value if provided.
|
|
1040
|
+
*/
|
|
1041
|
+
experimental_toolApprovalSecret?: WorkflowToolApprovalSecret;
|
|
1042
|
+
|
|
1005
1043
|
/**
|
|
1006
1044
|
* Callback function to be called after each step completes.
|
|
1007
1045
|
*/
|
|
@@ -1266,6 +1304,7 @@ export class WorkflowAgent<
|
|
|
1266
1304
|
private repairToolCall?: ToolCallRepairFunction<TBaseTools>;
|
|
1267
1305
|
private experimentalDownload?: DownloadFunction;
|
|
1268
1306
|
private experimentalSandbox?: SandboxSession;
|
|
1307
|
+
private experimentalToolApprovalSecret?: WorkflowToolApprovalSecret;
|
|
1269
1308
|
private prepareStep?: PrepareStepCallback<TBaseTools, TRuntimeContext>;
|
|
1270
1309
|
private allowSystemInMessages: boolean;
|
|
1271
1310
|
private constructorOnStepEnd?: WorkflowAgentOnStepEndCallback<
|
|
@@ -1305,6 +1344,8 @@ export class WorkflowAgent<
|
|
|
1305
1344
|
options.repairToolCall ?? options.experimental_repairToolCall;
|
|
1306
1345
|
this.experimentalDownload = options.experimental_download;
|
|
1307
1346
|
this.experimentalSandbox = options.experimental_sandbox;
|
|
1347
|
+
this.experimentalToolApprovalSecret =
|
|
1348
|
+
options.experimental_toolApprovalSecret;
|
|
1308
1349
|
this.prepareStep = options.prepareStep;
|
|
1309
1350
|
this.constructorOnStepEnd = options.onStepEnd ?? options.onStepFinish;
|
|
1310
1351
|
const { onFinish, onEnd = onFinish } = options;
|
|
@@ -1374,6 +1415,9 @@ export class WorkflowAgent<
|
|
|
1374
1415
|
options.activeTools ?? this.activeTools;
|
|
1375
1416
|
let effectiveDownloadFromPrepare =
|
|
1376
1417
|
options.experimental_download ?? this.experimentalDownload;
|
|
1418
|
+
const effectiveToolApprovalSecret =
|
|
1419
|
+
options.experimental_toolApprovalSecret ??
|
|
1420
|
+
this.experimentalToolApprovalSecret;
|
|
1377
1421
|
let effectiveTelemetryFromPrepare = options.telemetry ?? this.telemetry;
|
|
1378
1422
|
|
|
1379
1423
|
// Resolve messages for prepareCall: use messages directly, or convert prompt
|
|
@@ -1562,11 +1606,20 @@ export class WorkflowAgent<
|
|
|
1562
1606
|
}
|
|
1563
1607
|
|
|
1564
1608
|
// Re-validate through the shared core implementation: input schema,
|
|
1565
|
-
//
|
|
1566
|
-
// invalid input, denial, or signature errors to a tool error
|
|
1567
|
-
// so the agent loop can continue gracefully.
|
|
1609
|
+
// approval signature (when configured), and approval policy.
|
|
1610
|
+
// Convert invalid input, denial, or signature errors to a tool error
|
|
1611
|
+
// result so the agent loop can continue gracefully.
|
|
1568
1612
|
let revalidationReason: string | undefined;
|
|
1569
1613
|
try {
|
|
1614
|
+
await validateWorkflowToolApprovalSignature({
|
|
1615
|
+
secret: effectiveToolApprovalSecret,
|
|
1616
|
+
approvalId: approval.collected.approvalRequest.approvalId,
|
|
1617
|
+
toolCallId: approval.toolCallId,
|
|
1618
|
+
toolName: approval.toolName,
|
|
1619
|
+
input: approval.input,
|
|
1620
|
+
signature: approval.collected.approvalRequest.signature,
|
|
1621
|
+
});
|
|
1622
|
+
|
|
1570
1623
|
const { deniedToolApprovals: policyDenied, invalidToolApprovals } =
|
|
1571
1624
|
await validateApprovedToolApprovals({
|
|
1572
1625
|
approvedToolApprovals: [approval.collected],
|
|
@@ -2394,13 +2447,31 @@ export class WorkflowAgent<
|
|
|
2394
2447
|
return approvalNeeded[tcIndex];
|
|
2395
2448
|
});
|
|
2396
2449
|
if (approvalToolCalls.length > 0) {
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2450
|
+
// The signing step receives only a non-secret environment
|
|
2451
|
+
// variable reference. It resolves the secret inside the step,
|
|
2452
|
+
// and only the resulting signature is persisted in the stream.
|
|
2453
|
+
const approvalRequests = await Promise.all(
|
|
2454
|
+
approvalToolCalls.map(async tc => {
|
|
2455
|
+
const approvalId = `approval-${tc.toolCallId}`;
|
|
2456
|
+
const signature =
|
|
2457
|
+
effectiveToolApprovalSecret == null
|
|
2458
|
+
? undefined
|
|
2459
|
+
: await signWorkflowToolApproval({
|
|
2460
|
+
secret: effectiveToolApprovalSecret,
|
|
2461
|
+
approvalId,
|
|
2462
|
+
toolCallId: tc.toolCallId,
|
|
2463
|
+
toolName: tc.toolName,
|
|
2464
|
+
input: tc.input,
|
|
2465
|
+
});
|
|
2466
|
+
|
|
2467
|
+
return {
|
|
2468
|
+
approvalId,
|
|
2469
|
+
toolCallId: tc.toolCallId,
|
|
2470
|
+
...(signature != null ? { signature } : {}),
|
|
2471
|
+
};
|
|
2472
|
+
}),
|
|
2403
2473
|
);
|
|
2474
|
+
await writeApprovalRequests(options.writable, approvalRequests);
|
|
2404
2475
|
}
|
|
2405
2476
|
}
|
|
2406
2477
|
|
|
@@ -2727,16 +2798,21 @@ async function closeStream(
|
|
|
2727
2798
|
*/
|
|
2728
2799
|
async function writeApprovalRequests(
|
|
2729
2800
|
writable: WritableStream<any>,
|
|
2730
|
-
|
|
2801
|
+
approvalRequests: Array<{
|
|
2802
|
+
approvalId: string;
|
|
2803
|
+
toolCallId: string;
|
|
2804
|
+
signature?: string;
|
|
2805
|
+
}>,
|
|
2731
2806
|
) {
|
|
2732
2807
|
'use step';
|
|
2733
2808
|
const writer = writable.getWriter();
|
|
2734
2809
|
try {
|
|
2735
|
-
for (const
|
|
2810
|
+
for (const request of approvalRequests) {
|
|
2736
2811
|
await writer.write({
|
|
2737
2812
|
type: 'tool-approval-request',
|
|
2738
|
-
approvalId:
|
|
2739
|
-
toolCallId:
|
|
2813
|
+
approvalId: request.approvalId,
|
|
2814
|
+
toolCallId: request.toolCallId,
|
|
2815
|
+
...(request.signature != null ? { signature: request.signature } : {}),
|
|
2740
2816
|
});
|
|
2741
2817
|
}
|
|
2742
2818
|
} finally {
|
|
@@ -2744,6 +2820,116 @@ async function writeApprovalRequests(
|
|
|
2744
2820
|
}
|
|
2745
2821
|
}
|
|
2746
2822
|
|
|
2823
|
+
async function signWorkflowToolApproval({
|
|
2824
|
+
secret,
|
|
2825
|
+
approvalId,
|
|
2826
|
+
toolCallId,
|
|
2827
|
+
toolName,
|
|
2828
|
+
input,
|
|
2829
|
+
}: {
|
|
2830
|
+
secret: WorkflowToolApprovalSecret;
|
|
2831
|
+
approvalId: string;
|
|
2832
|
+
toolCallId: string;
|
|
2833
|
+
toolName: string;
|
|
2834
|
+
input: unknown;
|
|
2835
|
+
}): Promise<string> {
|
|
2836
|
+
'use step';
|
|
2837
|
+
|
|
2838
|
+
return signToolApproval({
|
|
2839
|
+
secret: getWorkflowToolApprovalSecret(secret),
|
|
2840
|
+
approvalId,
|
|
2841
|
+
toolCallId,
|
|
2842
|
+
toolName,
|
|
2843
|
+
input,
|
|
2844
|
+
});
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
async function verifyWorkflowToolApprovalSignature({
|
|
2848
|
+
secret,
|
|
2849
|
+
signature,
|
|
2850
|
+
approvalId,
|
|
2851
|
+
toolCallId,
|
|
2852
|
+
toolName,
|
|
2853
|
+
input,
|
|
2854
|
+
}: {
|
|
2855
|
+
secret: WorkflowToolApprovalSecret;
|
|
2856
|
+
signature: string;
|
|
2857
|
+
approvalId: string;
|
|
2858
|
+
toolCallId: string;
|
|
2859
|
+
toolName: string;
|
|
2860
|
+
input: unknown;
|
|
2861
|
+
}): Promise<boolean> {
|
|
2862
|
+
'use step';
|
|
2863
|
+
|
|
2864
|
+
return verifyToolApprovalSignature({
|
|
2865
|
+
secret: getWorkflowToolApprovalSecret(secret),
|
|
2866
|
+
signature,
|
|
2867
|
+
approvalId,
|
|
2868
|
+
toolCallId,
|
|
2869
|
+
toolName,
|
|
2870
|
+
input,
|
|
2871
|
+
});
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
async function validateWorkflowToolApprovalSignature({
|
|
2875
|
+
secret,
|
|
2876
|
+
signature,
|
|
2877
|
+
approvalId,
|
|
2878
|
+
toolCallId,
|
|
2879
|
+
toolName,
|
|
2880
|
+
input,
|
|
2881
|
+
}: {
|
|
2882
|
+
secret: WorkflowToolApprovalSecret | undefined;
|
|
2883
|
+
signature: string | undefined;
|
|
2884
|
+
approvalId: string;
|
|
2885
|
+
toolCallId: string;
|
|
2886
|
+
toolName: string;
|
|
2887
|
+
input: unknown;
|
|
2888
|
+
}): Promise<void> {
|
|
2889
|
+
if (secret == null) {
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
if (signature == null) {
|
|
2894
|
+
throw new InvalidToolApprovalSignatureError({
|
|
2895
|
+
approvalId,
|
|
2896
|
+
toolCallId,
|
|
2897
|
+
reason: 'missing signature',
|
|
2898
|
+
});
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
const valid = await verifyWorkflowToolApprovalSignature({
|
|
2902
|
+
secret,
|
|
2903
|
+
signature,
|
|
2904
|
+
approvalId,
|
|
2905
|
+
toolCallId,
|
|
2906
|
+
toolName,
|
|
2907
|
+
input,
|
|
2908
|
+
});
|
|
2909
|
+
|
|
2910
|
+
if (!valid) {
|
|
2911
|
+
throw new InvalidToolApprovalSignatureError({
|
|
2912
|
+
approvalId,
|
|
2913
|
+
toolCallId,
|
|
2914
|
+
reason: 'invalid signature',
|
|
2915
|
+
});
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
function getWorkflowToolApprovalSecret({
|
|
2920
|
+
environmentVariable,
|
|
2921
|
+
}: WorkflowToolApprovalSecret): string {
|
|
2922
|
+
const secret = process.env[environmentVariable];
|
|
2923
|
+
|
|
2924
|
+
if (secret == null) {
|
|
2925
|
+
throw new Error(
|
|
2926
|
+
`Tool approval secret environment variable "${environmentVariable}" is not set`,
|
|
2927
|
+
);
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
return secret;
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2747
2933
|
async function writeToolResults(
|
|
2748
2934
|
writable: WritableStream<any>,
|
|
2749
2935
|
results: Array<{
|