@kortyx/agent 0.32.0 → 0.33.0

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/dist/index.js CHANGED
@@ -2247,7 +2247,8 @@ var require_dist2 = __commonJS({
2247
2247
  if (!value.result || typeof value.result !== "object") return void 0;
2248
2248
  return {
2249
2249
  status: "completed",
2250
- result: value.result
2250
+ result: value.result,
2251
+ ...Array.isArray(value.resolvedInterrupts) ? { resolvedInterrupts: value.resolvedInterrupts } : {}
2251
2252
  };
2252
2253
  };
2253
2254
  var resolveHookStatePatch = (args) => ({
@@ -2305,6 +2306,10 @@ var require_dist2 = __commonJS({
2305
2306
  raw,
2306
2307
  "useInterrupt response"
2307
2308
  );
2309
+ if (args.onResolved) {
2310
+ const { __kortyxResumeStatePatch: _patch, ...meta } = enrichedRequest.meta ?? {};
2311
+ args.onResolved({ ...enrichedRequest, meta });
2312
+ }
2308
2313
  return Promise.resolve(response);
2309
2314
  };
2310
2315
  var CONTROL_TOOL_PREFIX = "kortyx_request_input__";
@@ -4889,7 +4894,7 @@ ${tryStringify2(args.response)}`
4889
4894
  interruptInstructions: [
4890
4895
  "Human-input rules:",
4891
4896
  "- The kortyx_request_input__* tools pause this reasoning operation and return the validated human response as a normal tool result.",
4892
- "- Call at most one human-input tool in a model turn, and do not combine it with other tool calls in that turn.",
4897
+ "- You may combine human-input tools with domain tools and output controls. Tools, emitted outputs, and human-input requests run in their requested order. Terminal output publishes last using the completed results.",
4893
4898
  interruptConfig.mode === "required" ? "- You must call at least one human-input tool before returning the final answer." : "- Human input is optional; call a human-input tool only when the request cannot be completed safely without it."
4894
4899
  ].join("\n")
4895
4900
  } : {},
@@ -4899,7 +4904,7 @@ ${tryStringify2(args.response)}`
4899
4904
  "- The kortyx_emit__* tools publish validated structured output and return an acknowledgement. You may continue with text or more tools afterward.",
4900
4905
  "- The kortyx_return__* tools publish a validated result and end this reasoning operation. If return tools are available, call exactly one before finishing.",
4901
4906
  "- For contracts with realtime streaming, use kortyx_stream_emit__* or kortyx_stream_return__*. These start a separate streamed JSON generation; its fields become visible as tokens arrive.",
4902
- "- Call a structured output control tool alone in its model turn."
4907
+ "- You may combine multiple emit controls, domain tools, and human-input requests in one turn. Tools, emitted outputs, and human-input requests run in their requested order; the terminal return runs last. Outputs after domain tools or human input are regenerated using their completed results. Request at most one terminal return per turn."
4903
4908
  ].join("\n")
4904
4909
  } : {}
4905
4910
  });
@@ -4928,6 +4933,19 @@ ${tryStringify2(args.response)}`
4928
4933
  );
4929
4934
  let pending = checkpoint?.pending;
4930
4935
  let pendingStepIndex = checkpoint?.pendingStepIndex;
4936
+ let outputContractCorrections = checkpoint?.outputContractCorrections ?? 0;
4937
+ const correctionExhausted = () => {
4938
+ const message = `useReason could not complete output-contract correction within toolExecution.maxSteps (${maxSteps}). Select exactly one terminal return contract; multiple emit contracts are allowed. Increase maxSteps to allow correction and output generation.`;
4939
+ return new import_errors62.ProviderRequestError(message, {
4940
+ code: "REASON_OUTPUT_CONTRACT_CORRECTION_EXHAUSTED",
4941
+ operation: "useReason",
4942
+ safeMessage: message,
4943
+ retryable: false,
4944
+ details: { maxSteps, outputContractCorrections }
4945
+ });
4946
+ };
4947
+ const resolvedInterrupts = checkpoint?.resolvedInterrupts ?? [];
4948
+ for (const request of resolvedInterrupts) ctx.node.replayInterrupt?.(request);
4931
4949
  const approvedCalls = checkpoint?.approvedCalls ?? [];
4932
4950
  const interruptHistory = checkpoint?.interruptHistory ?? [];
4933
4951
  const emissions = checkpoint?.emissions ?? [];
@@ -4939,8 +4957,10 @@ ${tryStringify2(args.response)}`
4939
4957
  messages,
4940
4958
  steps,
4941
4959
  approvedCalls,
4960
+ resolvedInterrupts,
4942
4961
  interruptHistory,
4943
4962
  emissions,
4963
+ outputContractCorrections,
4944
4964
  ...returned ? { returned } : {},
4945
4965
  finalizing,
4946
4966
  ...pending ? { pending } : {},
@@ -4948,6 +4968,10 @@ ${tryStringify2(args.response)}`
4948
4968
  };
4949
4969
  ctx.stateDirty = true;
4950
4970
  };
4971
+ const rememberInterrupt = (request) => {
4972
+ resolvedInterrupts.push(request);
4973
+ save();
4974
+ };
4951
4975
  const emitToolEvent = (event, payload) => {
4952
4976
  ctx.node.emit(event, {
4953
4977
  ...payload,
@@ -4957,7 +4981,7 @@ ${tryStringify2(args.response)}`
4957
4981
  });
4958
4982
  };
4959
4983
  try {
4960
- for (let stepIndex = pending ? pendingStepIndex ?? steps.length - 1 : steps.length; stepIndex < maxSteps; stepIndex += 1) {
4984
+ for (let stepIndex = pending ? pendingStepIndex ?? steps.length - 1 : steps.length; stepIndex < maxSteps; stepIndex = steps.length) {
4961
4985
  (0, import_core52.throwIfExecutionAborted)(abortSignal);
4962
4986
  traceSpan?.addEvent?.("useReason.tool-step.start", {
4963
4987
  stepIndex,
@@ -5094,273 +5118,301 @@ ${tryStringify2(args.response)}`
5094
5118
  const outputCalls = toolCalls.filter(
5095
5119
  (call) => outputByToolName.has(call.name)
5096
5120
  );
5097
- if (outputCalls.length > 0) {
5098
- if (toolCalls.length !== 1)
5099
- throw new Error(
5100
- "A useReason output contract call must be the only tool call in its model turn."
5101
- );
5102
- const call = outputCalls[0];
5103
- if (!call) throw new Error("Missing useReason output control call.");
5104
- const resolved = outputByToolName.get(call.name);
5105
- if (!resolved)
5106
- throw new Error(
5107
- `Unknown useReason output contract tool "${call.name}".`
5108
- );
5109
- if (toolResults.some((result3) => result3.toolCallId === call.id)) {
5110
- pending = void 0;
5111
- pendingStepIndex = void 0;
5112
- save();
5113
- continue;
5121
+ const returnCalls = outputCalls.filter(
5122
+ (call) => outputByToolName.get(call.name)?.kind === "return"
5123
+ );
5124
+ if (returnCalls.length > 1) {
5125
+ let corrected = false;
5126
+ for (const call of returnCalls) {
5127
+ if (toolResults.some((result3) => result3.toolCallId === call.id))
5128
+ continue;
5129
+ const result2 = {
5130
+ toolCallId: call.id,
5131
+ name: call.name,
5132
+ isError: true,
5133
+ content: "MULTIPLE_TERMINAL_OUTPUTS: No terminal output was published. Select exactly one return contract using the tool and human-input results. Do not repeat completed calls. Multiple emit contracts are allowed."
5134
+ };
5135
+ toolResults.push(result2);
5136
+ messages.push({ role: "tool", ...result2 });
5137
+ corrected = true;
5114
5138
  }
5115
- if (resolved.kind === "emit" && emissions.length >= (useReasonArgs.outputs?.maxEmissions ?? 16))
5116
- throw new Error(
5117
- `useReason reached outputs.maxEmissions (${useReasonArgs.outputs?.maxEmissions ?? 16}).`
5139
+ if (corrected) outputContractCorrections += 1;
5140
+ save();
5141
+ }
5142
+ const orderedCalls = [
5143
+ ...toolCalls.filter(
5144
+ (call) => outputByToolName.get(call.name)?.kind !== "return"
5145
+ ),
5146
+ ...returnCalls
5147
+ ];
5148
+ for (const toolCall of orderedCalls) {
5149
+ (0, import_core52.throwIfExecutionAborted)(abortSignal);
5150
+ if (outputByToolName.has(toolCall.name)) {
5151
+ const call = toolCall;
5152
+ const hasEvidenceCalls = toolResults.some(
5153
+ (result3) => !outputByToolName.has(result3.name)
5118
5154
  );
5119
- let data;
5120
- if (resolved.stream) {
5121
- if (!stream)
5155
+ const resolved = outputByToolName.get(call.name);
5156
+ if (!resolved)
5122
5157
  throw new Error(
5123
- `useReason streaming output contract "${resolved.name}" requires stream: true.`
5158
+ `Unknown useReason output contract tool "${call.name}".`
5124
5159
  );
5125
- if (stepIndex + 1 >= maxSteps)
5126
- throw new Error(
5127
- `useReason output streaming requires another model pass within maxSteps (${maxSteps}).`
5128
- );
5129
- const instruction = call.input && typeof call.input === "object" && !Array.isArray(call.input) ? call.input.instruction : void 0;
5130
- if (typeof instruction !== "string" || instruction.trim().length === 0)
5160
+ if (toolResults.some((result3) => result3.toolCallId === call.id)) {
5161
+ if (resolved.kind === "return" && returned) completed = true;
5162
+ continue;
5163
+ }
5164
+ if (resolved.kind === "emit" && emissions.length >= (useReasonArgs.outputs?.maxEmissions ?? 16))
5131
5165
  throw new Error(
5132
- `useReason streaming output contract "${resolved.name}" requires an instruction.`
5133
- );
5134
- const streamId = `${opId}:${resolved.kind}:${resolved.name}:${resolved.kind === "emit" ? emissions.length : "return"}`;
5135
- const structured = {
5136
- dataType: resolved.contract.schemaId,
5137
- schemaId: resolved.contract.schemaId,
5138
- schemaVersion: resolved.contract.schemaVersion,
5139
- fields: resolved.contract.stream?.fields
5140
- };
5141
- const inferred = inferOutputFormat(
5142
- resolved.contract.schema,
5143
- void 0
5144
- );
5145
- try {
5146
- const outputStep = await reasonEngine(
5147
- {
5148
- ...useReasonArgs,
5149
- tools: [],
5150
- ...inferred.responseFormat ? { responseFormat: inferred.responseFormat } : {},
5151
- messages: [
5152
- ...messages,
5153
- {
5154
- role: "tool",
5155
- toolCallId: call.id,
5156
- name: call.name,
5157
- content: "Generating the requested structured output."
5158
- },
5159
- {
5160
- role: "user",
5161
- content: withOutputGuardrails(
5162
- instruction,
5163
- resolved.contract.schema
5164
- )
5165
- }
5166
- ],
5167
- emit: false,
5168
- stream,
5169
- ...emit && stream ? {
5170
- onTextChunk: createStructuredOutputStreamer(
5171
- { structured },
5172
- id3,
5173
- streamId
5174
- )
5175
- } : {}
5176
- },
5177
- { ...id3 ? { id: id3 } : {}, opId }
5166
+ `useReason reached outputs.maxEmissions (${useReasonArgs.outputs?.maxEmissions ?? 16}).`
5178
5167
  );
5179
- if (outputStep.toolCalls?.length)
5168
+ let data;
5169
+ if (resolved.stream || hasEvidenceCalls) {
5170
+ if (resolved.stream && !stream)
5180
5171
  throw new Error(
5181
- `Provider requested a tool during streamed output contract "${resolved.name}".`
5172
+ `useReason streaming output contract "${resolved.name}" requires stream: true.`
5182
5173
  );
5183
- aggregatedUsage = mergeUsage(aggregatedUsage, outputStep.usage);
5184
- accumulateTokenUsage(outputStep.usage);
5185
- aggregatedWarnings = mergeWarnings(
5186
- aggregatedWarnings,
5187
- mergeWarnings(inferred.warnings, outputStep.warnings)
5188
- );
5189
- aggregatedProviderMetadata = mergeProviderMetadata(
5190
- aggregatedProviderMetadata,
5191
- outputStep.providerMetadata
5174
+ if (steps.length >= maxSteps) {
5175
+ const message = `useReason output generation requires another model pass within toolExecution.maxSteps (${maxSteps}). Increase maxSteps to allow outputs to use completed tool and interrupt results.`;
5176
+ throw new import_errors62.ProviderRequestError(message, {
5177
+ code: "REASON_OUTPUT_BUDGET_EXHAUSTED",
5178
+ operation: "useReason",
5179
+ safeMessage: message,
5180
+ retryable: false,
5181
+ details: { maxSteps, modelPasses: steps.length }
5182
+ });
5183
+ }
5184
+ const streamInstruction = call.input && typeof call.input === "object" && !Array.isArray(call.input) ? call.input.instruction : void 0;
5185
+ if (resolved.stream && (typeof streamInstruction !== "string" || streamInstruction.trim().length === 0))
5186
+ throw new Error(
5187
+ `useReason streaming output contract "${resolved.name}" requires an instruction.`
5188
+ );
5189
+ const instruction = [
5190
+ `Generate output contract "${resolved.name}": ${resolved.contract.description}`,
5191
+ "Use the completed tool and human-input results. Do not assume results for pending calls.",
5192
+ resolved.stream ? String(streamInstruction) : `Regenerate this original draft, which was written before those results were available: ${JSON.stringify(call.input)}`
5193
+ ].join("\n\n");
5194
+ const streamId = `${opId}:${resolved.kind}:${resolved.name}:${resolved.kind === "emit" ? emissions.length : "return"}`;
5195
+ const structured = {
5196
+ dataType: resolved.contract.schemaId,
5197
+ schemaId: resolved.contract.schemaId,
5198
+ schemaVersion: resolved.contract.schemaVersion,
5199
+ fields: resolved.contract.stream?.fields
5200
+ };
5201
+ const inferred = inferOutputFormat(
5202
+ resolved.contract.schema,
5203
+ void 0
5192
5204
  );
5193
- finalFinishReason = outputStep.finishReason;
5194
- finalRaw = outputStep.raw;
5195
- data = parseReasonOutputWithSchema({
5196
- text: outputStep.text,
5197
- schema: resolved.contract.schema,
5198
- ...outputStep.finishReason ? { finishReason: outputStep.finishReason } : {},
5199
- ...aggregatedUsage ? { usage: aggregatedUsage } : {},
5200
- label: `useReason output contract "${resolved.name}"`
5201
- });
5202
- steps.push({
5203
- stepIndex: stepIndex + 1,
5205
+ const outputRecord = {
5206
+ stepIndex: steps.length,
5204
5207
  kind: "output",
5205
- text: outputStep.text,
5208
+ text: "",
5206
5209
  toolCalls: [],
5207
- toolResults: [],
5208
- ...outputStep.usage ? { usage: outputStep.usage } : {},
5209
- ...outputStep.finishReason ? { finishReason: outputStep.finishReason } : {},
5210
- ...outputStep.providerMetadata ? { providerMetadata: outputStep.providerMetadata } : {},
5211
- ...outputStep.warnings ? { warnings: outputStep.warnings } : {}
5212
- });
5213
- stepIndex += 1;
5214
- } catch (error) {
5215
- traceSpan?.addEvent?.("useReason.output.invalidated", {
5216
- contract: resolved.name,
5217
- kind: resolved.kind,
5218
- schemaId: resolved.contract.schemaId,
5219
- schemaVersion: resolved.contract.schemaVersion,
5220
- streamId
5221
- });
5222
- if (emit) {
5223
- try {
5224
- ctx.node.emit("structured_data_invalidated", {
5225
- node: ctx.node.graph.node,
5226
- streamId
5227
- });
5228
- } catch {
5210
+ toolResults: []
5211
+ };
5212
+ steps.push(outputRecord);
5213
+ save();
5214
+ try {
5215
+ const outputStep = await reasonEngine(
5216
+ {
5217
+ ...useReasonArgs,
5218
+ tools: [],
5219
+ ...inferred.responseFormat ? { responseFormat: inferred.responseFormat } : {},
5220
+ messages: [
5221
+ ...messages,
5222
+ ...toolCalls.filter(
5223
+ (pendingCall) => !toolResults.some(
5224
+ (result3) => result3.toolCallId === pendingCall.id
5225
+ )
5226
+ ).map(
5227
+ (pendingCall) => ({
5228
+ role: "tool",
5229
+ toolCallId: pendingCall.id,
5230
+ name: pendingCall.name,
5231
+ content: "This call has not completed yet. Use only completed results; do not assume a result for this pending call."
5232
+ })
5233
+ ),
5234
+ {
5235
+ role: "user",
5236
+ content: withOutputGuardrails(
5237
+ instruction,
5238
+ resolved.contract.schema
5239
+ )
5240
+ }
5241
+ ],
5242
+ emit: false,
5243
+ stream: resolved.stream && stream,
5244
+ ...emit && resolved.stream && stream ? {
5245
+ onTextChunk: createStructuredOutputStreamer(
5246
+ { structured },
5247
+ id3,
5248
+ streamId
5249
+ )
5250
+ } : {}
5251
+ },
5252
+ { ...id3 ? { id: id3 } : {}, opId }
5253
+ );
5254
+ if (outputStep.toolCalls?.length)
5255
+ throw new Error(
5256
+ `Provider requested a tool during streamed output contract "${resolved.name}".`
5257
+ );
5258
+ aggregatedUsage = mergeUsage(aggregatedUsage, outputStep.usage);
5259
+ accumulateTokenUsage(outputStep.usage);
5260
+ aggregatedWarnings = mergeWarnings(
5261
+ aggregatedWarnings,
5262
+ mergeWarnings(inferred.warnings, outputStep.warnings)
5263
+ );
5264
+ aggregatedProviderMetadata = mergeProviderMetadata(
5265
+ aggregatedProviderMetadata,
5266
+ outputStep.providerMetadata
5267
+ );
5268
+ finalFinishReason = outputStep.finishReason;
5269
+ finalRaw = outputStep.raw;
5270
+ Object.assign(outputRecord, {
5271
+ stepIndex: outputRecord.stepIndex,
5272
+ kind: "output",
5273
+ text: outputStep.text,
5274
+ toolCalls: [],
5275
+ toolResults: [],
5276
+ ...outputStep.usage ? { usage: outputStep.usage } : {},
5277
+ ...outputStep.finishReason ? { finishReason: outputStep.finishReason } : {},
5278
+ ...outputStep.providerMetadata ? { providerMetadata: outputStep.providerMetadata } : {},
5279
+ ...outputStep.warnings ? { warnings: outputStep.warnings } : {}
5280
+ });
5281
+ save();
5282
+ data = parseReasonOutputWithSchema({
5283
+ text: outputStep.text,
5284
+ schema: resolved.contract.schema,
5285
+ ...outputStep.finishReason ? { finishReason: outputStep.finishReason } : {},
5286
+ ...aggregatedUsage ? { usage: aggregatedUsage } : {},
5287
+ label: `useReason output contract "${resolved.name}"`
5288
+ });
5289
+ } catch (error) {
5290
+ traceSpan?.addEvent?.("useReason.output.invalidated", {
5291
+ contract: resolved.name,
5292
+ kind: resolved.kind,
5293
+ schemaId: resolved.contract.schemaId,
5294
+ schemaVersion: resolved.contract.schemaVersion,
5295
+ streamId
5296
+ });
5297
+ if (emit && resolved.stream) {
5298
+ try {
5299
+ ctx.node.emit("structured_data_invalidated", {
5300
+ node: ctx.node.graph.node,
5301
+ streamId
5302
+ });
5303
+ } catch {
5304
+ }
5229
5305
  }
5306
+ throw error;
5230
5307
  }
5231
- throw error;
5308
+ } else {
5309
+ data = parseWithSchema(
5310
+ resolved.contract.schema,
5311
+ call.input,
5312
+ `useReason output contract "${resolved.name}"`
5313
+ );
5232
5314
  }
5233
- } else {
5234
- data = parseWithSchema(
5235
- resolved.contract.schema,
5236
- call.input,
5237
- `useReason output contract "${resolved.name}"`
5238
- );
5239
- }
5240
- const entry = { contract: resolved.name, data };
5241
- const emissionIndex = emissions.length;
5242
- if (resolved.kind === "emit")
5243
- emissions.push(entry);
5244
- else returned = entry;
5245
- if (emit)
5246
- emitStructuredData({
5247
- kind: "final",
5248
- data,
5249
- dataType: resolved.contract.schemaId,
5315
+ const entry = { contract: resolved.name, data };
5316
+ const emissionIndex = emissions.length;
5317
+ if (resolved.kind === "emit")
5318
+ emissions.push(entry);
5319
+ else returned = entry;
5320
+ if (emit)
5321
+ emitStructuredData({
5322
+ kind: "final",
5323
+ data,
5324
+ dataType: resolved.contract.schemaId,
5325
+ schemaId: resolved.contract.schemaId,
5326
+ schemaVersion: resolved.contract.schemaVersion,
5327
+ streamId: `${opId}:${resolved.kind}:${resolved.name}:${resolved.kind === "emit" ? emissionIndex : "return"}`,
5328
+ ...id3 ? { id: id3 } : {}
5329
+ });
5330
+ traceSpan?.addEvent?.("useReason.output.emitted", {
5331
+ contract: resolved.name,
5332
+ kind: resolved.kind,
5250
5333
  schemaId: resolved.contract.schemaId,
5251
5334
  schemaVersion: resolved.contract.schemaVersion,
5252
- streamId: `${opId}:${resolved.kind}:${resolved.name}:${resolved.kind === "emit" ? emissionIndex : "return"}`,
5253
- ...id3 ? { id: id3 } : {}
5335
+ emissionIndex,
5336
+ data
5254
5337
  });
5255
- traceSpan?.addEvent?.("useReason.output.emitted", {
5256
- contract: resolved.name,
5257
- kind: resolved.kind,
5258
- schemaId: resolved.contract.schemaId,
5259
- schemaVersion: resolved.contract.schemaVersion,
5260
- emissionIndex,
5261
- data
5262
- });
5263
- if (resolved.kind === "return") {
5264
- pending = void 0;
5265
- pendingStepIndex = void 0;
5266
- completed = true;
5338
+ const result2 = normalizeToolResult(call, { emitted: true, data });
5339
+ toolResults.push(result2);
5340
+ messages.push({
5341
+ role: "tool",
5342
+ content: result2.content,
5343
+ toolCallId: result2.toolCallId,
5344
+ name: result2.name
5345
+ });
5346
+ if (resolved.kind === "return") completed = true;
5267
5347
  save();
5268
- break;
5348
+ continue;
5269
5349
  }
5270
- const result2 = normalizeToolResult(call, { emitted: true, data });
5271
- toolResults.push(result2);
5272
- messages.push({
5273
- role: "tool",
5274
- content: result2.content,
5275
- toolCallId: result2.toolCallId,
5276
- name: result2.name
5277
- });
5278
- pending = void 0;
5279
- pendingStepIndex = void 0;
5280
- save();
5281
- continue;
5282
- }
5283
- const interruptCalls = toolCalls.filter(
5284
- (call) => interruptByToolName.has(call.name)
5285
- );
5286
- if (interruptCalls.length > 0) {
5287
- if (toolCalls.length !== 1)
5288
- throw new Error(
5289
- "A useReason interrupt contract call must be the only tool call in its model turn."
5290
- );
5291
- const interruptCall = interruptCalls[0];
5292
- if (!interruptCall)
5293
- throw new Error("Missing useReason interrupt control call.");
5294
- if (interruptHistory.length >= (interruptConfig?.maxRequests ?? 1))
5295
- throw new Error(
5296
- `useReason reached interrupts.maxRequests (${interruptConfig?.maxRequests ?? 1}).`
5297
- );
5298
- const resolved = interruptByToolName.get(interruptCall.name);
5299
- if (!resolved)
5300
- throw new Error(
5301
- `Unknown useReason interrupt contract tool "${interruptCall.name}".`
5350
+ if (interruptByToolName.has(toolCall.name)) {
5351
+ const resolved = interruptByToolName.get(toolCall.name);
5352
+ if (!resolved)
5353
+ throw new Error(
5354
+ `Unknown useReason interrupt contract tool "${toolCall.name}".`
5355
+ );
5356
+ if (toolResults.some((result3) => result3.toolCallId === toolCall.id)) {
5357
+ continue;
5358
+ }
5359
+ if (interruptHistory.length >= (interruptConfig?.maxRequests ?? 1))
5360
+ throw new Error(
5361
+ `useReason reached interrupts.maxRequests (${interruptConfig?.maxRequests ?? 1}).`
5362
+ );
5363
+ const request = parseWithSchema(
5364
+ resolved.contract.requestSchema,
5365
+ toolCall.input,
5366
+ `useReason interrupt contract "${resolved.name}" request`
5302
5367
  );
5303
- if (toolResults.some((result3) => result3.toolCallId === interruptCall.id)) {
5304
- pending = void 0;
5305
- pendingStepIndex = void 0;
5368
+ traceSpan?.addEvent?.("useReason.interrupt.requested", {
5369
+ checkpointKey,
5370
+ contract: resolved.name,
5371
+ interruptIndex: interruptHistory.length
5372
+ });
5373
+ const question = request && typeof request === "object" && !Array.isArray(request) && typeof request.question === "string" ? String(request.question) : void 0;
5374
+ const response = await awaitInterruptInternal({
5375
+ onResolved: rememberInterrupt,
5376
+ request: {
5377
+ kind: "custom",
5378
+ request,
5379
+ contract: resolved.name,
5380
+ ...question ? { question } : {},
5381
+ schemaId: resolved.contract.schemaId,
5382
+ schemaVersion: resolved.contract.schemaVersion
5383
+ },
5384
+ responseSchema: resolved.contract.responseSchema,
5385
+ id: `${id3 ?? "reason"}:${resolved.name}:${interruptHistory.length + 1}`,
5386
+ meta: {
5387
+ __kortyxReason: {
5388
+ opId,
5389
+ stepIndex,
5390
+ interruptIndex: interruptHistory.length,
5391
+ contract: resolved.name
5392
+ }
5393
+ }
5394
+ });
5395
+ const result2 = normalizeToolResult(toolCall, response);
5396
+ toolResults.push(result2);
5397
+ messages.push({
5398
+ role: "tool",
5399
+ content: result2.content,
5400
+ toolCallId: result2.toolCallId,
5401
+ name: result2.name
5402
+ });
5403
+ interruptHistory.push({
5404
+ contract: resolved.name,
5405
+ request,
5406
+ response
5407
+ });
5408
+ traceSpan?.addEvent?.("useReason.interrupt.resolved", {
5409
+ checkpointKey,
5410
+ contract: resolved.name,
5411
+ interruptIndex: interruptHistory.length - 1
5412
+ });
5306
5413
  save();
5307
5414
  continue;
5308
5415
  }
5309
- const request = parseWithSchema(
5310
- resolved.contract.requestSchema,
5311
- interruptCall.input,
5312
- `useReason interrupt contract "${resolved.name}" request`
5313
- );
5314
- traceSpan?.addEvent?.("useReason.interrupt.requested", {
5315
- checkpointKey,
5316
- contract: resolved.name,
5317
- interruptIndex: interruptHistory.length
5318
- });
5319
- const question = request && typeof request === "object" && !Array.isArray(request) && typeof request.question === "string" ? String(request.question) : void 0;
5320
- const response = await awaitInterruptInternal({
5321
- request: {
5322
- kind: "custom",
5323
- request,
5324
- contract: resolved.name,
5325
- ...question ? { question } : {},
5326
- schemaId: resolved.contract.schemaId,
5327
- schemaVersion: resolved.contract.schemaVersion
5328
- },
5329
- responseSchema: resolved.contract.responseSchema,
5330
- id: `${id3 ?? "reason"}:${resolved.name}:${interruptHistory.length + 1}`,
5331
- meta: {
5332
- __kortyxReason: {
5333
- opId,
5334
- stepIndex,
5335
- interruptIndex: interruptHistory.length,
5336
- contract: resolved.name
5337
- }
5338
- }
5339
- });
5340
- const result2 = normalizeToolResult(interruptCall, response);
5341
- toolResults.push(result2);
5342
- messages.push({
5343
- role: "tool",
5344
- content: result2.content,
5345
- toolCallId: result2.toolCallId,
5346
- name: result2.name
5347
- });
5348
- interruptHistory.push({
5349
- contract: resolved.name,
5350
- request,
5351
- response
5352
- });
5353
- traceSpan?.addEvent?.("useReason.interrupt.resolved", {
5354
- checkpointKey,
5355
- contract: resolved.name,
5356
- interruptIndex: interruptHistory.length - 1
5357
- });
5358
- pending = void 0;
5359
- pendingStepIndex = void 0;
5360
- save();
5361
- continue;
5362
- }
5363
- for (const toolCall of toolCalls) {
5364
5416
  if (toolResults.some((result2) => result2.toolCallId === toolCall.id)) {
5365
5417
  await replayToolObservations(
5366
5418
  steps[stepIndex]?.toolObservations?.filter(
@@ -5406,6 +5458,7 @@ ${tryStringify2(args.response)}`
5406
5458
  "waiting"
5407
5459
  );
5408
5460
  const approval = await awaitInterruptInternal({
5461
+ onResolved: rememberInterrupt,
5409
5462
  id: `tool:${toolCall.id}`,
5410
5463
  request: {
5411
5464
  kind: "choice",
@@ -5520,22 +5573,24 @@ ${tryStringify2(args.response)}`
5520
5573
  });
5521
5574
  }
5522
5575
  }
5523
- const completed2 = toolResults.at(-1);
5524
- if (completed2)
5576
+ const completedToolResult = toolResults.at(-1);
5577
+ if (completedToolResult)
5525
5578
  messages.push({
5526
5579
  role: "tool",
5527
- content: completed2.content,
5528
- toolCallId: completed2.toolCallId,
5529
- name: completed2.name,
5530
- ...completed2.isError !== void 0 ? { isError: completed2.isError } : {}
5580
+ content: completedToolResult.content,
5581
+ toolCallId: completedToolResult.toolCallId,
5582
+ name: completedToolResult.name,
5583
+ ...completedToolResult.isError !== void 0 ? { isError: completedToolResult.isError } : {}
5531
5584
  });
5532
5585
  save();
5533
5586
  }
5534
5587
  pending = void 0;
5535
5588
  pendingStepIndex = void 0;
5536
5589
  save();
5590
+ if (completed) break;
5537
5591
  }
5538
5592
  if (!completed) {
5593
+ if (outputContractCorrections > 0) throw correctionExhausted();
5539
5594
  throw new Error(
5540
5595
  `useReason tool loop reached maxSteps (${maxSteps}) before producing a final response.`
5541
5596
  );
@@ -5597,7 +5652,11 @@ ${tryStringify2(args.response)}`
5597
5652
  output: useReasonArgs.telemetry?.output ?? returned?.data ?? finalOutput ?? finalText
5598
5653
  }
5599
5654
  });
5600
- ctx.currentNodeState.byKey[checkpointKey] = { status: "completed", result };
5655
+ ctx.currentNodeState.byKey[checkpointKey] = {
5656
+ status: "completed",
5657
+ result,
5658
+ resolvedInterrupts
5659
+ };
5601
5660
  ctx.stateDirty = true;
5602
5661
  return result;
5603
5662
  } catch (error) {
@@ -5672,6 +5731,8 @@ ${tryStringify2(args.response)}`
5672
5731
  const opId = checkpointValue && typeof checkpointValue === "object" && "status" in checkpointValue && checkpointValue.status === "tool_loop" && "opId" in checkpointValue && typeof checkpointValue.opId === "string" ? checkpointValue.opId : provisionalOpId;
5673
5732
  const existingCompleted = readReasonCompletedCheckpoint(checkpointValue);
5674
5733
  if (existingCompleted) {
5734
+ for (const request of existingCompleted.resolvedInterrupts ?? [])
5735
+ ctx.node.replayInterrupt?.(request);
5675
5736
  await replayToolObservations(
5676
5737
  existingCompleted.result.steps?.flatMap(
5677
5738
  (step) => step.toolObservations ?? []