@kortyx/hooks 0.34.1 → 0.34.2

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,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.34.2](https://github.com/kortyx-io/kortyx/compare/hooks-v0.34.1...hooks-v0.34.2) (2026-10-13)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **evals:** preserve diagnostics and separate postcondition failures ([#337](https://github.com/kortyx-io/kortyx/issues/337)) ([d4fce4e](https://github.com/kortyx-io/kortyx/commit/d4fce4e670971d3b755835fc681c8125f67a9c70))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @kortyx/core bumped to 0.14.0
16
+ * @kortyx/providers bumped to 0.9.3
17
+ * @kortyx/telemetry-contracts bumped to 0.17.2
18
+ * @kortyx/prompts bumped to 0.2.1
19
+ * @kortyx/usage-telemetry bumped to 0.3.0
20
+
3
21
  ## [0.34.1](https://github.com/kortyx-io/kortyx/compare/hooks-v0.34.0...hooks-v0.34.1) (2026-10-13)
4
22
 
5
23
 
package/README.md CHANGED
@@ -137,3 +137,9 @@ Await groups sequentially within one node; children may contain nested groups.
137
137
  Native `Promise.all` calls and parallel graph edges remain unsupported.
138
138
 
139
139
  See [the execution and replay contract](../../docs/design-specs/child-workflows.md).
140
+
141
+ When a required `outputs.return` is missing, `useReason` raises the typed
142
+ `REASON_TERMINAL_OUTPUT_MISSING` validation error. Optional
143
+ `toolExecution: { maxSteps: 4, missingReturnCorrections: 1 }` allows an output-only
144
+ correction within the existing step budget, without replaying business tools.
145
+ The correction budget defaults to zero.
package/dist/index.d.ts CHANGED
@@ -262,6 +262,8 @@ type UseReasonInterruptConfig<TRequest extends InterruptInput = InterruptInput,
262
262
  schemaVersion?: string | undefined;
263
263
  };
264
264
  type UseReasonToolExecution = {
265
+ /** Extra model corrections for a missing terminal return; defaults to zero and shares maxSteps. Completed calls are not replayed. */
266
+ missingReturnCorrections?: number | undefined;
265
267
  /** Model-pass budget, including output generation and correction across resume. */
266
268
  maxSteps?: number | undefined;
267
269
  approval?: boolean | Record<string, boolean> | undefined;
package/dist/index.js CHANGED
@@ -245,7 +245,7 @@ var import_node = require("@kortyx/usage-telemetry/node");
245
245
  // package.json
246
246
  var package_default = {
247
247
  name: "@kortyx/hooks",
248
- version: "0.34.1",
248
+ version: "0.34.2",
249
249
  private: false,
250
250
  description: "Lifecycle hooks and extension points for Kortyx.",
251
251
  keywords: [
@@ -3167,6 +3167,18 @@ var runReasonToolLoop = async (args) => {
3167
3167
  let finalizing = checkpoint?.finalizing ?? false;
3168
3168
  let completed = false;
3169
3169
  const maxSteps = Math.max(1, useReasonArgs.toolExecution?.maxSteps ?? 3);
3170
+ const missingReturnCorrections = useReasonArgs.toolExecution?.missingReturnCorrections ?? 0;
3171
+ if (!Number.isSafeInteger(missingReturnCorrections) || missingReturnCorrections < 0)
3172
+ throw new import_errors6.KortyxError(
3173
+ "INVALID_REASON_CORRECTION_POLICY",
3174
+ "Invalid missing return correction budget.",
3175
+ {
3176
+ category: "configuration",
3177
+ retryable: false,
3178
+ safeMessage: "toolExecution.missingReturnCorrections must be a nonnegative safe integer."
3179
+ }
3180
+ );
3181
+ let missingReturnAttempts = checkpoint?.missingReturnAttempts ?? 0;
3170
3182
  const messages = checkpoint?.messages ?? createInitialMessages({
3171
3183
  messages: useReasonArgs.messages,
3172
3184
  inputOverride: Boolean(useReasonArgs.outputSchema),
@@ -3218,13 +3230,17 @@ var runReasonToolLoop = async (args) => {
3218
3230
  let outputContractCorrections = checkpoint?.outputContractCorrections ?? 0;
3219
3231
  const correctionExhausted = () => {
3220
3232
  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.`;
3221
- return new import_errors6.ProviderRequestError(message, {
3222
- code: "REASON_OUTPUT_CONTRACT_CORRECTION_EXHAUSTED",
3223
- operation: "useReason",
3224
- safeMessage: message,
3225
- retryable: false,
3226
- details: { maxSteps, outputContractCorrections }
3227
- });
3233
+ return new import_errors6.KortyxError(
3234
+ "REASON_OUTPUT_CONTRACT_CORRECTION_EXHAUSTED",
3235
+ message,
3236
+ {
3237
+ category: "validation",
3238
+ operation: "useReason",
3239
+ safeMessage: message,
3240
+ retryable: false,
3241
+ details: { maxSteps, outputContractCorrections }
3242
+ }
3243
+ );
3228
3244
  };
3229
3245
  const resolvedInterrupts = checkpoint?.resolvedInterrupts ?? [];
3230
3246
  for (const request of resolvedInterrupts) ctx.node.replayInterrupt?.(request);
@@ -3243,6 +3259,7 @@ var runReasonToolLoop = async (args) => {
3243
3259
  interruptHistory,
3244
3260
  emissions,
3245
3261
  outputContractCorrections,
3262
+ missingReturnAttempts,
3246
3263
  ...returned ? { returned } : {},
3247
3264
  finalizing,
3248
3265
  ...pending ? { pending } : {},
@@ -3278,7 +3295,7 @@ var runReasonToolLoop = async (args) => {
3278
3295
  const step = pending ?? await reasonEngine(
3279
3296
  {
3280
3297
  ...useReasonArgs,
3281
- tools: finalizing ? [] : toolDefinitions,
3298
+ tools: finalizing ? [] : missingReturnAttempts > 0 ? outputDefinitions : toolDefinitions,
3282
3299
  ...separateOutput && !finalizing ? { responseFormat: { type: "text" } } : {},
3283
3300
  messages,
3284
3301
  emit: false,
@@ -3309,6 +3326,16 @@ var runReasonToolLoop = async (args) => {
3309
3326
  );
3310
3327
  aggregatedWarnings = mergeWarnings(aggregatedWarnings, step.warnings);
3311
3328
  const toolCalls = step.toolCalls ?? [];
3329
+ if (missingReturnAttempts > 0 && toolCalls.some((call) => !outputByToolName.has(call.name)))
3330
+ throw new import_errors6.KortyxError(
3331
+ "REASON_OUTPUT_CORRECTION_TOOL_DISALLOWED",
3332
+ "A return correction requested a non-output tool.",
3333
+ {
3334
+ category: "validation",
3335
+ retryable: false,
3336
+ safeMessage: "A terminal output correction cannot rerun tools or human-input requests."
3337
+ }
3338
+ );
3312
3339
  const ids = /* @__PURE__ */ new Set();
3313
3340
  for (const call of toolCalls) {
3314
3341
  if (!call.id || ids.has(call.id) || !reused && knownToolCallIds.has(call.id))
@@ -3329,10 +3356,32 @@ var runReasonToolLoop = async (args) => {
3329
3356
  });
3330
3357
  if (!reused) for (const call of toolCalls) knownToolCallIds.add(call.id);
3331
3358
  if (toolCalls.length === 0) {
3332
- if (Object.keys(useReasonArgs.outputs?.return ?? {}).length > 0 && !returned)
3333
- throw new Error(
3334
- "useReason outputs.return requires one terminal output contract call."
3359
+ if (Object.keys(useReasonArgs.outputs?.return ?? {}).length > 0 && !returned) {
3360
+ if (missingReturnAttempts < missingReturnCorrections) {
3361
+ messages.push({ role: "assistant", content: step.text });
3362
+ messages.push({
3363
+ role: "user",
3364
+ content: "MISSING_TERMINAL_OUTPUT: No terminal output was published. Select exactly one configured return contract using the completed tool and human-input results. Do not repeat completed calls."
3365
+ });
3366
+ missingReturnAttempts += 1;
3367
+ outputContractCorrections += 1;
3368
+ pending = void 0;
3369
+ pendingStepIndex = void 0;
3370
+ save();
3371
+ continue;
3372
+ }
3373
+ throw new import_errors6.KortyxError(
3374
+ "REASON_TERMINAL_OUTPUT_MISSING",
3375
+ "useReason outputs.return requires one terminal output contract call.",
3376
+ {
3377
+ category: "validation",
3378
+ operation: "useReason",
3379
+ retryable: false,
3380
+ safeMessage: "useReason outputs.return requires one terminal output contract call.",
3381
+ details: { missingReturnAttempts }
3382
+ }
3335
3383
  );
3384
+ }
3336
3385
  if (interruptConfig?.mode === "required" && interruptHistory.length === 0)
3337
3386
  throw new Error(
3338
3387
  "useReason interrupts.mode is required, but the model returned a final answer without requesting human input."
@@ -3847,11 +3896,15 @@ var runReasonToolLoop = async (args) => {
3847
3896
  toolResults.push(result2);
3848
3897
  allToolResults.push(result2);
3849
3898
  if (shouldEmitTool) {
3899
+ const diagnosticId = steps[stepIndex]?.toolObservations?.slice().reverse().find(
3900
+ (observation) => observation.toolCallId === `${opId}:${toolCall.id}`
3901
+ )?.diagnosticId;
3850
3902
  emitToolEvent("tool-call-error", {
3851
3903
  tool: tool.name,
3852
3904
  toolCallId: toolCall.id,
3853
3905
  message: result2.content,
3854
- failure: result2.failure
3906
+ failure: result2.failure,
3907
+ ...diagnosticId ? { diagnosticId } : {}
3855
3908
  });
3856
3909
  }
3857
3910
  }