@kortyx/agent 0.25.1 → 0.26.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 +21 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +343 -40
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -525,6 +525,7 @@ var require_dist = __commonJS({
|
|
|
525
525
|
WorkflowCallError: () => WorkflowCallError,
|
|
526
526
|
completeResponse: () => completeResponse,
|
|
527
527
|
createWorkflowHooks: () => createWorkflowHooks,
|
|
528
|
+
defineInterruptContract: () => defineInterruptContract,
|
|
528
529
|
emitWorkflowCall: () => emitWorkflowCall2,
|
|
529
530
|
parallel: () => parallel,
|
|
530
531
|
reportError: () => reportError,
|
|
@@ -836,6 +837,95 @@ var require_dist = __commonJS({
|
|
|
836
837
|
);
|
|
837
838
|
return Promise.resolve(response);
|
|
838
839
|
};
|
|
840
|
+
var CONTROL_TOOL_PREFIX = "kortyx_request_input__";
|
|
841
|
+
var LEGACY_CONTRACT_NAME = "default";
|
|
842
|
+
var legacyWarningEmitted = false;
|
|
843
|
+
var legacyConfigs = /* @__PURE__ */ new WeakSet();
|
|
844
|
+
var defineInterruptContract = (contract) => {
|
|
845
|
+
if (!contract || typeof contract !== "object")
|
|
846
|
+
throw new Error("defineInterruptContract requires a contract object.");
|
|
847
|
+
for (const [field, value] of [
|
|
848
|
+
["description", contract.description],
|
|
849
|
+
["schemaId", contract.schemaId],
|
|
850
|
+
["schemaVersion", contract.schemaVersion]
|
|
851
|
+
])
|
|
852
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
853
|
+
throw new Error(
|
|
854
|
+
`Interrupt contract ${field} must be a non-empty string.`
|
|
855
|
+
);
|
|
856
|
+
if (typeof contract.requestSchema?.safeParse !== "function")
|
|
857
|
+
throw new Error(
|
|
858
|
+
"Interrupt contract requestSchema must provide safeParse(value)."
|
|
859
|
+
);
|
|
860
|
+
if (typeof contract.responseSchema?.safeParse !== "function")
|
|
861
|
+
throw new Error(
|
|
862
|
+
"Interrupt contract responseSchema must provide safeParse(value)."
|
|
863
|
+
);
|
|
864
|
+
return contract;
|
|
865
|
+
};
|
|
866
|
+
var interruptControlToolName = (name) => `${CONTROL_TOOL_PREFIX}${name}`;
|
|
867
|
+
var isInterruptControlToolName = (name) => name.startsWith(CONTROL_TOOL_PREFIX);
|
|
868
|
+
var assertInterruptContractName = (name) => {
|
|
869
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(name))
|
|
870
|
+
throw new Error(
|
|
871
|
+
`Invalid useReason interrupt contract name "${name}". Use letters, numbers, underscores, or hyphens, starting with a letter.`
|
|
872
|
+
);
|
|
873
|
+
if (interruptControlToolName(name).length > 64)
|
|
874
|
+
throw new Error(
|
|
875
|
+
`useReason interrupt contract name "${name}" is too long for a provider tool name.`
|
|
876
|
+
);
|
|
877
|
+
};
|
|
878
|
+
var warnLegacyUseReasonInterrupt = () => {
|
|
879
|
+
if (legacyWarningEmitted) return;
|
|
880
|
+
legacyWarningEmitted = true;
|
|
881
|
+
process.emitWarning(
|
|
882
|
+
"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.",
|
|
883
|
+
{
|
|
884
|
+
code: "KORTYX_USE_REASON_INTERRUPT_DEPRECATED",
|
|
885
|
+
type: "DeprecationWarning"
|
|
886
|
+
}
|
|
887
|
+
);
|
|
888
|
+
};
|
|
889
|
+
var normalizeReasonInterrupts = (args) => {
|
|
890
|
+
if (args.interrupt && args.interrupts)
|
|
891
|
+
throw new Error(
|
|
892
|
+
"useReason cannot receive both deprecated `interrupt` and `interrupts`. Migrate the legacy configuration to an interrupt contract."
|
|
893
|
+
);
|
|
894
|
+
if (args.interrupts) {
|
|
895
|
+
for (const [name, contract] of Object.entries(args.interrupts.contracts)) {
|
|
896
|
+
assertInterruptContractName(name);
|
|
897
|
+
defineInterruptContract(contract);
|
|
898
|
+
}
|
|
899
|
+
if (Object.keys(args.interrupts.contracts).length === 0)
|
|
900
|
+
throw new Error("useReason interrupts.contracts cannot be empty.");
|
|
901
|
+
if (args.interrupts.maxRequests !== void 0 && (!Number.isInteger(args.interrupts.maxRequests) || args.interrupts.maxRequests < 1))
|
|
902
|
+
throw new Error("useReason interrupts.maxRequests must be at least 1.");
|
|
903
|
+
return args.interrupts;
|
|
904
|
+
}
|
|
905
|
+
if (!args.interrupt) return void 0;
|
|
906
|
+
warnLegacyUseReasonInterrupt();
|
|
907
|
+
const normalized = {
|
|
908
|
+
mode: args.interrupt.mode,
|
|
909
|
+
maxRequests: 1,
|
|
910
|
+
contracts: {
|
|
911
|
+
[LEGACY_CONTRACT_NAME]: {
|
|
912
|
+
description: "Ask the user for the additional information required to complete this request.",
|
|
913
|
+
requestSchema: args.interrupt.requestSchema,
|
|
914
|
+
responseSchema: args.interrupt.responseSchema ?? {
|
|
915
|
+
safeParse: (value) => ({
|
|
916
|
+
success: true,
|
|
917
|
+
data: value
|
|
918
|
+
})
|
|
919
|
+
},
|
|
920
|
+
schemaId: args.interrupt.schemaId ?? "kortyx.legacy-interrupt",
|
|
921
|
+
schemaVersion: args.interrupt.schemaVersion ?? "1"
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
legacyConfigs.add(normalized);
|
|
926
|
+
return normalized;
|
|
927
|
+
};
|
|
928
|
+
var isLegacyInterruptContracts = (config) => Boolean(config && legacyConfigs.has(config));
|
|
839
929
|
var import_core62 = require("@kortyx/core");
|
|
840
930
|
function safeSpan(span) {
|
|
841
931
|
const guard = (fn) => (...args) => {
|
|
@@ -2812,6 +2902,7 @@ ${tryStringify2(args.response)}`
|
|
|
2812
2902
|
};
|
|
2813
2903
|
var import_core52 = require("@kortyx/core");
|
|
2814
2904
|
var import_errors62 = require("@kortyx/core/errors");
|
|
2905
|
+
var import_zod42 = require("zod");
|
|
2815
2906
|
var toToolDefinitions = (tools2) => tools2.map((tool) => ({
|
|
2816
2907
|
name: tool.name,
|
|
2817
2908
|
...tool.title ? { title: tool.title } : {},
|
|
@@ -2847,7 +2938,12 @@ ${tryStringify2(args.response)}`
|
|
|
2847
2938
|
};
|
|
2848
2939
|
};
|
|
2849
2940
|
var createInitialMessages = (args) => [
|
|
2850
|
-
...typeof args.system === "string"
|
|
2941
|
+
...typeof args.system === "string" || args.interruptInstructions ? [
|
|
2942
|
+
{
|
|
2943
|
+
role: "system",
|
|
2944
|
+
content: [args.system, args.interruptInstructions].filter((value) => Boolean(value)).join("\n\n")
|
|
2945
|
+
}
|
|
2946
|
+
] : [],
|
|
2851
2947
|
{ role: "user", content: String(args.input ?? "") }
|
|
2852
2948
|
];
|
|
2853
2949
|
var runReasonToolLoop = async (args) => {
|
|
@@ -2862,19 +2958,23 @@ ${tryStringify2(args.response)}`
|
|
|
2862
2958
|
const checkpoint = saved?.status === "tool_loop" ? saved : void 0;
|
|
2863
2959
|
const opId = checkpoint?.opId ?? args.opId;
|
|
2864
2960
|
const tools2 = useReasonArgs.tools ?? [];
|
|
2961
|
+
const interruptConfig = normalizeReasonInterrupts({
|
|
2962
|
+
interrupt: useReasonArgs.interrupt,
|
|
2963
|
+
interrupts: useReasonArgs.interrupts
|
|
2964
|
+
});
|
|
2965
|
+
const interruptByToolName = /* @__PURE__ */ new Map();
|
|
2865
2966
|
const toolByName = /* @__PURE__ */ new Map();
|
|
2866
2967
|
let separateOutput = false;
|
|
2867
2968
|
for (const tool of tools2) {
|
|
2969
|
+
if (isInterruptControlToolName(tool.name))
|
|
2970
|
+
throw new Error(
|
|
2971
|
+
`useReason tool name "${tool.name}" uses the reserved interrupt control-tool namespace.`
|
|
2972
|
+
);
|
|
2868
2973
|
if (toolByName.has(tool.name)) {
|
|
2869
2974
|
throw new Error(`useReason received duplicate tool name "${tool.name}".`);
|
|
2870
2975
|
}
|
|
2871
2976
|
toolByName.set(tool.name, tool);
|
|
2872
2977
|
}
|
|
2873
|
-
if (useReasonArgs.interrupt) {
|
|
2874
|
-
throw new Error(
|
|
2875
|
-
"useReason tools cannot be combined with useReason interrupt mode yet. Use toolExecution.approval for tool approval."
|
|
2876
|
-
);
|
|
2877
|
-
}
|
|
2878
2978
|
separateOutput = Boolean(
|
|
2879
2979
|
useReasonArgs.model.provider.getModel(useReasonArgs.model.modelId, {
|
|
2880
2980
|
...useReasonArgs.model.options,
|
|
@@ -2886,21 +2986,63 @@ ${tryStringify2(args.response)}`
|
|
|
2886
2986
|
) ?? {}
|
|
2887
2987
|
}).requiresSeparateStructuredOutput
|
|
2888
2988
|
);
|
|
2889
|
-
const
|
|
2989
|
+
const interruptDefinitions = [];
|
|
2990
|
+
for (const [name, contract] of Object.entries(
|
|
2991
|
+
interruptConfig?.contracts ?? {}
|
|
2992
|
+
)) {
|
|
2993
|
+
const toolName = interruptControlToolName(name);
|
|
2994
|
+
if (toolByName.has(toolName))
|
|
2995
|
+
throw new Error(
|
|
2996
|
+
`useReason tool name "${toolName}" is reserved for interrupt contracts.`
|
|
2997
|
+
);
|
|
2998
|
+
let inputSchema;
|
|
2999
|
+
try {
|
|
3000
|
+
inputSchema = (0, import_zod42.toJSONSchema)(contract.requestSchema);
|
|
3001
|
+
} catch (error) {
|
|
3002
|
+
throw new Error(
|
|
3003
|
+
`useReason interrupt contract "${name}" requestSchema must support JSON Schema conversion.`,
|
|
3004
|
+
{ cause: error }
|
|
3005
|
+
);
|
|
3006
|
+
}
|
|
3007
|
+
interruptByToolName.set(toolName, { name, contract });
|
|
3008
|
+
interruptDefinitions.push({
|
|
3009
|
+
name: toolName,
|
|
3010
|
+
title: `Request human input: ${name}`,
|
|
3011
|
+
description: [
|
|
3012
|
+
contract.description,
|
|
3013
|
+
"Call this only when human input is needed. Execution pauses until the validated response is returned as this tool's result."
|
|
3014
|
+
].join(" "),
|
|
3015
|
+
inputSchema,
|
|
3016
|
+
metadata: {
|
|
3017
|
+
kortyxControl: "interrupt",
|
|
3018
|
+
contract: name,
|
|
3019
|
+
schemaId: contract.schemaId,
|
|
3020
|
+
schemaVersion: contract.schemaVersion
|
|
3021
|
+
}
|
|
3022
|
+
});
|
|
3023
|
+
}
|
|
3024
|
+
const toolDefinitions = [
|
|
3025
|
+
...toToolDefinitions(tools2),
|
|
3026
|
+
...interruptDefinitions
|
|
3027
|
+
];
|
|
2890
3028
|
let finalizing = checkpoint?.finalizing ?? false;
|
|
2891
3029
|
let completed = false;
|
|
2892
3030
|
const maxSteps = Math.max(1, useReasonArgs.toolExecution?.maxSteps ?? 3);
|
|
2893
3031
|
const messages = checkpoint?.messages ?? createInitialMessages({
|
|
2894
3032
|
system: useReasonArgs.system,
|
|
2895
|
-
input: useReasonArgs.outputSchema ? withOutputGuardrails(useReasonArgs.input, useReasonArgs.outputSchema) : useReasonArgs.input
|
|
3033
|
+
input: useReasonArgs.outputSchema ? withOutputGuardrails(useReasonArgs.input, useReasonArgs.outputSchema) : useReasonArgs.input,
|
|
3034
|
+
...interruptConfig ? {
|
|
3035
|
+
interruptInstructions: [
|
|
3036
|
+
"Human-input rules:",
|
|
3037
|
+
"- The kortyx_request_input__* tools pause this reasoning operation and return the validated human response as a normal tool result.",
|
|
3038
|
+
"- Call at most one human-input tool in a model turn, and do not combine it with other tool calls in that turn.",
|
|
3039
|
+
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."
|
|
3040
|
+
].join("\n")
|
|
3041
|
+
} : {}
|
|
2896
3042
|
});
|
|
2897
3043
|
const steps = checkpoint?.steps ?? [];
|
|
2898
|
-
const allToolCalls = steps.flatMap(
|
|
2899
|
-
|
|
2900
|
-
);
|
|
2901
|
-
const allToolResults = steps.flatMap(
|
|
2902
|
-
(step) => step.toolResults
|
|
2903
|
-
);
|
|
3044
|
+
const allToolCalls = steps.flatMap((step) => step.toolCalls).filter((call) => !interruptByToolName.has(call.name));
|
|
3045
|
+
const allToolResults = steps.flatMap((step) => step.toolResults).filter((result) => !interruptByToolName.has(result.name));
|
|
2904
3046
|
let finalText = "";
|
|
2905
3047
|
let finalRaw;
|
|
2906
3048
|
let finalOutput;
|
|
@@ -2916,6 +3058,7 @@ ${tryStringify2(args.response)}`
|
|
|
2916
3058
|
);
|
|
2917
3059
|
let pending = checkpoint?.pending;
|
|
2918
3060
|
const approvedCalls = checkpoint?.approvedCalls ?? [];
|
|
3061
|
+
const interruptHistory = checkpoint?.interruptHistory ?? [];
|
|
2919
3062
|
const save = () => {
|
|
2920
3063
|
ctx.currentNodeState.byKey[checkpointKey] = {
|
|
2921
3064
|
status: "tool_loop",
|
|
@@ -2923,6 +3066,7 @@ ${tryStringify2(args.response)}`
|
|
|
2923
3066
|
messages,
|
|
2924
3067
|
steps,
|
|
2925
3068
|
approvedCalls,
|
|
3069
|
+
interruptHistory,
|
|
2926
3070
|
finalizing,
|
|
2927
3071
|
...pending ? { pending } : {}
|
|
2928
3072
|
};
|
|
@@ -3001,6 +3145,10 @@ ${tryStringify2(args.response)}`
|
|
|
3001
3145
|
...step.warnings ? { warnings: step.warnings } : {}
|
|
3002
3146
|
});
|
|
3003
3147
|
if (toolCalls.length === 0) {
|
|
3148
|
+
if (interruptConfig?.mode === "required" && interruptHistory.length === 0)
|
|
3149
|
+
throw new Error(
|
|
3150
|
+
"useReason interrupts.mode is required, but the model returned a final answer without requesting human input."
|
|
3151
|
+
);
|
|
3004
3152
|
if (separateOutput && !finalizing) {
|
|
3005
3153
|
if (args.allowValidatedToolOutput && useReasonArgs.outputSchema) {
|
|
3006
3154
|
try {
|
|
@@ -3045,7 +3193,9 @@ ${tryStringify2(args.response)}`
|
|
|
3045
3193
|
"Provider requested a tool during schema-only finalization."
|
|
3046
3194
|
);
|
|
3047
3195
|
if (!reused) {
|
|
3048
|
-
allToolCalls.push(
|
|
3196
|
+
allToolCalls.push(
|
|
3197
|
+
...toolCalls.filter((call) => !interruptByToolName.has(call.name))
|
|
3198
|
+
);
|
|
3049
3199
|
messages.push({
|
|
3050
3200
|
role: "assistant",
|
|
3051
3201
|
content: step.text,
|
|
@@ -3055,6 +3205,84 @@ ${tryStringify2(args.response)}`
|
|
|
3055
3205
|
}
|
|
3056
3206
|
pending = step;
|
|
3057
3207
|
save();
|
|
3208
|
+
const interruptCalls = toolCalls.filter(
|
|
3209
|
+
(call) => interruptByToolName.has(call.name)
|
|
3210
|
+
);
|
|
3211
|
+
if (interruptCalls.length > 0) {
|
|
3212
|
+
if (toolCalls.length !== 1)
|
|
3213
|
+
throw new Error(
|
|
3214
|
+
"A useReason interrupt contract call must be the only tool call in its model turn."
|
|
3215
|
+
);
|
|
3216
|
+
const interruptCall = interruptCalls[0];
|
|
3217
|
+
if (!interruptCall)
|
|
3218
|
+
throw new Error("Missing useReason interrupt control call.");
|
|
3219
|
+
if (interruptHistory.length >= (interruptConfig?.maxRequests ?? 1))
|
|
3220
|
+
throw new Error(
|
|
3221
|
+
`useReason reached interrupts.maxRequests (${interruptConfig?.maxRequests ?? 1}).`
|
|
3222
|
+
);
|
|
3223
|
+
const resolved = interruptByToolName.get(interruptCall.name);
|
|
3224
|
+
if (!resolved)
|
|
3225
|
+
throw new Error(
|
|
3226
|
+
`Unknown useReason interrupt contract tool "${interruptCall.name}".`
|
|
3227
|
+
);
|
|
3228
|
+
if (toolResults.some((result3) => result3.toolCallId === interruptCall.id)) {
|
|
3229
|
+
pending = void 0;
|
|
3230
|
+
save();
|
|
3231
|
+
continue;
|
|
3232
|
+
}
|
|
3233
|
+
const request = parseWithSchema(
|
|
3234
|
+
resolved.contract.requestSchema,
|
|
3235
|
+
interruptCall.input,
|
|
3236
|
+
`useReason interrupt contract "${resolved.name}" request`
|
|
3237
|
+
);
|
|
3238
|
+
traceSpan?.addEvent?.("useReason.interrupt.requested", {
|
|
3239
|
+
checkpointKey,
|
|
3240
|
+
contract: resolved.name,
|
|
3241
|
+
interruptIndex: interruptHistory.length
|
|
3242
|
+
});
|
|
3243
|
+
const question = request && typeof request === "object" && !Array.isArray(request) && typeof request.question === "string" ? String(request.question) : void 0;
|
|
3244
|
+
const response = await awaitInterruptInternal({
|
|
3245
|
+
request: {
|
|
3246
|
+
kind: "custom",
|
|
3247
|
+
request,
|
|
3248
|
+
contract: resolved.name,
|
|
3249
|
+
...question ? { question } : {},
|
|
3250
|
+
schemaId: resolved.contract.schemaId,
|
|
3251
|
+
schemaVersion: resolved.contract.schemaVersion
|
|
3252
|
+
},
|
|
3253
|
+
responseSchema: resolved.contract.responseSchema,
|
|
3254
|
+
id: `${id ?? "reason"}:${resolved.name}:${interruptHistory.length + 1}`,
|
|
3255
|
+
meta: {
|
|
3256
|
+
__kortyxReason: {
|
|
3257
|
+
opId,
|
|
3258
|
+
stepIndex,
|
|
3259
|
+
interruptIndex: interruptHistory.length,
|
|
3260
|
+
contract: resolved.name
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
});
|
|
3264
|
+
const result2 = normalizeToolResult(interruptCall, response);
|
|
3265
|
+
toolResults.push(result2);
|
|
3266
|
+
messages.push({
|
|
3267
|
+
role: "tool",
|
|
3268
|
+
content: result2.content,
|
|
3269
|
+
toolCallId: result2.toolCallId,
|
|
3270
|
+
name: result2.name
|
|
3271
|
+
});
|
|
3272
|
+
interruptHistory.push({
|
|
3273
|
+
contract: resolved.name,
|
|
3274
|
+
request,
|
|
3275
|
+
response
|
|
3276
|
+
});
|
|
3277
|
+
traceSpan?.addEvent?.("useReason.interrupt.resolved", {
|
|
3278
|
+
checkpointKey,
|
|
3279
|
+
contract: resolved.name,
|
|
3280
|
+
interruptIndex: interruptHistory.length - 1
|
|
3281
|
+
});
|
|
3282
|
+
pending = void 0;
|
|
3283
|
+
save();
|
|
3284
|
+
continue;
|
|
3285
|
+
}
|
|
3058
3286
|
for (const toolCall of toolCalls) {
|
|
3059
3287
|
if (toolResults.some((result2) => result2.toolCallId === toolCall.id)) {
|
|
3060
3288
|
await replayToolObservations(
|
|
@@ -3264,7 +3492,11 @@ ${tryStringify2(args.response)}`
|
|
|
3264
3492
|
...finalOutput !== void 0 ? { output: finalOutput } : {},
|
|
3265
3493
|
toolCalls: allToolCalls,
|
|
3266
3494
|
toolResults: allToolResults,
|
|
3267
|
-
steps
|
|
3495
|
+
steps,
|
|
3496
|
+
...interruptHistory.length > 0 ? { interruptHistory } : {},
|
|
3497
|
+
...isLegacyInterruptContracts(interruptConfig) && interruptHistory.length > 0 ? {
|
|
3498
|
+
interruptResponse: interruptHistory.at(-1)?.response
|
|
3499
|
+
} : {}
|
|
3268
3500
|
};
|
|
3269
3501
|
traceSpan?.end?.({
|
|
3270
3502
|
...aggregatedUsage !== void 0 ? { usage: aggregatedUsage } : {},
|
|
@@ -3275,7 +3507,8 @@ ${tryStringify2(args.response)}`
|
|
|
3275
3507
|
textLength: finalText.length,
|
|
3276
3508
|
toolCallCount: allToolCalls.length,
|
|
3277
3509
|
toolResultCount: allToolResults.length,
|
|
3278
|
-
toolStepCount: steps.length
|
|
3510
|
+
toolStepCount: steps.length,
|
|
3511
|
+
interruptCount: interruptHistory.length
|
|
3279
3512
|
},
|
|
3280
3513
|
telemetry: {
|
|
3281
3514
|
...useReasonArgs.telemetry ?? {},
|
|
@@ -3303,7 +3536,7 @@ ${tryStringify2(args.response)}`
|
|
|
3303
3536
|
var resolveEffectiveResponseFormatType = (args) => args.responseFormat?.type ?? args.model.options?.responseFormat?.type;
|
|
3304
3537
|
var assertReasoningThoughtsCompatibility = (args) => {
|
|
3305
3538
|
if (!resolveEffectiveReasoningIncludeThoughts(args)) return;
|
|
3306
|
-
const usesStructuredOutput = Boolean(args.outputSchema) || Boolean(args.interrupt) || Boolean(args.structured) || resolveEffectiveResponseFormatType(args) === "json";
|
|
3539
|
+
const usesStructuredOutput = Boolean(args.outputSchema) || Boolean(args.interrupt) || Boolean(args.interrupts) || Boolean(args.structured) || resolveEffectiveResponseFormatType(args) === "json";
|
|
3307
3540
|
if (!usesStructuredOutput) return;
|
|
3308
3541
|
throw new Error(
|
|
3309
3542
|
"useReason does not support reasoning.includeThoughts with structured output, interrupt mode, or JSON responseFormat. Disable includeThoughts for this call."
|
|
@@ -3311,6 +3544,10 @@ ${tryStringify2(args.response)}`
|
|
|
3311
3544
|
};
|
|
3312
3545
|
async function runReason(args) {
|
|
3313
3546
|
assertReasoningThoughtsCompatibility(args);
|
|
3547
|
+
const interruptConfig = normalizeReasonInterrupts({
|
|
3548
|
+
interrupt: args.interrupt,
|
|
3549
|
+
interrupts: args.interrupts
|
|
3550
|
+
});
|
|
3314
3551
|
const ctx = getHookContext();
|
|
3315
3552
|
const abortSignal = (0, import_core62.combineAbortSignals)(
|
|
3316
3553
|
ctx.node.abortSignal,
|
|
@@ -3325,20 +3562,23 @@ ${tryStringify2(args.response)}`
|
|
|
3325
3562
|
args.outputSchema,
|
|
3326
3563
|
args.responseFormat ?? args.model.options?.responseFormat
|
|
3327
3564
|
);
|
|
3565
|
+
const usesLegacyNoToolInterrupt = Boolean(
|
|
3566
|
+
args.interrupt && !args.interrupts && !args.tools?.length
|
|
3567
|
+
);
|
|
3328
3568
|
args = {
|
|
3329
3569
|
...args,
|
|
3330
3570
|
abortSignal,
|
|
3331
|
-
...!
|
|
3571
|
+
...!usesLegacyNoToolInterrupt && inferred.responseFormat ? { responseFormat: inferred.responseFormat } : {}
|
|
3332
3572
|
};
|
|
3333
3573
|
const id = typeof args.id === "string" && args.id.length > 0 ? args.id : void 0;
|
|
3334
|
-
const
|
|
3574
|
+
const provisionalOpId = createRuntimeId2();
|
|
3335
3575
|
const checkpointKey = resolveReasonCheckpointKey({
|
|
3336
3576
|
...id ? { id } : {},
|
|
3337
3577
|
autoIndex: ctx.reasonCallIndex++
|
|
3338
3578
|
});
|
|
3339
|
-
const
|
|
3340
|
-
|
|
3341
|
-
);
|
|
3579
|
+
const checkpointValue = ctx.currentNodeState.byKey[checkpointKey];
|
|
3580
|
+
const opId = checkpointValue && typeof checkpointValue === "object" && "status" in checkpointValue && checkpointValue.status === "tool_loop" && "opId" in checkpointValue && typeof checkpointValue.opId === "string" ? checkpointValue.opId : provisionalOpId;
|
|
3581
|
+
const existingCompleted = readReasonCompletedCheckpoint(checkpointValue);
|
|
3342
3582
|
if (existingCompleted) {
|
|
3343
3583
|
await replayToolObservations(
|
|
3344
3584
|
existingCompleted.result.steps?.flatMap(
|
|
@@ -3347,9 +3587,7 @@ ${tryStringify2(args.response)}`
|
|
|
3347
3587
|
);
|
|
3348
3588
|
return existingCompleted.result;
|
|
3349
3589
|
}
|
|
3350
|
-
const existingCheckpoint = readReasonCheckpoint(
|
|
3351
|
-
ctx.currentNodeState.byKey[checkpointKey]
|
|
3352
|
-
);
|
|
3590
|
+
const existingCheckpoint = readReasonCheckpoint(checkpointValue);
|
|
3353
3591
|
const reasonMeta = typeof id === "string" ? { id, opId } : { opId };
|
|
3354
3592
|
return withSafeTraceSpan2(
|
|
3355
3593
|
getReasonTraceAdapter(),
|
|
@@ -3368,7 +3606,8 @@ ${tryStringify2(args.response)}`
|
|
|
3368
3606
|
...args.telemetry?.prompt?.version !== void 0 ? { promptVersion: args.telemetry.prompt.version } : {},
|
|
3369
3607
|
...args.telemetry?.prompt?.type ? { promptType: args.telemetry.prompt.type } : {},
|
|
3370
3608
|
hasOutputSchema: Boolean(args.outputSchema),
|
|
3371
|
-
hasInterrupt: Boolean(
|
|
3609
|
+
hasInterrupt: Boolean(interruptConfig),
|
|
3610
|
+
interruptContractCount: Object.keys(interruptConfig?.contracts ?? {}).length,
|
|
3372
3611
|
hasStructured: Boolean(args.structured),
|
|
3373
3612
|
hasTools: Boolean(args.tools?.length)
|
|
3374
3613
|
},
|
|
@@ -3378,7 +3617,7 @@ ${tryStringify2(args.response)}`
|
|
|
3378
3617
|
}
|
|
3379
3618
|
},
|
|
3380
3619
|
async (traceSpan) => {
|
|
3381
|
-
if (args.tools?.length) {
|
|
3620
|
+
if ((args.tools?.length || args.interrupts) && !existingCheckpoint) {
|
|
3382
3621
|
return runReasonToolLoop({
|
|
3383
3622
|
useReasonArgs: args,
|
|
3384
3623
|
allowValidatedToolOutput,
|
|
@@ -3645,6 +3884,24 @@ ${tryStringify2(args.response)}`
|
|
|
3645
3884
|
return useReason(args);
|
|
3646
3885
|
}
|
|
3647
3886
|
function useInterrupt(args) {
|
|
3887
|
+
if ("contract" in args) {
|
|
3888
|
+
const contract = defineInterruptContract(args.contract);
|
|
3889
|
+
return awaitInterruptInternal({
|
|
3890
|
+
request: {
|
|
3891
|
+
kind: "custom",
|
|
3892
|
+
request: parseWithSchema(
|
|
3893
|
+
contract.requestSchema,
|
|
3894
|
+
args.request,
|
|
3895
|
+
"useInterrupt contract request"
|
|
3896
|
+
),
|
|
3897
|
+
schemaId: contract.schemaId,
|
|
3898
|
+
schemaVersion: contract.schemaVersion,
|
|
3899
|
+
...args.id ? { id: args.id } : {},
|
|
3900
|
+
...args.meta ? { meta: args.meta } : {}
|
|
3901
|
+
},
|
|
3902
|
+
responseSchema: contract.responseSchema
|
|
3903
|
+
});
|
|
3904
|
+
}
|
|
3648
3905
|
return awaitInterruptInternal(args);
|
|
3649
3906
|
}
|
|
3650
3907
|
function useAbortSignal() {
|
|
@@ -4950,7 +5207,10 @@ async function orchestrateGraphStream({
|
|
|
4950
5207
|
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4951
5208
|
resumeOutcome: "failed",
|
|
4952
5209
|
resumeError: (0, import_errors4.serializeFailure)(resumeError).message,
|
|
4953
|
-
failure: (0, import_errors4.serializeFailure)(resumeError)
|
|
5210
|
+
failure: (0, import_errors4.serializeFailure)(resumeError),
|
|
5211
|
+
...typeof config.telemetryInterruptContract === "string" ? { contract: config.telemetryInterruptContract } : {},
|
|
5212
|
+
...typeof config.telemetryInterruptSchemaId === "string" ? { schemaId: config.telemetryInterruptSchemaId } : {},
|
|
5213
|
+
...typeof config.telemetryInterruptSchemaVersion === "string" ? { schemaVersion: config.telemetryInterruptSchemaVersion } : {}
|
|
4954
5214
|
},
|
|
4955
5215
|
flush: true
|
|
4956
5216
|
});
|
|
@@ -5056,6 +5316,7 @@ async function orchestrateGraphStream({
|
|
|
5056
5316
|
const optionsList = Array.isArray(input.options) ? input.options : [];
|
|
5057
5317
|
const kind = input.kind || (input.multiple ? "multi-choice" : "choice");
|
|
5058
5318
|
const isText = kind === "text";
|
|
5319
|
+
const isCustom = kind === "custom";
|
|
5059
5320
|
const record = {
|
|
5060
5321
|
token,
|
|
5061
5322
|
ready: false,
|
|
@@ -5066,7 +5327,17 @@ async function orchestrateGraphStream({
|
|
|
5066
5327
|
workflow: payload.workflow || currentState.currentWorkflow,
|
|
5067
5328
|
node: payload.node || "",
|
|
5068
5329
|
state: { ...currentState, awaitingHumanInput: true },
|
|
5069
|
-
schema:
|
|
5330
|
+
schema: isCustom ? {
|
|
5331
|
+
kind: "custom",
|
|
5332
|
+
multiple: false,
|
|
5333
|
+
...input.question ? { question: input.question } : {},
|
|
5334
|
+
...typeof input.id === "string" && input.id.length > 0 ? { id: input.id } : {},
|
|
5335
|
+
...typeof input.contract === "string" && input.contract.length > 0 ? { contract: input.contract } : {},
|
|
5336
|
+
...Object.hasOwn(input, "request") ? { request: input.request } : {},
|
|
5337
|
+
...typeof input.schemaId === "string" && input.schemaId.length > 0 ? { schemaId: input.schemaId } : {},
|
|
5338
|
+
...typeof input.schemaVersion === "string" && input.schemaVersion.length > 0 ? { schemaVersion: input.schemaVersion } : {},
|
|
5339
|
+
...input.meta && typeof input.meta === "object" ? { meta: input.meta } : {}
|
|
5340
|
+
} : isText ? {
|
|
5070
5341
|
kind,
|
|
5071
5342
|
multiple: Boolean(input.multiple),
|
|
5072
5343
|
...input.question ? { question: input.question } : {},
|
|
@@ -5121,6 +5392,8 @@ async function orchestrateGraphStream({
|
|
|
5121
5392
|
kind: record.schema.kind,
|
|
5122
5393
|
multiple: record.schema.multiple,
|
|
5123
5394
|
question: record.schema.question,
|
|
5395
|
+
...record.schema.contract ? { contract: record.schema.contract } : {},
|
|
5396
|
+
...Object.hasOwn(record.schema, "request") ? { request: record.schema.request } : {},
|
|
5124
5397
|
...typeof record.schema.id === "string" && record.schema.id.length > 0 ? { id: record.schema.id } : {},
|
|
5125
5398
|
...typeof record.schema.schemaId === "string" && record.schema.schemaId.length > 0 ? { schemaId: record.schema.schemaId } : {},
|
|
5126
5399
|
...typeof record.schema.schemaVersion === "string" && record.schema.schemaVersion.length > 0 ? { schemaVersion: record.schema.schemaVersion } : {},
|
|
@@ -5132,6 +5405,11 @@ async function orchestrateGraphStream({
|
|
|
5132
5405
|
}))
|
|
5133
5406
|
}
|
|
5134
5407
|
});
|
|
5408
|
+
const captureRequest = shouldCaptureTelemetryContent(
|
|
5409
|
+
telemetryConfig.captureContent,
|
|
5410
|
+
"output"
|
|
5411
|
+
);
|
|
5412
|
+
const structuredRequest = isRecord3(record.schema.request) ? record.schema.request : void 0;
|
|
5135
5413
|
emitTelemetryEvent({
|
|
5136
5414
|
config,
|
|
5137
5415
|
type: "interrupt.created",
|
|
@@ -5147,11 +5425,11 @@ async function orchestrateGraphStream({
|
|
|
5147
5425
|
interactionMode: record.schema.kind === "text" ? "freeform" : record.options.length > 0 ? "static-options" : record.schema.schemaId ? "dynamic-picker" : "unknown",
|
|
5148
5426
|
...record.schema.schemaId ? { schemaId: record.schema.schemaId } : {},
|
|
5149
5427
|
...record.schema.schemaVersion ? { schemaVersion: record.schema.schemaVersion } : {},
|
|
5150
|
-
...
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5428
|
+
...record.schema.contract ? { contract: record.schema.contract } : {},
|
|
5429
|
+
requestCaptured: Boolean(captureRequest && structuredRequest),
|
|
5430
|
+
...captureRequest && structuredRequest ? { request: structuredRequest } : {},
|
|
5431
|
+
...typeof record.schema.question === "string" && captureRequest ? { question: record.schema.question } : {},
|
|
5432
|
+
...captureRequest ? {
|
|
5155
5433
|
options: record.options.map((option) => ({
|
|
5156
5434
|
id: option.id,
|
|
5157
5435
|
label: option.label,
|
|
@@ -5960,6 +6238,7 @@ function parseResumeMeta(msg) {
|
|
|
5960
6238
|
const token = typeof raw.token === "string" ? raw.token : "";
|
|
5961
6239
|
const requestId = typeof raw.requestId === "string" ? raw.requestId : "";
|
|
5962
6240
|
const cancel = raw.cancel === true;
|
|
6241
|
+
const hasValue = Object.hasOwn(raw, "value");
|
|
5963
6242
|
let selected = [];
|
|
5964
6243
|
const rawSelected = raw.selected;
|
|
5965
6244
|
if (typeof rawSelected === "string") selected = [rawSelected];
|
|
@@ -5969,7 +6248,13 @@ function parseResumeMeta(msg) {
|
|
|
5969
6248
|
else if (Array.isArray(raw.choices))
|
|
5970
6249
|
selected = raw.choices.map((c) => isRecord4(c) ? c.id : void 0).filter((id) => typeof id === "string");
|
|
5971
6250
|
if (!token || !requestId) return null;
|
|
5972
|
-
return {
|
|
6251
|
+
return {
|
|
6252
|
+
token,
|
|
6253
|
+
requestId,
|
|
6254
|
+
selected,
|
|
6255
|
+
cancel,
|
|
6256
|
+
...hasValue ? { value: raw.value, hasValue: true } : {}
|
|
6257
|
+
};
|
|
5973
6258
|
}
|
|
5974
6259
|
async function tryPrepareResumeStream({
|
|
5975
6260
|
limits,
|
|
@@ -6056,7 +6341,8 @@ async function tryPrepareResumeStream({
|
|
|
6056
6341
|
},
|
|
6057
6342
|
payload: {
|
|
6058
6343
|
interruptId: pending.requestId,
|
|
6059
|
-
reason: "cancelled_by_client"
|
|
6344
|
+
reason: "cancelled_by_client",
|
|
6345
|
+
...pending.schema.contract ? { contract: pending.schema.contract } : {}
|
|
6060
6346
|
},
|
|
6061
6347
|
flush: true
|
|
6062
6348
|
});
|
|
@@ -6136,7 +6422,7 @@ async function tryPrepareResumeStream({
|
|
|
6136
6422
|
resumeUpdate.runtime = resumeStatePatch;
|
|
6137
6423
|
}
|
|
6138
6424
|
const hasResumeUpdate = !isLimitPause && Object.keys(resumeUpdate).length > 0;
|
|
6139
|
-
const resumeValue = isLimitPause ? void 0 : meta.selected?.length && pending.schema.kind === "multi-choice" ? meta.selected.map((x) => String(x)) : meta.selected?.length ? String(meta.selected[0]) : void 0;
|
|
6425
|
+
const resumeValue = isLimitPause ? void 0 : meta.hasValue && pending.schema.kind === "custom" ? meta.value : meta.selected?.length && pending.schema.kind === "multi-choice" ? meta.selected.map((x) => String(x)) : meta.selected?.length ? String(meta.selected[0]) : void 0;
|
|
6140
6426
|
const resumeCheckpointId = typeof pending.graphCheckpointId === "string" && pending.graphCheckpointId.length > 0 ? pending.graphCheckpointId : void 0;
|
|
6141
6427
|
const resumedGraph = await (0, import_runtime2.createExecutionGraph)(wf, {
|
|
6142
6428
|
...telemetryConfig,
|
|
@@ -6146,7 +6432,7 @@ async function tryPrepareResumeStream({
|
|
|
6146
6432
|
...hasResumeUpdate ? { resumeUpdate } : {}
|
|
6147
6433
|
});
|
|
6148
6434
|
await store.delete(pending.token);
|
|
6149
|
-
const response = responseFromSelection(meta.selected);
|
|
6435
|
+
const response = meta.hasValue ? JSON.stringify(meta.value) : responseFromSelection(meta.selected);
|
|
6150
6436
|
const telemetry = isRecord4(telemetryConfig.telemetry) ? telemetryConfig.telemetry : {};
|
|
6151
6437
|
const responseCaptured = Boolean(
|
|
6152
6438
|
response && shouldCaptureTelemetryContent(telemetry.captureContent, "input")
|
|
@@ -6165,6 +6451,10 @@ async function tryPrepareResumeStream({
|
|
|
6165
6451
|
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6166
6452
|
resumeOutcome: "resumed",
|
|
6167
6453
|
responseCaptured,
|
|
6454
|
+
...pending.schema.contract ? { contract: pending.schema.contract } : {},
|
|
6455
|
+
...pending.schema.schemaId ? { schemaId: pending.schema.schemaId } : {},
|
|
6456
|
+
...pending.schema.schemaVersion ? { schemaVersion: pending.schema.schemaVersion } : {},
|
|
6457
|
+
...responseCaptured && meta.hasValue ? { responseValue: meta.value } : {},
|
|
6168
6458
|
...responseCaptured && response ? { response } : {}
|
|
6169
6459
|
},
|
|
6170
6460
|
flush: true
|
|
@@ -6186,6 +6476,11 @@ async function tryPrepareResumeStream({
|
|
|
6186
6476
|
resume: true,
|
|
6187
6477
|
telemetryInterruptId: pending.requestId,
|
|
6188
6478
|
telemetryInterruptNodeId: pending.node,
|
|
6479
|
+
...pending.schema.contract ? { telemetryInterruptContract: pending.schema.contract } : {},
|
|
6480
|
+
...pending.schema.schemaId ? { telemetryInterruptSchemaId: pending.schema.schemaId } : {},
|
|
6481
|
+
...pending.schema.schemaVersion ? {
|
|
6482
|
+
telemetryInterruptSchemaVersion: pending.schema.schemaVersion
|
|
6483
|
+
} : {},
|
|
6189
6484
|
...resumeValue !== void 0 ? { resumeValue } : {},
|
|
6190
6485
|
...resumeCheckpointId ? { resumeCheckpointId } : {},
|
|
6191
6486
|
...hasResumeUpdate ? { resumeUpdate } : {}
|
|
@@ -6219,6 +6514,7 @@ var responseSchema = import_zod3.z.discriminatedUnion("type", [
|
|
|
6219
6514
|
type: import_zod3.z.literal("select"),
|
|
6220
6515
|
ids: import_zod3.z.array(import_zod3.z.string().min(1)).min(1)
|
|
6221
6516
|
}).strict(),
|
|
6517
|
+
import_zod3.z.object({ type: import_zod3.z.literal("value"), value: import_zod3.z.unknown() }).strict(),
|
|
6222
6518
|
import_zod3.z.object({ type: import_zod3.z.literal("cancel") }).strict()
|
|
6223
6519
|
]);
|
|
6224
6520
|
function resultFromOutcome(outcome, runId, sessionId) {
|
|
@@ -6359,7 +6655,13 @@ async function resumeWorkflow(services, args) {
|
|
|
6359
6655
|
"Resume handle does not match the root execution and workflow version."
|
|
6360
6656
|
);
|
|
6361
6657
|
if (response.type === "cancel") return;
|
|
6362
|
-
if (response.type === "
|
|
6658
|
+
if (response.type === "value") {
|
|
6659
|
+
if (pending.schema.kind !== "custom")
|
|
6660
|
+
throw new ExecutionRequestError(
|
|
6661
|
+
"INVALID_RESPONSE",
|
|
6662
|
+
"Structured values are only valid for custom interrupts."
|
|
6663
|
+
);
|
|
6664
|
+
} else if (response.type === "text") {
|
|
6363
6665
|
if (pending.schema.kind !== "text")
|
|
6364
6666
|
throw new ExecutionRequestError(
|
|
6365
6667
|
"INVALID_RESPONSE",
|
|
@@ -6389,6 +6691,7 @@ async function resumeWorkflow(services, args) {
|
|
|
6389
6691
|
token: handle.token,
|
|
6390
6692
|
requestId: handle.requestId,
|
|
6391
6693
|
selected: response.type === "text" ? [response.text] : response.type === "select" ? response.ids : [],
|
|
6694
|
+
...response.type === "value" ? { value: response.value, hasValue: true } : {},
|
|
6392
6695
|
cancel: response.type === "cancel"
|
|
6393
6696
|
},
|
|
6394
6697
|
sessionId: handle.sessionId,
|