@ai-sdk/workflow 1.0.0-canary.86 → 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 CHANGED
@@ -1,5 +1,24 @@
1
1
  # @ai-sdk/workflow
2
2
 
3
+ ## 1.0.0-canary.87
4
+
5
+ ### Patch Changes
6
+
7
+ - bae5e2b: fix(security): re-validate tool approvals from client message history before execution
8
+
9
+ The approval-replay path in `generateText`/`streamText` (and `WorkflowAgent.stream`) reconstructed approved tool calls from the client-supplied messages array and executed them without re-validating input against the tool's schema or re-applying the approval policy. A client could forge an assistant message with a pre-approved tool-call part and have the server execute a tool with attacker-chosen arguments.
10
+
11
+ The replay path now validates HMAC signature (when `experimental_toolApprovalSecret` is configured), re-validates tool-call input against the tool's input schema, and re-resolves the approval policy before execution.
12
+
13
+ - 69d7128: fix(workflow): reuse the core tool-approval validation in WorkflowAgent
14
+
15
+ `WorkflowAgent.stream` previously reconstructed approved tool calls with a copy of the core collection logic and validated them inline. Because the logic was duplicated, it could drift from the hardened `generateText`/`streamText` implementation. WorkflowAgent now collects approvals via the shared `collectToolApprovals` and re-validates each one through the shared `validateApprovedToolApprovals` (input-schema re-validation, HMAC signature verification when configured, and approval-policy re-resolution) in addition to its existing `needsApproval` guard, so a client-forged approval cannot execute a tool with unvalidated input. The duplicated collector was removed; `collectToolApprovals` and `validateApprovedToolApprovals` are now exported from `ai/internal`.
16
+
17
+ - Updated dependencies [bae5e2b]
18
+ - Updated dependencies [69d7128]
19
+ - ai@7.0.0-canary.170
20
+ - @ai-sdk/provider-utils@5.0.0-canary.47
21
+
3
22
  ## 1.0.0-canary.86
4
23
 
5
24
  ### Patch Changes
package/dist/index.mjs CHANGED
@@ -9,10 +9,12 @@ import {
9
9
  } from "ai";
10
10
  import {
11
11
  createRestrictedTelemetryDispatcher as createRestrictedTelemetryDispatcher2,
12
+ collectToolApprovals,
12
13
  convertToLanguageModelPrompt,
13
14
  mergeAbortSignals,
14
15
  mergeCallbacks,
15
- standardizePrompt
16
+ standardizePrompt,
17
+ validateApprovedToolApprovals
16
18
  } from "ai/internal";
17
19
 
18
20
  // src/create-language-model-tool-result-output.ts
@@ -741,7 +743,7 @@ var WorkflowAgent = class {
741
743
  throw new Error("Not implemented");
742
744
  }
743
745
  async stream(options) {
744
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O;
746
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P;
745
747
  const { onFinish, onEnd = onFinish } = options;
746
748
  let effectiveModel = this.model;
747
749
  let effectiveInstructions = (_a = options.system) != null ? _a : this.instructions;
@@ -814,7 +816,26 @@ var WorkflowAgent = class {
814
816
  ...effectivePrompt != null ? { prompt: effectivePrompt } : { messages: effectiveMessages }
815
817
  });
816
818
  const download = (_i = options.experimental_download) != null ? _i : this.experimentalDownload;
817
- const { approvedToolApprovals, deniedToolApprovals } = collectToolApprovalsFromMessages(prompt.messages);
819
+ const collectedApprovals = collectToolApprovals({
820
+ messages: prompt.messages
821
+ });
822
+ const approvedToolApprovals = collectedApprovals.approvedToolApprovals.map(
823
+ (collected) => ({
824
+ toolCallId: collected.toolCall.toolCallId,
825
+ toolName: collected.toolCall.toolName,
826
+ input: collected.toolCall.input,
827
+ reason: collected.approvalResponse.reason,
828
+ collected
829
+ })
830
+ );
831
+ const deniedToolApprovals = collectedApprovals.deniedToolApprovals.map(
832
+ (collected) => ({
833
+ toolCallId: collected.toolCall.toolCallId,
834
+ toolName: collected.toolCall.toolName,
835
+ input: collected.toolCall.input,
836
+ reason: collected.approvalResponse.reason
837
+ })
838
+ );
818
839
  if (approvedToolApprovals.length > 0 || deniedToolApprovals.length > 0) {
819
840
  const _toolResultMessages = [];
820
841
  const toolResultContent = [];
@@ -822,6 +843,59 @@ var WorkflowAgent = class {
822
843
  for (const approval of approvedToolApprovals) {
823
844
  const tool2 = this.tools[approval.toolName];
824
845
  if (tool2 && typeof tool2.execute === "function") {
846
+ if (!tool2.needsApproval) {
847
+ const reason = `Tool "${approval.toolName}" does not require approval`;
848
+ toolResultContent.push({
849
+ type: "tool-result",
850
+ toolCallId: approval.toolCallId,
851
+ toolName: approval.toolName,
852
+ output: await createLanguageModelToolResultOutput({
853
+ toolCallId: approval.toolCallId,
854
+ toolName: approval.toolName,
855
+ input: approval.input,
856
+ output: reason,
857
+ tool: tool2,
858
+ errorMode: "text",
859
+ supportedUrls: {},
860
+ download
861
+ })
862
+ });
863
+ continue;
864
+ }
865
+ let revalidationReason;
866
+ try {
867
+ const { deniedToolApprovals: policyDenied } = await validateApprovedToolApprovals({
868
+ approvedToolApprovals: [approval.collected],
869
+ tools: this.tools,
870
+ toolApproval: void 0,
871
+ messages: prompt.messages,
872
+ toolsContext: effectiveToolsContext,
873
+ runtimeContext: effectiveRuntimeContext
874
+ });
875
+ if (policyDenied.length > 0) {
876
+ revalidationReason = (_j = policyDenied[0].approvalResponse.reason) != null ? _j : "Tool approval denied";
877
+ }
878
+ } catch (error) {
879
+ revalidationReason = getErrorMessage(error);
880
+ }
881
+ if (revalidationReason != null) {
882
+ toolResultContent.push({
883
+ type: "tool-result",
884
+ toolCallId: approval.toolCallId,
885
+ toolName: approval.toolName,
886
+ output: await createLanguageModelToolResultOutput({
887
+ toolCallId: approval.toolCallId,
888
+ toolName: approval.toolName,
889
+ input: approval.input,
890
+ output: revalidationReason,
891
+ tool: tool2,
892
+ errorMode: "text",
893
+ supportedUrls: {},
894
+ download
895
+ })
896
+ });
897
+ continue;
898
+ }
825
899
  try {
826
900
  const { execute } = tool2;
827
901
  const resolvedContext = await resolveToolContext({
@@ -836,7 +910,7 @@ var WorkflowAgent = class {
836
910
  input: approval.input
837
911
  };
838
912
  const messages2 = prompt.messages;
839
- await ((_j = telemetryDispatcher.onToolExecutionStart) == null ? void 0 : _j.call(telemetryDispatcher, {
913
+ await ((_k = telemetryDispatcher.onToolExecutionStart) == null ? void 0 : _k.call(telemetryDispatcher, {
840
914
  toolCall: toolCallEvent,
841
915
  stepNumber: 0,
842
916
  messages: messages2,
@@ -853,7 +927,7 @@ var WorkflowAgent = class {
853
927
  toolCallId: approval.toolCallId,
854
928
  execute: executeApprovedTool
855
929
  }) : await executeApprovedTool();
856
- await ((_k = telemetryDispatcher.onToolExecutionEnd) == null ? void 0 : _k.call(telemetryDispatcher, {
930
+ await ((_l = telemetryDispatcher.onToolExecutionEnd) == null ? void 0 : _l.call(telemetryDispatcher, {
857
931
  toolCall: toolCallEvent,
858
932
  stepNumber: 0,
859
933
  durationMs: Date.now() - startTime,
@@ -885,7 +959,7 @@ var WorkflowAgent = class {
885
959
  });
886
960
  } catch (error) {
887
961
  const errorMessage = getErrorMessage(error);
888
- await ((_l = telemetryDispatcher.onToolExecutionEnd) == null ? void 0 : _l.call(telemetryDispatcher, {
962
+ await ((_m = telemetryDispatcher.onToolExecutionEnd) == null ? void 0 : _m.call(telemetryDispatcher, {
889
963
  toolCall: {
890
964
  type: "tool-call",
891
965
  toolCallId: approval.toolCallId,
@@ -976,7 +1050,7 @@ var WorkflowAgent = class {
976
1050
  download
977
1051
  });
978
1052
  const effectiveAbortSignal = mergeAbortSignals(
979
- (_m = options.abortSignal) != null ? _m : effectiveGenerationSettings.abortSignal,
1053
+ (_n = options.abortSignal) != null ? _n : effectiveGenerationSettings.abortSignal,
980
1054
  options.timeout
981
1055
  );
982
1056
  const mergedGenerationSettings = {
@@ -1012,7 +1086,7 @@ var WorkflowAgent = class {
1012
1086
  };
1013
1087
  const mergedOnStepEnd = mergeCallbacks(
1014
1088
  this.constructorOnStepEnd,
1015
- (_n = options.onStepEnd) != null ? _n : options.onStepFinish
1089
+ (_o = options.onStepEnd) != null ? _o : options.onStepFinish
1016
1090
  );
1017
1091
  const mergedOnEnd = mergeCallbacks(
1018
1092
  this.constructorOnEnd,
@@ -1035,11 +1109,11 @@ var WorkflowAgent = class {
1035
1109
  options.onToolExecutionEnd
1036
1110
  );
1037
1111
  const effectiveToolChoice = effectiveToolChoiceFromPrepare;
1038
- const effectiveActiveTools = (_o = options.activeTools) != null ? _o : this.activeTools;
1039
- const effectiveTools = effectiveActiveTools && effectiveActiveTools.length > 0 ? (_p = filterActiveTools2({
1112
+ const effectiveActiveTools = (_p = options.activeTools) != null ? _p : this.activeTools;
1113
+ const effectiveTools = effectiveActiveTools && effectiveActiveTools.length > 0 ? (_q = filterActiveTools2({
1040
1114
  tools: this.tools,
1041
1115
  activeTools: effectiveActiveTools
1042
- })) != null ? _p : this.tools : this.tools;
1116
+ })) != null ? _q : this.tools : this.tools;
1043
1117
  const effectiveModelInfo = getModelInfo2(effectiveModel);
1044
1118
  let runtimeContext = effectiveRuntimeContext;
1045
1119
  let toolsContext = effectiveToolsContext;
@@ -1054,7 +1128,7 @@ var WorkflowAgent = class {
1054
1128
  toolsContext
1055
1129
  });
1056
1130
  }
1057
- await ((_s = telemetryDispatcher.onStart) == null ? void 0 : _s.call(telemetryDispatcher, {
1131
+ await ((_t = telemetryDispatcher.onStart) == null ? void 0 : _t.call(telemetryDispatcher, {
1058
1132
  callId: "workflow-agent",
1059
1133
  operationId: "ai.workflowAgent.stream",
1060
1134
  provider: effectiveModelInfo.provider,
@@ -1072,11 +1146,11 @@ var WorkflowAgent = class {
1072
1146
  frequencyPenalty: mergedGenerationSettings.frequencyPenalty,
1073
1147
  stopSequences: mergedGenerationSettings.stopSequences,
1074
1148
  seed: mergedGenerationSettings.seed,
1075
- maxRetries: (_q = mergedGenerationSettings.maxRetries) != null ? _q : 2,
1149
+ maxRetries: (_r = mergedGenerationSettings.maxRetries) != null ? _r : 2,
1076
1150
  timeout: void 0,
1077
1151
  headers: mergedGenerationSettings.headers,
1078
1152
  providerOptions: mergedGenerationSettings.providerOptions,
1079
- output: (_r = options.output) != null ? _r : this.output,
1153
+ output: (_s = options.output) != null ? _s : this.output,
1080
1154
  runtimeContext,
1081
1155
  toolsContext
1082
1156
  }));
@@ -1219,7 +1293,7 @@ var WorkflowAgent = class {
1219
1293
  }
1220
1294
  }));
1221
1295
  };
1222
- if ((_t = mergedGenerationSettings.abortSignal) == null ? void 0 : _t.aborted) {
1296
+ if ((_u = mergedGenerationSettings.abortSignal) == null ? void 0 : _u.aborted) {
1223
1297
  if (options.onAbort) {
1224
1298
  await options.onAbort({ steps });
1225
1299
  }
@@ -1236,19 +1310,19 @@ var WorkflowAgent = class {
1236
1310
  tools: effectiveTools,
1237
1311
  writable: options.writable,
1238
1312
  prompt: modelPrompt,
1239
- stopConditions: (_u = options.stopWhen) != null ? _u : this.stopWhen,
1313
+ stopConditions: (_v = options.stopWhen) != null ? _v : this.stopWhen,
1240
1314
  onStepEnd: mergedOnStepEnd,
1241
1315
  onStepStart: mergedOnStepStart,
1242
1316
  onError: options.onError,
1243
- prepareStep: (_v = options.prepareStep) != null ? _v : this.prepareStep,
1317
+ prepareStep: (_w = options.prepareStep) != null ? _w : this.prepareStep,
1244
1318
  generationSettings: mergedGenerationSettings,
1245
1319
  toolChoice: effectiveToolChoice,
1246
1320
  runtimeContext,
1247
1321
  toolsContext,
1248
1322
  telemetry: effectiveTelemetry,
1249
- includeRawChunks: (_w = options.includeRawChunks) != null ? _w : false,
1250
- repairToolCall: (_x = options.experimental_repairToolCall) != null ? _x : this.experimentalRepairToolCall,
1251
- responseFormat: await ((_z = (_y = options.output) != null ? _y : this.output) == null ? void 0 : _z.responseFormat)
1323
+ includeRawChunks: (_x = options.includeRawChunks) != null ? _x : false,
1324
+ repairToolCall: (_y = options.experimental_repairToolCall) != null ? _y : this.experimentalRepairToolCall,
1325
+ responseFormat: await ((_A = (_z = options.output) != null ? _z : this.output) == null ? void 0 : _A.responseFormat)
1252
1326
  });
1253
1327
  let finalMessages;
1254
1328
  let encounteredError;
@@ -1256,7 +1330,7 @@ var WorkflowAgent = class {
1256
1330
  try {
1257
1331
  let result = await iterator.next();
1258
1332
  while (!result.done) {
1259
- if ((_A = mergedGenerationSettings.abortSignal) == null ? void 0 : _A.aborted) {
1333
+ if ((_B = mergedGenerationSettings.abortSignal) == null ? void 0 : _B.aborted) {
1260
1334
  wasAborted = true;
1261
1335
  if (options.onAbort) {
1262
1336
  await options.onAbort({ steps });
@@ -1389,8 +1463,8 @@ var WorkflowAgent = class {
1389
1463
  await mergedOnEnd({
1390
1464
  steps,
1391
1465
  messages: messages2,
1392
- text: (_B = lastStep == null ? void 0 : lastStep.text) != null ? _B : "",
1393
- finishReason: (_C = lastStep == null ? void 0 : lastStep.finishReason) != null ? _C : "other",
1466
+ text: (_C = lastStep == null ? void 0 : lastStep.text) != null ? _C : "",
1467
+ finishReason: (_D = lastStep == null ? void 0 : lastStep.finishReason) != null ? _D : "other",
1394
1468
  usage: totalUsage,
1395
1469
  totalUsage,
1396
1470
  runtimeContext,
@@ -1402,7 +1476,7 @@ var WorkflowAgent = class {
1402
1476
  const telemetrySteps = steps.map(normalizeStepForTelemetry2);
1403
1477
  const lastStep = telemetrySteps[telemetrySteps.length - 1];
1404
1478
  const totalUsage = aggregateUsage(steps);
1405
- await ((_D = telemetryDispatcher.onEnd) == null ? void 0 : _D.call(telemetryDispatcher, {
1479
+ await ((_E = telemetryDispatcher.onEnd) == null ? void 0 : _E.call(telemetryDispatcher, {
1406
1480
  ...lastStep,
1407
1481
  steps: telemetrySteps,
1408
1482
  usage: totalUsage,
@@ -1427,8 +1501,8 @@ var WorkflowAgent = class {
1427
1501
  }
1428
1502
  }
1429
1503
  if (options.writable) {
1430
- const sendFinish = (_E = options.sendFinish) != null ? _E : true;
1431
- const preventClose = (_F = options.preventClose) != null ? _F : false;
1504
+ const sendFinish = (_F = options.sendFinish) != null ? _F : true;
1505
+ const preventClose = (_G = options.preventClose) != null ? _G : false;
1432
1506
  if (sendFinish || !preventClose) {
1433
1507
  await closeStream(options.writable, preventClose, sendFinish);
1434
1508
  }
@@ -1551,10 +1625,10 @@ var WorkflowAgent = class {
1551
1625
  } else if (options.onError) {
1552
1626
  await options.onError({ error });
1553
1627
  }
1554
- await ((_G = telemetryDispatcher.onError) == null ? void 0 : _G.call(telemetryDispatcher, error));
1628
+ await ((_H = telemetryDispatcher.onError) == null ? void 0 : _H.call(telemetryDispatcher, error));
1555
1629
  }
1556
1630
  const messages = finalMessages != null ? finalMessages : prompt.messages;
1557
- const effectiveOutput = (_H = options.output) != null ? _H : this.output;
1631
+ const effectiveOutput = (_I = options.output) != null ? _I : this.output;
1558
1632
  let experimentalOutput = void 0;
1559
1633
  if (effectiveOutput && steps.length > 0) {
1560
1634
  const lastStep = steps[steps.length - 1];
@@ -1582,8 +1656,8 @@ var WorkflowAgent = class {
1582
1656
  await mergedOnEnd({
1583
1657
  steps,
1584
1658
  messages,
1585
- text: (_I = lastStep == null ? void 0 : lastStep.text) != null ? _I : "",
1586
- finishReason: (_J = lastStep == null ? void 0 : lastStep.finishReason) != null ? _J : "other",
1659
+ text: (_J = lastStep == null ? void 0 : lastStep.text) != null ? _J : "",
1660
+ finishReason: (_K = lastStep == null ? void 0 : lastStep.finishReason) != null ? _K : "other",
1587
1661
  usage: totalUsage,
1588
1662
  totalUsage,
1589
1663
  runtimeContext,
@@ -1595,7 +1669,7 @@ var WorkflowAgent = class {
1595
1669
  const telemetrySteps = steps.map(normalizeStepForTelemetry2);
1596
1670
  const lastStep = telemetrySteps[telemetrySteps.length - 1];
1597
1671
  const totalUsage = aggregateUsage(steps);
1598
- await ((_K = telemetryDispatcher.onEnd) == null ? void 0 : _K.call(telemetryDispatcher, {
1672
+ await ((_L = telemetryDispatcher.onEnd) == null ? void 0 : _L.call(telemetryDispatcher, {
1599
1673
  ...lastStep,
1600
1674
  steps: telemetrySteps,
1601
1675
  usage: totalUsage,
@@ -1604,8 +1678,8 @@ var WorkflowAgent = class {
1604
1678
  }
1605
1679
  if (encounteredError) {
1606
1680
  if (options.writable) {
1607
- const sendFinish = (_L = options.sendFinish) != null ? _L : true;
1608
- const preventClose = (_M = options.preventClose) != null ? _M : false;
1681
+ const sendFinish = (_M = options.sendFinish) != null ? _M : true;
1682
+ const preventClose = (_N = options.preventClose) != null ? _N : false;
1609
1683
  if (sendFinish || !preventClose) {
1610
1684
  await closeStream(options.writable, preventClose, sendFinish);
1611
1685
  }
@@ -1613,8 +1687,8 @@ var WorkflowAgent = class {
1613
1687
  throw encounteredError;
1614
1688
  }
1615
1689
  if (options.writable) {
1616
- const sendFinish = (_N = options.sendFinish) != null ? _N : true;
1617
- const preventClose = (_O = options.preventClose) != null ? _O : false;
1690
+ const sendFinish = (_O = options.sendFinish) != null ? _O : true;
1691
+ const preventClose = (_P = options.preventClose) != null ? _P : false;
1618
1692
  if (sendFinish || !preventClose) {
1619
1693
  await closeStream(options.writable, preventClose, sendFinish);
1620
1694
  }
@@ -1861,70 +1935,6 @@ async function executeTool(toolCall, tools, messages, context, download) {
1861
1935
  isError: false
1862
1936
  };
1863
1937
  }
1864
- function collectToolApprovalsFromMessages(messages) {
1865
- var _a;
1866
- const lastMessage = messages.at(-1);
1867
- if ((lastMessage == null ? void 0 : lastMessage.role) !== "tool") {
1868
- return { approvedToolApprovals: [], deniedToolApprovals: [] };
1869
- }
1870
- const toolCallsByToolCallId = {};
1871
- for (const message of messages) {
1872
- if (message.role === "assistant" && Array.isArray(message.content)) {
1873
- for (const part of message.content) {
1874
- if (part.type === "tool-call") {
1875
- toolCallsByToolCallId[part.toolCallId] = {
1876
- toolName: part.toolName,
1877
- input: (_a = part.input) != null ? _a : part.args
1878
- };
1879
- }
1880
- }
1881
- }
1882
- }
1883
- const approvalRequestsByApprovalId = {};
1884
- for (const message of messages) {
1885
- if (message.role === "assistant" && Array.isArray(message.content)) {
1886
- for (const part of message.content) {
1887
- if (part.type === "tool-approval-request") {
1888
- approvalRequestsByApprovalId[part.approvalId] = {
1889
- approvalId: part.approvalId,
1890
- toolCallId: part.toolCallId
1891
- };
1892
- }
1893
- }
1894
- }
1895
- }
1896
- const existingToolResults = /* @__PURE__ */ new Set();
1897
- for (const part of lastMessage.content) {
1898
- if (part.type === "tool-result") {
1899
- existingToolResults.add(part.toolCallId);
1900
- }
1901
- }
1902
- const approvedToolApprovals = [];
1903
- const deniedToolApprovals = [];
1904
- const approvalResponses = lastMessage.content.filter(
1905
- (part) => part.type === "tool-approval-response"
1906
- );
1907
- for (const response of approvalResponses) {
1908
- const approvalRequest = approvalRequestsByApprovalId[response.approvalId];
1909
- if (approvalRequest == null) continue;
1910
- if (existingToolResults.has(approvalRequest.toolCallId)) continue;
1911
- const toolCall = toolCallsByToolCallId[approvalRequest.toolCallId];
1912
- if (toolCall == null) continue;
1913
- const approval = {
1914
- toolCallId: approvalRequest.toolCallId,
1915
- toolName: toolCall.toolName,
1916
- input: toolCall.input,
1917
- approvalId: response.approvalId,
1918
- reason: response.reason
1919
- };
1920
- if (response.approved) {
1921
- approvedToolApprovals.push(approval);
1922
- } else {
1923
- deniedToolApprovals.push(approval);
1924
- }
1925
- }
1926
- return { approvedToolApprovals, deniedToolApprovals };
1927
- }
1928
1938
 
1929
1939
  // src/to-ui-message-chunk.ts
1930
1940
  function toUIMessageChunk(part) {