effect-agent 0.1.0-beta.93 → 0.1.0-beta.94

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.
Files changed (146) hide show
  1. package/dist/{Agent-RklVROrm.mjs → Agent-CG_VMrtA.mjs} +2 -2
  2. package/dist/{Agent-RklVROrm.mjs.map → Agent-CG_VMrtA.mjs.map} +1 -1
  3. package/dist/{Agent-CN0EWRsR.d.mts → Agent-CM6JubNX.d.mts} +2 -2
  4. package/dist/{AgentPolicy-CetmQrf3.d.mts → AgentPolicy-BqDoSXO6.d.mts} +2 -2
  5. package/dist/{AgentRuntime-BPjQM1Nv.mjs → AgentRuntime-Cdvqw7l_.mjs} +88 -32
  6. package/dist/AgentRuntime-Cdvqw7l_.mjs.map +1 -0
  7. package/dist/{AgentRuntime-uxR_a4VM.d.mts → AgentRuntime-CufqQ4JM.d.mts} +8 -8
  8. package/dist/{ContextCompactor-Dxu5zhOL.d.mts → ContextCompactor-WwSIG_Z2.d.mts} +2 -2
  9. package/dist/{DurableAgentRuntime-CdrBuKZY.mjs → DurableAgentRuntime-BaGsWDHO.mjs} +9 -5
  10. package/dist/DurableAgentRuntime-BaGsWDHO.mjs.map +1 -0
  11. package/dist/{EventSource-c6GRTCsl.d.mts → EventSource-DVfX1GRH.d.mts} +3 -3
  12. package/dist/{Messaging-CkcIdt4I.d.mts → Messaging-QMYhp297.d.mts} +3 -3
  13. package/dist/{MessagingHost-CRk0kXzT.d.mts → MessagingHost-gAxs5gme.d.mts} +3 -3
  14. package/dist/{Output-D6SkedVx.d.mts → Output-CjocJzsl.d.mts} +2 -2
  15. package/dist/{PreparedInputAdmission-QzFbW2LQ.d.mts → PreparedInputAdmission-DoWaMC2R.d.mts} +4 -4
  16. package/dist/{Records-vFp_ZQ9Y.d.mts → Records-B84j3Jq_.d.mts} +21 -9
  17. package/dist/{Records-C9cezOEf.mjs → Records-CsEsSW2o.mjs} +5 -2
  18. package/dist/Records-CsEsSW2o.mjs.map +1 -0
  19. package/dist/{RunJournal-lwbrh2hZ.mjs → RunJournal-yYpUlTOE.mjs} +3 -2
  20. package/dist/RunJournal-yYpUlTOE.mjs.map +1 -0
  21. package/dist/{RunOptions-g1aigcKI.d.mts → RunOptions-CE-G3niT.d.mts} +14 -8
  22. package/dist/{Schedule-BkZvKyMw.d.mts → Schedule-DCYBfhgT.d.mts} +2 -2
  23. package/dist/{Scheduling-D0QqOoeT.mjs → Scheduling-CUuYc3-D.mjs} +3 -3
  24. package/dist/{Scheduling-D0QqOoeT.mjs.map → Scheduling-CUuYc3-D.mjs.map} +1 -1
  25. package/dist/{Subagent-CnTzCy8k.mjs → Subagent-Cbfzylz1.mjs} +3 -3
  26. package/dist/{Subagent-CnTzCy8k.mjs.map → Subagent-Cbfzylz1.mjs.map} +1 -1
  27. package/dist/{Subagent-DyfzHNdv.d.mts → Subagent-DscbtbNY.d.mts} +6 -6
  28. package/dist/{SubagentHost-CJUBQlcc.d.mts → SubagentHost-DaFMZnVR.d.mts} +4 -4
  29. package/dist/{Subscription-CKQ0KDHN.d.mts → Subscription-C8IHdmFk.d.mts} +4 -4
  30. package/dist/{SubscriptionInput-Cth8F_7b.d.mts → SubscriptionInput-DhM3jUW2.d.mts} +3 -3
  31. package/dist/{ToolResult-DmmgoChv.d.mts → ToolResult-BKdqWRff.d.mts} +20 -3
  32. package/dist/{Worker-CaPYykpe.d.mts → Worker--j87JI__.d.mts} +2 -2
  33. package/dist/capabilities/Approval.mjs +1 -1
  34. package/dist/capabilities/Commands.d.mts +1 -1
  35. package/dist/capabilities/Messaging.d.mts +2 -2
  36. package/dist/capabilities/ModelContext.mjs +1 -1
  37. package/dist/capabilities/RunHooks.d.mts +1 -1
  38. package/dist/capabilities/Subagent.d.mts +2 -2
  39. package/dist/capabilities/Subagent.mjs +1 -1
  40. package/dist/capabilities/ToolDiscovery.d.mts +19 -4
  41. package/dist/capabilities/ToolDiscovery.mjs +33 -14
  42. package/dist/capabilities/ToolDiscovery.mjs.map +1 -1
  43. package/dist/core/Agent.d.mts +1 -1
  44. package/dist/core/Agent.mjs +1 -1
  45. package/dist/core/AgentPolicy.d.mts +1 -1
  46. package/dist/core/Thread.mjs +1 -1
  47. package/dist/core/ToolResult.d.mts +2 -2
  48. package/dist/core/ToolResult.mjs +17 -1
  49. package/dist/core/ToolResult.mjs.map +1 -1
  50. package/dist/core/Worker.d.mts +1 -1
  51. package/dist/core/Worker.mjs +1 -1
  52. package/dist/durable/ActivityStore.d.mts +1 -1
  53. package/dist/durable/ActivityStore.mjs +1 -1
  54. package/dist/durable/Admin.d.mts +1 -1
  55. package/dist/durable/Admin.mjs +1 -1
  56. package/dist/durable/AgentRegistration.d.mts +1 -1
  57. package/dist/durable/Certification.d.mts +1 -1
  58. package/dist/durable/CommittedActivity.d.mts +1 -1
  59. package/dist/durable/CommittedActivity.mjs +1 -1
  60. package/dist/durable/Digest.d.mts +1 -1
  61. package/dist/durable/Digest.mjs +1 -1
  62. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  63. package/dist/durable/DurableAgentRuntime.mjs +1 -1
  64. package/dist/durable/EventSource.d.mts +1 -1
  65. package/dist/durable/EventSource.mjs +1 -1
  66. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  67. package/dist/durable/GitHubWorkflowSource.mjs +1 -1
  68. package/dist/durable/MessageDelivery.d.mts +23 -23
  69. package/dist/durable/MessageDelivery.mjs +1 -1
  70. package/dist/durable/MessageDeliveryStoreConformance.d.mts +40 -40
  71. package/dist/durable/MessageDeliveryStoreConformance.mjs +1 -1
  72. package/dist/durable/PersistentHistory.d.mts +1 -1
  73. package/dist/durable/PersistentHistory.mjs +2 -2
  74. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  75. package/dist/durable/Records.d.mts +1 -1
  76. package/dist/durable/Records.mjs +1 -1
  77. package/dist/durable/Recovery.d.mts +1 -1
  78. package/dist/durable/Recovery.mjs +1 -1
  79. package/dist/durable/RunJournal.d.mts +1 -1
  80. package/dist/durable/RunJournal.mjs +1 -1
  81. package/dist/durable/Schedule.d.mts +1 -1
  82. package/dist/durable/Schedule.mjs +1 -1
  83. package/dist/durable/ScheduleStoreConformance.d.mts +1 -1
  84. package/dist/durable/ScheduleStoreConformance.mjs +1 -1
  85. package/dist/durable/ScheduleTransition.d.mts +1 -1
  86. package/dist/durable/ScheduleTransition.mjs +1 -1
  87. package/dist/durable/Scheduling.d.mts +2 -2
  88. package/dist/durable/Scheduling.mjs +1 -1
  89. package/dist/durable/SqlStorageV2Upgrade.d.mts +2 -2
  90. package/dist/durable/SqlStorageV2Upgrade.mjs +1 -1
  91. package/dist/durable/SqlSubscriptionStore.d.mts +2 -2
  92. package/dist/durable/SqlSubscriptionStore.mjs +1 -1
  93. package/dist/durable/SubmissionLedger.d.mts +1 -1
  94. package/dist/durable/SubmissionLedger.mjs +1 -1
  95. package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
  96. package/dist/durable/SubmissionLedgerConformance.mjs +2 -2
  97. package/dist/durable/SubmissionStatus.d.mts +1 -1
  98. package/dist/durable/Subscription.d.mts +1 -1
  99. package/dist/durable/Subscription.mjs +1 -1
  100. package/dist/durable/SubscriptionInput.d.mts +1 -1
  101. package/dist/durable/SubscriptionInput.mjs +1 -1
  102. package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
  103. package/dist/durable/SubscriptionStoreConformance.mjs +1 -1
  104. package/dist/durable/SubscriptionTools.d.mts +3 -3
  105. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  106. package/dist/durable/SubscriptionTransition.mjs +1 -1
  107. package/dist/durable/Subscriptions.d.mts +5 -5
  108. package/dist/durable/Subscriptions.mjs +2 -2
  109. package/dist/durable/ThreadContextHistory.d.mts +1 -1
  110. package/dist/durable/ThreadContextHistory.mjs +1 -1
  111. package/dist/durable/ThreadContextHistoryProjection.d.mts +1 -1
  112. package/dist/durable/ThreadContextHistoryProjection.mjs +1 -1
  113. package/dist/durable/ThreadInvariants.d.mts +1 -1
  114. package/dist/durable/ThreadInvariants.mjs +1 -1
  115. package/dist/durable/ThreadProjection.d.mts +1 -1
  116. package/dist/durable/ThreadProjection.mjs +1 -1
  117. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  118. package/dist/durable/ThreadStore.d.mts +1 -1
  119. package/dist/durable/ThreadStore.mjs +1 -1
  120. package/dist/durable/ThreadStoreConformance.d.mts +1 -1
  121. package/dist/durable/ThreadStoreConformance.mjs +1 -1
  122. package/dist/durable/ToolReconciler.mjs +1 -1
  123. package/dist/durable/WorkerHost.d.mts +4 -4
  124. package/dist/engine/AgentRuntime.d.mts +1 -1
  125. package/dist/engine/AgentRuntime.mjs +1 -1
  126. package/dist/engine/Compaction.d.mts +1 -1
  127. package/dist/engine/ContextCompactor.d.mts +1 -1
  128. package/dist/engine/MessagingHost.d.mts +1 -1
  129. package/dist/engine/Output.d.mts +1 -1
  130. package/dist/engine/RunOptions.d.mts +1 -1
  131. package/dist/engine/RunOptions.mjs.map +1 -1
  132. package/dist/engine/SubagentHost.d.mts +1 -1
  133. package/dist/index.d.mts +18 -18
  134. package/dist/index.mjs +8 -8
  135. package/package.json +1 -1
  136. package/src/capabilities/ToolDiscovery.ts +48 -18
  137. package/src/core/ToolResult.ts +20 -0
  138. package/src/durable/DurableAgentRuntime.ts +7 -0
  139. package/src/durable/Records.ts +3 -0
  140. package/src/durable/RunJournal.ts +5 -0
  141. package/src/engine/RunOptions.ts +9 -3
  142. package/src/engine/internal/agent-runtime.ts +254 -67
  143. package/dist/AgentRuntime-BPjQM1Nv.mjs.map +0 -1
  144. package/dist/DurableAgentRuntime-CdrBuKZY.mjs.map +0 -1
  145. package/dist/Records-C9cezOEf.mjs.map +0 -1
  146. package/dist/RunJournal-lwbrh2hZ.mjs.map +0 -1
@@ -17,6 +17,7 @@ import {
17
17
  Queue,
18
18
  Result,
19
19
  Schema,
20
+ SchemaGetter,
20
21
  Scope,
21
22
  Semaphore,
22
23
  Stream,
@@ -100,6 +101,7 @@ import {
100
101
  import type { Selection } from "../../core/ToolExposure.ts";
101
102
  import { AdditionalToolCatalog, DiscoveryTool, Snapshot } from "../../core/ToolExposure.ts";
102
103
  import {
104
+ ToolParameterRejection,
103
105
  applyToolResultBounds,
104
106
  unserializableToolResult,
105
107
  type ToolResultBounds,
@@ -646,6 +648,8 @@ interface TurnTrace {
646
648
  /** Turn completion held with provider results so malformed trailing parts append neither. */
647
649
  turnCompletion: { readonly finishReason: Response.FinishReason } | undefined;
648
650
  readonly applicationToolCalls: Array<Response.ToolCallPart<string, unknown>>;
651
+ /** Fresh validation failures retained independently of executable parameters. */
652
+ readonly toolParameterRejections: Map<string, ToolParameterRejection>;
649
653
  /** Durable-hook view of the application calls, in declaration order (encoded parameters). */
650
654
  readonly applicationCallDescriptors: Array<RunToolCallDescriptor>;
651
655
  readonly applicationToolResults: Array<{
@@ -861,15 +865,58 @@ const inspectModelResponsePartCapacity = (
861
865
  });
862
866
 
863
867
  /**
864
- * `disableToolCallResolution` keeps model Tool Call parameters in their
865
- * encoded wire form. Build the response ownership codec from those encoded
866
- * parameter Schemas; the engine decodes and canonically re-encodes each call
867
- * against the Definition-owned Schema before it enters history or executes.
868
+ * LanguageModel validates encoded parameters even with automatic resolution disabled.
869
+ * Defer only recoverable application parameter validation; the declaration's JSON codec
870
+ * still exposes the original schema to providers and preserves their wire normalization.
871
+ * No handler is installed or executed by this transport-only Toolkit.
868
872
  */
873
+ const defersToolParameters = (tool: Tool.Any): boolean =>
874
+ tool.failureMode === "return" &&
875
+ !(Tool.isProviderDefined(tool) && !tool.requiresHandler) &&
876
+ !(Tool.isDynamic(tool) && tool.jsonSchema !== undefined);
877
+
878
+ const deferredToolParameterToolkit = <Tools extends Record<string, Tool.Any>>(
879
+ toolkit: Toolkit.Toolkit<Tools>,
880
+ ): Toolkit.Toolkit<Record<string, Tool.Any>> => {
881
+ // Preserve the native request unchanged when it has no recoverable parameters.
882
+ // Toolkit's handler parameter variance is irrelevant to this resolution-disabled transport.
883
+ if (!Object.values(toolkit.tools).some(defersToolParameters))
884
+ return toolkit as unknown as Toolkit.Toolkit<Record<string, Tool.Any>>;
885
+
886
+ return Toolkit.make(
887
+ ...Object.values(toolkit.tools).map((tool) =>
888
+ !defersToolParameters(tool)
889
+ ? tool
890
+ : tool.setParameters(
891
+ Schema.declareConstructor<unknown>()(
892
+ [],
893
+ () => (input) =>
894
+ Schema.decodeUnknownEffect(Schema.toEncoded(tool.parametersSchema))(input).pipe(
895
+ Effect.orElseSucceed(() => input),
896
+ ),
897
+ {
898
+ description: Tool.getDescription(tool),
899
+ toCodecJson: () =>
900
+ Schema.link<unknown>()(Schema.toEncoded(tool.parametersSchema), {
901
+ decode: SchemaGetter.passthrough(),
902
+ encode: SchemaGetter.passthrough(),
903
+ }),
904
+ },
905
+ ),
906
+ ),
907
+ ),
908
+ );
909
+ };
910
+
911
+ /** Own fresh JSON arguments without accepting them as executable parameters. */
869
912
  const encodedToolParameterToolkit = (toolkit: Toolkit.Any): Toolkit.Any =>
870
913
  Toolkit.make(
871
914
  ...Object.values(toolkit.tools).map((tool) =>
872
- tool.setParameters(Schema.toEncoded(tool.parametersSchema)),
915
+ tool.setParameters(
916
+ tool.failureMode === "return" && !(Tool.isProviderDefined(tool) && !tool.requiresHandler)
917
+ ? Schema.Json
918
+ : Schema.toEncoded(tool.parametersSchema),
919
+ ),
873
920
  ),
874
921
  );
875
922
 
@@ -1011,7 +1058,7 @@ interface PreparedToolCall<Tools extends Record<string, Tool.Any>> {
1011
1058
  readonly call: Response.ToolCallPart<string, unknown>;
1012
1059
  readonly name: keyof Tools & string;
1013
1060
  readonly toolCallId: ToolCallId;
1014
- readonly decodedParams: Tool.Parameters<ToolUnion<Tools>>;
1061
+ readonly validation: Result.Result<Tool.Parameters<ToolUnion<Tools>>, ToolParameterRejection>;
1015
1062
  readonly tool: ToolUnion<Tools>;
1016
1063
  readonly declarationIndex: number;
1017
1064
  }
@@ -1128,7 +1175,7 @@ const decodeToolCallParameters = <Tools extends Record<string, Tool.Any>>(
1128
1175
  tool: ToolUnion<Tools>,
1129
1176
  toolName: string,
1130
1177
  encodedParams: unknown,
1131
- boundary: "execution" | "resume" = "execution",
1178
+ boundary: "model" | "execution" | "resume" = "execution",
1132
1179
  ): Effect.Effect<
1133
1180
  Tool.Parameters<ToolUnion<Tools>>,
1134
1181
  ModelProtocolError,
@@ -1145,20 +1192,24 @@ const decodeToolCallParameters = <Tools extends Record<string, Tool.Any>>(
1145
1192
  return decodeParameters(encodedParams).pipe(
1146
1193
  Effect.mapError((cause) =>
1147
1194
  ModelProtocolError.make({
1148
- message: `Recorded parameters for Tool ${toolName} failed validation${boundary === "resume" ? " on resume" : ""}: ${cause.message}`,
1195
+ message:
1196
+ boundary === "model"
1197
+ ? `Invalid parameters for Tool ${toolName}: ${cause.message}`
1198
+ : `Recorded parameters for Tool ${toolName} failed validation${boundary === "resume" ? " on resume" : ""}: ${cause.message}`,
1149
1199
  }),
1150
1200
  ),
1151
1201
  );
1152
1202
  };
1153
1203
 
1154
1204
  /**
1155
- * Decode canonical parameters for approval while retaining their encoded
1156
- * form for the native `Toolkit.handle` boundary.
1205
+ * Decode executable parameters for approval while retaining their encoded form for
1206
+ * Toolkit.handle. A recorded rejection is a terminal failure, never an executable call.
1157
1207
  */
1158
1208
  const prepareToolCall = <Tools extends Record<string, Tool.Any>>(
1159
1209
  toolkit: Toolkit.WithHandler<Tools>,
1160
1210
  call: Response.ToolCallPart<string, unknown>,
1161
1211
  declarationIndex: number,
1212
+ rejection?: ToolParameterRejection,
1162
1213
  ): Effect.Effect<
1163
1214
  PreparedToolCall<Tools>,
1164
1215
  ModelProtocolError,
@@ -1173,20 +1224,16 @@ const prepareToolCall = <Tools extends Record<string, Tool.Any>>(
1173
1224
  }
1174
1225
  const tool = toolkit.tools[name] as ToolUnion<Tools>;
1175
1226
 
1176
- return decodeToolCallId(call.id).pipe(
1177
- Effect.flatMap((toolCallId) =>
1178
- decodeToolCallParameters<Tools>(tool, call.name, call.params).pipe(
1179
- Effect.map((decodedParams) => ({
1180
- call,
1181
- name,
1182
- toolCallId,
1183
- decodedParams,
1184
- tool,
1185
- declarationIndex,
1186
- })),
1187
- ),
1188
- ),
1189
- );
1227
+ return Effect.gen(function* () {
1228
+ const toolCallId = yield* decodeToolCallId(call.id);
1229
+
1230
+ const validation: PreparedToolCall<Tools>["validation"] =
1231
+ rejection === undefined
1232
+ ? Result.succeed(yield* decodeToolCallParameters<Tools>(tool, call.name, call.params))
1233
+ : Result.fail(rejection);
1234
+
1235
+ return { call, name, toolCallId, validation, tool, declarationIndex };
1236
+ });
1190
1237
  };
1191
1238
 
1192
1239
  /**
@@ -1577,6 +1624,9 @@ const approvalDecision = <Tools extends Record<string, Tool.Any>, Error, Require
1577
1624
  Requirements
1578
1625
  > =>
1579
1626
  Effect.gen(function* () {
1627
+ if (Result.isFailure(prepared.validation)) return { required: false } as const;
1628
+ const decodedParams = prepared.validation.success;
1629
+
1580
1630
  const approval = prepared.tool.needsApproval;
1581
1631
 
1582
1632
  if (approval === undefined || approval === false) {
@@ -1586,7 +1636,7 @@ const approvalDecision = <Tools extends Record<string, Tool.Any>, Error, Require
1586
1636
  const required =
1587
1637
  typeof approval === "function"
1588
1638
  ? yield* Effect.suspend(() => {
1589
- const result = approval(prepared.decodedParams, {
1639
+ const result = approval(decodedParams, {
1590
1640
  toolCallId: prepared.call.id,
1591
1641
  messages: context.history.content,
1592
1642
  });
@@ -1623,7 +1673,7 @@ const approvalDecision = <Tools extends Record<string, Tool.Any>, Error, Require
1623
1673
  turnId,
1624
1674
  toolCallId,
1625
1675
  toolName: prepared.call.name,
1626
- parameters: prepared.decodedParams,
1676
+ parameters: decodedParams,
1627
1677
  });
1628
1678
 
1629
1679
  return {
@@ -2044,29 +2094,31 @@ const executePreparedToolCall = <Tools extends Record<string, Tool.Any>>(
2044
2094
  );
2045
2095
  };
2046
2096
 
2047
- const started = Stream.fromEffect(
2048
- Effect.gen(function* () {
2049
- const toolCallId = yield* decodeToolCallId(call.id);
2097
+ const started = Result.isFailure(prepared.validation)
2098
+ ? Stream.empty
2099
+ : Stream.fromEffect(
2100
+ Effect.gen(function* () {
2101
+ const toolCallId = yield* decodeToolCallId(call.id);
2050
2102
 
2051
- yield* Effect.logDebug("agent tool handler started").pipe(
2052
- Effect.annotateLogs({
2053
- agentId: context.agentId,
2054
- runId: context.runId,
2055
- turnId,
2056
- ...(telemetryToolCallId === undefined ? {} : { toolCallId: telemetryToolCallId }),
2057
- toolName: call.name,
2058
- }),
2059
- );
2060
- yield* Metric.update(toolCounter, 1);
2103
+ yield* Effect.logDebug("agent tool handler started").pipe(
2104
+ Effect.annotateLogs({
2105
+ agentId: context.agentId,
2106
+ runId: context.runId,
2107
+ turnId,
2108
+ ...(telemetryToolCallId === undefined ? {} : { toolCallId: telemetryToolCallId }),
2109
+ toolName: call.name,
2110
+ }),
2111
+ );
2112
+ yield* Metric.update(toolCounter, 1);
2061
2113
 
2062
- return ToolCallStarted.make({
2063
- ...(yield* eventBase(context)),
2064
- turnId,
2065
- toolCallId,
2066
- toolName: call.name,
2067
- });
2068
- }).pipe(Effect.withLogSpan("AgentRuntime.tool")),
2069
- );
2114
+ return ToolCallStarted.make({
2115
+ ...(yield* eventBase(context)),
2116
+ turnId,
2117
+ toolCallId,
2118
+ toolName: call.name,
2119
+ });
2120
+ }).pipe(Effect.withLogSpan("AgentRuntime.tool")),
2121
+ );
2070
2122
 
2071
2123
  // Dynamic Tool lookup erases the name/ParametersEncoded correlation. The native handler
2072
2124
  // decodes these already-preflighted canonical parameters; only its input signature is widened.
@@ -2076,15 +2128,37 @@ const executePreparedToolCall = <Tools extends Record<string, Tool.Any>>(
2076
2128
  toolCallId: string,
2077
2129
  ) => ReturnType<typeof toolkit.handle>;
2078
2130
 
2131
+ const rejection = Result.isFailure(prepared.validation) ? prepared.validation.failure : undefined;
2132
+
2133
+ const handlerResults: Stream.Stream<
2134
+ Tool.HandlerResult<Tool.Any>,
2135
+ ToolExecutionError,
2136
+ ToolSpanTelemetry | Tool.HandlerServices<ToolUnion<Tools>>
2137
+ > = rejection === undefined
2138
+ ? Stream.unwrap(
2139
+ Effect.flatMap(ToolSpanTelemetry, ({ isolateToolkitHandle }) =>
2140
+ isolateToolkitHandle(handle.call(toolkit, prepared.name, call.params, call.id)),
2141
+ ),
2142
+ )
2143
+ : Stream.fromEffect(
2144
+ Schema.decodeEffect(Schema.toCodecJson(AiError.AiError))(rejection.error).pipe(
2145
+ Effect.mapError(() =>
2146
+ ModelProtocolError.make({ message: "Invalid parameter rejection evidence" }),
2147
+ ),
2148
+ Effect.map((error) => ({
2149
+ result: error,
2150
+ encodedResult: rejection.error,
2151
+ isFailure: true,
2152
+ preliminary: false,
2153
+ })),
2154
+ ),
2155
+ );
2156
+
2079
2157
  const results: Stream.Stream<
2080
2158
  RunEvent,
2081
2159
  ToolExecutionError,
2082
2160
  ToolSpanTelemetry | Tool.HandlerServices<ToolUnion<Tools>>
2083
- > = Stream.unwrap(
2084
- Effect.flatMap(ToolSpanTelemetry, ({ isolateToolkitHandle }) =>
2085
- isolateToolkitHandle(handle.call(toolkit, prepared.name, call.params, call.id)),
2086
- ),
2087
- ).pipe(
2161
+ > = handlerResults.pipe(
2088
2162
  Stream.mapEffect((result): Effect.Effect<RunEvent | undefined, ModelProtocolError> =>
2089
2163
  Effect.gen(function* () {
2090
2164
  if (terminal || trace.finalToolResultIds.has(call.id)) {
@@ -2395,9 +2469,15 @@ const executeToolBatch = <Tools extends Record<string, Tool.Any>, HookError, Hoo
2395
2469
  });
2396
2470
  }
2397
2471
 
2398
- // Resolve every name and decode every parameter object before any call stream is constructed.
2472
+ // Resolve the whole batch before constructing streams. Only calls with recorded
2473
+ // parameter rejection evidence bypass executable-parameter decoding and approval.
2399
2474
  const prepared = yield* Effect.forEach(calls, (call, declarationIndex) =>
2400
- prepareToolCall(toolkit, call, declarationIndex),
2475
+ prepareToolCall(
2476
+ toolkit,
2477
+ call,
2478
+ declarationIndex,
2479
+ trace.toolParameterRejections.get(call.id),
2480
+ ),
2401
2481
  );
2402
2482
 
2403
2483
  if (
@@ -2425,7 +2505,9 @@ const executeToolBatch = <Tools extends Record<string, Tool.Any>, HookError, Hoo
2425
2505
  // Reuse the canonical wire-form descriptors already committed with the response. Handler
2426
2506
  // parameters were decoded/re-encoded separately above, so the host policy never receives
2427
2507
  // the handler's live parameter object.
2428
- const descriptors = trace.applicationCallDescriptors;
2508
+ const descriptors = trace.applicationCallDescriptors.filter(
2509
+ (call) => !trace.toolParameterRejections.has(call.toolCallId),
2510
+ );
2429
2511
 
2430
2512
  const durability = options.durability;
2431
2513
  // The hook's requirements are captured here (they are already part of
@@ -4711,19 +4793,52 @@ const eventsForPart = Effect.fnUntraced(function* <Tools extends Record<string,
4711
4793
  const toolCallId = yield* decodeToolCallId(part.id);
4712
4794
  const tool = tools[part.name] as ToolUnion<Tools>;
4713
4795
 
4714
- const decodedParameters = yield* decodeToolCallParameters<Tools>(
4715
- tool,
4716
- part.name,
4717
- part.params,
4796
+ const decoded = yield* Effect.result(
4797
+ decodeToolCallParameters<Tools>(tool, part.name, part.params, "model"),
4718
4798
  );
4719
4799
 
4720
- const encodedParameters = yield* encodeToolCallParameters<Tools>(
4721
- tool,
4722
- part.name,
4723
- decodedParameters,
4724
- );
4800
+ let parameters: Schema.Json;
4801
+
4802
+ if (Result.isFailure(decoded)) {
4803
+ if (part.providerExecuted || tool.failureMode !== "return") return yield* decoded.failure;
4804
+ parameters = yield* decodeEventJson(part.params, "Tool parameters");
4805
+
4806
+ // Approval needs decoded arguments, so validation precedes Toolkit.handle. Use
4807
+ // Effect's native error and codec; calling the handler to discover this failure
4808
+ // would cross the authorization boundary if a decoder changed its answer.
4809
+ const error = AiError.make({
4810
+ module: "Toolkit",
4811
+ method: `${part.name}.handle`,
4812
+ reason: new AiError.ToolParameterValidationError({
4813
+ toolName: part.name,
4814
+ description: decoded.failure.message,
4815
+ }),
4816
+ });
4817
+
4818
+ const encodedError = yield* Schema.encodeEffect(Schema.toCodecJson(AiError.AiError))(
4819
+ error,
4820
+ ).pipe(
4821
+ Effect.mapError(() =>
4822
+ ModelProtocolError.make({ message: "Unable to encode parameter rejection" }),
4823
+ ),
4824
+ );
4725
4825
 
4726
- const parameters = yield* decodeEventJson(encodedParameters, "Tool parameters");
4826
+ const rejection = yield* Schema.decodeUnknownEffect(ToolParameterRejection)({
4827
+ toolCallId,
4828
+ parameters,
4829
+ error: encodedError,
4830
+ }).pipe(
4831
+ Effect.mapError(() =>
4832
+ ModelProtocolError.make({ message: "Invalid parameter rejection evidence" }),
4833
+ ),
4834
+ );
4835
+
4836
+ trace.toolParameterRejections.set(part.id, rejection);
4837
+ } else {
4838
+ parameters = yield* encodeToolCallParameters<Tools>(tool, part.name, decoded.success).pipe(
4839
+ Effect.flatMap((encoded) => decodeEventJson(encoded, "Tool parameters")),
4840
+ );
4841
+ }
4727
4842
 
4728
4843
  const canonicalCall = Response.makePart("tool-call", {
4729
4844
  id: part.id,
@@ -5397,6 +5512,7 @@ const makeTurn = <
5397
5512
  providerStagedPayloadBytes: 0,
5398
5513
  turnCompletion: undefined,
5399
5514
  applicationToolCalls: [],
5515
+ toolParameterRejections: new Map(),
5400
5516
  applicationCallDescriptors: [],
5401
5517
  applicationToolResults: [],
5402
5518
  finished: false,
@@ -6081,7 +6197,7 @@ const makeTurn = <
6081
6197
  // including the overflow retry — carries it at the last
6082
6198
  // system block.
6083
6199
  prompt: providerPrompt,
6084
- toolkit: requestToolkit,
6200
+ toolkit: deferredToolParameterToolkit(requestToolkit),
6085
6201
  disableToolCallResolution: true,
6086
6202
  // Exact required Tool selection preserves the toolkit. A oneOf
6087
6203
  // subset can drop other schemas and break the cached prefix.
@@ -6758,6 +6874,11 @@ const makeTurn = <
6758
6874
  turnId,
6759
6875
  responseMessages: promptFromTurnParts(trace),
6760
6876
  calls: trace.applicationCallDescriptors,
6877
+ ...(trace.toolParameterRejections.size === 0
6878
+ ? {}
6879
+ : {
6880
+ toolParameterRejections: [...trace.toolParameterRejections.values()],
6881
+ }),
6761
6882
  toolExposure: snapshot,
6762
6883
  });
6763
6884
  }
@@ -7148,6 +7269,7 @@ const makeResumeTurn = <
7148
7269
  providerStagedPayloadBytes: 0,
7149
7270
  turnCompletion: undefined,
7150
7271
  applicationToolCalls: [],
7272
+ toolParameterRejections: new Map(),
7151
7273
  applicationCallDescriptors: [],
7152
7274
  applicationToolResults: [],
7153
7275
  finished: true,
@@ -7160,6 +7282,49 @@ const makeResumeTurn = <
7160
7282
  { readonly index: number; readonly name: string; readonly providerExecuted: boolean }
7161
7283
  >();
7162
7284
 
7285
+ const rejections = yield* Schema.decodeEffect(Schema.Array(ToolParameterRejection))(
7286
+ resume.toolParameterRejections ?? [],
7287
+ ).pipe(
7288
+ Effect.mapError(() =>
7289
+ ModelProtocolError.make({ message: "Invalid parameter rejection evidence on resume" }),
7290
+ ),
7291
+ );
7292
+
7293
+ const sameJson = Schema.toEquivalence(Schema.Json);
7294
+
7295
+ for (const rejection of rejections) {
7296
+ const call = resume.calls.find((call) => call.id === rejection.toolCallId);
7297
+
7298
+ if (
7299
+ call === undefined ||
7300
+ call.providerExecuted ||
7301
+ call.name !== rejection.error.reason.toolName ||
7302
+ !sameJson(yield* decodeEventJson(call.params, "Tool parameters"), rejection.parameters) ||
7303
+ trace.toolParameterRejections.has(call.id) ||
7304
+ tools[call.name]?.failureMode !== "return"
7305
+ ) {
7306
+ return yield* ModelProtocolError.make({
7307
+ message: "Parameter rejection does not match recorded Tool Call on resume",
7308
+ });
7309
+ }
7310
+
7311
+ const validation = yield* Effect.result(
7312
+ decodeToolCallParameters<Tools>(
7313
+ tools[call.name] as ToolUnion<Tools>,
7314
+ call.name,
7315
+ call.params,
7316
+ "resume",
7317
+ ),
7318
+ );
7319
+
7320
+ if (Result.isSuccess(validation)) {
7321
+ return yield* ModelProtocolError.make({
7322
+ message: "Recorded parameter rejection contains valid arguments on resume",
7323
+ });
7324
+ }
7325
+ trace.toolParameterRejections.set(call.id, rejection);
7326
+ }
7327
+
7163
7328
  for (const call of resume.calls) {
7164
7329
  if (!hasTool(tools, call.name)) {
7165
7330
  return failRunEventStream(
@@ -7174,7 +7339,9 @@ const makeResumeTurn = <
7174
7339
  const tool = tools[call.name] as ToolUnion<Tools>;
7175
7340
  const toolCallId = yield* decodeToolCallId(call.id);
7176
7341
 
7177
- yield* decodeToolCallParameters<Tools>(tool, call.name, call.params, "resume");
7342
+ if (!trace.toolParameterRejections.has(call.id)) {
7343
+ yield* decodeToolCallParameters<Tools>(tool, call.name, call.params, "resume");
7344
+ }
7178
7345
  const parameters = yield* decodeEventJson(call.params, "Tool parameters");
7179
7346
  const providerExecuted = call.providerExecuted === true;
7180
7347
 
@@ -7267,6 +7434,26 @@ const makeResumeTurn = <
7267
7434
  providerCallIds,
7268
7435
  );
7269
7436
 
7437
+ const rejection = trace.toolParameterRejections.get(settledCall.id);
7438
+
7439
+ if (
7440
+ rejection !== undefined &&
7441
+ (!settledCall.isFailure ||
7442
+ settledCall.toolSelection !== undefined ||
7443
+ (settledCall.budgetRejected !== true &&
7444
+ !sameJson(
7445
+ settledCall.result,
7446
+ yield* decodeEventJson(
7447
+ boundEncodedToolResult(rejection.error, agent.definition.policy.toolResultBounds),
7448
+ "Rejected Tool result",
7449
+ ),
7450
+ )))
7451
+ ) {
7452
+ return yield* ModelProtocolError.make({
7453
+ message: "Settled result contradicts parameter rejection on resume",
7454
+ });
7455
+ }
7456
+
7270
7457
  const declared = declarationByCallId.get(settledCall.id);
7271
7458
 
7272
7459
  if (declared === undefined) {