@kortyx/hooks 0.26.0 → 0.28.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/CHANGELOG.md +30 -0
- package/README.md +10 -3
- package/dist/index.d.ts +52 -8
- package/dist/index.js +341 -44
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +5 -3
- package/dist/internal.js +45 -16
- package/dist/internal.js.map +1 -1
- package/dist/{tracing-BUpf8RrV.d.ts → tracing-BsxYlgEw.d.ts} +11 -2
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -23,8 +23,10 @@ __export(index_exports, {
|
|
|
23
23
|
WorkflowCallError: () => WorkflowCallError,
|
|
24
24
|
completeResponse: () => completeResponse,
|
|
25
25
|
createWorkflowHooks: () => createWorkflowHooks,
|
|
26
|
+
defineInterruptContract: () => defineInterruptContract,
|
|
26
27
|
emitWorkflowCall: () => emitWorkflowCall,
|
|
27
28
|
parallel: () => parallel,
|
|
29
|
+
reportError: () => reportError,
|
|
28
30
|
runWithHookContext: () => runWithHookContext,
|
|
29
31
|
useAbortSignal: () => useAbortSignal,
|
|
30
32
|
useInterrupt: () => useInterrupt,
|
|
@@ -348,6 +350,97 @@ var awaitInterruptInternal = (args) => {
|
|
|
348
350
|
return Promise.resolve(response);
|
|
349
351
|
};
|
|
350
352
|
|
|
353
|
+
// src/interrupt-contract.ts
|
|
354
|
+
var CONTROL_TOOL_PREFIX = "kortyx_request_input__";
|
|
355
|
+
var LEGACY_CONTRACT_NAME = "default";
|
|
356
|
+
var legacyWarningEmitted = false;
|
|
357
|
+
var legacyConfigs = /* @__PURE__ */ new WeakSet();
|
|
358
|
+
var defineInterruptContract = (contract) => {
|
|
359
|
+
if (!contract || typeof contract !== "object")
|
|
360
|
+
throw new Error("defineInterruptContract requires a contract object.");
|
|
361
|
+
for (const [field, value] of [
|
|
362
|
+
["description", contract.description],
|
|
363
|
+
["schemaId", contract.schemaId],
|
|
364
|
+
["schemaVersion", contract.schemaVersion]
|
|
365
|
+
])
|
|
366
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
367
|
+
throw new Error(
|
|
368
|
+
`Interrupt contract ${field} must be a non-empty string.`
|
|
369
|
+
);
|
|
370
|
+
if (typeof contract.requestSchema?.safeParse !== "function")
|
|
371
|
+
throw new Error(
|
|
372
|
+
"Interrupt contract requestSchema must provide safeParse(value)."
|
|
373
|
+
);
|
|
374
|
+
if (typeof contract.responseSchema?.safeParse !== "function")
|
|
375
|
+
throw new Error(
|
|
376
|
+
"Interrupt contract responseSchema must provide safeParse(value)."
|
|
377
|
+
);
|
|
378
|
+
return contract;
|
|
379
|
+
};
|
|
380
|
+
var interruptControlToolName = (name) => `${CONTROL_TOOL_PREFIX}${name}`;
|
|
381
|
+
var isInterruptControlToolName = (name) => name.startsWith(CONTROL_TOOL_PREFIX);
|
|
382
|
+
var assertInterruptContractName = (name) => {
|
|
383
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(name))
|
|
384
|
+
throw new Error(
|
|
385
|
+
`Invalid useReason interrupt contract name "${name}". Use letters, numbers, underscores, or hyphens, starting with a letter.`
|
|
386
|
+
);
|
|
387
|
+
if (interruptControlToolName(name).length > 64)
|
|
388
|
+
throw new Error(
|
|
389
|
+
`useReason interrupt contract name "${name}" is too long for a provider tool name.`
|
|
390
|
+
);
|
|
391
|
+
};
|
|
392
|
+
var warnLegacyUseReasonInterrupt = () => {
|
|
393
|
+
if (legacyWarningEmitted) return;
|
|
394
|
+
legacyWarningEmitted = true;
|
|
395
|
+
process.emitWarning(
|
|
396
|
+
"useReason({ interrupt }) is deprecated and will be removed in the next major release. Define an interrupt contract with defineInterruptContract() and pass it through useReason({ interrupts: { contracts } }). See docs/internal/next-major-removals.md.",
|
|
397
|
+
{
|
|
398
|
+
code: "KORTYX_USE_REASON_INTERRUPT_DEPRECATED",
|
|
399
|
+
type: "DeprecationWarning"
|
|
400
|
+
}
|
|
401
|
+
);
|
|
402
|
+
};
|
|
403
|
+
var normalizeReasonInterrupts = (args) => {
|
|
404
|
+
if (args.interrupt && args.interrupts)
|
|
405
|
+
throw new Error(
|
|
406
|
+
"useReason cannot receive both deprecated `interrupt` and `interrupts`. Migrate the legacy configuration to an interrupt contract."
|
|
407
|
+
);
|
|
408
|
+
if (args.interrupts) {
|
|
409
|
+
for (const [name, contract] of Object.entries(args.interrupts.contracts)) {
|
|
410
|
+
assertInterruptContractName(name);
|
|
411
|
+
defineInterruptContract(contract);
|
|
412
|
+
}
|
|
413
|
+
if (Object.keys(args.interrupts.contracts).length === 0)
|
|
414
|
+
throw new Error("useReason interrupts.contracts cannot be empty.");
|
|
415
|
+
if (args.interrupts.maxRequests !== void 0 && (!Number.isInteger(args.interrupts.maxRequests) || args.interrupts.maxRequests < 1))
|
|
416
|
+
throw new Error("useReason interrupts.maxRequests must be at least 1.");
|
|
417
|
+
return args.interrupts;
|
|
418
|
+
}
|
|
419
|
+
if (!args.interrupt) return void 0;
|
|
420
|
+
warnLegacyUseReasonInterrupt();
|
|
421
|
+
const normalized = {
|
|
422
|
+
mode: args.interrupt.mode,
|
|
423
|
+
maxRequests: 1,
|
|
424
|
+
contracts: {
|
|
425
|
+
[LEGACY_CONTRACT_NAME]: {
|
|
426
|
+
description: "Ask the user for the additional information required to complete this request.",
|
|
427
|
+
requestSchema: args.interrupt.requestSchema,
|
|
428
|
+
responseSchema: args.interrupt.responseSchema ?? {
|
|
429
|
+
safeParse: (value) => ({
|
|
430
|
+
success: true,
|
|
431
|
+
data: value
|
|
432
|
+
})
|
|
433
|
+
},
|
|
434
|
+
schemaId: args.interrupt.schemaId ?? "kortyx.legacy-interrupt",
|
|
435
|
+
schemaVersion: args.interrupt.schemaVersion ?? "1"
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
legacyConfigs.add(normalized);
|
|
440
|
+
return normalized;
|
|
441
|
+
};
|
|
442
|
+
var isLegacyInterruptContracts = (config) => Boolean(config && legacyConfigs.has(config));
|
|
443
|
+
|
|
351
444
|
// src/reason/use-reason.ts
|
|
352
445
|
var import_core6 = require("@kortyx/core");
|
|
353
446
|
|
|
@@ -531,26 +624,53 @@ var import_errors3 = require("@kortyx/core/errors");
|
|
|
531
624
|
|
|
532
625
|
// src/error-diagnostics.ts
|
|
533
626
|
var import_errors2 = require("@kortyx/core/errors");
|
|
534
|
-
|
|
627
|
+
var MAX_STACK_LENGTH = 32768;
|
|
628
|
+
var MAX_CAUSE_DEPTH = 4;
|
|
629
|
+
var projectDetails = (value, depth, seen) => {
|
|
630
|
+
const objectValue = value && typeof value === "object" ? value : void 0;
|
|
631
|
+
if (objectValue) {
|
|
632
|
+
if (seen.has(objectValue))
|
|
633
|
+
return { type: "Error", message: "Circular error cause." };
|
|
634
|
+
seen.add(objectValue);
|
|
635
|
+
}
|
|
636
|
+
const name = (0, import_errors2.errorProperty)(value, "name") ?? (value instanceof Error ? value.name : void 0);
|
|
637
|
+
const message = (0, import_errors2.errorProperty)(value, "message");
|
|
638
|
+
const stack = (0, import_errors2.errorProperty)(value, "stack") ?? (value instanceof Error ? (() => {
|
|
639
|
+
try {
|
|
640
|
+
return value.stack;
|
|
641
|
+
} catch {
|
|
642
|
+
return void 0;
|
|
643
|
+
}
|
|
644
|
+
})() : void 0);
|
|
645
|
+
const cause = depth < MAX_CAUSE_DEPTH ? (0, import_errors2.errorProperty)(value, "cause") : null;
|
|
646
|
+
return {
|
|
647
|
+
type: typeof name === "string" ? name.slice(0, 256) : "Error",
|
|
648
|
+
message: (typeof value === "string" ? value : typeof message === "string" ? message : "Execution reported a fault.").slice(0, 8192),
|
|
649
|
+
...typeof stack === "string" ? { stack: stack.slice(0, MAX_STACK_LENGTH) } : {},
|
|
650
|
+
...cause !== null && cause !== void 0 ? { cause: projectDetails(cause, depth + 1, seen) } : {}
|
|
651
|
+
};
|
|
652
|
+
};
|
|
653
|
+
var sanitizeProjectedDetails = (details, depth = 0) => ({
|
|
654
|
+
...typeof details.type === "string" ? { type: details.type.slice(0, 256) } : {},
|
|
655
|
+
message: typeof details.message === "string" ? details.message.slice(0, 8192) : "Execution reported a fault.",
|
|
656
|
+
...typeof details.stack === "string" ? { stack: details.stack.slice(0, MAX_STACK_LENGTH) } : {},
|
|
657
|
+
...depth < MAX_CAUSE_DEPTH && details.cause ? { cause: sanitizeProjectedDetails(details.cause, depth + 1) } : {}
|
|
658
|
+
});
|
|
659
|
+
function exceptionDiagnostics(error, project) {
|
|
535
660
|
try {
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
const details = project ? project(error) : {
|
|
539
|
-
type: typeof name === "string" ? name : "Error",
|
|
540
|
-
message: typeof error === "string" ? error : typeof message === "string" ? message : "Execution reported a fault."
|
|
541
|
-
};
|
|
542
|
-
if (!details) return {};
|
|
543
|
-
const projectedMessage = details.message;
|
|
544
|
-
const projectedType = details.type;
|
|
545
|
-
if (typeof projectedMessage !== "string") return {};
|
|
546
|
-
return {
|
|
547
|
-
errorMessage: projectedMessage.slice(0, 8192),
|
|
548
|
-
...typeof projectedType === "string" ? { errorType: projectedType.slice(0, 256) } : {}
|
|
549
|
-
};
|
|
661
|
+
const details = project ? project(error) : projectDetails(error, 0, /* @__PURE__ */ new WeakSet());
|
|
662
|
+
return details ? sanitizeProjectedDetails(details) : null;
|
|
550
663
|
} catch {
|
|
551
|
-
return
|
|
664
|
+
return null;
|
|
552
665
|
}
|
|
553
666
|
}
|
|
667
|
+
function errorDiagnostics(error, project) {
|
|
668
|
+
const details = exceptionDiagnostics(error, project);
|
|
669
|
+
return details ? {
|
|
670
|
+
errorMessage: details.message,
|
|
671
|
+
...details.type ? { errorType: details.type } : {}
|
|
672
|
+
} : {};
|
|
673
|
+
}
|
|
554
674
|
|
|
555
675
|
// src/workflow-telemetry.ts
|
|
556
676
|
var import_node_crypto2 = require("crypto");
|
|
@@ -2334,6 +2454,7 @@ var emitReasonStructuredOutput = (args) => {
|
|
|
2334
2454
|
// src/reason/tools.ts
|
|
2335
2455
|
var import_core5 = require("@kortyx/core");
|
|
2336
2456
|
var import_errors6 = require("@kortyx/core/errors");
|
|
2457
|
+
var import_zod4 = require("zod");
|
|
2337
2458
|
var toToolDefinitions = (tools2) => tools2.map((tool) => ({
|
|
2338
2459
|
name: tool.name,
|
|
2339
2460
|
...tool.title ? { title: tool.title } : {},
|
|
@@ -2369,7 +2490,12 @@ var normalizeToolResult = (toolCall, value) => {
|
|
|
2369
2490
|
};
|
|
2370
2491
|
};
|
|
2371
2492
|
var createInitialMessages = (args) => [
|
|
2372
|
-
...typeof args.system === "string"
|
|
2493
|
+
...typeof args.system === "string" || args.interruptInstructions ? [
|
|
2494
|
+
{
|
|
2495
|
+
role: "system",
|
|
2496
|
+
content: [args.system, args.interruptInstructions].filter((value) => Boolean(value)).join("\n\n")
|
|
2497
|
+
}
|
|
2498
|
+
] : [],
|
|
2373
2499
|
{ role: "user", content: String(args.input ?? "") }
|
|
2374
2500
|
];
|
|
2375
2501
|
var runReasonToolLoop = async (args) => {
|
|
@@ -2384,19 +2510,23 @@ var runReasonToolLoop = async (args) => {
|
|
|
2384
2510
|
const checkpoint = saved?.status === "tool_loop" ? saved : void 0;
|
|
2385
2511
|
const opId = checkpoint?.opId ?? args.opId;
|
|
2386
2512
|
const tools2 = useReasonArgs.tools ?? [];
|
|
2513
|
+
const interruptConfig = normalizeReasonInterrupts({
|
|
2514
|
+
interrupt: useReasonArgs.interrupt,
|
|
2515
|
+
interrupts: useReasonArgs.interrupts
|
|
2516
|
+
});
|
|
2517
|
+
const interruptByToolName = /* @__PURE__ */ new Map();
|
|
2387
2518
|
const toolByName = /* @__PURE__ */ new Map();
|
|
2388
2519
|
let separateOutput = false;
|
|
2389
2520
|
for (const tool of tools2) {
|
|
2521
|
+
if (isInterruptControlToolName(tool.name))
|
|
2522
|
+
throw new Error(
|
|
2523
|
+
`useReason tool name "${tool.name}" uses the reserved interrupt control-tool namespace.`
|
|
2524
|
+
);
|
|
2390
2525
|
if (toolByName.has(tool.name)) {
|
|
2391
2526
|
throw new Error(`useReason received duplicate tool name "${tool.name}".`);
|
|
2392
2527
|
}
|
|
2393
2528
|
toolByName.set(tool.name, tool);
|
|
2394
2529
|
}
|
|
2395
|
-
if (useReasonArgs.interrupt) {
|
|
2396
|
-
throw new Error(
|
|
2397
|
-
"useReason tools cannot be combined with useReason interrupt mode yet. Use toolExecution.approval for tool approval."
|
|
2398
|
-
);
|
|
2399
|
-
}
|
|
2400
2530
|
separateOutput = Boolean(
|
|
2401
2531
|
useReasonArgs.model.provider.getModel(useReasonArgs.model.modelId, {
|
|
2402
2532
|
...useReasonArgs.model.options,
|
|
@@ -2408,21 +2538,63 @@ var runReasonToolLoop = async (args) => {
|
|
|
2408
2538
|
) ?? {}
|
|
2409
2539
|
}).requiresSeparateStructuredOutput
|
|
2410
2540
|
);
|
|
2411
|
-
const
|
|
2541
|
+
const interruptDefinitions = [];
|
|
2542
|
+
for (const [name, contract] of Object.entries(
|
|
2543
|
+
interruptConfig?.contracts ?? {}
|
|
2544
|
+
)) {
|
|
2545
|
+
const toolName = interruptControlToolName(name);
|
|
2546
|
+
if (toolByName.has(toolName))
|
|
2547
|
+
throw new Error(
|
|
2548
|
+
`useReason tool name "${toolName}" is reserved for interrupt contracts.`
|
|
2549
|
+
);
|
|
2550
|
+
let inputSchema;
|
|
2551
|
+
try {
|
|
2552
|
+
inputSchema = (0, import_zod4.toJSONSchema)(contract.requestSchema);
|
|
2553
|
+
} catch (error) {
|
|
2554
|
+
throw new Error(
|
|
2555
|
+
`useReason interrupt contract "${name}" requestSchema must support JSON Schema conversion.`,
|
|
2556
|
+
{ cause: error }
|
|
2557
|
+
);
|
|
2558
|
+
}
|
|
2559
|
+
interruptByToolName.set(toolName, { name, contract });
|
|
2560
|
+
interruptDefinitions.push({
|
|
2561
|
+
name: toolName,
|
|
2562
|
+
title: `Request human input: ${name}`,
|
|
2563
|
+
description: [
|
|
2564
|
+
contract.description,
|
|
2565
|
+
"Call this only when human input is needed. Execution pauses until the validated response is returned as this tool's result."
|
|
2566
|
+
].join(" "),
|
|
2567
|
+
inputSchema,
|
|
2568
|
+
metadata: {
|
|
2569
|
+
kortyxControl: "interrupt",
|
|
2570
|
+
contract: name,
|
|
2571
|
+
schemaId: contract.schemaId,
|
|
2572
|
+
schemaVersion: contract.schemaVersion
|
|
2573
|
+
}
|
|
2574
|
+
});
|
|
2575
|
+
}
|
|
2576
|
+
const toolDefinitions = [
|
|
2577
|
+
...toToolDefinitions(tools2),
|
|
2578
|
+
...interruptDefinitions
|
|
2579
|
+
];
|
|
2412
2580
|
let finalizing = checkpoint?.finalizing ?? false;
|
|
2413
2581
|
let completed = false;
|
|
2414
2582
|
const maxSteps = Math.max(1, useReasonArgs.toolExecution?.maxSteps ?? 3);
|
|
2415
2583
|
const messages = checkpoint?.messages ?? createInitialMessages({
|
|
2416
2584
|
system: useReasonArgs.system,
|
|
2417
|
-
input: useReasonArgs.outputSchema ? withOutputGuardrails(useReasonArgs.input, useReasonArgs.outputSchema) : useReasonArgs.input
|
|
2585
|
+
input: useReasonArgs.outputSchema ? withOutputGuardrails(useReasonArgs.input, useReasonArgs.outputSchema) : useReasonArgs.input,
|
|
2586
|
+
...interruptConfig ? {
|
|
2587
|
+
interruptInstructions: [
|
|
2588
|
+
"Human-input rules:",
|
|
2589
|
+
"- The kortyx_request_input__* tools pause this reasoning operation and return the validated human response as a normal tool result.",
|
|
2590
|
+
"- Call at most one human-input tool in a model turn, and do not combine it with other tool calls in that turn.",
|
|
2591
|
+
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."
|
|
2592
|
+
].join("\n")
|
|
2593
|
+
} : {}
|
|
2418
2594
|
});
|
|
2419
2595
|
const steps = checkpoint?.steps ?? [];
|
|
2420
|
-
const allToolCalls = steps.flatMap(
|
|
2421
|
-
|
|
2422
|
-
);
|
|
2423
|
-
const allToolResults = steps.flatMap(
|
|
2424
|
-
(step) => step.toolResults
|
|
2425
|
-
);
|
|
2596
|
+
const allToolCalls = steps.flatMap((step) => step.toolCalls).filter((call) => !interruptByToolName.has(call.name));
|
|
2597
|
+
const allToolResults = steps.flatMap((step) => step.toolResults).filter((result) => !interruptByToolName.has(result.name));
|
|
2426
2598
|
let finalText = "";
|
|
2427
2599
|
let finalRaw;
|
|
2428
2600
|
let finalOutput;
|
|
@@ -2438,6 +2610,7 @@ var runReasonToolLoop = async (args) => {
|
|
|
2438
2610
|
);
|
|
2439
2611
|
let pending = checkpoint?.pending;
|
|
2440
2612
|
const approvedCalls = checkpoint?.approvedCalls ?? [];
|
|
2613
|
+
const interruptHistory = checkpoint?.interruptHistory ?? [];
|
|
2441
2614
|
const save = () => {
|
|
2442
2615
|
ctx.currentNodeState.byKey[checkpointKey] = {
|
|
2443
2616
|
status: "tool_loop",
|
|
@@ -2445,6 +2618,7 @@ var runReasonToolLoop = async (args) => {
|
|
|
2445
2618
|
messages,
|
|
2446
2619
|
steps,
|
|
2447
2620
|
approvedCalls,
|
|
2621
|
+
interruptHistory,
|
|
2448
2622
|
finalizing,
|
|
2449
2623
|
...pending ? { pending } : {}
|
|
2450
2624
|
};
|
|
@@ -2523,6 +2697,10 @@ var runReasonToolLoop = async (args) => {
|
|
|
2523
2697
|
...step.warnings ? { warnings: step.warnings } : {}
|
|
2524
2698
|
});
|
|
2525
2699
|
if (toolCalls.length === 0) {
|
|
2700
|
+
if (interruptConfig?.mode === "required" && interruptHistory.length === 0)
|
|
2701
|
+
throw new Error(
|
|
2702
|
+
"useReason interrupts.mode is required, but the model returned a final answer without requesting human input."
|
|
2703
|
+
);
|
|
2526
2704
|
if (separateOutput && !finalizing) {
|
|
2527
2705
|
if (args.allowValidatedToolOutput && useReasonArgs.outputSchema) {
|
|
2528
2706
|
try {
|
|
@@ -2567,7 +2745,9 @@ var runReasonToolLoop = async (args) => {
|
|
|
2567
2745
|
"Provider requested a tool during schema-only finalization."
|
|
2568
2746
|
);
|
|
2569
2747
|
if (!reused) {
|
|
2570
|
-
allToolCalls.push(
|
|
2748
|
+
allToolCalls.push(
|
|
2749
|
+
...toolCalls.filter((call) => !interruptByToolName.has(call.name))
|
|
2750
|
+
);
|
|
2571
2751
|
messages.push({
|
|
2572
2752
|
role: "assistant",
|
|
2573
2753
|
content: step.text,
|
|
@@ -2577,6 +2757,84 @@ var runReasonToolLoop = async (args) => {
|
|
|
2577
2757
|
}
|
|
2578
2758
|
pending = step;
|
|
2579
2759
|
save();
|
|
2760
|
+
const interruptCalls = toolCalls.filter(
|
|
2761
|
+
(call) => interruptByToolName.has(call.name)
|
|
2762
|
+
);
|
|
2763
|
+
if (interruptCalls.length > 0) {
|
|
2764
|
+
if (toolCalls.length !== 1)
|
|
2765
|
+
throw new Error(
|
|
2766
|
+
"A useReason interrupt contract call must be the only tool call in its model turn."
|
|
2767
|
+
);
|
|
2768
|
+
const interruptCall = interruptCalls[0];
|
|
2769
|
+
if (!interruptCall)
|
|
2770
|
+
throw new Error("Missing useReason interrupt control call.");
|
|
2771
|
+
if (interruptHistory.length >= (interruptConfig?.maxRequests ?? 1))
|
|
2772
|
+
throw new Error(
|
|
2773
|
+
`useReason reached interrupts.maxRequests (${interruptConfig?.maxRequests ?? 1}).`
|
|
2774
|
+
);
|
|
2775
|
+
const resolved = interruptByToolName.get(interruptCall.name);
|
|
2776
|
+
if (!resolved)
|
|
2777
|
+
throw new Error(
|
|
2778
|
+
`Unknown useReason interrupt contract tool "${interruptCall.name}".`
|
|
2779
|
+
);
|
|
2780
|
+
if (toolResults.some((result3) => result3.toolCallId === interruptCall.id)) {
|
|
2781
|
+
pending = void 0;
|
|
2782
|
+
save();
|
|
2783
|
+
continue;
|
|
2784
|
+
}
|
|
2785
|
+
const request = parseWithSchema(
|
|
2786
|
+
resolved.contract.requestSchema,
|
|
2787
|
+
interruptCall.input,
|
|
2788
|
+
`useReason interrupt contract "${resolved.name}" request`
|
|
2789
|
+
);
|
|
2790
|
+
traceSpan?.addEvent?.("useReason.interrupt.requested", {
|
|
2791
|
+
checkpointKey,
|
|
2792
|
+
contract: resolved.name,
|
|
2793
|
+
interruptIndex: interruptHistory.length
|
|
2794
|
+
});
|
|
2795
|
+
const question = request && typeof request === "object" && !Array.isArray(request) && typeof request.question === "string" ? String(request.question) : void 0;
|
|
2796
|
+
const response = await awaitInterruptInternal({
|
|
2797
|
+
request: {
|
|
2798
|
+
kind: "custom",
|
|
2799
|
+
request,
|
|
2800
|
+
contract: resolved.name,
|
|
2801
|
+
...question ? { question } : {},
|
|
2802
|
+
schemaId: resolved.contract.schemaId,
|
|
2803
|
+
schemaVersion: resolved.contract.schemaVersion
|
|
2804
|
+
},
|
|
2805
|
+
responseSchema: resolved.contract.responseSchema,
|
|
2806
|
+
id: `${id ?? "reason"}:${resolved.name}:${interruptHistory.length + 1}`,
|
|
2807
|
+
meta: {
|
|
2808
|
+
__kortyxReason: {
|
|
2809
|
+
opId,
|
|
2810
|
+
stepIndex,
|
|
2811
|
+
interruptIndex: interruptHistory.length,
|
|
2812
|
+
contract: resolved.name
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
});
|
|
2816
|
+
const result2 = normalizeToolResult(interruptCall, response);
|
|
2817
|
+
toolResults.push(result2);
|
|
2818
|
+
messages.push({
|
|
2819
|
+
role: "tool",
|
|
2820
|
+
content: result2.content,
|
|
2821
|
+
toolCallId: result2.toolCallId,
|
|
2822
|
+
name: result2.name
|
|
2823
|
+
});
|
|
2824
|
+
interruptHistory.push({
|
|
2825
|
+
contract: resolved.name,
|
|
2826
|
+
request,
|
|
2827
|
+
response
|
|
2828
|
+
});
|
|
2829
|
+
traceSpan?.addEvent?.("useReason.interrupt.resolved", {
|
|
2830
|
+
checkpointKey,
|
|
2831
|
+
contract: resolved.name,
|
|
2832
|
+
interruptIndex: interruptHistory.length - 1
|
|
2833
|
+
});
|
|
2834
|
+
pending = void 0;
|
|
2835
|
+
save();
|
|
2836
|
+
continue;
|
|
2837
|
+
}
|
|
2580
2838
|
for (const toolCall of toolCalls) {
|
|
2581
2839
|
if (toolResults.some((result2) => result2.toolCallId === toolCall.id)) {
|
|
2582
2840
|
await replayToolObservations(
|
|
@@ -2786,7 +3044,11 @@ var runReasonToolLoop = async (args) => {
|
|
|
2786
3044
|
...finalOutput !== void 0 ? { output: finalOutput } : {},
|
|
2787
3045
|
toolCalls: allToolCalls,
|
|
2788
3046
|
toolResults: allToolResults,
|
|
2789
|
-
steps
|
|
3047
|
+
steps,
|
|
3048
|
+
...interruptHistory.length > 0 ? { interruptHistory } : {},
|
|
3049
|
+
...isLegacyInterruptContracts(interruptConfig) && interruptHistory.length > 0 ? {
|
|
3050
|
+
interruptResponse: interruptHistory.at(-1)?.response
|
|
3051
|
+
} : {}
|
|
2790
3052
|
};
|
|
2791
3053
|
traceSpan?.end?.({
|
|
2792
3054
|
...aggregatedUsage !== void 0 ? { usage: aggregatedUsage } : {},
|
|
@@ -2797,7 +3059,8 @@ var runReasonToolLoop = async (args) => {
|
|
|
2797
3059
|
textLength: finalText.length,
|
|
2798
3060
|
toolCallCount: allToolCalls.length,
|
|
2799
3061
|
toolResultCount: allToolResults.length,
|
|
2800
|
-
toolStepCount: steps.length
|
|
3062
|
+
toolStepCount: steps.length,
|
|
3063
|
+
interruptCount: interruptHistory.length
|
|
2801
3064
|
},
|
|
2802
3065
|
telemetry: {
|
|
2803
3066
|
...useReasonArgs.telemetry ?? {},
|
|
@@ -2827,7 +3090,7 @@ var resolveEffectiveReasoningIncludeThoughts = (args) => args.reasoning?.include
|
|
|
2827
3090
|
var resolveEffectiveResponseFormatType = (args) => args.responseFormat?.type ?? args.model.options?.responseFormat?.type;
|
|
2828
3091
|
var assertReasoningThoughtsCompatibility = (args) => {
|
|
2829
3092
|
if (!resolveEffectiveReasoningIncludeThoughts(args)) return;
|
|
2830
|
-
const usesStructuredOutput = Boolean(args.outputSchema) || Boolean(args.interrupt) || Boolean(args.structured) || resolveEffectiveResponseFormatType(args) === "json";
|
|
3093
|
+
const usesStructuredOutput = Boolean(args.outputSchema) || Boolean(args.interrupt) || Boolean(args.interrupts) || Boolean(args.structured) || resolveEffectiveResponseFormatType(args) === "json";
|
|
2831
3094
|
if (!usesStructuredOutput) return;
|
|
2832
3095
|
throw new Error(
|
|
2833
3096
|
"useReason does not support reasoning.includeThoughts with structured output, interrupt mode, or JSON responseFormat. Disable includeThoughts for this call."
|
|
@@ -2835,6 +3098,10 @@ var assertReasoningThoughtsCompatibility = (args) => {
|
|
|
2835
3098
|
};
|
|
2836
3099
|
async function runReason(args) {
|
|
2837
3100
|
assertReasoningThoughtsCompatibility(args);
|
|
3101
|
+
const interruptConfig = normalizeReasonInterrupts({
|
|
3102
|
+
interrupt: args.interrupt,
|
|
3103
|
+
interrupts: args.interrupts
|
|
3104
|
+
});
|
|
2838
3105
|
const ctx = getHookContext();
|
|
2839
3106
|
const abortSignal = (0, import_core6.combineAbortSignals)(
|
|
2840
3107
|
ctx.node.abortSignal,
|
|
@@ -2849,20 +3116,23 @@ async function runReason(args) {
|
|
|
2849
3116
|
args.outputSchema,
|
|
2850
3117
|
args.responseFormat ?? args.model.options?.responseFormat
|
|
2851
3118
|
);
|
|
3119
|
+
const usesLegacyNoToolInterrupt = Boolean(
|
|
3120
|
+
args.interrupt && !args.interrupts && !args.tools?.length
|
|
3121
|
+
);
|
|
2852
3122
|
args = {
|
|
2853
3123
|
...args,
|
|
2854
3124
|
abortSignal,
|
|
2855
|
-
...!
|
|
3125
|
+
...!usesLegacyNoToolInterrupt && inferred.responseFormat ? { responseFormat: inferred.responseFormat } : {}
|
|
2856
3126
|
};
|
|
2857
3127
|
const id = typeof args.id === "string" && args.id.length > 0 ? args.id : void 0;
|
|
2858
|
-
const
|
|
3128
|
+
const provisionalOpId = createRuntimeId2();
|
|
2859
3129
|
const checkpointKey = resolveReasonCheckpointKey({
|
|
2860
3130
|
...id ? { id } : {},
|
|
2861
3131
|
autoIndex: ctx.reasonCallIndex++
|
|
2862
3132
|
});
|
|
2863
|
-
const
|
|
2864
|
-
|
|
2865
|
-
);
|
|
3133
|
+
const checkpointValue = ctx.currentNodeState.byKey[checkpointKey];
|
|
3134
|
+
const opId = checkpointValue && typeof checkpointValue === "object" && "status" in checkpointValue && checkpointValue.status === "tool_loop" && "opId" in checkpointValue && typeof checkpointValue.opId === "string" ? checkpointValue.opId : provisionalOpId;
|
|
3135
|
+
const existingCompleted = readReasonCompletedCheckpoint(checkpointValue);
|
|
2866
3136
|
if (existingCompleted) {
|
|
2867
3137
|
await replayToolObservations(
|
|
2868
3138
|
existingCompleted.result.steps?.flatMap(
|
|
@@ -2871,9 +3141,7 @@ async function runReason(args) {
|
|
|
2871
3141
|
);
|
|
2872
3142
|
return existingCompleted.result;
|
|
2873
3143
|
}
|
|
2874
|
-
const existingCheckpoint = readReasonCheckpoint(
|
|
2875
|
-
ctx.currentNodeState.byKey[checkpointKey]
|
|
2876
|
-
);
|
|
3144
|
+
const existingCheckpoint = readReasonCheckpoint(checkpointValue);
|
|
2877
3145
|
const reasonMeta = typeof id === "string" ? { id, opId } : { opId };
|
|
2878
3146
|
return withSafeTraceSpan(
|
|
2879
3147
|
getReasonTraceAdapter(),
|
|
@@ -2892,7 +3160,8 @@ async function runReason(args) {
|
|
|
2892
3160
|
...args.telemetry?.prompt?.version !== void 0 ? { promptVersion: args.telemetry.prompt.version } : {},
|
|
2893
3161
|
...args.telemetry?.prompt?.type ? { promptType: args.telemetry.prompt.type } : {},
|
|
2894
3162
|
hasOutputSchema: Boolean(args.outputSchema),
|
|
2895
|
-
hasInterrupt: Boolean(
|
|
3163
|
+
hasInterrupt: Boolean(interruptConfig),
|
|
3164
|
+
interruptContractCount: Object.keys(interruptConfig?.contracts ?? {}).length,
|
|
2896
3165
|
hasStructured: Boolean(args.structured),
|
|
2897
3166
|
hasTools: Boolean(args.tools?.length)
|
|
2898
3167
|
},
|
|
@@ -2902,7 +3171,7 @@ async function runReason(args) {
|
|
|
2902
3171
|
}
|
|
2903
3172
|
},
|
|
2904
3173
|
async (traceSpan) => {
|
|
2905
|
-
if (args.tools?.length) {
|
|
3174
|
+
if ((args.tools?.length || args.interrupts) && !existingCheckpoint) {
|
|
2906
3175
|
return runReasonToolLoop({
|
|
2907
3176
|
useReasonArgs: args,
|
|
2908
3177
|
allowValidatedToolOutput,
|
|
@@ -3171,6 +3440,24 @@ function useReason2(args) {
|
|
|
3171
3440
|
return useReason(args);
|
|
3172
3441
|
}
|
|
3173
3442
|
function useInterrupt(args) {
|
|
3443
|
+
if ("contract" in args) {
|
|
3444
|
+
const contract = defineInterruptContract(args.contract);
|
|
3445
|
+
return awaitInterruptInternal({
|
|
3446
|
+
request: {
|
|
3447
|
+
kind: "custom",
|
|
3448
|
+
request: parseWithSchema(
|
|
3449
|
+
contract.requestSchema,
|
|
3450
|
+
args.request,
|
|
3451
|
+
"useInterrupt contract request"
|
|
3452
|
+
),
|
|
3453
|
+
schemaId: contract.schemaId,
|
|
3454
|
+
schemaVersion: contract.schemaVersion,
|
|
3455
|
+
...args.id ? { id: args.id } : {},
|
|
3456
|
+
...args.meta ? { meta: args.meta } : {}
|
|
3457
|
+
},
|
|
3458
|
+
responseSchema: contract.responseSchema
|
|
3459
|
+
});
|
|
3460
|
+
}
|
|
3174
3461
|
return awaitInterruptInternal(args);
|
|
3175
3462
|
}
|
|
3176
3463
|
function useAbortSignal() {
|
|
@@ -3387,6 +3674,14 @@ function parallel(values) {
|
|
|
3387
3674
|
return promise;
|
|
3388
3675
|
}
|
|
3389
3676
|
|
|
3677
|
+
// src/report-error.ts
|
|
3678
|
+
function reportError(error, options = {}) {
|
|
3679
|
+
try {
|
|
3680
|
+
getReasonTraceAdapter()?.reportError?.(error, options);
|
|
3681
|
+
} catch {
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3390
3685
|
// src/workflow.ts
|
|
3391
3686
|
var import_node_crypto6 = require("crypto");
|
|
3392
3687
|
var import_core8 = require("@kortyx/core");
|
|
@@ -3661,8 +3956,10 @@ async function runWorkflowCall(args, task) {
|
|
|
3661
3956
|
WorkflowCallError,
|
|
3662
3957
|
completeResponse,
|
|
3663
3958
|
createWorkflowHooks,
|
|
3959
|
+
defineInterruptContract,
|
|
3664
3960
|
emitWorkflowCall,
|
|
3665
3961
|
parallel,
|
|
3962
|
+
reportError,
|
|
3666
3963
|
runWithHookContext,
|
|
3667
3964
|
useAbortSignal,
|
|
3668
3965
|
useInterrupt,
|