@kortyx/agent 0.14.2 → 0.16.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 +40 -0
- package/LICENSE +51 -43
- package/dist/index.d.ts +22 -4
- package/dist/index.js +3192 -35
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
1
2
|
var __defProp = Object.defineProperty;
|
|
2
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
8
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
9
|
+
};
|
|
5
10
|
var __export = (target, all) => {
|
|
6
11
|
for (var name in all)
|
|
7
12
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -12,9 +17,2554 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
12
17
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
18
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
19
|
}
|
|
15
|
-
return to;
|
|
16
|
-
};
|
|
17
|
-
var
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
+
mod
|
|
29
|
+
));
|
|
30
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
31
|
+
|
|
32
|
+
// ../hooks/dist/index.js
|
|
33
|
+
var require_dist = __commonJS({
|
|
34
|
+
"../hooks/dist/index.js"(exports2, module2) {
|
|
35
|
+
var __defProp2 = Object.defineProperty;
|
|
36
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
37
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
38
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
39
|
+
var __export2 = (target, all) => {
|
|
40
|
+
for (var name in all)
|
|
41
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
42
|
+
};
|
|
43
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
44
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
45
|
+
for (let key of __getOwnPropNames2(from))
|
|
46
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
47
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
48
|
+
}
|
|
49
|
+
return to;
|
|
50
|
+
};
|
|
51
|
+
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
52
|
+
var index_exports2 = {};
|
|
53
|
+
__export2(index_exports2, {
|
|
54
|
+
WorkflowCallError: () => WorkflowCallError,
|
|
55
|
+
createWorkflowHooks: () => createWorkflowHooks,
|
|
56
|
+
emitWorkflowCall: () => emitWorkflowCall2,
|
|
57
|
+
runWithHookContext: () => runWithHookContext,
|
|
58
|
+
useInterrupt: () => useInterrupt,
|
|
59
|
+
useNodeState: () => useNodeState,
|
|
60
|
+
useReason: () => useReason2,
|
|
61
|
+
useRuntimeContext: () => useRuntimeContext,
|
|
62
|
+
useStructuredData: () => useStructuredData,
|
|
63
|
+
useWorkflow: () => useWorkflow,
|
|
64
|
+
useWorkflowState: () => useWorkflowState,
|
|
65
|
+
workflowCallFingerprint: () => workflowCallFingerprint
|
|
66
|
+
});
|
|
67
|
+
module2.exports = __toCommonJS2(index_exports2);
|
|
68
|
+
var import_node_async_hooks = require("async_hooks");
|
|
69
|
+
var storage = new import_node_async_hooks.AsyncLocalStorage();
|
|
70
|
+
var isRecord5 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
71
|
+
var normalizeNodeState = (value) => {
|
|
72
|
+
if (Array.isArray(value)) {
|
|
73
|
+
return { byIndex: [...value], byKey: {} };
|
|
74
|
+
}
|
|
75
|
+
if (isRecord5(value)) {
|
|
76
|
+
const byIndex = Array.isArray(value.byIndex) ? [...value.byIndex] : [];
|
|
77
|
+
const byKey = isRecord5(value.byKey) ? { ...value.byKey } : {};
|
|
78
|
+
return { byIndex, byKey };
|
|
79
|
+
}
|
|
80
|
+
return { byIndex: [], byKey: {} };
|
|
81
|
+
};
|
|
82
|
+
var cloneWorkflowState = (value) => isRecord5(value) ? { ...value } : {};
|
|
83
|
+
var cloneTokenUsage = (value) => {
|
|
84
|
+
if (!isRecord5(value)) return void 0;
|
|
85
|
+
const input = typeof value.input === "number" ? value.input : void 0;
|
|
86
|
+
const output = typeof value.output === "number" ? value.output : void 0;
|
|
87
|
+
const total = typeof value.total === "number" ? value.total : void 0;
|
|
88
|
+
if (input === void 0 && output === void 0 && total === void 0) {
|
|
89
|
+
return void 0;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
input: input ?? 0,
|
|
93
|
+
output: output ?? 0,
|
|
94
|
+
total: total ?? 0
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
var getHookRuntimeState = (state) => {
|
|
98
|
+
const runtime = state.runtime ?? {};
|
|
99
|
+
const internal = isRecord5(runtime.__kortyx) ? runtime.__kortyx : void 0;
|
|
100
|
+
const result = {};
|
|
101
|
+
if (isRecord5(internal?.nodeState)) result.nodeState = internal?.nodeState;
|
|
102
|
+
if (isRecord5(internal?.workflowState)) {
|
|
103
|
+
result.workflowState = internal?.workflowState;
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
};
|
|
107
|
+
var createInternalContext = (ctx) => {
|
|
108
|
+
const hookRuntimeState = getHookRuntimeState(ctx.state);
|
|
109
|
+
const nodeId = ctx.node.graph.node;
|
|
110
|
+
const storedNodeState = isRecord5(hookRuntimeState.nodeState) ? hookRuntimeState.nodeState : void 0;
|
|
111
|
+
const storedNodeId = typeof storedNodeState?.nodeId === "string" ? String(storedNodeState.nodeId) : "";
|
|
112
|
+
const storedState = storedNodeState?.state;
|
|
113
|
+
const currentNodeState = storedNodeId && storedNodeId === nodeId ? normalizeNodeState(storedState) : { byIndex: [], byKey: {} };
|
|
114
|
+
return {
|
|
115
|
+
...ctx,
|
|
116
|
+
workflowCallActive: false,
|
|
117
|
+
workflowCallIds: /* @__PURE__ */ new Set(),
|
|
118
|
+
nodeStateIndex: 0,
|
|
119
|
+
reasonCallIndex: 0,
|
|
120
|
+
currentNodeState,
|
|
121
|
+
workflowState: cloneWorkflowState(hookRuntimeState.workflowState),
|
|
122
|
+
tokenUsage: cloneTokenUsage(ctx.state.runtime?.tokenUsage),
|
|
123
|
+
stateDirty: false,
|
|
124
|
+
runtimeDirty: false
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
var buildRuntimeStateUpdates = (ctx) => {
|
|
128
|
+
const updates = {};
|
|
129
|
+
if (ctx.stateDirty) {
|
|
130
|
+
updates.__kortyx = {
|
|
131
|
+
nodeState: {
|
|
132
|
+
nodeId: ctx.node.graph.node,
|
|
133
|
+
state: ctx.currentNodeState
|
|
134
|
+
},
|
|
135
|
+
workflowState: ctx.workflowState
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (ctx.runtimeDirty && ctx.tokenUsage) {
|
|
139
|
+
updates.tokenUsage = ctx.tokenUsage;
|
|
140
|
+
}
|
|
141
|
+
return Object.keys(updates).length > 0 ? updates : null;
|
|
142
|
+
};
|
|
143
|
+
var cleanupCompletedReasonCheckpoints = (ctx) => {
|
|
144
|
+
let changed = false;
|
|
145
|
+
for (const [key, value] of Object.entries(ctx.currentNodeState.byKey)) {
|
|
146
|
+
if (!key.startsWith("__useReason:")) continue;
|
|
147
|
+
if (!isRecord5(value) || value.status !== "completed") continue;
|
|
148
|
+
delete ctx.currentNodeState.byKey[key];
|
|
149
|
+
changed = true;
|
|
150
|
+
}
|
|
151
|
+
if (changed) ctx.stateDirty = true;
|
|
152
|
+
};
|
|
153
|
+
var toTokenUsageDelta = (usage) => {
|
|
154
|
+
if (!usage) return void 0;
|
|
155
|
+
const input = usage.input ?? 0;
|
|
156
|
+
const output = usage.output ?? 0;
|
|
157
|
+
const total = usage.total ?? input + output + (usage.reasoning ?? 0);
|
|
158
|
+
if (input === 0 && output === 0 && total === 0) {
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
input,
|
|
163
|
+
output,
|
|
164
|
+
total
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
function accumulateTokenUsage(usage) {
|
|
168
|
+
const delta = toTokenUsageDelta(usage);
|
|
169
|
+
if (!delta) return;
|
|
170
|
+
const ctx = getHookContext();
|
|
171
|
+
const current = ctx.tokenUsage ?? {
|
|
172
|
+
input: 0,
|
|
173
|
+
output: 0,
|
|
174
|
+
total: 0
|
|
175
|
+
};
|
|
176
|
+
ctx.tokenUsage = {
|
|
177
|
+
input: current.input + delta.input,
|
|
178
|
+
output: current.output + delta.output,
|
|
179
|
+
total: current.total + delta.total
|
|
180
|
+
};
|
|
181
|
+
ctx.runtimeDirty = true;
|
|
182
|
+
}
|
|
183
|
+
function getReasonTraceAdapter() {
|
|
184
|
+
return getHookContext().reasonTrace;
|
|
185
|
+
}
|
|
186
|
+
async function runWithHookContext(ctx, fn) {
|
|
187
|
+
const internal = createInternalContext(ctx);
|
|
188
|
+
try {
|
|
189
|
+
const result = await storage.run(internal, fn);
|
|
190
|
+
cleanupCompletedReasonCheckpoints(internal);
|
|
191
|
+
for (const key of Object.keys(internal.currentNodeState.byKey)) {
|
|
192
|
+
if (key.startsWith("__useWorkflow:") || key === "__useWorkflowActivation") {
|
|
193
|
+
internal.currentNodeState.byKey[key] = null;
|
|
194
|
+
internal.stateDirty = true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return { result, runtimeUpdates: buildRuntimeStateUpdates(internal) };
|
|
198
|
+
} catch (err) {
|
|
199
|
+
const runtimeUpdates = buildRuntimeStateUpdates(internal);
|
|
200
|
+
if (runtimeUpdates) {
|
|
201
|
+
const currentRuntime = isRecord5(internal.state.runtime) ? internal.state.runtime : {};
|
|
202
|
+
internal.state.runtime = {
|
|
203
|
+
...currentRuntime,
|
|
204
|
+
...runtimeUpdates
|
|
205
|
+
};
|
|
206
|
+
if (err && typeof err === "object") {
|
|
207
|
+
err.__kortyxHookStatePatch = runtimeUpdates;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
throw err;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function getHookContext() {
|
|
214
|
+
const ctx = storage.getStore();
|
|
215
|
+
if (!ctx) {
|
|
216
|
+
throw new Error("Hooks can only be used while a node is executing.");
|
|
217
|
+
}
|
|
218
|
+
return ctx;
|
|
219
|
+
}
|
|
220
|
+
var resolveReasonCheckpointKey = (args) => typeof args.id === "string" && args.id.length > 0 ? `__useReason:${args.id}` : `__useReason:auto:${args.autoIndex}`;
|
|
221
|
+
var readReasonCheckpoint = (raw) => {
|
|
222
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
223
|
+
const value = raw;
|
|
224
|
+
if (value.status !== "awaiting_interrupt") return void 0;
|
|
225
|
+
if (typeof value.firstText !== "string") return void 0;
|
|
226
|
+
if (!value.request || typeof value.request !== "object") return void 0;
|
|
227
|
+
return {
|
|
228
|
+
status: "awaiting_interrupt",
|
|
229
|
+
request: value.request,
|
|
230
|
+
firstText: value.firstText,
|
|
231
|
+
...Object.hasOwn(value, "firstRaw") ? { firstRaw: value.firstRaw } : {},
|
|
232
|
+
...Object.hasOwn(value, "firstUsage") ? { firstUsage: value.firstUsage } : {},
|
|
233
|
+
...Object.hasOwn(value, "firstFinishReason") ? { firstFinishReason: value.firstFinishReason } : {},
|
|
234
|
+
...Object.hasOwn(value, "firstProviderMetadata") ? {
|
|
235
|
+
firstProviderMetadata: value.firstProviderMetadata
|
|
236
|
+
} : {},
|
|
237
|
+
...Object.hasOwn(value, "firstWarnings") ? { firstWarnings: value.firstWarnings } : {},
|
|
238
|
+
...Object.hasOwn(value, "firstOutput") ? { firstOutput: value.firstOutput } : {}
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
var readReasonCompletedCheckpoint = (raw) => {
|
|
242
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
243
|
+
const value = raw;
|
|
244
|
+
if (value.status !== "completed") return void 0;
|
|
245
|
+
if (!value.result || typeof value.result !== "object") return void 0;
|
|
246
|
+
return {
|
|
247
|
+
status: "completed",
|
|
248
|
+
result: value.result
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
var resolveHookStatePatch = (args) => ({
|
|
252
|
+
__kortyx: {
|
|
253
|
+
nodeState: {
|
|
254
|
+
nodeId: args.nodeId,
|
|
255
|
+
state: args.currentNodeState
|
|
256
|
+
},
|
|
257
|
+
workflowState: args.workflowState
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
var parseWithSchema = (schema, value, label) => {
|
|
261
|
+
if (!schema) return value;
|
|
262
|
+
const parsed = schema.safeParse(value);
|
|
263
|
+
if (parsed.success) return parsed.data;
|
|
264
|
+
const parsedError = "error" in parsed && parsed.error ? parsed.error : void 0;
|
|
265
|
+
const reason = typeof parsedError?.message === "string" && parsedError.message.length > 0 ? parsedError.message : "invalid payload";
|
|
266
|
+
throw new Error(`${label} validation failed: ${reason}`);
|
|
267
|
+
};
|
|
268
|
+
var awaitInterruptInternal = (args) => {
|
|
269
|
+
const ctx = getHookContext();
|
|
270
|
+
const request = parseWithSchema(
|
|
271
|
+
args.requestSchema,
|
|
272
|
+
args.request,
|
|
273
|
+
"useInterrupt request"
|
|
274
|
+
);
|
|
275
|
+
const userMeta = args.meta && typeof args.meta === "object" && !Array.isArray(args.meta) ? args.meta : {};
|
|
276
|
+
const resumeStatePatch = resolveHookStatePatch({
|
|
277
|
+
nodeId: ctx.node.graph.node,
|
|
278
|
+
currentNodeState: ctx.currentNodeState,
|
|
279
|
+
workflowState: ctx.workflowState
|
|
280
|
+
});
|
|
281
|
+
const enrichedRequest = {
|
|
282
|
+
...request,
|
|
283
|
+
...typeof args.schemaId === "string" && args.schemaId.length > 0 ? { schemaId: args.schemaId } : {},
|
|
284
|
+
...typeof args.schemaVersion === "string" && args.schemaVersion.length > 0 ? { schemaVersion: args.schemaVersion } : {},
|
|
285
|
+
...typeof args.id === "string" && args.id.length > 0 ? { id: args.id } : {},
|
|
286
|
+
meta: {
|
|
287
|
+
...userMeta,
|
|
288
|
+
__kortyxResumeStatePatch: {
|
|
289
|
+
...resumeStatePatch,
|
|
290
|
+
...ctx.tokenUsage ? { tokenUsage: ctx.tokenUsage } : {}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
const raw = ctx.node.awaitInterrupt(enrichedRequest);
|
|
295
|
+
const response = parseWithSchema(
|
|
296
|
+
args.responseSchema,
|
|
297
|
+
raw,
|
|
298
|
+
"useInterrupt response"
|
|
299
|
+
);
|
|
300
|
+
return Promise.resolve(response);
|
|
301
|
+
};
|
|
302
|
+
var import_node_crypto4 = require("crypto");
|
|
303
|
+
var parseStructuredPath = (path, source) => {
|
|
304
|
+
if (path.length === 0) {
|
|
305
|
+
throw new Error(`${source} path must be a non-empty dot-separated string.`);
|
|
306
|
+
}
|
|
307
|
+
const parts = path.split(".");
|
|
308
|
+
if (parts.some((part) => part.length === 0)) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`${source} path "${path}" must not contain empty segments.`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
return parts;
|
|
314
|
+
};
|
|
315
|
+
var assertStructuredPath = (path, source) => {
|
|
316
|
+
parseStructuredPath(path, source);
|
|
317
|
+
};
|
|
318
|
+
var createStructuredStreamId = () => {
|
|
319
|
+
try {
|
|
320
|
+
return (0, import_node_crypto4.randomUUID)();
|
|
321
|
+
} catch {
|
|
322
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
var emitStructuredData = (args) => {
|
|
326
|
+
const ctx = getHookContext();
|
|
327
|
+
const streamId = typeof args.streamId === "string" && args.streamId.length > 0 ? args.streamId : createStructuredStreamId();
|
|
328
|
+
const dataType = typeof args.dataType === "string" && args.dataType.length > 0 ? args.dataType : "generic";
|
|
329
|
+
const base = {
|
|
330
|
+
node: ctx.node.graph.node,
|
|
331
|
+
streamId,
|
|
332
|
+
dataType,
|
|
333
|
+
...typeof args.schemaId === "string" && args.schemaId.length > 0 ? { schemaId: args.schemaId } : {},
|
|
334
|
+
...typeof args.schemaVersion === "string" && args.schemaVersion.length > 0 ? { schemaVersion: args.schemaVersion } : {},
|
|
335
|
+
...typeof args.id === "string" && args.id.length > 0 ? { id: args.id } : {}
|
|
336
|
+
};
|
|
337
|
+
if (args.kind === "set") {
|
|
338
|
+
assertStructuredPath(args.path, "useStructuredData");
|
|
339
|
+
const value = parseWithSchema(
|
|
340
|
+
args.valueSchema,
|
|
341
|
+
args.value,
|
|
342
|
+
"useStructuredData value"
|
|
343
|
+
);
|
|
344
|
+
ctx.node.emit("structured_data", {
|
|
345
|
+
...base,
|
|
346
|
+
kind: "set",
|
|
347
|
+
path: args.path,
|
|
348
|
+
value
|
|
349
|
+
});
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (args.kind === "append") {
|
|
353
|
+
assertStructuredPath(args.path, "useStructuredData");
|
|
354
|
+
const items = args.items.map(
|
|
355
|
+
(item) => parseWithSchema(args.itemSchema, item, "useStructuredData append item")
|
|
356
|
+
);
|
|
357
|
+
ctx.node.emit("structured_data", {
|
|
358
|
+
...base,
|
|
359
|
+
kind: "append",
|
|
360
|
+
path: args.path,
|
|
361
|
+
items
|
|
362
|
+
});
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (args.kind === "text-delta") {
|
|
366
|
+
assertStructuredPath(args.path, "useStructuredData");
|
|
367
|
+
ctx.node.emit("structured_data", {
|
|
368
|
+
...base,
|
|
369
|
+
kind: "text-delta",
|
|
370
|
+
path: args.path,
|
|
371
|
+
delta: args.delta
|
|
372
|
+
});
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const validated = parseWithSchema(
|
|
376
|
+
args.dataSchema,
|
|
377
|
+
args.data,
|
|
378
|
+
"useStructuredData data"
|
|
379
|
+
);
|
|
380
|
+
ctx.node.emit("structured_data", {
|
|
381
|
+
...base,
|
|
382
|
+
kind: "final",
|
|
383
|
+
data: validated
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
var shouldEmitStructured = (cfg) => Boolean(cfg);
|
|
387
|
+
var shouldStreamStructured = (cfg) => Boolean(cfg) && (cfg?.stream ?? true);
|
|
388
|
+
var import_node_crypto32 = require("crypto");
|
|
389
|
+
var import_node_crypto22 = require("crypto");
|
|
390
|
+
var toError = (value) => value instanceof Error ? value : new Error(String(value));
|
|
391
|
+
var createRuntimeId = () => {
|
|
392
|
+
try {
|
|
393
|
+
return (0, import_node_crypto22.randomUUID)();
|
|
394
|
+
} catch {
|
|
395
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
var emitNodeEvent = (emitEvent, nodeId, event, payload) => {
|
|
399
|
+
if (!emitEvent) return;
|
|
400
|
+
emitEvent(event, {
|
|
401
|
+
...payload,
|
|
402
|
+
...typeof nodeId === "string" && nodeId.length > 0 ? { node: nodeId } : {}
|
|
403
|
+
});
|
|
404
|
+
};
|
|
405
|
+
async function runReasonEngine(args) {
|
|
406
|
+
const stream = args.stream ?? args.model.options?.streaming ?? true;
|
|
407
|
+
const emit = args.emit ?? true;
|
|
408
|
+
const temperature = args.temperature ?? args.model.options?.temperature ?? args.defaultTemperature;
|
|
409
|
+
const maxOutputTokens = args.maxOutputTokens ?? args.model.options?.maxOutputTokens;
|
|
410
|
+
const stopSequences = args.stopSequences ?? args.model.options?.stopSequences;
|
|
411
|
+
const abortSignal = args.abortSignal ?? args.model.options?.abortSignal;
|
|
412
|
+
const reasoning = args.reasoning ?? args.model.options?.reasoning;
|
|
413
|
+
const responseFormat = args.responseFormat ?? args.model.options?.responseFormat;
|
|
414
|
+
const providerOptions = args.providerOptions ?? args.model.options?.providerOptions;
|
|
415
|
+
const tools = args.tools ?? args.model.options?.tools;
|
|
416
|
+
const model = args.model.provider.getModel(args.model.modelId, {
|
|
417
|
+
...temperature !== void 0 ? { temperature } : {},
|
|
418
|
+
streaming: stream,
|
|
419
|
+
...maxOutputTokens !== void 0 ? { maxOutputTokens } : {},
|
|
420
|
+
...stopSequences !== void 0 ? { stopSequences } : {},
|
|
421
|
+
...abortSignal !== void 0 ? { abortSignal } : {},
|
|
422
|
+
...reasoning !== void 0 ? { reasoning } : {},
|
|
423
|
+
...responseFormat !== void 0 ? { responseFormat } : {},
|
|
424
|
+
...tools !== void 0 ? { tools } : {},
|
|
425
|
+
...providerOptions !== void 0 ? { providerOptions } : {}
|
|
426
|
+
});
|
|
427
|
+
const messages = args.messages !== void 0 ? args.messages : [
|
|
428
|
+
...typeof args.system === "string" && args.system.length > 0 ? [{ role: "system", content: args.system }] : [],
|
|
429
|
+
{ role: "user", content: String(args.input ?? "") }
|
|
430
|
+
];
|
|
431
|
+
const opId = typeof args.opId === "string" && args.opId.length > 0 ? args.opId : createRuntimeId();
|
|
432
|
+
const segmentId = typeof args.segmentId === "string" && args.segmentId.length > 0 ? args.segmentId : createRuntimeId();
|
|
433
|
+
const commonMeta = {
|
|
434
|
+
...typeof args.id === "string" && args.id.length > 0 ? { id: args.id } : {},
|
|
435
|
+
opId,
|
|
436
|
+
segmentId
|
|
437
|
+
};
|
|
438
|
+
const traceSpan = args.reasonTrace?.startSpan({
|
|
439
|
+
name: "runReasonEngine",
|
|
440
|
+
attributes: {
|
|
441
|
+
...commonMeta,
|
|
442
|
+
providerId: args.model.provider.id,
|
|
443
|
+
modelId: args.model.modelId,
|
|
444
|
+
stream,
|
|
445
|
+
emit,
|
|
446
|
+
...args.telemetry?.operation ? { operation: args.telemetry.operation } : {},
|
|
447
|
+
...args.telemetry?.prompt?.name ? { promptName: args.telemetry.prompt.name } : {},
|
|
448
|
+
...args.telemetry?.prompt?.version !== void 0 ? { promptVersion: args.telemetry.prompt.version } : {},
|
|
449
|
+
...args.telemetry?.prompt?.type ? { promptType: args.telemetry.prompt.type } : {},
|
|
450
|
+
...typeof args.nodeId === "string" && args.nodeId.length > 0 ? { nodeId: args.nodeId } : {}
|
|
451
|
+
},
|
|
452
|
+
telemetry: {
|
|
453
|
+
...args.telemetry ?? {},
|
|
454
|
+
input: args.telemetry?.input ?? messages
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
const modelRequestStartedAt = Date.now();
|
|
458
|
+
let firstTextTokenAt;
|
|
459
|
+
let lastTextTokenAt;
|
|
460
|
+
const generationTimingAttributes = () => firstTextTokenAt === void 0 || lastTextTokenAt === void 0 ? {} : {
|
|
461
|
+
ttftMs: Math.max(0, firstTextTokenAt - modelRequestStartedAt),
|
|
462
|
+
streamDurationMs: Math.max(0, lastTextTokenAt - firstTextTokenAt),
|
|
463
|
+
timeToLastTokenMs: Math.max(
|
|
464
|
+
0,
|
|
465
|
+
lastTextTokenAt - modelRequestStartedAt
|
|
466
|
+
)
|
|
467
|
+
};
|
|
468
|
+
const endTrace = (span, result) => {
|
|
469
|
+
span?.end?.({
|
|
470
|
+
...result.usage !== void 0 ? { usage: result.usage } : {},
|
|
471
|
+
...result.finishReason !== void 0 ? { finishReason: result.finishReason } : {},
|
|
472
|
+
...result.providerMetadata !== void 0 ? { providerMetadata: result.providerMetadata } : {},
|
|
473
|
+
...result.warnings !== void 0 ? { warnings: result.warnings } : {},
|
|
474
|
+
attributes: {
|
|
475
|
+
textLength: result.text.length,
|
|
476
|
+
...generationTimingAttributes()
|
|
477
|
+
},
|
|
478
|
+
telemetry: {
|
|
479
|
+
...args.telemetry ?? {},
|
|
480
|
+
output: args.telemetry?.output ?? result.text
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
};
|
|
484
|
+
const failTrace = (span, error) => {
|
|
485
|
+
span?.fail?.(error, {
|
|
486
|
+
attributes: {
|
|
487
|
+
providerId: args.model.provider.id,
|
|
488
|
+
modelId: args.model.modelId
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
};
|
|
492
|
+
try {
|
|
493
|
+
if (stream) {
|
|
494
|
+
let final = "";
|
|
495
|
+
let raw;
|
|
496
|
+
let usage;
|
|
497
|
+
let finishReason;
|
|
498
|
+
let toolCalls;
|
|
499
|
+
let providerMetadata;
|
|
500
|
+
let warnings;
|
|
501
|
+
if (emit) {
|
|
502
|
+
emitNodeEvent(args.emitEvent, args.nodeId, "text-start", {
|
|
503
|
+
...commonMeta
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
const response2 = await model.stream(messages);
|
|
507
|
+
for await (const chunk of response2) {
|
|
508
|
+
switch (chunk.type) {
|
|
509
|
+
case "text-delta": {
|
|
510
|
+
if (chunk.delta.length === 0) break;
|
|
511
|
+
const tokenAt = Date.now();
|
|
512
|
+
firstTextTokenAt ??= tokenAt;
|
|
513
|
+
lastTextTokenAt = tokenAt;
|
|
514
|
+
final += chunk.delta;
|
|
515
|
+
args.onTextChunk?.(chunk.delta);
|
|
516
|
+
if (chunk.raw !== void 0) {
|
|
517
|
+
raw = chunk.raw;
|
|
518
|
+
}
|
|
519
|
+
if (chunk.providerMetadata !== void 0) {
|
|
520
|
+
providerMetadata = chunk.providerMetadata;
|
|
521
|
+
}
|
|
522
|
+
if (emit) {
|
|
523
|
+
emitNodeEvent(args.emitEvent, args.nodeId, "text-delta", {
|
|
524
|
+
...commonMeta,
|
|
525
|
+
delta: chunk.delta
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
case "finish": {
|
|
531
|
+
if (chunk.raw !== void 0) {
|
|
532
|
+
raw = chunk.raw;
|
|
533
|
+
}
|
|
534
|
+
if (chunk.usage !== void 0) {
|
|
535
|
+
usage = chunk.usage;
|
|
536
|
+
}
|
|
537
|
+
if (chunk.finishReason !== void 0) {
|
|
538
|
+
finishReason = chunk.finishReason;
|
|
539
|
+
}
|
|
540
|
+
if (chunk.toolCalls !== void 0) {
|
|
541
|
+
toolCalls = chunk.toolCalls;
|
|
542
|
+
}
|
|
543
|
+
if (chunk.providerMetadata !== void 0) {
|
|
544
|
+
providerMetadata = chunk.providerMetadata;
|
|
545
|
+
}
|
|
546
|
+
if (chunk.warnings !== void 0) {
|
|
547
|
+
warnings = chunk.warnings;
|
|
548
|
+
}
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
case "raw": {
|
|
552
|
+
raw = chunk.raw;
|
|
553
|
+
if (chunk.providerMetadata !== void 0) {
|
|
554
|
+
providerMetadata = chunk.providerMetadata;
|
|
555
|
+
}
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
case "error": {
|
|
559
|
+
if (chunk.raw !== void 0) {
|
|
560
|
+
raw = chunk.raw;
|
|
561
|
+
}
|
|
562
|
+
if (chunk.providerMetadata !== void 0) {
|
|
563
|
+
providerMetadata = chunk.providerMetadata;
|
|
564
|
+
}
|
|
565
|
+
if (chunk.warnings !== void 0) {
|
|
566
|
+
warnings = chunk.warnings;
|
|
567
|
+
}
|
|
568
|
+
throw toError(chunk.error);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (emit) {
|
|
573
|
+
emitNodeEvent(args.emitEvent, args.nodeId, "text-end", {
|
|
574
|
+
...commonMeta
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
const result2 = {
|
|
578
|
+
text: final,
|
|
579
|
+
...raw !== void 0 ? { raw } : {},
|
|
580
|
+
...usage !== void 0 ? { usage } : {},
|
|
581
|
+
...finishReason !== void 0 ? { finishReason } : {},
|
|
582
|
+
...toolCalls !== void 0 ? { toolCalls } : {},
|
|
583
|
+
...providerMetadata !== void 0 ? { providerMetadata } : {},
|
|
584
|
+
...warnings !== void 0 ? { warnings } : {}
|
|
585
|
+
};
|
|
586
|
+
endTrace(traceSpan, result2);
|
|
587
|
+
return result2;
|
|
588
|
+
}
|
|
589
|
+
const response = await model.invoke(messages);
|
|
590
|
+
if (emit) {
|
|
591
|
+
emitNodeEvent(args.emitEvent, args.nodeId, "text-start", {
|
|
592
|
+
...commonMeta
|
|
593
|
+
});
|
|
594
|
+
if (response.content) {
|
|
595
|
+
emitNodeEvent(args.emitEvent, args.nodeId, "text-delta", {
|
|
596
|
+
...commonMeta,
|
|
597
|
+
delta: response.content
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
emitNodeEvent(args.emitEvent, args.nodeId, "text-end", {
|
|
601
|
+
...commonMeta
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
const result = {
|
|
605
|
+
text: response.content,
|
|
606
|
+
...response.raw !== void 0 ? { raw: response.raw } : {},
|
|
607
|
+
...response.usage !== void 0 ? { usage: response.usage } : {},
|
|
608
|
+
...response.finishReason !== void 0 ? { finishReason: response.finishReason } : {},
|
|
609
|
+
...response.toolCalls !== void 0 ? { toolCalls: response.toolCalls } : {},
|
|
610
|
+
...response.providerMetadata !== void 0 ? { providerMetadata: response.providerMetadata } : {},
|
|
611
|
+
...response.warnings !== void 0 ? { warnings: response.warnings } : {}
|
|
612
|
+
};
|
|
613
|
+
endTrace(traceSpan, result);
|
|
614
|
+
return result;
|
|
615
|
+
} catch (error) {
|
|
616
|
+
failTrace(traceSpan, error);
|
|
617
|
+
throw error;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
var createRuntimeId2 = () => {
|
|
621
|
+
try {
|
|
622
|
+
return (0, import_node_crypto32.randomUUID)();
|
|
623
|
+
} catch {
|
|
624
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
async function reasonEngine(args, meta, inputOverride) {
|
|
628
|
+
const ctx = getHookContext();
|
|
629
|
+
return runReasonEngine({
|
|
630
|
+
model: args.model,
|
|
631
|
+
input: inputOverride ?? args.input,
|
|
632
|
+
system: args.system,
|
|
633
|
+
temperature: args.temperature,
|
|
634
|
+
maxOutputTokens: args.maxOutputTokens,
|
|
635
|
+
stopSequences: args.stopSequences,
|
|
636
|
+
abortSignal: args.abortSignal,
|
|
637
|
+
reasoning: args.reasoning,
|
|
638
|
+
responseFormat: args.responseFormat,
|
|
639
|
+
providerOptions: args.providerOptions,
|
|
640
|
+
tools: args.tools,
|
|
641
|
+
messages: args.messages,
|
|
642
|
+
defaultTemperature: ctx.node.config?.model?.temperature,
|
|
643
|
+
stream: args.stream,
|
|
644
|
+
emit: args.emit,
|
|
645
|
+
onTextChunk: args.onTextChunk,
|
|
646
|
+
telemetry: args.telemetry,
|
|
647
|
+
nodeId: ctx.node.graph.node,
|
|
648
|
+
emitEvent: ctx.node.emit,
|
|
649
|
+
reasonTrace: ctx.reasonTrace,
|
|
650
|
+
id: meta.id,
|
|
651
|
+
opId: meta.opId
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
var tryJsonParse = (value) => {
|
|
655
|
+
try {
|
|
656
|
+
return { ok: true, value: JSON.parse(value) };
|
|
657
|
+
} catch (error) {
|
|
658
|
+
return {
|
|
659
|
+
ok: false,
|
|
660
|
+
error: error instanceof Error ? error.message : String(error)
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
var extractJsonCodeBlock = (value) => {
|
|
665
|
+
const match = value.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
666
|
+
if (!match || typeof match[1] !== "string") return null;
|
|
667
|
+
const inner = match[1].trim();
|
|
668
|
+
return inner.length > 0 ? inner : null;
|
|
669
|
+
};
|
|
670
|
+
var tryStringify = (value) => {
|
|
671
|
+
if (typeof value === "string") return value;
|
|
672
|
+
try {
|
|
673
|
+
return JSON.stringify(value, null, 2);
|
|
674
|
+
} catch {
|
|
675
|
+
return String(value);
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
var isRecord22 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
679
|
+
var resolveOutputCandidate = (text) => {
|
|
680
|
+
const trimmed = text.trim();
|
|
681
|
+
if (!trimmed) return text;
|
|
682
|
+
const direct = tryJsonParse(trimmed);
|
|
683
|
+
if (direct.ok) return direct.value;
|
|
684
|
+
const block = extractJsonCodeBlock(trimmed);
|
|
685
|
+
if (block) {
|
|
686
|
+
const parsedBlock = tryJsonParse(block);
|
|
687
|
+
if (parsedBlock.ok) return parsedBlock.value;
|
|
688
|
+
}
|
|
689
|
+
return text;
|
|
690
|
+
};
|
|
691
|
+
var looksLikeStructuredJsonAttempt = (value) => {
|
|
692
|
+
const trimmed = value.trim();
|
|
693
|
+
if (!trimmed) return false;
|
|
694
|
+
return trimmed.startsWith("{") || trimmed.startsWith("[") || /^```(?:json)?/i.test(trimmed);
|
|
695
|
+
};
|
|
696
|
+
var isLikelyTruncatedStructuredOutput = (text) => {
|
|
697
|
+
const trimmed = text.trim();
|
|
698
|
+
if (!looksLikeStructuredJsonAttempt(trimmed)) return false;
|
|
699
|
+
const direct = tryJsonParse(trimmed);
|
|
700
|
+
if (!direct.ok && direct.error) return true;
|
|
701
|
+
const block = extractJsonCodeBlock(trimmed);
|
|
702
|
+
if (!block) return false;
|
|
703
|
+
const parsedBlock = tryJsonParse(block);
|
|
704
|
+
return !parsedBlock.ok && Boolean(parsedBlock.error);
|
|
705
|
+
};
|
|
706
|
+
var createTruncatedStructuredOutputError = (label) => new Error(
|
|
707
|
+
`${label} was truncated before producing valid structured output. The model stopped due to output length. Increase maxOutputTokens or simplify the requested output.`
|
|
708
|
+
);
|
|
709
|
+
var createInvalidStructuredOutputError = (label) => new Error(
|
|
710
|
+
`${label} did not produce valid structured output. The model returned text instead of the expected JSON payload. This often happens when output is truncated or the provider does not follow JSON mode. Increase maxOutputTokens or simplify the requested output.`
|
|
711
|
+
);
|
|
712
|
+
var parseReasonOutputWithSchema = (args) => {
|
|
713
|
+
const candidate = resolveOutputCandidate(args.text);
|
|
714
|
+
try {
|
|
715
|
+
return parseWithSchema(args.schema, candidate, args.label);
|
|
716
|
+
} catch (error) {
|
|
717
|
+
if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
|
|
718
|
+
throw createTruncatedStructuredOutputError(args.label);
|
|
719
|
+
}
|
|
720
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
721
|
+
throw createInvalidStructuredOutputError(args.label);
|
|
722
|
+
}
|
|
723
|
+
throw error;
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
var parseInterruptFirstPassResult = (args) => {
|
|
727
|
+
const candidate = resolveOutputCandidate(args.text);
|
|
728
|
+
if (!isRecord22(candidate)) {
|
|
729
|
+
if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
|
|
730
|
+
throw createTruncatedStructuredOutputError("useReason output");
|
|
731
|
+
}
|
|
732
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
733
|
+
throw createInvalidStructuredOutputError("useReason output");
|
|
734
|
+
}
|
|
735
|
+
throw new Error(
|
|
736
|
+
"useReason first pass with interrupt must return a JSON object."
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
const mode = args.mode ?? "required";
|
|
740
|
+
let interruptRequired = true;
|
|
741
|
+
if (mode === "optional") {
|
|
742
|
+
if (candidate.decision !== "continue" && candidate.decision !== "interrupt") {
|
|
743
|
+
throw new Error(
|
|
744
|
+
'useReason optional interrupt first pass must include decision "continue" or "interrupt".'
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
interruptRequired = candidate.decision === "interrupt";
|
|
748
|
+
if (!interruptRequired) {
|
|
749
|
+
const hasUnexpectedRequest = [
|
|
750
|
+
candidate.interruptRequest,
|
|
751
|
+
candidate.request,
|
|
752
|
+
candidate.interrupt
|
|
753
|
+
].some((value) => value !== null && value !== void 0);
|
|
754
|
+
if (hasUnexpectedRequest) {
|
|
755
|
+
throw new Error(
|
|
756
|
+
'useReason optional interrupt first pass with decision "continue" must not include an interrupt request.'
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
let request;
|
|
762
|
+
if (interruptRequired) {
|
|
763
|
+
const requestCandidates = [
|
|
764
|
+
candidate.interruptRequest,
|
|
765
|
+
candidate.request,
|
|
766
|
+
candidate.interrupt,
|
|
767
|
+
mode === "required" ? candidate : void 0
|
|
768
|
+
];
|
|
769
|
+
const requestPayload = requestCandidates.find(
|
|
770
|
+
(value) => value !== null && value !== void 0
|
|
771
|
+
);
|
|
772
|
+
request = parseWithSchema(
|
|
773
|
+
args.requestSchema,
|
|
774
|
+
requestPayload,
|
|
775
|
+
"useReason interrupt.request"
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
let output;
|
|
779
|
+
if (args.outputSchema) {
|
|
780
|
+
const outputPayload = Object.hasOwn(candidate, "output") ? candidate.output : candidate;
|
|
781
|
+
try {
|
|
782
|
+
output = parseWithSchema(
|
|
783
|
+
args.outputSchema,
|
|
784
|
+
outputPayload,
|
|
785
|
+
"useReason output"
|
|
786
|
+
);
|
|
787
|
+
} catch (error) {
|
|
788
|
+
if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
|
|
789
|
+
throw createTruncatedStructuredOutputError("useReason output");
|
|
790
|
+
}
|
|
791
|
+
if (typeof outputPayload === "string" && outputPayload.trim().length > 0) {
|
|
792
|
+
throw createInvalidStructuredOutputError("useReason output");
|
|
793
|
+
}
|
|
794
|
+
throw error;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
const explicitDraft = typeof candidate.draftText === "string" && candidate.draftText.length > 0 ? candidate.draftText : typeof candidate.text === "string" && candidate.text.length > 0 ? candidate.text : void 0;
|
|
798
|
+
if (!args.outputSchema && !explicitDraft) {
|
|
799
|
+
throw new Error(
|
|
800
|
+
"useReason first pass with interrupt requires `draftText` when outputSchema is not provided."
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
const draftText = explicitDraft ?? (output !== void 0 ? tryStringify(output) : String(args.text ?? ""));
|
|
804
|
+
if (!interruptRequired) {
|
|
805
|
+
return {
|
|
806
|
+
draftText,
|
|
807
|
+
interruptRequired: false,
|
|
808
|
+
...output !== void 0 ? { output } : {}
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
return {
|
|
812
|
+
draftText,
|
|
813
|
+
interruptRequired: true,
|
|
814
|
+
request,
|
|
815
|
+
...output !== void 0 ? { output } : {}
|
|
816
|
+
};
|
|
817
|
+
};
|
|
818
|
+
var import_zod3 = require("zod");
|
|
819
|
+
var tryStringify2 = (value) => {
|
|
820
|
+
if (typeof value === "string") return value;
|
|
821
|
+
try {
|
|
822
|
+
return JSON.stringify(value, null, 2);
|
|
823
|
+
} catch {
|
|
824
|
+
return String(value);
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
var tryGetSchemaHint = (schema) => {
|
|
828
|
+
try {
|
|
829
|
+
const hint = (0, import_zod3.toJSONSchema)(schema);
|
|
830
|
+
return JSON.stringify(hint, null, 2);
|
|
831
|
+
} catch {
|
|
832
|
+
return void 0;
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
var withSchemaHint = (input, label, schema) => {
|
|
836
|
+
const hint = tryGetSchemaHint(schema);
|
|
837
|
+
if (!hint) return input;
|
|
838
|
+
return `${input}
|
|
839
|
+
|
|
840
|
+
${label}:
|
|
841
|
+
${hint}`;
|
|
842
|
+
};
|
|
843
|
+
var defaultInterruptFirstPassInput = (args) => {
|
|
844
|
+
const mode = args.mode ?? "required";
|
|
845
|
+
const base = mode === "optional" ? args.outputSchema ? [
|
|
846
|
+
args.input,
|
|
847
|
+
"Output rules:",
|
|
848
|
+
"- Return JSON only. No markdown fences.",
|
|
849
|
+
"- Return an object with keys: decision, output, interruptRequest, draftText.",
|
|
850
|
+
'- decision must be either "continue" or "interrupt".',
|
|
851
|
+
'- Use decision "continue" when you can produce the final output without asking the user.',
|
|
852
|
+
'- Use decision "interrupt" only when user input is needed before finalizing.',
|
|
853
|
+
"- output must match the expected output JSON schema.",
|
|
854
|
+
'- When decision is "interrupt", interruptRequest must match the expected interrupt request JSON schema and must be a non-null object.',
|
|
855
|
+
'- When decision is "continue", interruptRequest must be null.',
|
|
856
|
+
"- draftText should be a concise plain-language summary of output."
|
|
857
|
+
].join("\n\n") : [
|
|
858
|
+
args.input,
|
|
859
|
+
"Output rules:",
|
|
860
|
+
"- Return JSON only. No markdown fences.",
|
|
861
|
+
"- Return an object with keys: decision, draftText, interruptRequest.",
|
|
862
|
+
'- decision must be either "continue" or "interrupt".',
|
|
863
|
+
'- Use decision "continue" when you can produce the final answer without asking the user.',
|
|
864
|
+
'- Use decision "interrupt" only when user input is needed before finalizing.',
|
|
865
|
+
"- draftText should be the current answer text.",
|
|
866
|
+
'- When decision is "interrupt", interruptRequest must match the expected interrupt request JSON schema and must be a non-null object.',
|
|
867
|
+
'- When decision is "continue", interruptRequest must be null.'
|
|
868
|
+
].join("\n\n") : args.outputSchema ? [
|
|
869
|
+
args.input,
|
|
870
|
+
"Output rules:",
|
|
871
|
+
"- Return JSON only. No markdown fences.",
|
|
872
|
+
"- Return an object with keys: output, interruptRequest, draftText.",
|
|
873
|
+
"- output must match the expected output JSON schema.",
|
|
874
|
+
"- interruptRequest must match the expected interrupt request JSON schema.",
|
|
875
|
+
"- interruptRequest must be a non-null object.",
|
|
876
|
+
"- draftText should be a concise plain-language summary of output.",
|
|
877
|
+
"- If uncertain, still return a valid interruptRequest with safe default options."
|
|
878
|
+
].join("\n\n") : [
|
|
879
|
+
args.input,
|
|
880
|
+
"Output rules:",
|
|
881
|
+
"- Return JSON only. No markdown fences.",
|
|
882
|
+
"- Return an object with keys: draftText, interruptRequest.",
|
|
883
|
+
"- draftText should be the current draft answer text.",
|
|
884
|
+
"- interruptRequest must match the expected interrupt request JSON schema.",
|
|
885
|
+
"- interruptRequest must be a non-null object.",
|
|
886
|
+
"- If uncertain, still return a valid interruptRequest with safe default options."
|
|
887
|
+
].join("\n\n");
|
|
888
|
+
const withInterruptSchema = withSchemaHint(
|
|
889
|
+
base,
|
|
890
|
+
"Expected interrupt request JSON schema (for `interruptRequest`)",
|
|
891
|
+
args.requestSchema
|
|
892
|
+
);
|
|
893
|
+
if (!args.outputSchema) return withInterruptSchema;
|
|
894
|
+
const withOutputSchema = withSchemaHint(
|
|
895
|
+
withInterruptSchema,
|
|
896
|
+
"Expected output JSON schema (for `output`)",
|
|
897
|
+
args.outputSchema
|
|
898
|
+
);
|
|
899
|
+
return [
|
|
900
|
+
withOutputSchema,
|
|
901
|
+
"JSON template:",
|
|
902
|
+
mode === "optional" ? '{ "decision": "continue", "output": { ... }, "interruptRequest": null, "draftText": "..." }' : '{ "output": { ... }, "interruptRequest": { ... }, "draftText": "..." }'
|
|
903
|
+
].join("\n\n");
|
|
904
|
+
};
|
|
905
|
+
var defaultContinuationInput = (args) => [
|
|
906
|
+
"You are continuing a paused reasoning task after user input.",
|
|
907
|
+
"Update the previous result using the user response and return the final answer only.",
|
|
908
|
+
"If the previous output was JSON, return JSON in the same schema.",
|
|
909
|
+
"",
|
|
910
|
+
`Original request:
|
|
911
|
+
${args.input}`,
|
|
912
|
+
`Draft answer text:
|
|
913
|
+
${args.draftText}`,
|
|
914
|
+
`Draft output object:
|
|
915
|
+
${tryStringify2(args.draftOutput ?? args.draftText)}`,
|
|
916
|
+
`Interrupt request shown to user:
|
|
917
|
+
${tryStringify2(args.request)}`,
|
|
918
|
+
`User response:
|
|
919
|
+
${tryStringify2(args.response)}`
|
|
920
|
+
].join("\n\n");
|
|
921
|
+
var withOutputGuardrails = (input, schema) => {
|
|
922
|
+
const base = [
|
|
923
|
+
input,
|
|
924
|
+
"Output rules:",
|
|
925
|
+
"- Return only JSON, no markdown fences.",
|
|
926
|
+
"- Ensure the response conforms to the expected output schema.",
|
|
927
|
+
"- Do not include explanatory text outside the JSON object."
|
|
928
|
+
].join("\n\n");
|
|
929
|
+
return withSchemaHint(base, "Expected output JSON schema", schema);
|
|
930
|
+
};
|
|
931
|
+
var withStructuredStreamHints = (input, args) => {
|
|
932
|
+
const rules = [];
|
|
933
|
+
for (const path of args.setFieldPaths ?? []) {
|
|
934
|
+
rules.push(
|
|
935
|
+
`- For the field path pattern \`${path}\`, write one stable final value and do not rewrite it after it is complete.`
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
for (const path of args.appendFieldPaths ?? []) {
|
|
939
|
+
rules.push(
|
|
940
|
+
`- For the array field path pattern \`${path}\`, write items in final order and finish each item before starting the next.`
|
|
941
|
+
);
|
|
942
|
+
rules.push(
|
|
943
|
+
`- Once an item in \`${path}\` is complete, do not rewrite or reorder earlier items.`
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
for (const path of args.textDeltaFieldPaths ?? []) {
|
|
947
|
+
rules.push(
|
|
948
|
+
`- For the string field path pattern \`${path}\`, write the value progressively by appending text to the end.`
|
|
949
|
+
);
|
|
950
|
+
rules.push(
|
|
951
|
+
`- Do not rewrite or replace the earlier prefix of \`${path}\` once emitted.`
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
if (rules.length === 0) return input;
|
|
955
|
+
return [
|
|
956
|
+
input,
|
|
957
|
+
"Streaming rules:",
|
|
958
|
+
...rules,
|
|
959
|
+
"- Keep the overall response as valid JSON."
|
|
960
|
+
].join("\n\n");
|
|
961
|
+
};
|
|
962
|
+
var mergeUsage = (left, right) => {
|
|
963
|
+
if (!left) return right;
|
|
964
|
+
if (!right) return left;
|
|
965
|
+
const sum = (a, b) => a === void 0 && b === void 0 ? void 0 : (a ?? 0) + (b ?? 0);
|
|
966
|
+
const raw = left.raw || right.raw ? {
|
|
967
|
+
...left.raw ?? {},
|
|
968
|
+
...right.raw ?? {}
|
|
969
|
+
} : void 0;
|
|
970
|
+
const input = sum(left.input, right.input);
|
|
971
|
+
const output = sum(left.output, right.output);
|
|
972
|
+
const total = sum(left.total, right.total);
|
|
973
|
+
const reasoning = sum(left.reasoning, right.reasoning);
|
|
974
|
+
const cacheRead = sum(left.cacheRead, right.cacheRead);
|
|
975
|
+
const cacheWrite = sum(left.cacheWrite, right.cacheWrite);
|
|
976
|
+
const merged = {};
|
|
977
|
+
if (input !== void 0) merged.input = input;
|
|
978
|
+
if (output !== void 0) merged.output = output;
|
|
979
|
+
if (total !== void 0) merged.total = total;
|
|
980
|
+
if (reasoning !== void 0) merged.reasoning = reasoning;
|
|
981
|
+
if (cacheRead !== void 0) merged.cacheRead = cacheRead;
|
|
982
|
+
if (cacheWrite !== void 0) merged.cacheWrite = cacheWrite;
|
|
983
|
+
if (raw) merged.raw = raw;
|
|
984
|
+
return merged;
|
|
985
|
+
};
|
|
986
|
+
var mergeWarnings = (left, right) => {
|
|
987
|
+
if (!left?.length) return right;
|
|
988
|
+
if (!right?.length) return left;
|
|
989
|
+
const seen = /* @__PURE__ */ new Set();
|
|
990
|
+
const merged = [];
|
|
991
|
+
for (const warning of [...left, ...right]) {
|
|
992
|
+
const key = JSON.stringify(warning);
|
|
993
|
+
if (seen.has(key)) continue;
|
|
994
|
+
seen.add(key);
|
|
995
|
+
merged.push(warning);
|
|
996
|
+
}
|
|
997
|
+
return merged;
|
|
998
|
+
};
|
|
999
|
+
var mergeProviderMetadata = (left, right) => {
|
|
1000
|
+
if (!left) return right;
|
|
1001
|
+
if (!right) return left;
|
|
1002
|
+
return {
|
|
1003
|
+
...left,
|
|
1004
|
+
...right
|
|
1005
|
+
};
|
|
1006
|
+
};
|
|
1007
|
+
var emitReasonStructuredOutput = (args) => {
|
|
1008
|
+
if (!args.emit || !shouldEmitStructured(args.structured)) return;
|
|
1009
|
+
emitStructuredData({
|
|
1010
|
+
dataType: args.structured?.dataType ?? "reason-output",
|
|
1011
|
+
data: args.output,
|
|
1012
|
+
kind: "final",
|
|
1013
|
+
...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
|
|
1014
|
+
...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
|
|
1015
|
+
...args.id ? { id: args.id } : {},
|
|
1016
|
+
streamId: args.opId
|
|
1017
|
+
});
|
|
1018
|
+
};
|
|
1019
|
+
var resolveAppendFieldPaths = (cfg) => {
|
|
1020
|
+
const fields = cfg?.fields;
|
|
1021
|
+
if (!fields) return [];
|
|
1022
|
+
const appendPaths = Object.entries(fields).filter(([, mode]) => mode === "append").map(([path]) => path);
|
|
1023
|
+
for (const path of appendPaths) {
|
|
1024
|
+
assertStructuredPath(path, "useReason structured append");
|
|
1025
|
+
}
|
|
1026
|
+
return appendPaths;
|
|
1027
|
+
};
|
|
1028
|
+
var resolveSetFieldPaths = (cfg) => {
|
|
1029
|
+
const fields = cfg?.fields;
|
|
1030
|
+
if (!fields) return [];
|
|
1031
|
+
const setPaths = Object.entries(fields).filter(([, mode]) => mode === "set").map(([path]) => path);
|
|
1032
|
+
for (const path of setPaths) {
|
|
1033
|
+
assertStructuredPath(path, "useReason structured set");
|
|
1034
|
+
}
|
|
1035
|
+
return setPaths;
|
|
1036
|
+
};
|
|
1037
|
+
var resolveTextDeltaFieldPaths = (cfg) => {
|
|
1038
|
+
const fields = cfg?.fields;
|
|
1039
|
+
if (!fields) return [];
|
|
1040
|
+
const textPaths = Object.entries(fields).filter(([, mode]) => mode === "text-delta").map(([path]) => path);
|
|
1041
|
+
for (const path of textPaths) {
|
|
1042
|
+
assertStructuredPath(path, "useReason structured text-delta");
|
|
1043
|
+
}
|
|
1044
|
+
return textPaths;
|
|
1045
|
+
};
|
|
1046
|
+
var extractCompletedStringValueAt = (args) => {
|
|
1047
|
+
let value = "";
|
|
1048
|
+
for (let i = args.start; i < args.text.length; i += 1) {
|
|
1049
|
+
const ch = args.text[i];
|
|
1050
|
+
if (ch === void 0) break;
|
|
1051
|
+
if (ch === '"') {
|
|
1052
|
+
return {
|
|
1053
|
+
complete: true,
|
|
1054
|
+
value
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
if (ch !== "\\") {
|
|
1058
|
+
value += ch;
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
const next = args.text[i + 1];
|
|
1062
|
+
if (!next) {
|
|
1063
|
+
return {
|
|
1064
|
+
complete: false
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
if (next === '"' || next === "\\" || next === "/" || next === "b" || next === "f" || next === "n" || next === "r" || next === "t") {
|
|
1068
|
+
value += next === "b" ? "\b" : next === "f" ? "\f" : next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? " " : next;
|
|
1069
|
+
i += 1;
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
if (next === "u") {
|
|
1073
|
+
const hex = args.text.slice(i + 2, i + 6);
|
|
1074
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
1075
|
+
return {
|
|
1076
|
+
complete: false
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
value += String.fromCharCode(Number.parseInt(hex, 16));
|
|
1080
|
+
i += 5;
|
|
1081
|
+
continue;
|
|
1082
|
+
}
|
|
1083
|
+
return {
|
|
1084
|
+
complete: false
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
return {
|
|
1088
|
+
complete: false
|
|
1089
|
+
};
|
|
1090
|
+
};
|
|
1091
|
+
var skipWhitespace = (text, start) => {
|
|
1092
|
+
let i = start;
|
|
1093
|
+
while (i < text.length && /\s/.test(text[i] ?? "")) i += 1;
|
|
1094
|
+
return i;
|
|
1095
|
+
};
|
|
1096
|
+
var pathSegmentMatches = (patternPart, actualPart) => patternPart === "*" || patternPart === actualPart;
|
|
1097
|
+
var pathMatchesPattern = (actualParts, patternParts) => actualParts.length === patternParts.length && actualParts.every(
|
|
1098
|
+
(part, index) => pathSegmentMatches(patternParts[index] ?? "", part)
|
|
1099
|
+
);
|
|
1100
|
+
var isPathPrefix = (prefix, full) => prefix.length <= full.length && prefix.every((part, index) => pathSegmentMatches(full[index] ?? "", part));
|
|
1101
|
+
var parseStringToken = (text, start) => {
|
|
1102
|
+
if (text[start] !== '"') return { complete: false };
|
|
1103
|
+
const parsed = extractCompletedStringValueAt({ text, start: start + 1 });
|
|
1104
|
+
if (!parsed.complete || parsed.value === void 0)
|
|
1105
|
+
return { complete: false };
|
|
1106
|
+
let inEscape = false;
|
|
1107
|
+
for (let i = start + 1; i < text.length; i += 1) {
|
|
1108
|
+
const ch = text[i];
|
|
1109
|
+
if (ch === void 0) break;
|
|
1110
|
+
if (inEscape) {
|
|
1111
|
+
inEscape = false;
|
|
1112
|
+
if (ch === "u") i += 4;
|
|
1113
|
+
continue;
|
|
1114
|
+
}
|
|
1115
|
+
if (ch === "\\") {
|
|
1116
|
+
inEscape = true;
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
1119
|
+
if (ch === '"') {
|
|
1120
|
+
return {
|
|
1121
|
+
complete: true,
|
|
1122
|
+
end: i + 1,
|
|
1123
|
+
value: parsed.value
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
return { complete: false };
|
|
1128
|
+
};
|
|
1129
|
+
var skipNestedValue = (text, start) => {
|
|
1130
|
+
const opener = text[start];
|
|
1131
|
+
const closer = opener === "{" ? "}" : "]";
|
|
1132
|
+
let inString = false;
|
|
1133
|
+
let isEscaped = false;
|
|
1134
|
+
let nesting = 0;
|
|
1135
|
+
for (let i = start; i < text.length; i += 1) {
|
|
1136
|
+
const ch = text[i];
|
|
1137
|
+
if (ch === void 0) break;
|
|
1138
|
+
if (inString) {
|
|
1139
|
+
if (isEscaped) {
|
|
1140
|
+
isEscaped = false;
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
if (ch === "\\") {
|
|
1144
|
+
isEscaped = true;
|
|
1145
|
+
continue;
|
|
1146
|
+
}
|
|
1147
|
+
if (ch === '"') {
|
|
1148
|
+
inString = false;
|
|
1149
|
+
}
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
if (ch === '"') {
|
|
1153
|
+
inString = true;
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
if (ch === opener) {
|
|
1157
|
+
nesting += 1;
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1160
|
+
if (ch === closer) {
|
|
1161
|
+
nesting -= 1;
|
|
1162
|
+
if (nesting === 0) {
|
|
1163
|
+
return { complete: true, end: i + 1 };
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
return { complete: false };
|
|
1168
|
+
};
|
|
1169
|
+
var skipCompleteValue = (text, start) => {
|
|
1170
|
+
const i = skipWhitespace(text, start);
|
|
1171
|
+
const first = text[i];
|
|
1172
|
+
if (!first) return { complete: false };
|
|
1173
|
+
if (first === '"') {
|
|
1174
|
+
const parsed = parseStringToken(text, i);
|
|
1175
|
+
return parsed.complete && parsed.end !== void 0 ? { complete: true, end: parsed.end } : { complete: false };
|
|
1176
|
+
}
|
|
1177
|
+
if (first === "{" || first === "[") {
|
|
1178
|
+
return skipNestedValue(text, i);
|
|
1179
|
+
}
|
|
1180
|
+
for (let j = i; j < text.length; j += 1) {
|
|
1181
|
+
const ch = text[j];
|
|
1182
|
+
if (ch !== "," && ch !== "}" && ch !== "]") continue;
|
|
1183
|
+
const raw = text.slice(i, j).trim();
|
|
1184
|
+
if (!raw) return { complete: false };
|
|
1185
|
+
try {
|
|
1186
|
+
JSON.parse(raw);
|
|
1187
|
+
return { complete: true, end: j };
|
|
1188
|
+
} catch {
|
|
1189
|
+
return { complete: false };
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return { complete: false };
|
|
1193
|
+
};
|
|
1194
|
+
var locateValueStart = (args) => {
|
|
1195
|
+
const [first] = locateValueStarts(args);
|
|
1196
|
+
return first ? {
|
|
1197
|
+
found: true,
|
|
1198
|
+
start: first.start
|
|
1199
|
+
} : {
|
|
1200
|
+
found: false
|
|
1201
|
+
};
|
|
1202
|
+
};
|
|
1203
|
+
var locateValueStarts = (args) => {
|
|
1204
|
+
const targetParts = parseStructuredPath(
|
|
1205
|
+
args.path,
|
|
1206
|
+
"useReason structured field"
|
|
1207
|
+
);
|
|
1208
|
+
const matches = [];
|
|
1209
|
+
const visitValue = (start, currentParts) => {
|
|
1210
|
+
const valueStart = skipWhitespace(args.text, start);
|
|
1211
|
+
if (pathMatchesPattern(currentParts, targetParts)) {
|
|
1212
|
+
matches.push({
|
|
1213
|
+
path: currentParts.join("."),
|
|
1214
|
+
start: valueStart
|
|
1215
|
+
});
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
if (!isPathPrefix(currentParts, targetParts)) {
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
const first = args.text[valueStart];
|
|
1222
|
+
if (first === "{") visitObject(valueStart + 1, currentParts);
|
|
1223
|
+
if (first === "[") visitArray(valueStart + 1, currentParts);
|
|
1224
|
+
};
|
|
1225
|
+
const visitObject = (start, currentParts) => {
|
|
1226
|
+
let i = skipWhitespace(args.text, start);
|
|
1227
|
+
while (i < args.text.length) {
|
|
1228
|
+
const ch = args.text[i];
|
|
1229
|
+
if (ch === "}") return;
|
|
1230
|
+
if (ch === ",") {
|
|
1231
|
+
i = skipWhitespace(args.text, i + 1);
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
if (ch !== '"') return;
|
|
1235
|
+
const key = parseStringToken(args.text, i);
|
|
1236
|
+
if (!key.complete || key.end === void 0 || key.value === void 0) {
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
i = skipWhitespace(args.text, key.end);
|
|
1240
|
+
if (args.text[i] !== ":") return;
|
|
1241
|
+
const valueStart = skipWhitespace(args.text, i + 1);
|
|
1242
|
+
const childParts = [...currentParts, key.value];
|
|
1243
|
+
if (pathMatchesPattern(childParts, targetParts)) {
|
|
1244
|
+
matches.push({
|
|
1245
|
+
path: childParts.join("."),
|
|
1246
|
+
start: valueStart
|
|
1247
|
+
});
|
|
1248
|
+
} else if (isPathPrefix(childParts, targetParts)) {
|
|
1249
|
+
visitValue(valueStart, childParts);
|
|
1250
|
+
}
|
|
1251
|
+
const skipped = skipCompleteValue(args.text, valueStart);
|
|
1252
|
+
if (!skipped.complete || skipped.end === void 0) {
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
i = skipWhitespace(args.text, skipped.end);
|
|
1256
|
+
if (args.text[i] === ",") {
|
|
1257
|
+
i = skipWhitespace(args.text, i + 1);
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
if (args.text[i] === "}") return;
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
const visitArray = (start, currentParts) => {
|
|
1265
|
+
let i = skipWhitespace(args.text, start);
|
|
1266
|
+
let index = 0;
|
|
1267
|
+
while (i < args.text.length) {
|
|
1268
|
+
if (args.text[i] === "]") return;
|
|
1269
|
+
const childParts = [...currentParts, String(index)];
|
|
1270
|
+
if (pathMatchesPattern(childParts, targetParts)) {
|
|
1271
|
+
matches.push({
|
|
1272
|
+
path: childParts.join("."),
|
|
1273
|
+
start: i
|
|
1274
|
+
});
|
|
1275
|
+
} else if (isPathPrefix(childParts, targetParts)) {
|
|
1276
|
+
visitValue(i, childParts);
|
|
1277
|
+
}
|
|
1278
|
+
const skipped = skipCompleteValue(args.text, i);
|
|
1279
|
+
if (!skipped.complete || skipped.end === void 0) {
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
i = skipWhitespace(args.text, skipped.end);
|
|
1283
|
+
if (args.text[i] === ",") {
|
|
1284
|
+
index += 1;
|
|
1285
|
+
i = skipWhitespace(args.text, i + 1);
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
if (args.text[i] === "]") return;
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1292
|
+
visitValue(0, []);
|
|
1293
|
+
return matches;
|
|
1294
|
+
};
|
|
1295
|
+
var extractCompletedValueAt = (args) => {
|
|
1296
|
+
const start = skipWhitespace(args.text, args.start);
|
|
1297
|
+
const first = args.text[start];
|
|
1298
|
+
if (!first) return { complete: false };
|
|
1299
|
+
if (first === '"') {
|
|
1300
|
+
const parsed = extractCompletedStringValueAt({
|
|
1301
|
+
text: args.text,
|
|
1302
|
+
start: start + 1
|
|
1303
|
+
});
|
|
1304
|
+
return parsed.complete ? {
|
|
1305
|
+
complete: true,
|
|
1306
|
+
value: parsed.value
|
|
1307
|
+
} : {
|
|
1308
|
+
complete: false
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
if (first === "{") {
|
|
1312
|
+
return extractCompletedNestedValueAt({
|
|
1313
|
+
text: args.text,
|
|
1314
|
+
start,
|
|
1315
|
+
opener: "{",
|
|
1316
|
+
closer: "}"
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
if (first === "[") {
|
|
1320
|
+
return extractCompletedNestedValueAt({
|
|
1321
|
+
text: args.text,
|
|
1322
|
+
start,
|
|
1323
|
+
opener: "[",
|
|
1324
|
+
closer: "]"
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
for (let i = start; i < args.text.length; i += 1) {
|
|
1328
|
+
const ch = args.text[i];
|
|
1329
|
+
if (ch === void 0) break;
|
|
1330
|
+
if (ch !== "," && ch !== "}" && ch !== "]") continue;
|
|
1331
|
+
const raw = args.text.slice(start, i).trim();
|
|
1332
|
+
if (!raw) return { complete: false };
|
|
1333
|
+
try {
|
|
1334
|
+
return {
|
|
1335
|
+
complete: true,
|
|
1336
|
+
value: JSON.parse(raw)
|
|
1337
|
+
};
|
|
1338
|
+
} catch {
|
|
1339
|
+
return { complete: false };
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
return { complete: false };
|
|
1343
|
+
};
|
|
1344
|
+
var extractCompletedNestedValueAt = (args) => {
|
|
1345
|
+
let inString = false;
|
|
1346
|
+
let isEscaped = false;
|
|
1347
|
+
let nesting = 0;
|
|
1348
|
+
for (let i = args.start; i < args.text.length; i += 1) {
|
|
1349
|
+
const ch = args.text[i];
|
|
1350
|
+
if (ch === void 0) break;
|
|
1351
|
+
if (inString) {
|
|
1352
|
+
if (isEscaped) {
|
|
1353
|
+
isEscaped = false;
|
|
1354
|
+
continue;
|
|
1355
|
+
}
|
|
1356
|
+
if (ch === "\\") {
|
|
1357
|
+
isEscaped = true;
|
|
1358
|
+
continue;
|
|
1359
|
+
}
|
|
1360
|
+
if (ch === '"') {
|
|
1361
|
+
inString = false;
|
|
1362
|
+
}
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
if (ch === '"') {
|
|
1366
|
+
inString = true;
|
|
1367
|
+
continue;
|
|
1368
|
+
}
|
|
1369
|
+
if (ch === args.opener) {
|
|
1370
|
+
nesting += 1;
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1373
|
+
if (ch === args.closer) {
|
|
1374
|
+
nesting -= 1;
|
|
1375
|
+
if (nesting === 0) {
|
|
1376
|
+
const raw = args.text.slice(args.start, i + 1);
|
|
1377
|
+
try {
|
|
1378
|
+
return {
|
|
1379
|
+
complete: true,
|
|
1380
|
+
value: JSON.parse(raw)
|
|
1381
|
+
};
|
|
1382
|
+
} catch {
|
|
1383
|
+
return {
|
|
1384
|
+
complete: false
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
return {
|
|
1391
|
+
complete: false
|
|
1392
|
+
};
|
|
1393
|
+
};
|
|
1394
|
+
var extractCompletedFieldValues = (args) => {
|
|
1395
|
+
const located = locateValueStarts(args);
|
|
1396
|
+
const values = [];
|
|
1397
|
+
for (const match of located) {
|
|
1398
|
+
const parsed = extractCompletedValueAt({
|
|
1399
|
+
text: args.text,
|
|
1400
|
+
start: match.start
|
|
1401
|
+
});
|
|
1402
|
+
if (!parsed.complete) continue;
|
|
1403
|
+
values.push({
|
|
1404
|
+
path: match.path,
|
|
1405
|
+
value: parsed.value
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
return values;
|
|
1409
|
+
};
|
|
1410
|
+
var extractCompletedArrayItems = (args) => {
|
|
1411
|
+
const located = locateValueStart(args);
|
|
1412
|
+
if (!located.found || located.start === void 0) return [];
|
|
1413
|
+
const arrayStart = skipWhitespace(args.text, located.start);
|
|
1414
|
+
if (args.text[arrayStart] !== "[") return [];
|
|
1415
|
+
const start = arrayStart + 1;
|
|
1416
|
+
const items = [];
|
|
1417
|
+
let inString = false;
|
|
1418
|
+
let isEscaped = false;
|
|
1419
|
+
let nesting = 0;
|
|
1420
|
+
let itemStart = -1;
|
|
1421
|
+
for (let i = start; i < args.text.length; i += 1) {
|
|
1422
|
+
const ch = args.text[i];
|
|
1423
|
+
if (ch === void 0) break;
|
|
1424
|
+
if (inString) {
|
|
1425
|
+
if (isEscaped) {
|
|
1426
|
+
isEscaped = false;
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
if (ch === "\\") {
|
|
1430
|
+
isEscaped = true;
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
if (ch === '"') {
|
|
1434
|
+
inString = false;
|
|
1435
|
+
}
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
if (ch === '"') {
|
|
1439
|
+
inString = true;
|
|
1440
|
+
if (itemStart < 0) itemStart = i;
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
if (itemStart < 0) {
|
|
1444
|
+
if (/\s/.test(ch) || ch === ",") continue;
|
|
1445
|
+
if (ch === "]") break;
|
|
1446
|
+
itemStart = i;
|
|
1447
|
+
if (ch === "{" || ch === "[") {
|
|
1448
|
+
nesting = 1;
|
|
1449
|
+
} else {
|
|
1450
|
+
nesting = 0;
|
|
1451
|
+
}
|
|
1452
|
+
continue;
|
|
1453
|
+
}
|
|
1454
|
+
if (ch === "{" || ch === "[") {
|
|
1455
|
+
nesting += 1;
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
if (ch === "}" || ch === "]") {
|
|
1459
|
+
if (nesting > 0) {
|
|
1460
|
+
nesting -= 1;
|
|
1461
|
+
continue;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
if (nesting === 0 && (ch === "," || ch === "]")) {
|
|
1465
|
+
const raw = args.text.slice(itemStart, i).trim();
|
|
1466
|
+
if (raw) {
|
|
1467
|
+
try {
|
|
1468
|
+
items.push(JSON.parse(raw));
|
|
1469
|
+
} catch {
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
itemStart = -1;
|
|
1473
|
+
if (ch === "]") break;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
return items;
|
|
1477
|
+
};
|
|
1478
|
+
var extractCompletedArrayItemGroups = (args) => {
|
|
1479
|
+
return locateValueStarts(args).flatMap((match) => {
|
|
1480
|
+
const items = extractCompletedArrayItems({
|
|
1481
|
+
text: args.text,
|
|
1482
|
+
path: match.path
|
|
1483
|
+
});
|
|
1484
|
+
return items.length > 0 ? [
|
|
1485
|
+
{
|
|
1486
|
+
path: match.path,
|
|
1487
|
+
items
|
|
1488
|
+
}
|
|
1489
|
+
] : [];
|
|
1490
|
+
});
|
|
1491
|
+
};
|
|
1492
|
+
var extractStreamingStringValue = (args) => {
|
|
1493
|
+
const located = locateValueStart(args);
|
|
1494
|
+
if (!located.found || located.start === void 0) {
|
|
1495
|
+
return {
|
|
1496
|
+
found: located.found,
|
|
1497
|
+
complete: false,
|
|
1498
|
+
value: ""
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
const stringStart = skipWhitespace(args.text, located.start);
|
|
1502
|
+
if (args.text[stringStart] !== '"') {
|
|
1503
|
+
return {
|
|
1504
|
+
found: true,
|
|
1505
|
+
complete: false,
|
|
1506
|
+
value: ""
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
let value = "";
|
|
1510
|
+
for (let i = stringStart + 1; i < args.text.length; i += 1) {
|
|
1511
|
+
const ch = args.text[i];
|
|
1512
|
+
if (ch === void 0) break;
|
|
1513
|
+
if (ch === '"') {
|
|
1514
|
+
return {
|
|
1515
|
+
found: true,
|
|
1516
|
+
complete: true,
|
|
1517
|
+
value
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
if (ch !== "\\") {
|
|
1521
|
+
value += ch;
|
|
1522
|
+
continue;
|
|
1523
|
+
}
|
|
1524
|
+
const next = args.text[i + 1];
|
|
1525
|
+
if (!next) {
|
|
1526
|
+
return {
|
|
1527
|
+
found: true,
|
|
1528
|
+
complete: false,
|
|
1529
|
+
value
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
if (next === '"' || next === "\\" || next === "/" || next === "b" || next === "f" || next === "n" || next === "r" || next === "t") {
|
|
1533
|
+
value += next === "b" ? "\b" : next === "f" ? "\f" : next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? " " : next;
|
|
1534
|
+
i += 1;
|
|
1535
|
+
continue;
|
|
1536
|
+
}
|
|
1537
|
+
if (next === "u") {
|
|
1538
|
+
const hex = args.text.slice(i + 2, i + 6);
|
|
1539
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
1540
|
+
return {
|
|
1541
|
+
found: true,
|
|
1542
|
+
complete: false,
|
|
1543
|
+
value
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
value += String.fromCharCode(Number.parseInt(hex, 16));
|
|
1547
|
+
i += 5;
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
return {
|
|
1551
|
+
found: true,
|
|
1552
|
+
complete: false,
|
|
1553
|
+
value
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
return {
|
|
1557
|
+
found: true,
|
|
1558
|
+
complete: false,
|
|
1559
|
+
value
|
|
1560
|
+
};
|
|
1561
|
+
};
|
|
1562
|
+
var extractStreamingStringValues = (args) => {
|
|
1563
|
+
return locateValueStarts(args).flatMap((match) => {
|
|
1564
|
+
const current = extractStreamingStringValue({
|
|
1565
|
+
text: args.text,
|
|
1566
|
+
path: match.path
|
|
1567
|
+
});
|
|
1568
|
+
return current.found ? [
|
|
1569
|
+
{
|
|
1570
|
+
path: match.path,
|
|
1571
|
+
complete: current.complete,
|
|
1572
|
+
value: current.value
|
|
1573
|
+
}
|
|
1574
|
+
] : [];
|
|
1575
|
+
});
|
|
1576
|
+
};
|
|
1577
|
+
var toErrorMessage2 = (error) => error instanceof Error ? error.message : String(error);
|
|
1578
|
+
var toToolDefinitions = (tools) => tools.map((tool) => ({
|
|
1579
|
+
name: tool.name,
|
|
1580
|
+
...tool.title ? { title: tool.title } : {},
|
|
1581
|
+
...tool.description ? { description: tool.description } : {},
|
|
1582
|
+
inputSchema: tool.inputSchema,
|
|
1583
|
+
...tool.outputSchema !== void 0 ? { outputSchema: tool.outputSchema } : {},
|
|
1584
|
+
...tool.annotations !== void 0 ? { annotations: tool.annotations } : {},
|
|
1585
|
+
...tool.metadata !== void 0 ? { metadata: tool.metadata } : {}
|
|
1586
|
+
}));
|
|
1587
|
+
var resolveToolExecutionBoolean = (value, toolName, fallback) => {
|
|
1588
|
+
if (typeof value === "boolean") return value;
|
|
1589
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1590
|
+
return value[toolName] ?? fallback;
|
|
1591
|
+
}
|
|
1592
|
+
return fallback;
|
|
1593
|
+
};
|
|
1594
|
+
var normalizeToolResult = (toolCall, value) => {
|
|
1595
|
+
if (value && typeof value === "object" && !Array.isArray(value) && "toolCallId" in value && "name" in value && "content" in value) {
|
|
1596
|
+
return value;
|
|
1597
|
+
}
|
|
1598
|
+
let content;
|
|
1599
|
+
try {
|
|
1600
|
+
content = typeof value === "string" ? value : JSON.stringify(value);
|
|
1601
|
+
} catch {
|
|
1602
|
+
content = String(value);
|
|
1603
|
+
}
|
|
1604
|
+
return {
|
|
1605
|
+
toolCallId: toolCall.id,
|
|
1606
|
+
name: toolCall.name,
|
|
1607
|
+
content,
|
|
1608
|
+
...value !== void 0 && value !== null && typeof value === "object" && !Array.isArray(value) ? { structuredContent: value } : {}
|
|
1609
|
+
};
|
|
1610
|
+
};
|
|
1611
|
+
var closeOwnedTools = async (tools) => {
|
|
1612
|
+
const closeFns = /* @__PURE__ */ new Set();
|
|
1613
|
+
for (const tool of tools) {
|
|
1614
|
+
if (tool.closeAfterUse === false || !tool.close) continue;
|
|
1615
|
+
closeFns.add(tool.close);
|
|
1616
|
+
}
|
|
1617
|
+
await Promise.all(
|
|
1618
|
+
[...closeFns].map(async (close) => {
|
|
1619
|
+
await close();
|
|
1620
|
+
})
|
|
1621
|
+
);
|
|
1622
|
+
};
|
|
1623
|
+
var createInitialMessages = (args) => [
|
|
1624
|
+
...typeof args.system === "string" && args.system.length > 0 ? [{ role: "system", content: args.system }] : [],
|
|
1625
|
+
{ role: "user", content: String(args.input ?? "") }
|
|
1626
|
+
];
|
|
1627
|
+
var runReasonToolLoop = async (args) => {
|
|
1628
|
+
const { useReasonArgs, id, opId, traceSpan } = args;
|
|
1629
|
+
const ctx = getHookContext();
|
|
1630
|
+
const tools = useReasonArgs.tools ?? [];
|
|
1631
|
+
const toolByName = /* @__PURE__ */ new Map();
|
|
1632
|
+
let validationCompleted = false;
|
|
1633
|
+
try {
|
|
1634
|
+
for (const tool of tools) {
|
|
1635
|
+
if (toolByName.has(tool.name)) {
|
|
1636
|
+
throw new Error(
|
|
1637
|
+
`useReason received duplicate tool name "${tool.name}".`
|
|
1638
|
+
);
|
|
1639
|
+
}
|
|
1640
|
+
toolByName.set(tool.name, tool);
|
|
1641
|
+
}
|
|
1642
|
+
if (useReasonArgs.interrupt) {
|
|
1643
|
+
throw new Error(
|
|
1644
|
+
"useReason tools cannot be combined with useReason interrupt mode yet. Use toolExecution.approval for tool approval."
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1647
|
+
validationCompleted = true;
|
|
1648
|
+
} finally {
|
|
1649
|
+
if (!validationCompleted) {
|
|
1650
|
+
await closeOwnedTools(tools);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
const toolDefinitions = toToolDefinitions(tools);
|
|
1654
|
+
const maxSteps = Math.max(1, useReasonArgs.toolExecution?.maxSteps ?? 3);
|
|
1655
|
+
const messages = createInitialMessages({
|
|
1656
|
+
system: useReasonArgs.system,
|
|
1657
|
+
input: useReasonArgs.input
|
|
1658
|
+
});
|
|
1659
|
+
const steps = [];
|
|
1660
|
+
const allToolCalls = [];
|
|
1661
|
+
const allToolResults = [];
|
|
1662
|
+
let finalText = "";
|
|
1663
|
+
let finalRaw;
|
|
1664
|
+
let finalOutput;
|
|
1665
|
+
let aggregatedUsage;
|
|
1666
|
+
let finalFinishReason;
|
|
1667
|
+
let aggregatedProviderMetadata;
|
|
1668
|
+
let aggregatedWarnings;
|
|
1669
|
+
const emitToolEvent = (event, payload) => {
|
|
1670
|
+
ctx.node.emit(event, {
|
|
1671
|
+
...payload,
|
|
1672
|
+
node: ctx.node.graph.node,
|
|
1673
|
+
...id ? { id } : {},
|
|
1674
|
+
opId
|
|
1675
|
+
});
|
|
1676
|
+
};
|
|
1677
|
+
try {
|
|
1678
|
+
for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
|
|
1679
|
+
traceSpan?.addEvent?.("useReason.tool-step.start", {
|
|
1680
|
+
stepIndex,
|
|
1681
|
+
toolCount: toolDefinitions.length
|
|
1682
|
+
});
|
|
1683
|
+
const step = await reasonEngine(
|
|
1684
|
+
{
|
|
1685
|
+
...useReasonArgs,
|
|
1686
|
+
tools: toolDefinitions,
|
|
1687
|
+
messages,
|
|
1688
|
+
emit: false,
|
|
1689
|
+
stream: false
|
|
1690
|
+
},
|
|
1691
|
+
{ ...id ? { id } : {}, opId }
|
|
1692
|
+
);
|
|
1693
|
+
finalText = step.text;
|
|
1694
|
+
finalRaw = step.raw;
|
|
1695
|
+
aggregatedUsage = mergeUsage(aggregatedUsage, step.usage);
|
|
1696
|
+
finalFinishReason = step.finishReason;
|
|
1697
|
+
aggregatedProviderMetadata = mergeProviderMetadata(
|
|
1698
|
+
aggregatedProviderMetadata,
|
|
1699
|
+
step.providerMetadata
|
|
1700
|
+
);
|
|
1701
|
+
aggregatedWarnings = mergeWarnings(aggregatedWarnings, step.warnings);
|
|
1702
|
+
const toolCalls = step.toolCalls ?? [];
|
|
1703
|
+
const toolResults = [];
|
|
1704
|
+
steps.push({
|
|
1705
|
+
stepIndex,
|
|
1706
|
+
text: step.text,
|
|
1707
|
+
toolCalls,
|
|
1708
|
+
toolResults,
|
|
1709
|
+
...step.usage ? { usage: step.usage } : {},
|
|
1710
|
+
...step.finishReason ? { finishReason: step.finishReason } : {},
|
|
1711
|
+
...step.providerMetadata ? { providerMetadata: step.providerMetadata } : {},
|
|
1712
|
+
...step.warnings ? { warnings: step.warnings } : {}
|
|
1713
|
+
});
|
|
1714
|
+
if (toolCalls.length === 0) {
|
|
1715
|
+
if ((useReasonArgs.emit ?? true) && step.text.length > 0) {
|
|
1716
|
+
emitToolEvent("text-start", {});
|
|
1717
|
+
emitToolEvent("text-delta", { delta: step.text });
|
|
1718
|
+
emitToolEvent("text-end", {});
|
|
1719
|
+
}
|
|
1720
|
+
break;
|
|
1721
|
+
}
|
|
1722
|
+
allToolCalls.push(...toolCalls);
|
|
1723
|
+
messages.push({
|
|
1724
|
+
role: "assistant",
|
|
1725
|
+
content: step.text,
|
|
1726
|
+
toolCalls
|
|
1727
|
+
});
|
|
1728
|
+
for (const toolCall of toolCalls) {
|
|
1729
|
+
const tool = toolByName.get(toolCall.name);
|
|
1730
|
+
if (!tool) {
|
|
1731
|
+
throw new Error(
|
|
1732
|
+
`Model requested unknown tool "${toolCall.name}" from useReason.`
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
const shouldEmitTool = resolveToolExecutionBoolean(
|
|
1736
|
+
useReasonArgs.toolExecution?.emit,
|
|
1737
|
+
tool.name,
|
|
1738
|
+
false
|
|
1739
|
+
);
|
|
1740
|
+
const shouldApproveTool = resolveToolExecutionBoolean(
|
|
1741
|
+
useReasonArgs.toolExecution?.approval,
|
|
1742
|
+
tool.name,
|
|
1743
|
+
false
|
|
1744
|
+
);
|
|
1745
|
+
traceSpan?.addEvent?.("useReason.tool-call.start", {
|
|
1746
|
+
stepIndex,
|
|
1747
|
+
tool: tool.name,
|
|
1748
|
+
toolCallId: toolCall.id
|
|
1749
|
+
});
|
|
1750
|
+
if (shouldEmitTool) {
|
|
1751
|
+
emitToolEvent("tool-call-start", {
|
|
1752
|
+
tool: tool.name,
|
|
1753
|
+
toolCallId: toolCall.id,
|
|
1754
|
+
input: toolCall.input
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
if (shouldApproveTool) {
|
|
1758
|
+
const approval = await awaitInterruptInternal({
|
|
1759
|
+
id: `tool:${toolCall.id}`,
|
|
1760
|
+
request: {
|
|
1761
|
+
kind: "choice",
|
|
1762
|
+
question: `Approve ${tool.name}?`,
|
|
1763
|
+
options: [
|
|
1764
|
+
{ id: "approve", label: "Approve" },
|
|
1765
|
+
{ id: "deny", label: "Deny" }
|
|
1766
|
+
],
|
|
1767
|
+
meta: {
|
|
1768
|
+
tool: tool.name,
|
|
1769
|
+
toolCallId: toolCall.id,
|
|
1770
|
+
input: toolCall.input
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
});
|
|
1774
|
+
const approved = Array.isArray(approval) ? approval.includes("approve") : approval === "approve";
|
|
1775
|
+
if (!approved) {
|
|
1776
|
+
const denied = {
|
|
1777
|
+
toolCallId: toolCall.id,
|
|
1778
|
+
name: tool.name,
|
|
1779
|
+
content: "Tool call denied by user.",
|
|
1780
|
+
isError: true
|
|
1781
|
+
};
|
|
1782
|
+
toolResults.push(denied);
|
|
1783
|
+
allToolResults.push(denied);
|
|
1784
|
+
if (shouldEmitTool) {
|
|
1785
|
+
emitToolEvent("tool-call-result", {
|
|
1786
|
+
tool: tool.name,
|
|
1787
|
+
toolCallId: toolCall.id,
|
|
1788
|
+
content: denied.content,
|
|
1789
|
+
isError: true
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1792
|
+
continue;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
try {
|
|
1796
|
+
const rawResult = await tool.execute(toolCall.input, {
|
|
1797
|
+
toolCallId: toolCall.id,
|
|
1798
|
+
...useReasonArgs.abortSignal ? { abortSignal: useReasonArgs.abortSignal } : {}
|
|
1799
|
+
});
|
|
1800
|
+
const result2 = normalizeToolResult(toolCall, rawResult);
|
|
1801
|
+
toolResults.push(result2);
|
|
1802
|
+
allToolResults.push(result2);
|
|
1803
|
+
if (shouldEmitTool) {
|
|
1804
|
+
emitToolEvent("tool-call-result", {
|
|
1805
|
+
tool: result2.name,
|
|
1806
|
+
toolCallId: result2.toolCallId,
|
|
1807
|
+
content: result2.content,
|
|
1808
|
+
...result2.structuredContent !== void 0 ? { structuredContent: result2.structuredContent } : {},
|
|
1809
|
+
...result2.isError !== void 0 ? { isError: result2.isError } : {}
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
traceSpan?.addEvent?.("useReason.tool-call.complete", {
|
|
1813
|
+
stepIndex,
|
|
1814
|
+
tool: result2.name,
|
|
1815
|
+
toolCallId: result2.toolCallId,
|
|
1816
|
+
isError: Boolean(result2.isError)
|
|
1817
|
+
});
|
|
1818
|
+
} catch (error) {
|
|
1819
|
+
const result2 = {
|
|
1820
|
+
toolCallId: toolCall.id,
|
|
1821
|
+
name: tool.name,
|
|
1822
|
+
content: toErrorMessage2(error),
|
|
1823
|
+
isError: true
|
|
1824
|
+
};
|
|
1825
|
+
toolResults.push(result2);
|
|
1826
|
+
allToolResults.push(result2);
|
|
1827
|
+
if (shouldEmitTool) {
|
|
1828
|
+
emitToolEvent("tool-call-error", {
|
|
1829
|
+
tool: tool.name,
|
|
1830
|
+
toolCallId: toolCall.id,
|
|
1831
|
+
message: result2.content
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
traceSpan?.addEvent?.("useReason.tool-call.error", {
|
|
1835
|
+
stepIndex,
|
|
1836
|
+
tool: tool.name,
|
|
1837
|
+
toolCallId: toolCall.id,
|
|
1838
|
+
message: result2.content
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
const currentStep = steps[steps.length - 1];
|
|
1843
|
+
if (currentStep) {
|
|
1844
|
+
steps[steps.length - 1] = {
|
|
1845
|
+
...currentStep,
|
|
1846
|
+
toolResults
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
for (const result2 of toolResults) {
|
|
1850
|
+
messages.push({
|
|
1851
|
+
role: "tool",
|
|
1852
|
+
content: result2.content,
|
|
1853
|
+
toolCallId: result2.toolCallId,
|
|
1854
|
+
name: result2.name,
|
|
1855
|
+
...result2.structuredContent !== void 0 ? { structuredContent: result2.structuredContent } : {},
|
|
1856
|
+
...result2.isError !== void 0 ? { isError: result2.isError } : {},
|
|
1857
|
+
...result2.raw !== void 0 ? { raw: result2.raw } : {}
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
if (steps.length >= maxSteps && steps.at(-1)?.toolCalls.length) {
|
|
1862
|
+
throw new Error(
|
|
1863
|
+
`useReason tool loop reached maxSteps (${maxSteps}) before producing a final response.`
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1866
|
+
if (useReasonArgs.outputSchema) {
|
|
1867
|
+
finalOutput = parseReasonOutputWithSchema({
|
|
1868
|
+
text: finalText,
|
|
1869
|
+
schema: useReasonArgs.outputSchema,
|
|
1870
|
+
...finalFinishReason ? { finishReason: finalFinishReason } : {},
|
|
1871
|
+
label: "useReason output"
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
if (finalOutput !== void 0) {
|
|
1875
|
+
emitReasonStructuredOutput({
|
|
1876
|
+
...id ? { id } : {},
|
|
1877
|
+
opId,
|
|
1878
|
+
output: finalOutput,
|
|
1879
|
+
structured: useReasonArgs.structured,
|
|
1880
|
+
emit: useReasonArgs.emit ?? true
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
const result = {
|
|
1884
|
+
...id ? { id } : {},
|
|
1885
|
+
opId,
|
|
1886
|
+
text: finalText,
|
|
1887
|
+
...finalRaw !== void 0 ? { raw: finalRaw } : {},
|
|
1888
|
+
...aggregatedUsage !== void 0 ? { usage: aggregatedUsage } : {},
|
|
1889
|
+
...finalFinishReason !== void 0 ? { finishReason: finalFinishReason } : {},
|
|
1890
|
+
...aggregatedProviderMetadata !== void 0 ? { providerMetadata: aggregatedProviderMetadata } : {},
|
|
1891
|
+
...aggregatedWarnings !== void 0 ? { warnings: aggregatedWarnings } : {},
|
|
1892
|
+
...finalOutput !== void 0 ? { output: finalOutput } : {},
|
|
1893
|
+
toolCalls: allToolCalls,
|
|
1894
|
+
toolResults: allToolResults,
|
|
1895
|
+
steps
|
|
1896
|
+
};
|
|
1897
|
+
traceSpan?.end?.({
|
|
1898
|
+
...aggregatedUsage !== void 0 ? { usage: aggregatedUsage } : {},
|
|
1899
|
+
...finalFinishReason !== void 0 ? { finishReason: finalFinishReason } : {},
|
|
1900
|
+
...aggregatedProviderMetadata !== void 0 ? { providerMetadata: aggregatedProviderMetadata } : {},
|
|
1901
|
+
...aggregatedWarnings !== void 0 ? { warnings: aggregatedWarnings } : {},
|
|
1902
|
+
attributes: {
|
|
1903
|
+
textLength: finalText.length,
|
|
1904
|
+
toolCallCount: allToolCalls.length,
|
|
1905
|
+
toolResultCount: allToolResults.length,
|
|
1906
|
+
toolStepCount: steps.length
|
|
1907
|
+
},
|
|
1908
|
+
telemetry: {
|
|
1909
|
+
...useReasonArgs.telemetry ?? {},
|
|
1910
|
+
output: useReasonArgs.telemetry?.output ?? finalOutput ?? finalText
|
|
1911
|
+
}
|
|
1912
|
+
});
|
|
1913
|
+
return result;
|
|
1914
|
+
} catch (error) {
|
|
1915
|
+
traceSpan?.fail?.(error, {
|
|
1916
|
+
attributes: {
|
|
1917
|
+
toolStepCount: steps.length,
|
|
1918
|
+
toolCallCount: allToolCalls.length
|
|
1919
|
+
}
|
|
1920
|
+
});
|
|
1921
|
+
throw error;
|
|
1922
|
+
} finally {
|
|
1923
|
+
await closeOwnedTools(tools);
|
|
1924
|
+
}
|
|
1925
|
+
};
|
|
1926
|
+
var shouldStreamReasonStructured = (args) => (args.emit ?? true) && shouldStreamStructured(args.structured);
|
|
1927
|
+
var resolveEffectiveReasoningIncludeThoughts = (args) => args.reasoning?.includeThoughts ?? args.model.options?.reasoning?.includeThoughts ?? false;
|
|
1928
|
+
var resolveEffectiveResponseFormatType = (args) => args.responseFormat?.type ?? args.model.options?.responseFormat?.type;
|
|
1929
|
+
var assertReasoningThoughtsCompatibility = (args) => {
|
|
1930
|
+
if (!resolveEffectiveReasoningIncludeThoughts(args)) return;
|
|
1931
|
+
const usesStructuredOutput = Boolean(args.outputSchema) || Boolean(args.interrupt) || Boolean(args.structured) || resolveEffectiveResponseFormatType(args) === "json";
|
|
1932
|
+
if (!usesStructuredOutput) return;
|
|
1933
|
+
throw new Error(
|
|
1934
|
+
"useReason does not support reasoning.includeThoughts with structured output, interrupt mode, or JSON responseFormat. Disable includeThoughts for this call."
|
|
1935
|
+
);
|
|
1936
|
+
};
|
|
1937
|
+
async function useReason(args) {
|
|
1938
|
+
assertReasoningThoughtsCompatibility(args);
|
|
1939
|
+
const ctx = getHookContext();
|
|
1940
|
+
const id = typeof args.id === "string" && args.id.length > 0 ? args.id : void 0;
|
|
1941
|
+
const opId = createRuntimeId2();
|
|
1942
|
+
const checkpointKey = resolveReasonCheckpointKey({
|
|
1943
|
+
...id ? { id } : {},
|
|
1944
|
+
autoIndex: ctx.reasonCallIndex++
|
|
1945
|
+
});
|
|
1946
|
+
const existingCompleted = readReasonCompletedCheckpoint(
|
|
1947
|
+
ctx.currentNodeState.byKey[checkpointKey]
|
|
1948
|
+
);
|
|
1949
|
+
if (existingCompleted) {
|
|
1950
|
+
return existingCompleted.result;
|
|
1951
|
+
}
|
|
1952
|
+
const existingCheckpoint = readReasonCheckpoint(
|
|
1953
|
+
ctx.currentNodeState.byKey[checkpointKey]
|
|
1954
|
+
);
|
|
1955
|
+
const reasonMeta = typeof id === "string" ? { id, opId } : { opId };
|
|
1956
|
+
const traceSpan = getReasonTraceAdapter()?.startSpan({
|
|
1957
|
+
name: "useReason",
|
|
1958
|
+
attributes: {
|
|
1959
|
+
...id ? { id } : {},
|
|
1960
|
+
opId,
|
|
1961
|
+
nodeId: ctx.node.graph.node,
|
|
1962
|
+
providerId: args.model.provider.id,
|
|
1963
|
+
modelId: args.model.modelId,
|
|
1964
|
+
stream: args.stream ?? args.model.options?.streaming ?? true,
|
|
1965
|
+
emit: args.emit ?? true,
|
|
1966
|
+
...args.telemetry?.operation ? { operation: args.telemetry.operation } : {},
|
|
1967
|
+
...args.telemetry?.prompt?.name ? { promptName: args.telemetry.prompt.name } : {},
|
|
1968
|
+
...args.telemetry?.prompt?.version !== void 0 ? { promptVersion: args.telemetry.prompt.version } : {},
|
|
1969
|
+
...args.telemetry?.prompt?.type ? { promptType: args.telemetry.prompt.type } : {},
|
|
1970
|
+
hasOutputSchema: Boolean(args.outputSchema),
|
|
1971
|
+
hasInterrupt: Boolean(args.interrupt),
|
|
1972
|
+
hasStructured: Boolean(args.structured),
|
|
1973
|
+
hasTools: Boolean(args.tools?.length)
|
|
1974
|
+
},
|
|
1975
|
+
telemetry: {
|
|
1976
|
+
...args.telemetry ?? {},
|
|
1977
|
+
input: args.telemetry?.input ?? args.input
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1980
|
+
if (args.tools?.length) {
|
|
1981
|
+
return runReasonToolLoop({
|
|
1982
|
+
useReasonArgs: args,
|
|
1983
|
+
...id ? { id } : {},
|
|
1984
|
+
opId,
|
|
1985
|
+
...traceSpan ? { traceSpan } : {}
|
|
1986
|
+
});
|
|
1987
|
+
}
|
|
1988
|
+
const suppressTextStream = Boolean(args.outputSchema || args.interrupt);
|
|
1989
|
+
const setFieldPaths = resolveSetFieldPaths(args.structured);
|
|
1990
|
+
const appendFieldPaths = resolveAppendFieldPaths(args.structured);
|
|
1991
|
+
const textDeltaFieldPaths = resolveTextDeltaFieldPaths(args.structured);
|
|
1992
|
+
const useStructuredIncrementalStreaming = Boolean(
|
|
1993
|
+
args.outputSchema && !args.interrupt && (setFieldPaths.length > 0 || appendFieldPaths.length > 0 || textDeltaFieldPaths.length > 0) && args.stream !== false && shouldStreamReasonStructured(args)
|
|
1994
|
+
);
|
|
1995
|
+
let firstText = "";
|
|
1996
|
+
let firstRaw;
|
|
1997
|
+
let firstOutput;
|
|
1998
|
+
let firstInterruptRequest;
|
|
1999
|
+
let firstInterruptRequired = false;
|
|
2000
|
+
let finalText = "";
|
|
2001
|
+
let finalRaw;
|
|
2002
|
+
let finalOutput;
|
|
2003
|
+
let aggregatedUsage;
|
|
2004
|
+
let finalFinishReason;
|
|
2005
|
+
let aggregatedProviderMetadata;
|
|
2006
|
+
let aggregatedWarnings;
|
|
2007
|
+
const emittedSetValues = /* @__PURE__ */ new Map();
|
|
2008
|
+
const emittedAppendCounts = /* @__PURE__ */ new Map();
|
|
2009
|
+
const emittedTextValues = /* @__PURE__ */ new Map();
|
|
2010
|
+
if (existingCheckpoint) {
|
|
2011
|
+
firstText = existingCheckpoint.firstText;
|
|
2012
|
+
firstRaw = existingCheckpoint.firstRaw;
|
|
2013
|
+
firstOutput = existingCheckpoint.firstOutput;
|
|
2014
|
+
finalText = firstText;
|
|
2015
|
+
finalRaw = firstRaw;
|
|
2016
|
+
finalOutput = firstOutput;
|
|
2017
|
+
aggregatedUsage = existingCheckpoint.firstUsage;
|
|
2018
|
+
finalFinishReason = existingCheckpoint.firstFinishReason;
|
|
2019
|
+
aggregatedProviderMetadata = existingCheckpoint.firstProviderMetadata;
|
|
2020
|
+
aggregatedWarnings = existingCheckpoint.firstWarnings;
|
|
2021
|
+
traceSpan?.addEvent?.("useReason.resume", {
|
|
2022
|
+
checkpointKey
|
|
2023
|
+
});
|
|
2024
|
+
} else {
|
|
2025
|
+
const first = await reasonEngine(
|
|
2026
|
+
{
|
|
2027
|
+
...args,
|
|
2028
|
+
emit: suppressTextStream ? false : args.emit,
|
|
2029
|
+
stream: useStructuredIncrementalStreaming ? true : suppressTextStream ? false : args.stream,
|
|
2030
|
+
...useStructuredIncrementalStreaming ? {
|
|
2031
|
+
onTextChunk: (delta) => {
|
|
2032
|
+
firstText += delta;
|
|
2033
|
+
finalText = firstText;
|
|
2034
|
+
for (const fieldPath of setFieldPaths) {
|
|
2035
|
+
const values = extractCompletedFieldValues({
|
|
2036
|
+
text: firstText,
|
|
2037
|
+
path: fieldPath
|
|
2038
|
+
});
|
|
2039
|
+
for (const { path, value } of values) {
|
|
2040
|
+
const nextSerialized = JSON.stringify(value);
|
|
2041
|
+
if (emittedSetValues.get(path) === nextSerialized) continue;
|
|
2042
|
+
emittedSetValues.set(path, nextSerialized);
|
|
2043
|
+
emitStructuredData({
|
|
2044
|
+
kind: "set",
|
|
2045
|
+
path,
|
|
2046
|
+
value,
|
|
2047
|
+
dataType: args.structured?.dataType ?? "reason-output",
|
|
2048
|
+
...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
|
|
2049
|
+
...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
|
|
2050
|
+
...id ? { id } : {},
|
|
2051
|
+
streamId: opId
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
for (const fieldPath of appendFieldPaths) {
|
|
2056
|
+
const groups = extractCompletedArrayItemGroups({
|
|
2057
|
+
text: firstText,
|
|
2058
|
+
path: fieldPath
|
|
2059
|
+
});
|
|
2060
|
+
for (const { path, items } of groups) {
|
|
2061
|
+
const emittedAppendCount = emittedAppendCounts.get(path) ?? 0;
|
|
2062
|
+
if (items.length <= emittedAppendCount) continue;
|
|
2063
|
+
const nextItems = items.slice(emittedAppendCount);
|
|
2064
|
+
emittedAppendCounts.set(path, items.length);
|
|
2065
|
+
emitStructuredData({
|
|
2066
|
+
kind: "append",
|
|
2067
|
+
path,
|
|
2068
|
+
items: nextItems,
|
|
2069
|
+
dataType: args.structured?.dataType ?? "reason-output",
|
|
2070
|
+
...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
|
|
2071
|
+
...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
|
|
2072
|
+
...id ? { id } : {},
|
|
2073
|
+
streamId: opId
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
for (const fieldPath of textDeltaFieldPaths) {
|
|
2078
|
+
const values = extractStreamingStringValues({
|
|
2079
|
+
text: firstText,
|
|
2080
|
+
path: fieldPath
|
|
2081
|
+
});
|
|
2082
|
+
for (const { path, value } of values) {
|
|
2083
|
+
const emittedTextValue = emittedTextValues.get(path) ?? "";
|
|
2084
|
+
if (value.length <= emittedTextValue.length || !value.startsWith(emittedTextValue)) {
|
|
2085
|
+
continue;
|
|
2086
|
+
}
|
|
2087
|
+
const nextDelta = value.slice(emittedTextValue.length);
|
|
2088
|
+
emittedTextValues.set(path, value);
|
|
2089
|
+
if (nextDelta.length === 0) continue;
|
|
2090
|
+
emitStructuredData({
|
|
2091
|
+
kind: "text-delta",
|
|
2092
|
+
path,
|
|
2093
|
+
delta: nextDelta,
|
|
2094
|
+
dataType: args.structured?.dataType ?? "reason-output",
|
|
2095
|
+
...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
|
|
2096
|
+
...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
|
|
2097
|
+
...id ? { id } : {},
|
|
2098
|
+
streamId: opId
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
} : {}
|
|
2104
|
+
},
|
|
2105
|
+
reasonMeta,
|
|
2106
|
+
args.interrupt ? defaultInterruptFirstPassInput({
|
|
2107
|
+
input: args.input,
|
|
2108
|
+
requestSchema: args.interrupt.requestSchema,
|
|
2109
|
+
mode: args.interrupt.mode ?? "required",
|
|
2110
|
+
...args.outputSchema ? { outputSchema: args.outputSchema } : {}
|
|
2111
|
+
}) : args.outputSchema ? withOutputGuardrails(
|
|
2112
|
+
withStructuredStreamHints(args.input, {
|
|
2113
|
+
...setFieldPaths.length > 0 ? { setFieldPaths } : {},
|
|
2114
|
+
...appendFieldPaths.length > 0 ? { appendFieldPaths } : {},
|
|
2115
|
+
...textDeltaFieldPaths.length > 0 ? { textDeltaFieldPaths } : {}
|
|
2116
|
+
}),
|
|
2117
|
+
args.outputSchema
|
|
2118
|
+
) : void 0
|
|
2119
|
+
);
|
|
2120
|
+
firstRaw = first.raw;
|
|
2121
|
+
finalRaw = first.raw;
|
|
2122
|
+
accumulateTokenUsage(first.usage);
|
|
2123
|
+
aggregatedUsage = mergeUsage(aggregatedUsage, first.usage);
|
|
2124
|
+
finalFinishReason = first.finishReason;
|
|
2125
|
+
aggregatedProviderMetadata = mergeProviderMetadata(
|
|
2126
|
+
aggregatedProviderMetadata,
|
|
2127
|
+
first.providerMetadata
|
|
2128
|
+
);
|
|
2129
|
+
aggregatedWarnings = mergeWarnings(aggregatedWarnings, first.warnings);
|
|
2130
|
+
traceSpan?.addEvent?.("useReason.first-pass.complete", {
|
|
2131
|
+
textLength: first.text.length,
|
|
2132
|
+
hasInterrupt: Boolean(args.interrupt)
|
|
2133
|
+
});
|
|
2134
|
+
if (args.interrupt) {
|
|
2135
|
+
const firstPass = parseInterruptFirstPassResult({
|
|
2136
|
+
text: first.text,
|
|
2137
|
+
requestSchema: args.interrupt.requestSchema,
|
|
2138
|
+
...first.finishReason ? { finishReason: first.finishReason } : {},
|
|
2139
|
+
...args.outputSchema ? { outputSchema: args.outputSchema } : {},
|
|
2140
|
+
mode: args.interrupt.mode ?? "required"
|
|
2141
|
+
});
|
|
2142
|
+
firstText = firstPass.draftText;
|
|
2143
|
+
finalText = firstPass.draftText;
|
|
2144
|
+
firstInterruptRequired = firstPass.interruptRequired;
|
|
2145
|
+
firstInterruptRequest = firstPass.request;
|
|
2146
|
+
firstOutput = firstPass.output;
|
|
2147
|
+
finalOutput = firstPass.output;
|
|
2148
|
+
} else {
|
|
2149
|
+
firstText = first.text;
|
|
2150
|
+
finalText = first.text;
|
|
2151
|
+
if (args.outputSchema) {
|
|
2152
|
+
firstOutput = parseReasonOutputWithSchema({
|
|
2153
|
+
text: first.text,
|
|
2154
|
+
schema: args.outputSchema,
|
|
2155
|
+
...first.finishReason ? { finishReason: first.finishReason } : {},
|
|
2156
|
+
label: "useReason output"
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
finalOutput = firstOutput;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
let interruptResponse;
|
|
2163
|
+
if (args.interrupt && (existingCheckpoint || firstInterruptRequired)) {
|
|
2164
|
+
const requestSchema = args.interrupt.requestSchema;
|
|
2165
|
+
const interruptRequest = existingCheckpoint ? parseWithSchema(
|
|
2166
|
+
requestSchema,
|
|
2167
|
+
existingCheckpoint.request,
|
|
2168
|
+
"useReason interrupt.request"
|
|
2169
|
+
) : firstInterruptRequest;
|
|
2170
|
+
if (!interruptRequest) {
|
|
2171
|
+
throw new Error(
|
|
2172
|
+
"useReason interrupt request is missing; first pass did not produce a valid interrupt payload."
|
|
2173
|
+
);
|
|
2174
|
+
}
|
|
2175
|
+
if (!existingCheckpoint) {
|
|
2176
|
+
ctx.currentNodeState.byKey[checkpointKey] = {
|
|
2177
|
+
status: "awaiting_interrupt",
|
|
2178
|
+
request: interruptRequest,
|
|
2179
|
+
firstText,
|
|
2180
|
+
...firstRaw !== void 0 ? { firstRaw } : {},
|
|
2181
|
+
...aggregatedUsage !== void 0 ? { firstUsage: aggregatedUsage } : {},
|
|
2182
|
+
...finalFinishReason !== void 0 ? { firstFinishReason: finalFinishReason } : {},
|
|
2183
|
+
...aggregatedProviderMetadata !== void 0 ? { firstProviderMetadata: aggregatedProviderMetadata } : {},
|
|
2184
|
+
...aggregatedWarnings !== void 0 ? { firstWarnings: aggregatedWarnings } : {},
|
|
2185
|
+
...firstOutput !== void 0 ? { firstOutput } : {}
|
|
2186
|
+
};
|
|
2187
|
+
ctx.stateDirty = true;
|
|
2188
|
+
}
|
|
2189
|
+
const resumeStatePatch = resolveHookStatePatch({
|
|
2190
|
+
nodeId: ctx.node.graph.node,
|
|
2191
|
+
currentNodeState: ctx.currentNodeState,
|
|
2192
|
+
workflowState: ctx.workflowState
|
|
2193
|
+
});
|
|
2194
|
+
interruptResponse = await awaitInterruptInternal({
|
|
2195
|
+
request: interruptRequest,
|
|
2196
|
+
requestSchema,
|
|
2197
|
+
responseSchema: args.interrupt.responseSchema,
|
|
2198
|
+
...args.interrupt.schemaId ? { schemaId: args.interrupt.schemaId } : {},
|
|
2199
|
+
...args.interrupt.schemaVersion ? { schemaVersion: args.interrupt.schemaVersion } : {},
|
|
2200
|
+
...id ? { id } : {},
|
|
2201
|
+
meta: {
|
|
2202
|
+
__kortyxResumeStatePatch: resumeStatePatch
|
|
2203
|
+
}
|
|
2204
|
+
});
|
|
2205
|
+
traceSpan?.addEvent?.("useReason.interrupt.resolved", {
|
|
2206
|
+
checkpointKey
|
|
2207
|
+
});
|
|
2208
|
+
if (Object.hasOwn(ctx.currentNodeState.byKey, checkpointKey)) {
|
|
2209
|
+
delete ctx.currentNodeState.byKey[checkpointKey];
|
|
2210
|
+
ctx.stateDirty = true;
|
|
2211
|
+
}
|
|
2212
|
+
const continuationInput = defaultContinuationInput({
|
|
2213
|
+
input: args.input,
|
|
2214
|
+
draftText: firstText,
|
|
2215
|
+
...firstOutput !== void 0 ? { draftOutput: firstOutput } : {},
|
|
2216
|
+
request: interruptRequest,
|
|
2217
|
+
response: interruptResponse
|
|
2218
|
+
});
|
|
2219
|
+
const second = await reasonEngine(
|
|
2220
|
+
{
|
|
2221
|
+
...args,
|
|
2222
|
+
emit: suppressTextStream ? false : args.emit,
|
|
2223
|
+
stream: suppressTextStream ? false : args.stream
|
|
2224
|
+
},
|
|
2225
|
+
reasonMeta,
|
|
2226
|
+
args.outputSchema ? withOutputGuardrails(
|
|
2227
|
+
continuationInput,
|
|
2228
|
+
args.outputSchema
|
|
2229
|
+
) : continuationInput
|
|
2230
|
+
);
|
|
2231
|
+
finalText = second.text;
|
|
2232
|
+
finalRaw = second.raw;
|
|
2233
|
+
accumulateTokenUsage(second.usage);
|
|
2234
|
+
aggregatedUsage = mergeUsage(aggregatedUsage, second.usage);
|
|
2235
|
+
finalFinishReason = second.finishReason;
|
|
2236
|
+
aggregatedProviderMetadata = mergeProviderMetadata(
|
|
2237
|
+
aggregatedProviderMetadata,
|
|
2238
|
+
second.providerMetadata
|
|
2239
|
+
);
|
|
2240
|
+
aggregatedWarnings = mergeWarnings(aggregatedWarnings, second.warnings);
|
|
2241
|
+
traceSpan?.addEvent?.("useReason.continuation.complete", {
|
|
2242
|
+
textLength: second.text.length
|
|
2243
|
+
});
|
|
2244
|
+
if (args.outputSchema) {
|
|
2245
|
+
finalOutput = parseReasonOutputWithSchema({
|
|
2246
|
+
text: finalText,
|
|
2247
|
+
schema: args.outputSchema,
|
|
2248
|
+
...second.finishReason ? { finishReason: second.finishReason } : {},
|
|
2249
|
+
label: "useReason output"
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
if (finalOutput !== void 0) {
|
|
2254
|
+
emitReasonStructuredOutput({
|
|
2255
|
+
...id ? { id } : {},
|
|
2256
|
+
opId,
|
|
2257
|
+
output: finalOutput,
|
|
2258
|
+
structured: args.structured,
|
|
2259
|
+
emit: args.emit ?? true
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
const result = {
|
|
2263
|
+
...id ? { id } : {},
|
|
2264
|
+
opId,
|
|
2265
|
+
text: finalText,
|
|
2266
|
+
...finalRaw !== void 0 ? { raw: finalRaw } : {},
|
|
2267
|
+
...aggregatedUsage !== void 0 ? { usage: aggregatedUsage } : {},
|
|
2268
|
+
...finalFinishReason !== void 0 ? { finishReason: finalFinishReason } : {},
|
|
2269
|
+
...aggregatedProviderMetadata !== void 0 ? { providerMetadata: aggregatedProviderMetadata } : {},
|
|
2270
|
+
...aggregatedWarnings !== void 0 ? { warnings: aggregatedWarnings } : {},
|
|
2271
|
+
...finalOutput !== void 0 ? { output: finalOutput } : {},
|
|
2272
|
+
...interruptResponse !== void 0 ? { interruptResponse } : {}
|
|
2273
|
+
};
|
|
2274
|
+
if (args.interrupt) {
|
|
2275
|
+
ctx.currentNodeState.byKey[checkpointKey] = {
|
|
2276
|
+
status: "completed",
|
|
2277
|
+
result
|
|
2278
|
+
};
|
|
2279
|
+
ctx.stateDirty = true;
|
|
2280
|
+
}
|
|
2281
|
+
traceSpan?.end?.({
|
|
2282
|
+
...aggregatedUsage !== void 0 ? { usage: aggregatedUsage } : {},
|
|
2283
|
+
...finalFinishReason !== void 0 ? { finishReason: finalFinishReason } : {},
|
|
2284
|
+
...aggregatedProviderMetadata !== void 0 ? { providerMetadata: aggregatedProviderMetadata } : {},
|
|
2285
|
+
...aggregatedWarnings !== void 0 ? { warnings: aggregatedWarnings } : {},
|
|
2286
|
+
attributes: {
|
|
2287
|
+
textLength: finalText.length,
|
|
2288
|
+
resumedFromCheckpoint: Boolean(existingCheckpoint),
|
|
2289
|
+
interrupted: Boolean(interruptResponse !== void 0)
|
|
2290
|
+
},
|
|
2291
|
+
telemetry: {
|
|
2292
|
+
...args.telemetry ?? {},
|
|
2293
|
+
output: args.telemetry?.output ?? finalOutput ?? finalText
|
|
2294
|
+
}
|
|
2295
|
+
});
|
|
2296
|
+
return result;
|
|
2297
|
+
}
|
|
2298
|
+
function useStructuredData(args) {
|
|
2299
|
+
emitStructuredData(args);
|
|
2300
|
+
}
|
|
2301
|
+
function useReason2(args) {
|
|
2302
|
+
return useReason(args);
|
|
2303
|
+
}
|
|
2304
|
+
function useInterrupt(args) {
|
|
2305
|
+
return awaitInterruptInternal(args);
|
|
2306
|
+
}
|
|
2307
|
+
function useRuntimeContext() {
|
|
2308
|
+
const ctx = getHookContext();
|
|
2309
|
+
const config = ctx.state.config;
|
|
2310
|
+
const context = config && typeof config === "object" && "context" in config ? config.context : void 0;
|
|
2311
|
+
return context && typeof context === "object" && !Array.isArray(context) ? context : {};
|
|
2312
|
+
}
|
|
2313
|
+
function useNodeState(initialValue) {
|
|
2314
|
+
const ctx = getHookContext();
|
|
2315
|
+
const nodeState = ctx.currentNodeState;
|
|
2316
|
+
const index = ctx.nodeStateIndex++;
|
|
2317
|
+
if (index >= nodeState.byIndex.length) {
|
|
2318
|
+
nodeState.byIndex[index] = initialValue;
|
|
2319
|
+
ctx.stateDirty = true;
|
|
2320
|
+
}
|
|
2321
|
+
const getValue = () => nodeState.byIndex[index];
|
|
2322
|
+
const setValue = (next) => {
|
|
2323
|
+
const prev = getValue();
|
|
2324
|
+
const resolved = typeof next === "function" ? next(prev) : next;
|
|
2325
|
+
nodeState.byIndex[index] = resolved;
|
|
2326
|
+
ctx.stateDirty = true;
|
|
2327
|
+
};
|
|
2328
|
+
return [getValue(), setValue];
|
|
2329
|
+
}
|
|
2330
|
+
function useWorkflowState(key, ...rest) {
|
|
2331
|
+
const ctx = getHookContext();
|
|
2332
|
+
const workflowState = ctx.workflowState;
|
|
2333
|
+
const hasInitial = rest.length > 0;
|
|
2334
|
+
const initialValue = rest[0];
|
|
2335
|
+
if (!Object.hasOwn(workflowState, key) && hasInitial) {
|
|
2336
|
+
workflowState[key] = initialValue;
|
|
2337
|
+
ctx.stateDirty = true;
|
|
2338
|
+
}
|
|
2339
|
+
const getValue = () => workflowState[key];
|
|
2340
|
+
const setValue = (next) => {
|
|
2341
|
+
const prev = getValue();
|
|
2342
|
+
const resolved = typeof next === "function" ? next(prev) : next;
|
|
2343
|
+
workflowState[key] = resolved;
|
|
2344
|
+
ctx.stateDirty = true;
|
|
2345
|
+
};
|
|
2346
|
+
return [getValue(), setValue];
|
|
2347
|
+
}
|
|
2348
|
+
var import_node_crypto5 = require("crypto");
|
|
2349
|
+
var import_node_crypto42 = require("crypto");
|
|
2350
|
+
function emitWorkflowCall2(telemetry, type, payload) {
|
|
2351
|
+
const base = telemetry?.correlation;
|
|
2352
|
+
if (!telemetry?.reporter || !telemetry.environment || !telemetry.service || !base?.runId || !base.workflowId)
|
|
2353
|
+
return;
|
|
2354
|
+
try {
|
|
2355
|
+
const active = telemetry.trace?.getActiveContext?.();
|
|
2356
|
+
void Promise.resolve(
|
|
2357
|
+
telemetry.reporter.emit([
|
|
2358
|
+
{
|
|
2359
|
+
schemaVersion: 1,
|
|
2360
|
+
eventId: (0, import_node_crypto42.randomUUID)(),
|
|
2361
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2362
|
+
environment: telemetry.environment,
|
|
2363
|
+
service: telemetry.service,
|
|
2364
|
+
correlation: {
|
|
2365
|
+
...base,
|
|
2366
|
+
runId: base.runId,
|
|
2367
|
+
workflowId: base.workflowId,
|
|
2368
|
+
...active
|
|
2369
|
+
},
|
|
2370
|
+
type,
|
|
2371
|
+
payload
|
|
2372
|
+
}
|
|
2373
|
+
])
|
|
2374
|
+
).catch(() => {
|
|
2375
|
+
});
|
|
2376
|
+
} catch {
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
function workflowCallContent(telemetry, side, value) {
|
|
2380
|
+
const policy = telemetry?.captureContent;
|
|
2381
|
+
if (!(policy === true || policy && typeof policy === "object" && policy[side]))
|
|
2382
|
+
return {};
|
|
2383
|
+
const serialized = JSON.stringify(value);
|
|
2384
|
+
return serialized && Buffer.byteLength(serialized, "utf8") <= 16384 ? { [side]: JSON.parse(serialized) } : { [`${side}Omitted`]: "Content exceeds 16 KiB" };
|
|
2385
|
+
}
|
|
2386
|
+
var WorkflowCallError = class extends Error {
|
|
2387
|
+
constructor(workflow, message) {
|
|
2388
|
+
super(`Workflow '${workflow}': ${message}`);
|
|
2389
|
+
this.workflow = workflow;
|
|
2390
|
+
this.name = "WorkflowCallError";
|
|
2391
|
+
}
|
|
2392
|
+
workflow;
|
|
2393
|
+
};
|
|
2394
|
+
function workflowCallFingerprint(value) {
|
|
2395
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2396
|
+
const canonical = (v) => {
|
|
2397
|
+
if (v === null || typeof v === "string" || typeof v === "boolean") return v;
|
|
2398
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
2399
|
+
if (typeof v !== "object" || !v || seen.has(v)) {
|
|
2400
|
+
throw new Error("Workflow input and output must be JSON serializable.");
|
|
2401
|
+
}
|
|
2402
|
+
if (!Array.isArray(v) && Object.getPrototypeOf(v) !== Object.prototype) {
|
|
2403
|
+
throw new Error(
|
|
2404
|
+
"Workflow input and output must contain plain JSON objects."
|
|
2405
|
+
);
|
|
2406
|
+
}
|
|
2407
|
+
seen.add(v);
|
|
2408
|
+
const result = Array.isArray(v) ? v.map(canonical) : Object.fromEntries(
|
|
2409
|
+
Object.keys(v).sort().map((key) => [
|
|
2410
|
+
key,
|
|
2411
|
+
canonical(v[key])
|
|
2412
|
+
])
|
|
2413
|
+
);
|
|
2414
|
+
seen.delete(v);
|
|
2415
|
+
return result;
|
|
2416
|
+
};
|
|
2417
|
+
return JSON.stringify(canonical(value));
|
|
2418
|
+
}
|
|
2419
|
+
function createWorkflowHooks(workflows) {
|
|
2420
|
+
return {
|
|
2421
|
+
useWorkflow: (args) => {
|
|
2422
|
+
const workflow = workflows[args.workflow];
|
|
2423
|
+
if (!workflow || workflow.id !== args.workflow)
|
|
2424
|
+
throw new Error("Workflow registry keys must match definition ids.");
|
|
2425
|
+
return useWorkflow({ ...args, workflow });
|
|
2426
|
+
}
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
async function useWorkflow(args) {
|
|
2430
|
+
const ctx = getHookContext();
|
|
2431
|
+
const service = ctx.node.callWorkflow;
|
|
2432
|
+
if (!service)
|
|
2433
|
+
throw new Error("useWorkflow requires an agent workflow registry.");
|
|
2434
|
+
if (!args.id.trim())
|
|
2435
|
+
throw new Error("useWorkflow requires a stable, nonempty id.");
|
|
2436
|
+
if (ctx.workflowCallActive)
|
|
2437
|
+
throw new Error(
|
|
2438
|
+
"Concurrent workflow calls are not supported; await each call."
|
|
2439
|
+
);
|
|
2440
|
+
if (ctx.workflowCallIds.has(args.id))
|
|
2441
|
+
throw new Error(
|
|
2442
|
+
`Duplicate workflow call id '${args.id}' in one node activation.`
|
|
2443
|
+
);
|
|
2444
|
+
if (ctx.workflowCallIds.size >= 64)
|
|
2445
|
+
throw new Error(
|
|
2446
|
+
"A node may call at most 64 child workflows per activation."
|
|
2447
|
+
);
|
|
2448
|
+
ctx.workflowCallIds.add(args.id);
|
|
2449
|
+
const definition = typeof args.workflow === "string" ? void 0 : args.workflow;
|
|
2450
|
+
const workflow = definition?.id ?? args.workflow;
|
|
2451
|
+
const input = args.input;
|
|
2452
|
+
const fingerprint = workflowCallFingerprint({
|
|
2453
|
+
workflow,
|
|
2454
|
+
input,
|
|
2455
|
+
version: definition?.version ?? null
|
|
2456
|
+
});
|
|
2457
|
+
const key = `__useWorkflow:${args.id}`;
|
|
2458
|
+
let record = ctx.currentNodeState.byKey[key];
|
|
2459
|
+
if (record && record.fingerprint !== fingerprint)
|
|
2460
|
+
throw new WorkflowCallError(
|
|
2461
|
+
workflow,
|
|
2462
|
+
"call input or target changed during replay"
|
|
2463
|
+
);
|
|
2464
|
+
record ??= {
|
|
2465
|
+
fingerprint,
|
|
2466
|
+
invocationId: (0, import_node_crypto5.randomUUID)(),
|
|
2467
|
+
status: "running",
|
|
2468
|
+
interrupts: []
|
|
2469
|
+
};
|
|
2470
|
+
ctx.currentNodeState.byKey[key] = record;
|
|
2471
|
+
ctx.stateDirty = true;
|
|
2472
|
+
ctx.workflowCallActive = true;
|
|
2473
|
+
const telemetry = ctx.node.workflowCallTelemetry;
|
|
2474
|
+
const activationKey = "__useWorkflowActivation";
|
|
2475
|
+
ctx.currentNodeState.byKey[activationKey] ??= (0, import_node_crypto5.randomUUID)();
|
|
2476
|
+
const callerNodeExecutionId = ctx.currentNodeState.byKey[activationKey];
|
|
2477
|
+
const metadata2 = {
|
|
2478
|
+
invocationId: record.invocationId,
|
|
2479
|
+
parentInvocationId: telemetry?.correlation?.invocationId ?? null,
|
|
2480
|
+
branchId: telemetry?.correlation?.branchId ?? telemetry?.correlation?.runId ?? "unknown",
|
|
2481
|
+
callId: args.id,
|
|
2482
|
+
callerNodeId: ctx.node.graph.node,
|
|
2483
|
+
callerNodeExecutionId,
|
|
2484
|
+
sourceWorkflowId: ctx.node.graph.name,
|
|
2485
|
+
targetWorkflowId: workflow,
|
|
2486
|
+
targetVersion: definition?.version ?? null
|
|
2487
|
+
};
|
|
2488
|
+
record.telemetry = metadata2;
|
|
2489
|
+
const report = (status, content = {}) => {
|
|
2490
|
+
record.sequence = (record.sequence ?? 0) + 1;
|
|
2491
|
+
emitWorkflowCall2(telemetry, `workflow.call.${status}`, {
|
|
2492
|
+
...metadata2,
|
|
2493
|
+
sequence: record.sequence,
|
|
2494
|
+
...content
|
|
2495
|
+
});
|
|
2496
|
+
};
|
|
2497
|
+
const cached = record.status === "completed";
|
|
2498
|
+
try {
|
|
2499
|
+
for (const interruption of record.interrupts) {
|
|
2500
|
+
interruption.response = await awaitInterruptInternal({
|
|
2501
|
+
request: interruption.request,
|
|
2502
|
+
...interruption.request.meta ? { meta: interruption.request.meta } : {}
|
|
2503
|
+
});
|
|
2504
|
+
}
|
|
2505
|
+
if (record.status === "failed")
|
|
2506
|
+
throw new WorkflowCallError(workflow, record.error ?? "child failed");
|
|
2507
|
+
while (record.status !== "completed") {
|
|
2508
|
+
let outcome;
|
|
2509
|
+
try {
|
|
2510
|
+
report(
|
|
2511
|
+
record.snapshot ? "resumed" : "started",
|
|
2512
|
+
workflowCallContent(telemetry, "input", input)
|
|
2513
|
+
);
|
|
2514
|
+
outcome = await service({
|
|
2515
|
+
callerNodeExecutionId,
|
|
2516
|
+
id: args.id,
|
|
2517
|
+
workflow,
|
|
2518
|
+
input,
|
|
2519
|
+
definition,
|
|
2520
|
+
invocationId: record.invocationId,
|
|
2521
|
+
...record.snapshot ? { snapshot: record.snapshot } : {},
|
|
2522
|
+
...record.interrupts.length ? { response: record.interrupts.at(-1)?.response } : {}
|
|
2523
|
+
});
|
|
2524
|
+
} catch (error) {
|
|
2525
|
+
record.status = "failed";
|
|
2526
|
+
record.error = error instanceof Error ? error.message : String(error);
|
|
2527
|
+
report("failed", { error: "Child workflow failed" });
|
|
2528
|
+
throw new WorkflowCallError(workflow, record.error);
|
|
2529
|
+
}
|
|
2530
|
+
if (outcome.status === "completed") {
|
|
2531
|
+
record.data = outcome.data;
|
|
2532
|
+
accumulateTokenUsage(outcome.usage);
|
|
2533
|
+
workflowCallFingerprint(record.data);
|
|
2534
|
+
record.status = "completed";
|
|
2535
|
+
delete record.snapshot;
|
|
2536
|
+
report(
|
|
2537
|
+
"completed",
|
|
2538
|
+
workflowCallContent(telemetry, "output", record.data)
|
|
2539
|
+
);
|
|
2540
|
+
} else {
|
|
2541
|
+
record.status = "interrupted";
|
|
2542
|
+
record.snapshot = outcome.snapshot;
|
|
2543
|
+
report("suspended", {
|
|
2544
|
+
leaf: outcome.request.meta?.workflowCall ?? null
|
|
2545
|
+
});
|
|
2546
|
+
const interruption = {
|
|
2547
|
+
request: outcome.request
|
|
2548
|
+
};
|
|
2549
|
+
record.interrupts.push(interruption);
|
|
2550
|
+
interruption.response = await awaitInterruptInternal({
|
|
2551
|
+
request: interruption.request,
|
|
2552
|
+
...interruption.request.meta ? { meta: interruption.request.meta } : {}
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
if (cached)
|
|
2557
|
+
report("reused", workflowCallContent(telemetry, "output", record.data));
|
|
2558
|
+
const data = record.data ?? {};
|
|
2559
|
+
return {
|
|
2560
|
+
data: JSON.parse(JSON.stringify(data))
|
|
2561
|
+
};
|
|
2562
|
+
} finally {
|
|
2563
|
+
ctx.workflowCallActive = false;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
});
|
|
18
2568
|
|
|
19
2569
|
// src/index.ts
|
|
20
2570
|
var index_exports = {};
|
|
@@ -29,6 +2579,7 @@ __export(index_exports, {
|
|
|
29
2579
|
parseChatRequestBody: () => parseChatRequestBody,
|
|
30
2580
|
parseCheckpointRequestBody: () => parseCheckpointRequestBody,
|
|
31
2581
|
parseResumeMeta: () => parseResumeMeta,
|
|
2582
|
+
projectWorkflowTopology: () => projectWorkflowTopology,
|
|
32
2583
|
streamChat: () => streamChat,
|
|
33
2584
|
streamChatFromRoute: () => streamChatFromRoute,
|
|
34
2585
|
transformGraphStreamForUI: () => transformGraphStreamForUI,
|
|
@@ -187,6 +2738,329 @@ var import_providers = require("@kortyx/providers");
|
|
|
187
2738
|
var import_runtime4 = require("@kortyx/runtime");
|
|
188
2739
|
var import_zod2 = require("zod");
|
|
189
2740
|
|
|
2741
|
+
// src/telemetry/events.ts
|
|
2742
|
+
var import_node_crypto = require("crypto");
|
|
2743
|
+
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2744
|
+
var createId = () => {
|
|
2745
|
+
try {
|
|
2746
|
+
return (0, import_node_crypto.randomUUID)();
|
|
2747
|
+
} catch {
|
|
2748
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2749
|
+
}
|
|
2750
|
+
};
|
|
2751
|
+
var shouldCaptureTelemetryContent = (value, side) => {
|
|
2752
|
+
if (value === true) return true;
|
|
2753
|
+
return Boolean(
|
|
2754
|
+
value && typeof value === "object" && !Array.isArray(value) && value[side] === true
|
|
2755
|
+
);
|
|
2756
|
+
};
|
|
2757
|
+
var emitTelemetryEvent = (args) => {
|
|
2758
|
+
const telemetry = isRecord(args.config.telemetry) ? args.config.telemetry : void 0;
|
|
2759
|
+
const base = telemetry?.correlation;
|
|
2760
|
+
const runId = args.correlation?.runId ?? base?.runId;
|
|
2761
|
+
const workflowId = args.correlation?.workflowId ?? base?.workflowId;
|
|
2762
|
+
if (!telemetry?.reporter || !telemetry.environment || !telemetry.service || !runId || !workflowId) {
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
const context = isRecord(args.config.context) ? args.config.context : {};
|
|
2766
|
+
const event = {
|
|
2767
|
+
schemaVersion: 1,
|
|
2768
|
+
eventId: createId(),
|
|
2769
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2770
|
+
environment: telemetry.environment,
|
|
2771
|
+
service: telemetry.service,
|
|
2772
|
+
correlation: {
|
|
2773
|
+
runId,
|
|
2774
|
+
workflowId,
|
|
2775
|
+
...base?.sessionId ? { sessionId: base.sessionId } : {},
|
|
2776
|
+
...base?.workflowRevisionId ? { workflowRevisionId: base.workflowRevisionId } : {},
|
|
2777
|
+
...base?.topologyHash ? { topologyHash: base.topologyHash } : {},
|
|
2778
|
+
...base?.nodeId ? { nodeId: base.nodeId } : {},
|
|
2779
|
+
...args.correlation ?? {}
|
|
2780
|
+
},
|
|
2781
|
+
...telemetry.metadata || telemetry.tags || context.userId || context.tenantId ? {
|
|
2782
|
+
context: {
|
|
2783
|
+
...typeof context.userId === "string" ? { userId: context.userId } : {},
|
|
2784
|
+
...typeof context.tenantId === "string" ? { tenantId: context.tenantId } : {},
|
|
2785
|
+
...telemetry.tags ? { tags: telemetry.tags } : {},
|
|
2786
|
+
...telemetry.metadata ? { metadata: telemetry.metadata } : {}
|
|
2787
|
+
}
|
|
2788
|
+
} : {},
|
|
2789
|
+
type: args.type,
|
|
2790
|
+
payload: {
|
|
2791
|
+
...args.payload,
|
|
2792
|
+
...args.type === "run.cancelled" ? { branchId: args.config.executionBranchId ?? runId } : {}
|
|
2793
|
+
}
|
|
2794
|
+
};
|
|
2795
|
+
try {
|
|
2796
|
+
void Promise.resolve(telemetry.reporter.emit([event])).then(async () => {
|
|
2797
|
+
if (!args.flush) return;
|
|
2798
|
+
const flush = telemetry.flush;
|
|
2799
|
+
if (typeof flush === "function") await flush.call(telemetry);
|
|
2800
|
+
}).catch(() => {
|
|
2801
|
+
});
|
|
2802
|
+
} catch {
|
|
2803
|
+
}
|
|
2804
|
+
};
|
|
2805
|
+
|
|
2806
|
+
// src/telemetry/topology.ts
|
|
2807
|
+
var import_node_crypto2 = require("crypto");
|
|
2808
|
+
var isRecord2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2809
|
+
var stableValue = (value) => {
|
|
2810
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
2811
|
+
if (!isRecord2(value)) return value;
|
|
2812
|
+
return Object.fromEntries(
|
|
2813
|
+
Object.entries(value).filter(([, nested]) => typeof nested !== "function").sort(([a], [b]) => a.localeCompare(b)).map(([key, nested]) => [key, stableValue(nested)])
|
|
2814
|
+
);
|
|
2815
|
+
};
|
|
2816
|
+
var asString = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
|
|
2817
|
+
var metadata = (value) => isRecord2(value) ? stableValue(value) : void 0;
|
|
2818
|
+
var tagsFrom = (workflow) => {
|
|
2819
|
+
const raw = isRecord2(workflow.metadata) ? workflow.metadata.tags : void 0;
|
|
2820
|
+
if (!Array.isArray(raw)) return void 0;
|
|
2821
|
+
const tags = raw.filter((tag) => typeof tag === "string").sort();
|
|
2822
|
+
return tags.length > 0 ? tags : void 0;
|
|
2823
|
+
};
|
|
2824
|
+
var wordsFrom = (value) => value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").split(/[^a-zA-Z0-9]+/).map((word) => word.toLowerCase()).filter(Boolean);
|
|
2825
|
+
var camelFromWorkflowId = (workflowId) => {
|
|
2826
|
+
const [first = "", ...rest] = wordsFrom(workflowId);
|
|
2827
|
+
return [
|
|
2828
|
+
first,
|
|
2829
|
+
...rest.map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`)
|
|
2830
|
+
].join("");
|
|
2831
|
+
};
|
|
2832
|
+
var resolveMemberTransitionTarget = (memberName, knownWorkflowIds) => {
|
|
2833
|
+
const exact = knownWorkflowIds.find(
|
|
2834
|
+
(workflowId) => camelFromWorkflowId(workflowId) === memberName
|
|
2835
|
+
);
|
|
2836
|
+
if (exact) return exact;
|
|
2837
|
+
const memberWords = new Set(wordsFrom(memberName));
|
|
2838
|
+
const candidates = knownWorkflowIds.map((workflowId) => {
|
|
2839
|
+
const workflowWords = wordsFrom(workflowId);
|
|
2840
|
+
const matched = workflowWords.filter((word) => memberWords.has(word));
|
|
2841
|
+
return {
|
|
2842
|
+
workflowId,
|
|
2843
|
+
matched: matched.length,
|
|
2844
|
+
total: workflowWords.length
|
|
2845
|
+
};
|
|
2846
|
+
}).filter(
|
|
2847
|
+
(candidate) => candidate.total > 0 && candidate.matched === candidate.total
|
|
2848
|
+
).sort(
|
|
2849
|
+
(a, b) => b.matched - a.matched || a.workflowId.localeCompare(b.workflowId)
|
|
2850
|
+
);
|
|
2851
|
+
if (candidates.length !== 1) return void 0;
|
|
2852
|
+
return candidates[0]?.workflowId;
|
|
2853
|
+
};
|
|
2854
|
+
var transitionTargetsFromRun = (run, knownWorkflowIds) => {
|
|
2855
|
+
if (typeof run !== "function") return [];
|
|
2856
|
+
const source = Function.prototype.toString.call(run);
|
|
2857
|
+
const targets = /* @__PURE__ */ new Set();
|
|
2858
|
+
for (const match of source.matchAll(
|
|
2859
|
+
/\btransitionTo\s*:\s*(['"`])([^'"`$]+)\1/g
|
|
2860
|
+
)) {
|
|
2861
|
+
const target = match[2]?.trim();
|
|
2862
|
+
if (target) targets.add(target);
|
|
2863
|
+
}
|
|
2864
|
+
for (const match of source.matchAll(
|
|
2865
|
+
/\btransitionTo\s*:\s*(?:[A-Za-z_$][\w$]*\.)+([A-Za-z_$][\w$]*)/g
|
|
2866
|
+
)) {
|
|
2867
|
+
const memberName = match[1];
|
|
2868
|
+
const target = resolveMemberTransitionTarget(memberName, knownWorkflowIds);
|
|
2869
|
+
if (target) targets.add(target);
|
|
2870
|
+
}
|
|
2871
|
+
for (const match of source.matchAll(
|
|
2872
|
+
/\btransitionTo\s*:\s*(?:[A-Za-z_$][\w$]*\.)*[A-Za-z_$][\w$]*\[['"`]([A-Za-z_$][\w$]*)['"`]\]/g
|
|
2873
|
+
)) {
|
|
2874
|
+
const memberName = match[1];
|
|
2875
|
+
const target = resolveMemberTransitionTarget(memberName, knownWorkflowIds);
|
|
2876
|
+
if (target) targets.add(target);
|
|
2877
|
+
}
|
|
2878
|
+
return [...targets].sort();
|
|
2879
|
+
};
|
|
2880
|
+
var transitionsFromNodes = (workflow, knownWorkflowIds) => {
|
|
2881
|
+
const transitions = /* @__PURE__ */ new Map();
|
|
2882
|
+
for (const [sourceNodeId, node] of Object.entries(
|
|
2883
|
+
workflow.nodes
|
|
2884
|
+
)) {
|
|
2885
|
+
for (const targetWorkflowId of transitionTargetsFromRun(
|
|
2886
|
+
node.run,
|
|
2887
|
+
knownWorkflowIds
|
|
2888
|
+
)) {
|
|
2889
|
+
if (targetWorkflowId === workflow.id) continue;
|
|
2890
|
+
const key = `${sourceNodeId}:${targetWorkflowId}`;
|
|
2891
|
+
transitions.set(key, { sourceNodeId, targetWorkflowId });
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
return [...transitions.values()].sort(
|
|
2895
|
+
(a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))
|
|
2896
|
+
);
|
|
2897
|
+
};
|
|
2898
|
+
var projectWorkflowTopology = (args) => {
|
|
2899
|
+
const nodes = Object.entries(
|
|
2900
|
+
args.workflow.nodes
|
|
2901
|
+
).map(([id, node]) => {
|
|
2902
|
+
const nodeMetadata = metadata(node.metadata);
|
|
2903
|
+
const params = metadata(node.params);
|
|
2904
|
+
const model = isRecord2(node.params) ? isRecord2(node.params.model) ? node.params.model : void 0 : void 0;
|
|
2905
|
+
const provider = isRecord2(model) ? asString(model.provider) ?? asString(model.providerId) : void 0;
|
|
2906
|
+
const modelName = isRecord2(model) ? asString(model.name) ?? asString(model.modelId) : void 0;
|
|
2907
|
+
return {
|
|
2908
|
+
id,
|
|
2909
|
+
...asString(nodeMetadata?.label) ? { label: asString(nodeMetadata?.label) } : {},
|
|
2910
|
+
...asString(nodeMetadata?.type) ? { type: asString(nodeMetadata?.type) } : {},
|
|
2911
|
+
...provider ? { provider } : {},
|
|
2912
|
+
...modelName ? { model: modelName } : {},
|
|
2913
|
+
...nodeMetadata ? { metadata: nodeMetadata } : {},
|
|
2914
|
+
// These fields feed the hash but are intentionally not sent as Studio
|
|
2915
|
+
// display metadata. They are stripped below after hashing.
|
|
2916
|
+
_execution: {
|
|
2917
|
+
...typeof node.run === "string" ? { run: node.run } : {},
|
|
2918
|
+
...params ? { params } : {},
|
|
2919
|
+
...node.behavior ? { behavior: stableValue(node.behavior) } : {}
|
|
2920
|
+
}
|
|
2921
|
+
};
|
|
2922
|
+
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
2923
|
+
const edges = args.workflow.edges.map((edge) => {
|
|
2924
|
+
const condition = isRecord2(edge[2]) ? asString(edge[2].when) : void 0;
|
|
2925
|
+
return {
|
|
2926
|
+
sourceNodeId: edge[0],
|
|
2927
|
+
targetNodeId: edge[1],
|
|
2928
|
+
...condition ? { condition } : {}
|
|
2929
|
+
};
|
|
2930
|
+
}).sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
2931
|
+
const transitions = transitionsFromNodes(
|
|
2932
|
+
args.workflow,
|
|
2933
|
+
args.knownWorkflowIds ?? []
|
|
2934
|
+
);
|
|
2935
|
+
const tags = tagsFrom(args.workflow);
|
|
2936
|
+
const hashInput = stableValue({
|
|
2937
|
+
workflowId: args.workflow.id,
|
|
2938
|
+
nodes: nodes.map((node) => ({ id: node.id, ...node._execution })),
|
|
2939
|
+
edges,
|
|
2940
|
+
transitions
|
|
2941
|
+
});
|
|
2942
|
+
const topologyHash = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(hashInput)).digest("hex");
|
|
2943
|
+
return {
|
|
2944
|
+
schemaVersion: 1,
|
|
2945
|
+
environment: args.environment,
|
|
2946
|
+
service: args.service,
|
|
2947
|
+
workflow: {
|
|
2948
|
+
id: args.workflow.id,
|
|
2949
|
+
declaredVersion: args.workflow.version,
|
|
2950
|
+
...args.workflow.description ? { description: args.workflow.description } : {},
|
|
2951
|
+
...tags ? { tags } : {},
|
|
2952
|
+
topologyHash,
|
|
2953
|
+
nodes: nodes.map(({ _execution: _ignored, ...node }) => node),
|
|
2954
|
+
edges,
|
|
2955
|
+
...transitions.length > 0 ? { transitions } : {}
|
|
2956
|
+
}
|
|
2957
|
+
};
|
|
2958
|
+
};
|
|
2959
|
+
var prepareWorkflowTelemetry = (args) => {
|
|
2960
|
+
const telemetry = isRecord2(args.config.telemetry) ? args.config.telemetry : void 0;
|
|
2961
|
+
if (!telemetry?.reporter || !telemetry.environment || !telemetry.service) {
|
|
2962
|
+
return args.config;
|
|
2963
|
+
}
|
|
2964
|
+
let snapshot;
|
|
2965
|
+
try {
|
|
2966
|
+
snapshot = projectWorkflowTopology({
|
|
2967
|
+
workflow: args.workflow,
|
|
2968
|
+
environment: telemetry.environment,
|
|
2969
|
+
service: telemetry.service,
|
|
2970
|
+
knownWorkflowIds: args.knownWorkflowIds
|
|
2971
|
+
});
|
|
2972
|
+
} catch {
|
|
2973
|
+
return args.config;
|
|
2974
|
+
}
|
|
2975
|
+
let workflowRevisionId;
|
|
2976
|
+
try {
|
|
2977
|
+
workflowRevisionId = telemetry.reporter.getWorkflowRevisionId?.({
|
|
2978
|
+
environment: snapshot.environment,
|
|
2979
|
+
workflowId: snapshot.workflow.id,
|
|
2980
|
+
topologyHash: snapshot.workflow.topologyHash
|
|
2981
|
+
});
|
|
2982
|
+
void Promise.resolve(
|
|
2983
|
+
telemetry.reporter.ensureWorkflowTopology(snapshot)
|
|
2984
|
+
).catch(() => {
|
|
2985
|
+
});
|
|
2986
|
+
} catch {
|
|
2987
|
+
}
|
|
2988
|
+
return {
|
|
2989
|
+
...args.config,
|
|
2990
|
+
prepareChildTelemetry: (workflow, config) => prepareWorkflowTelemetry({
|
|
2991
|
+
config,
|
|
2992
|
+
workflow,
|
|
2993
|
+
runId: args.runId,
|
|
2994
|
+
sessionId: args.sessionId,
|
|
2995
|
+
knownWorkflowIds: args.knownWorkflowIds
|
|
2996
|
+
}),
|
|
2997
|
+
telemetry: {
|
|
2998
|
+
...telemetry,
|
|
2999
|
+
correlation: {
|
|
3000
|
+
...telemetry.correlation ?? {},
|
|
3001
|
+
runId: args.runId,
|
|
3002
|
+
...args.sessionId ? { sessionId: args.sessionId } : {},
|
|
3003
|
+
workflowId: snapshot.workflow.id,
|
|
3004
|
+
topologyHash: snapshot.workflow.topologyHash,
|
|
3005
|
+
...workflowRevisionId ? { workflowRevisionId } : {}
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
};
|
|
3009
|
+
};
|
|
3010
|
+
|
|
3011
|
+
// src/telemetry/workflow-call-branch.ts
|
|
3012
|
+
var import_node_crypto3 = require("crypto");
|
|
3013
|
+
var import_hooks = __toESM(require_dist());
|
|
3014
|
+
function restoreWorkflowCallBranch(requests, telemetry, sourceRunId) {
|
|
3015
|
+
const branchId = (0, import_node_crypto3.randomUUID)();
|
|
3016
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
3017
|
+
for (const request of requests) {
|
|
3018
|
+
if (request.state)
|
|
3019
|
+
request.state.config = {
|
|
3020
|
+
...request.state.config,
|
|
3021
|
+
executionBranchId: branchId
|
|
3022
|
+
};
|
|
3023
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3024
|
+
const visit = (value) => {
|
|
3025
|
+
if (!value || typeof value !== "object" || seen.has(value)) return;
|
|
3026
|
+
seen.add(value);
|
|
3027
|
+
const record = value;
|
|
3028
|
+
if (typeof record.invocationId === "string" && typeof record.fingerprint === "string" && Array.isArray(record.interrupts)) {
|
|
3029
|
+
const previous = record.telemetry;
|
|
3030
|
+
if (previous) {
|
|
3031
|
+
record.sequence = 0;
|
|
3032
|
+
record.telemetry = { ...previous, branchId };
|
|
3033
|
+
if (!emitted.has(record.invocationId)) {
|
|
3034
|
+
emitted.add(record.invocationId);
|
|
3035
|
+
(0, import_hooks.emitWorkflowCall)(
|
|
3036
|
+
telemetry ? {
|
|
3037
|
+
...telemetry,
|
|
3038
|
+
correlation: {
|
|
3039
|
+
runId: request.runId,
|
|
3040
|
+
workflowId: request.workflow,
|
|
3041
|
+
...request.sessionId ? { sessionId: request.sessionId } : {}
|
|
3042
|
+
}
|
|
3043
|
+
} : void 0,
|
|
3044
|
+
"workflow.call.restored",
|
|
3045
|
+
{
|
|
3046
|
+
...previous,
|
|
3047
|
+
branchId,
|
|
3048
|
+
sourceBranchId: previous.branchId,
|
|
3049
|
+
sourceRunId,
|
|
3050
|
+
sequence: 0,
|
|
3051
|
+
status: record.status
|
|
3052
|
+
}
|
|
3053
|
+
);
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
for (const child of Object.values(record)) visit(child);
|
|
3058
|
+
};
|
|
3059
|
+
visit(request);
|
|
3060
|
+
}
|
|
3061
|
+
return branchId;
|
|
3062
|
+
}
|
|
3063
|
+
|
|
190
3064
|
// src/chat/process-chat.ts
|
|
191
3065
|
var import_runtime3 = require("@kortyx/runtime");
|
|
192
3066
|
|
|
@@ -263,50 +3137,71 @@ async function* transformGraphStreamForUI(stream, options = {}) {
|
|
|
263
3137
|
}
|
|
264
3138
|
|
|
265
3139
|
// src/orchestrator.ts
|
|
266
|
-
var
|
|
267
|
-
var isGraphState = (value) =>
|
|
3140
|
+
var isRecord3 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3141
|
+
var isGraphState = (value) => isRecord3(value) && typeof value.currentWorkflow === "string";
|
|
268
3142
|
var getGraphCheckpointId = (snapshot) => {
|
|
269
|
-
if (!
|
|
270
|
-
const config =
|
|
271
|
-
const configurable = config &&
|
|
3143
|
+
if (!isRecord3(snapshot)) return void 0;
|
|
3144
|
+
const config = isRecord3(snapshot.config) ? snapshot.config : void 0;
|
|
3145
|
+
const configurable = config && isRecord3(config.configurable) ? config.configurable : void 0;
|
|
272
3146
|
const checkpointId = configurable?.checkpoint_id;
|
|
273
3147
|
return typeof checkpointId === "string" && checkpointId.length > 0 ? checkpointId : void 0;
|
|
274
3148
|
};
|
|
275
3149
|
var getTraceAdapter = (config) => {
|
|
276
|
-
const telemetry =
|
|
3150
|
+
const telemetry = isRecord3(config.telemetry) ? config.telemetry : void 0;
|
|
277
3151
|
const traceAdapter = telemetry?.trace ?? config.reasonTrace;
|
|
278
|
-
return
|
|
3152
|
+
return isRecord3(traceAdapter) ? traceAdapter : void 0;
|
|
279
3153
|
};
|
|
280
3154
|
async function orchestrateGraphStream({
|
|
281
3155
|
sessionId,
|
|
282
3156
|
runId,
|
|
283
3157
|
graph,
|
|
284
3158
|
state,
|
|
285
|
-
config,
|
|
3159
|
+
config: initialConfig,
|
|
286
3160
|
selectWorkflow,
|
|
3161
|
+
knownWorkflowIds,
|
|
287
3162
|
frameworkAdapter
|
|
288
3163
|
}) {
|
|
289
3164
|
const out = new import_node_stream.PassThrough({ objectMode: true });
|
|
3165
|
+
let config = initialConfig;
|
|
290
3166
|
let currentGraph = graph;
|
|
291
3167
|
let currentState = state;
|
|
292
3168
|
let finished = false;
|
|
293
3169
|
let structuredSeq = 0;
|
|
294
3170
|
const debugEnabled = Boolean(config?.features?.tracing);
|
|
295
3171
|
const traceAdapter = getTraceAdapter(config);
|
|
296
|
-
const contextMeta =
|
|
297
|
-
const telemetryConfig =
|
|
3172
|
+
const contextMeta = isRecord3(config.context) ? config.context : {};
|
|
3173
|
+
const telemetryConfig = isRecord3(config.telemetry) ? config.telemetry : {};
|
|
3174
|
+
const emitResumeFailure = (resumeError) => {
|
|
3175
|
+
const interruptId = typeof config.telemetryInterruptId === "string" ? config.telemetryInterruptId : void 0;
|
|
3176
|
+
if (!interruptId) return;
|
|
3177
|
+
emitTelemetryEvent({
|
|
3178
|
+
config,
|
|
3179
|
+
type: "interrupt.resolved",
|
|
3180
|
+
correlation: typeof config.telemetryInterruptNodeId === "string" ? { nodeId: config.telemetryInterruptNodeId } : void 0,
|
|
3181
|
+
payload: {
|
|
3182
|
+
interruptId,
|
|
3183
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3184
|
+
resumeOutcome: "failed",
|
|
3185
|
+
resumeError: resumeError instanceof Error ? resumeError.message : String(resumeError)
|
|
3186
|
+
},
|
|
3187
|
+
flush: true
|
|
3188
|
+
});
|
|
3189
|
+
};
|
|
3190
|
+
const telemetryCorrelation = isRecord3(telemetryConfig.correlation) ? telemetryConfig.correlation : {};
|
|
298
3191
|
const runSpanArgs = {
|
|
299
3192
|
name: "kortyx.run",
|
|
300
3193
|
attributes: {
|
|
301
3194
|
...sessionId ? { sessionId } : {},
|
|
302
3195
|
runId,
|
|
303
3196
|
workflowId: state.currentWorkflow,
|
|
3197
|
+
...typeof telemetryCorrelation.topologyHash === "string" ? { topologyHash: telemetryCorrelation.topologyHash } : {},
|
|
3198
|
+
...typeof telemetryCorrelation.workflowRevisionId === "string" ? { workflowRevisionId: telemetryCorrelation.workflowRevisionId } : {},
|
|
304
3199
|
...typeof contextMeta.userId === "string" ? { userId: contextMeta.userId } : {},
|
|
305
3200
|
...typeof contextMeta.tenantId === "string" ? { tenantId: contextMeta.tenantId } : {}
|
|
306
3201
|
},
|
|
307
3202
|
telemetry: {
|
|
308
3203
|
metadata: {
|
|
309
|
-
...
|
|
3204
|
+
...isRecord3(telemetryConfig.metadata) ? telemetryConfig.metadata : {},
|
|
310
3205
|
...contextMeta
|
|
311
3206
|
},
|
|
312
3207
|
tags: Array.isArray(telemetryConfig.tags) ? telemetryConfig.tags : void 0,
|
|
@@ -328,7 +3223,9 @@ async function orchestrateGraphStream({
|
|
|
328
3223
|
}
|
|
329
3224
|
const pending = {
|
|
330
3225
|
to: null,
|
|
331
|
-
payload: {}
|
|
3226
|
+
payload: {},
|
|
3227
|
+
sourceNodeId: void 0,
|
|
3228
|
+
sourceWorkflowId: void 0
|
|
332
3229
|
};
|
|
333
3230
|
let lastStatusMsg = "";
|
|
334
3231
|
let lastStatusAt = 0;
|
|
@@ -350,6 +3247,21 @@ async function orchestrateGraphStream({
|
|
|
350
3247
|
let pendingRecordToken = null;
|
|
351
3248
|
let wroteHumanInput = false;
|
|
352
3249
|
const pendingStore = frameworkAdapter?.pendingRequests;
|
|
3250
|
+
const sealPendingSnapshots = async () => {
|
|
3251
|
+
if (typeof frameworkAdapter?.checkpointer?.getTuple !== "function") return;
|
|
3252
|
+
for (const [token, request] of activePendingRequests) {
|
|
3253
|
+
const graphSnapshot = await (0, import_runtime.captureGraphSnapshot)(
|
|
3254
|
+
frameworkAdapter.checkpointer,
|
|
3255
|
+
runId,
|
|
3256
|
+
request.graphCheckpointId
|
|
3257
|
+
);
|
|
3258
|
+
if (graphSnapshot) {
|
|
3259
|
+
const sealed = { ...request, graphSnapshot };
|
|
3260
|
+
activePendingRequests.set(token, sealed);
|
|
3261
|
+
await pendingStore?.update(token, { graphSnapshot });
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
};
|
|
353
3265
|
const pendingTtlMs = frameworkAdapter?.ttlMs ?? 15 * 60 * 1e3;
|
|
354
3266
|
const persistAndEmitInterrupt = async (payload) => {
|
|
355
3267
|
if (wroteHumanInput) return;
|
|
@@ -430,6 +3342,37 @@ async function orchestrateGraphStream({
|
|
|
430
3342
|
}))
|
|
431
3343
|
}
|
|
432
3344
|
});
|
|
3345
|
+
emitTelemetryEvent({
|
|
3346
|
+
config,
|
|
3347
|
+
type: "interrupt.created",
|
|
3348
|
+
correlation: { nodeId: record.node },
|
|
3349
|
+
payload: {
|
|
3350
|
+
workflowCall: record.schema.meta?.workflowCall ?? null,
|
|
3351
|
+
workflowCallPath: record.schema.meta?.workflowCallPath ?? null,
|
|
3352
|
+
branchId: config.executionBranchId ?? runId,
|
|
3353
|
+
interruptId: record.requestId,
|
|
3354
|
+
requestId: record.requestId,
|
|
3355
|
+
kind: record.schema.kind,
|
|
3356
|
+
interactionMode: record.schema.kind === "text" ? "freeform" : record.options.length > 0 ? "static-options" : record.schema.schemaId ? "dynamic-picker" : "unknown",
|
|
3357
|
+
...record.schema.schemaId ? { schemaId: record.schema.schemaId } : {},
|
|
3358
|
+
...record.schema.schemaVersion ? { schemaVersion: record.schema.schemaVersion } : {},
|
|
3359
|
+
...typeof record.schema.question === "string" && shouldCaptureTelemetryContent(telemetryConfig.captureContent, "output") ? { question: record.schema.question } : {},
|
|
3360
|
+
...shouldCaptureTelemetryContent(
|
|
3361
|
+
telemetryConfig.captureContent,
|
|
3362
|
+
"output"
|
|
3363
|
+
) ? {
|
|
3364
|
+
options: record.options.map((option) => ({
|
|
3365
|
+
id: option.id,
|
|
3366
|
+
label: option.label,
|
|
3367
|
+
...option.description ? { description: option.description } : {}
|
|
3368
|
+
}))
|
|
3369
|
+
} : {},
|
|
3370
|
+
optionCount: record.options.length,
|
|
3371
|
+
nodeId: record.node,
|
|
3372
|
+
expiresAt: new Date(record.createdAt + record.ttlMs).toISOString()
|
|
3373
|
+
},
|
|
3374
|
+
flush: true
|
|
3375
|
+
});
|
|
433
3376
|
wroteHumanInput = true;
|
|
434
3377
|
};
|
|
435
3378
|
const forwardEmit = (event, payload) => {
|
|
@@ -595,6 +3538,9 @@ async function orchestrateGraphStream({
|
|
|
595
3538
|
});
|
|
596
3539
|
pending.to = payload?.transitionTo ?? null;
|
|
597
3540
|
pending.payload = payload?.payload ?? {};
|
|
3541
|
+
const transition = payload;
|
|
3542
|
+
pending.sourceNodeId = typeof transition.node === "string" ? transition.node : void 0;
|
|
3543
|
+
pending.sourceWorkflowId = typeof transition.workflow === "string" ? transition.workflow : void 0;
|
|
598
3544
|
return;
|
|
599
3545
|
}
|
|
600
3546
|
if (event === "interrupt") {
|
|
@@ -619,6 +3565,7 @@ async function orchestrateGraphStream({
|
|
|
619
3565
|
let workflowDoneData;
|
|
620
3566
|
currentGraph.config = currentGraph.config || {};
|
|
621
3567
|
currentGraph.config.emit = forwardEmit;
|
|
3568
|
+
currentGraph.config.executionRunId = runId;
|
|
622
3569
|
const threadId = currentGraph.config?.session?.id || sessionId || "anonymous-session";
|
|
623
3570
|
const checkpointNs = "";
|
|
624
3571
|
namespacesUsed.add(checkpointNs);
|
|
@@ -637,7 +3584,7 @@ async function orchestrateGraphStream({
|
|
|
637
3584
|
checkpoint_ns: checkpointNs
|
|
638
3585
|
}
|
|
639
3586
|
});
|
|
640
|
-
const values =
|
|
3587
|
+
const values = isRecord3(snapshot) && "values" in snapshot ? snapshot.values : snapshot;
|
|
641
3588
|
const checkpointId = getGraphCheckpointId(snapshot) ?? await readLatestGraphCheckpointId();
|
|
642
3589
|
if (!isGraphState(values)) {
|
|
643
3590
|
return checkpointId ? { state: currentState, checkpointId } : null;
|
|
@@ -696,11 +3643,40 @@ async function orchestrateGraphStream({
|
|
|
696
3643
|
}
|
|
697
3644
|
const transitionTo = pending.to;
|
|
698
3645
|
const transitionPayload = pending.payload;
|
|
3646
|
+
const sourceNodeId = pending.sourceNodeId;
|
|
3647
|
+
const sourceWorkflowId = pending.sourceWorkflowId;
|
|
699
3648
|
pending.to = null;
|
|
700
3649
|
pending.payload = {};
|
|
3650
|
+
pending.sourceNodeId = void 0;
|
|
3651
|
+
pending.sourceWorkflowId = void 0;
|
|
701
3652
|
if (transitionTo) {
|
|
702
3653
|
try {
|
|
703
3654
|
const nextWorkflow = await selectWorkflow(transitionTo);
|
|
3655
|
+
const sourceTelemetry = isRecord3(config.telemetry) ? config.telemetry : {};
|
|
3656
|
+
config = prepareWorkflowTelemetry({
|
|
3657
|
+
config,
|
|
3658
|
+
workflow: nextWorkflow,
|
|
3659
|
+
runId,
|
|
3660
|
+
sessionId,
|
|
3661
|
+
knownWorkflowIds
|
|
3662
|
+
});
|
|
3663
|
+
const targetTelemetry = isRecord3(config.telemetry) ? config.telemetry : {};
|
|
3664
|
+
emitTelemetryEvent({
|
|
3665
|
+
config,
|
|
3666
|
+
type: "workflow.transitioned",
|
|
3667
|
+
...sourceNodeId ? { correlation: { nodeId: sourceNodeId } } : {},
|
|
3668
|
+
payload: {
|
|
3669
|
+
sourceNodeId: sourceNodeId ?? "",
|
|
3670
|
+
sourceWorkflowId: sourceWorkflowId ?? String(currentState.currentWorkflow),
|
|
3671
|
+
...typeof sourceTelemetry.correlation === "object" && sourceTelemetry.correlation && typeof sourceTelemetry.correlation.workflowRevisionId === "string" ? {
|
|
3672
|
+
sourceWorkflowRevisionId: sourceTelemetry.correlation.workflowRevisionId
|
|
3673
|
+
} : {},
|
|
3674
|
+
targetWorkflowId: transitionTo,
|
|
3675
|
+
...typeof targetTelemetry.correlation === "object" && targetTelemetry.correlation && typeof targetTelemetry.correlation.workflowRevisionId === "string" ? {
|
|
3676
|
+
targetWorkflowRevisionId: targetTelemetry.correlation.workflowRevisionId
|
|
3677
|
+
} : {}
|
|
3678
|
+
}
|
|
3679
|
+
});
|
|
704
3680
|
const nextGraph = await (0, import_runtime.createExecutionGraph)(nextWorkflow, {
|
|
705
3681
|
...config,
|
|
706
3682
|
emit: forwardEmit
|
|
@@ -715,6 +3691,7 @@ async function orchestrateGraphStream({
|
|
|
715
3691
|
currentState = {
|
|
716
3692
|
...currentState,
|
|
717
3693
|
currentWorkflow: transitionTo,
|
|
3694
|
+
config,
|
|
718
3695
|
input: newInput,
|
|
719
3696
|
data: mergedData,
|
|
720
3697
|
ui: {}
|
|
@@ -783,6 +3760,7 @@ async function orchestrateGraphStream({
|
|
|
783
3760
|
});
|
|
784
3761
|
runTraceSpan.end?.(runTraceEndArgs());
|
|
785
3762
|
const resolvedSessionId2 = config?.session?.id || sessionId || "";
|
|
3763
|
+
await sealPendingSnapshots();
|
|
786
3764
|
if (resolvedSessionId2 && frameworkAdapter?.sessionCheckpoints) {
|
|
787
3765
|
try {
|
|
788
3766
|
const checkpoint = await frameworkAdapter.sessionCheckpoints.append(
|
|
@@ -804,6 +3782,16 @@ async function orchestrateGraphStream({
|
|
|
804
3782
|
turnIndex: checkpoint.turnIndex,
|
|
805
3783
|
...checkpoint.label ? { label: checkpoint.label } : {}
|
|
806
3784
|
});
|
|
3785
|
+
emitTelemetryEvent({
|
|
3786
|
+
config,
|
|
3787
|
+
type: "session.checkpointed",
|
|
3788
|
+
payload: {
|
|
3789
|
+
checkpointId: checkpoint.id,
|
|
3790
|
+
turnIndex: checkpoint.turnIndex,
|
|
3791
|
+
...checkpoint.parentCheckpointId ? { parentCheckpointId: checkpoint.parentCheckpointId } : {},
|
|
3792
|
+
nodes: checkpoint.nodes
|
|
3793
|
+
}
|
|
3794
|
+
});
|
|
807
3795
|
} catch (error) {
|
|
808
3796
|
console.error("[orchestrator] session checkpoint failed", error);
|
|
809
3797
|
}
|
|
@@ -834,6 +3822,7 @@ async function orchestrateGraphStream({
|
|
|
834
3822
|
}
|
|
835
3823
|
}
|
|
836
3824
|
const resolvedSessionId = config?.session?.id || sessionId || "";
|
|
3825
|
+
await sealPendingSnapshots();
|
|
837
3826
|
if (resolvedSessionId && frameworkAdapter?.sessionCheckpoints) {
|
|
838
3827
|
try {
|
|
839
3828
|
const checkpoint = await frameworkAdapter.sessionCheckpoints.append({
|
|
@@ -853,6 +3842,16 @@ async function orchestrateGraphStream({
|
|
|
853
3842
|
turnIndex: checkpoint.turnIndex,
|
|
854
3843
|
...checkpoint.label ? { label: checkpoint.label } : {}
|
|
855
3844
|
});
|
|
3845
|
+
emitTelemetryEvent({
|
|
3846
|
+
config,
|
|
3847
|
+
type: "session.checkpointed",
|
|
3848
|
+
payload: {
|
|
3849
|
+
checkpointId: checkpoint.id,
|
|
3850
|
+
turnIndex: checkpoint.turnIndex,
|
|
3851
|
+
...checkpoint.parentCheckpointId ? { parentCheckpointId: checkpoint.parentCheckpointId } : {},
|
|
3852
|
+
nodes: checkpoint.nodes
|
|
3853
|
+
}
|
|
3854
|
+
});
|
|
856
3855
|
} catch (error) {
|
|
857
3856
|
console.error("[orchestrator] session checkpoint failed", error);
|
|
858
3857
|
}
|
|
@@ -887,6 +3886,7 @@ async function orchestrateGraphStream({
|
|
|
887
3886
|
}) : runLoop(fallbackRunSpan ?? {});
|
|
888
3887
|
runPromise.catch((err) => {
|
|
889
3888
|
fallbackRunSpan?.fail?.(err);
|
|
3889
|
+
emitResumeFailure(err);
|
|
890
3890
|
console.error("[error:orchestrateGraphStream]", err);
|
|
891
3891
|
out.write({
|
|
892
3892
|
type: "error",
|
|
@@ -899,11 +3899,16 @@ async function orchestrateGraphStream({
|
|
|
899
3899
|
}
|
|
900
3900
|
|
|
901
3901
|
// src/interrupt/resume-handler.ts
|
|
902
|
-
var
|
|
3902
|
+
var isRecord4 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3903
|
+
var responseFromSelection = (selected) => {
|
|
3904
|
+
if (selected.length === 0) return void 0;
|
|
3905
|
+
if (selected.length === 1) return selected[0];
|
|
3906
|
+
return JSON.stringify(selected);
|
|
3907
|
+
};
|
|
903
3908
|
function parseResumeMeta(msg) {
|
|
904
3909
|
if (!msg || !msg.metadata) return null;
|
|
905
3910
|
const raw = msg.metadata.resume;
|
|
906
|
-
if (!
|
|
3911
|
+
if (!isRecord4(raw)) return null;
|
|
907
3912
|
const token = typeof raw.token === "string" ? raw.token : "";
|
|
908
3913
|
const requestId = typeof raw.requestId === "string" ? raw.requestId : "";
|
|
909
3914
|
const cancel = raw.cancel === true;
|
|
@@ -911,10 +3916,10 @@ function parseResumeMeta(msg) {
|
|
|
911
3916
|
const rawSelected = raw.selected;
|
|
912
3917
|
if (typeof rawSelected === "string") selected = [rawSelected];
|
|
913
3918
|
else if (Array.isArray(rawSelected)) selected = rawSelected.map(String);
|
|
914
|
-
else if (
|
|
3919
|
+
else if (isRecord4(raw.choice) && typeof raw.choice.id === "string")
|
|
915
3920
|
selected = [raw.choice.id];
|
|
916
3921
|
else if (Array.isArray(raw.choices))
|
|
917
|
-
selected = raw.choices.map((c) =>
|
|
3922
|
+
selected = raw.choices.map((c) => isRecord4(c) ? c.id : void 0).filter((id) => typeof id === "string");
|
|
918
3923
|
if (!token || !requestId) return null;
|
|
919
3924
|
return { token, requestId, selected, cancel };
|
|
920
3925
|
}
|
|
@@ -923,6 +3928,7 @@ async function tryPrepareResumeStream({
|
|
|
923
3928
|
sessionId,
|
|
924
3929
|
config,
|
|
925
3930
|
selectWorkflow,
|
|
3931
|
+
knownWorkflowIds,
|
|
926
3932
|
defaultWorkflowId,
|
|
927
3933
|
applyResumeSelection,
|
|
928
3934
|
frameworkAdapter
|
|
@@ -933,14 +3939,34 @@ async function tryPrepareResumeStream({
|
|
|
933
3939
|
if (!store) return null;
|
|
934
3940
|
const pending = await store.get(meta.token);
|
|
935
3941
|
if (!pending || pending.requestId !== meta.requestId) {
|
|
936
|
-
|
|
937
|
-
|
|
3942
|
+
throw new Error(
|
|
3943
|
+
"Interrupt is expired, already consumed, or does not match the request."
|
|
938
3944
|
);
|
|
939
|
-
return null;
|
|
940
3945
|
}
|
|
3946
|
+
if (pending.sessionId && pending.sessionId !== sessionId)
|
|
3947
|
+
throw new Error("Interrupt belongs to another session.");
|
|
3948
|
+
if (store.take && !await store.take(meta.token))
|
|
3949
|
+
throw new Error("Interrupt has already been resumed or cancelled.");
|
|
941
3950
|
if (meta.cancel) {
|
|
942
3951
|
await store.delete(pending.token);
|
|
943
|
-
|
|
3952
|
+
emitTelemetryEvent({
|
|
3953
|
+
config,
|
|
3954
|
+
type: "interrupt.cancelled",
|
|
3955
|
+
correlation: {
|
|
3956
|
+
runId: pending.runId,
|
|
3957
|
+
sessionId,
|
|
3958
|
+
workflowId: pending.workflow,
|
|
3959
|
+
nodeId: pending.node
|
|
3960
|
+
},
|
|
3961
|
+
payload: {
|
|
3962
|
+
interruptId: pending.requestId,
|
|
3963
|
+
reason: "cancelled_by_client"
|
|
3964
|
+
},
|
|
3965
|
+
flush: true
|
|
3966
|
+
});
|
|
3967
|
+
return (async function* () {
|
|
3968
|
+
yield { type: "done" };
|
|
3969
|
+
})();
|
|
944
3970
|
}
|
|
945
3971
|
console.log(
|
|
946
3972
|
`[resume] token=${meta.token} requestId=${meta.requestId} selected=${JSON.stringify(
|
|
@@ -948,10 +3974,10 @@ async function tryPrepareResumeStream({
|
|
|
948
3974
|
)} sessionId=${sessionId}`
|
|
949
3975
|
);
|
|
950
3976
|
const resumeData = applyResumeSelection ? applyResumeSelection({ pending, selected: meta.selected }) : meta.selected?.length ? { coordinates: String(meta.selected[0]) } : {};
|
|
951
|
-
const resumeDataPatch =
|
|
952
|
-
const pendingMeta =
|
|
953
|
-
const resumeStatePatch =
|
|
954
|
-
const pendingData =
|
|
3977
|
+
const resumeDataPatch = isRecord4(resumeData) ? resumeData : {};
|
|
3978
|
+
const pendingMeta = isRecord4(pending.schema?.meta) ? pending.schema.meta : {};
|
|
3979
|
+
const resumeStatePatch = isRecord4(pendingMeta.__kortyxResumeStatePatch) ? pendingMeta.__kortyxResumeStatePatch : void 0;
|
|
3980
|
+
const pendingData = isRecord4(pending.state?.data) ? pending.state?.data : {};
|
|
955
3981
|
const workflowId = typeof pending.workflow === "string" && pending.workflow.trim() ? pending.workflow : typeof defaultWorkflowId === "string" && defaultWorkflowId.trim() ? defaultWorkflowId : "job-search";
|
|
956
3982
|
const resumedState = {
|
|
957
3983
|
// For static breakpoints, resume with null input (set in orchestrator),
|
|
@@ -969,7 +3995,29 @@ async function tryPrepareResumeStream({
|
|
|
969
3995
|
}
|
|
970
3996
|
};
|
|
971
3997
|
const wf = await selectWorkflow(resumedState.currentWorkflow);
|
|
972
|
-
const
|
|
3998
|
+
const telemetryConfig = prepareWorkflowTelemetry({
|
|
3999
|
+
config: {
|
|
4000
|
+
...config,
|
|
4001
|
+
...pending.state?.config?.executionBranchId ? { executionBranchId: pending.state.config.executionBranchId } : {}
|
|
4002
|
+
},
|
|
4003
|
+
workflow: wf,
|
|
4004
|
+
runId: pending.runId,
|
|
4005
|
+
sessionId,
|
|
4006
|
+
knownWorkflowIds
|
|
4007
|
+
});
|
|
4008
|
+
resumedState.config = telemetryConfig;
|
|
4009
|
+
const resumeUpdate = pending.graphSnapshot ? {
|
|
4010
|
+
config: {
|
|
4011
|
+
...telemetryConfig,
|
|
4012
|
+
context: pending.state?.config?.context ?? telemetryConfig.context
|
|
4013
|
+
}
|
|
4014
|
+
} : {};
|
|
4015
|
+
if (pending.graphSnapshot && frameworkAdapter)
|
|
4016
|
+
await (0, import_runtime2.restoreGraphSnapshot)(
|
|
4017
|
+
frameworkAdapter.checkpointer,
|
|
4018
|
+
pending.graphSnapshot,
|
|
4019
|
+
pending.runId
|
|
4020
|
+
);
|
|
973
4021
|
if (Object.keys(resumeDataPatch).length > 0) {
|
|
974
4022
|
resumeUpdate.data = {
|
|
975
4023
|
...pendingData,
|
|
@@ -983,26 +4031,52 @@ async function tryPrepareResumeStream({
|
|
|
983
4031
|
const resumeValue = meta.selected?.length && pending.schema.kind === "multi-choice" ? meta.selected.map((x) => String(x)) : meta.selected?.length ? String(meta.selected[0]) : void 0;
|
|
984
4032
|
const resumeCheckpointId = typeof pending.graphCheckpointId === "string" && pending.graphCheckpointId.length > 0 ? pending.graphCheckpointId : void 0;
|
|
985
4033
|
const resumedGraph = await (0, import_runtime2.createExecutionGraph)(wf, {
|
|
986
|
-
...
|
|
4034
|
+
...telemetryConfig,
|
|
987
4035
|
resume: true,
|
|
988
4036
|
...resumeValue !== void 0 ? { resumeValue } : {},
|
|
989
4037
|
...resumeCheckpointId ? { resumeCheckpointId } : {},
|
|
990
4038
|
...hasResumeUpdate ? { resumeUpdate } : {}
|
|
991
4039
|
});
|
|
992
4040
|
await store.delete(pending.token);
|
|
4041
|
+
const response = responseFromSelection(meta.selected);
|
|
4042
|
+
const telemetry = isRecord4(telemetryConfig.telemetry) ? telemetryConfig.telemetry : {};
|
|
4043
|
+
const responseCaptured = Boolean(
|
|
4044
|
+
response && shouldCaptureTelemetryContent(telemetry.captureContent, "input")
|
|
4045
|
+
);
|
|
4046
|
+
emitTelemetryEvent({
|
|
4047
|
+
config: telemetryConfig,
|
|
4048
|
+
type: "interrupt.resolved",
|
|
4049
|
+
correlation: {
|
|
4050
|
+
runId: pending.runId,
|
|
4051
|
+
sessionId,
|
|
4052
|
+
workflowId,
|
|
4053
|
+
nodeId: pending.node
|
|
4054
|
+
},
|
|
4055
|
+
payload: {
|
|
4056
|
+
interruptId: pending.requestId,
|
|
4057
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4058
|
+
resumeOutcome: "resumed",
|
|
4059
|
+
responseCaptured,
|
|
4060
|
+
...responseCaptured && response ? { response } : {}
|
|
4061
|
+
},
|
|
4062
|
+
flush: true
|
|
4063
|
+
});
|
|
993
4064
|
const args = {
|
|
994
4065
|
sessionId,
|
|
995
4066
|
runId: pending.runId,
|
|
996
4067
|
graph: resumedGraph,
|
|
997
4068
|
state: resumedState,
|
|
998
4069
|
config: {
|
|
999
|
-
...
|
|
4070
|
+
...telemetryConfig,
|
|
1000
4071
|
resume: true,
|
|
4072
|
+
telemetryInterruptId: pending.requestId,
|
|
4073
|
+
telemetryInterruptNodeId: pending.node,
|
|
1001
4074
|
...resumeValue !== void 0 ? { resumeValue } : {},
|
|
1002
4075
|
...resumeCheckpointId ? { resumeCheckpointId } : {},
|
|
1003
4076
|
...hasResumeUpdate ? { resumeUpdate } : {}
|
|
1004
4077
|
},
|
|
1005
4078
|
selectWorkflow,
|
|
4079
|
+
knownWorkflowIds,
|
|
1006
4080
|
frameworkAdapter
|
|
1007
4081
|
};
|
|
1008
4082
|
const stream = await orchestrateGraphStream(args);
|
|
@@ -1030,12 +4104,13 @@ async function streamChat({
|
|
|
1030
4104
|
loadRuntimeConfig,
|
|
1031
4105
|
selectWorkflow,
|
|
1032
4106
|
workflowRegistry,
|
|
4107
|
+
knownWorkflowIds,
|
|
1033
4108
|
frameworkAdapter,
|
|
1034
4109
|
getProvider,
|
|
1035
4110
|
applyResumeSelection
|
|
1036
4111
|
}) {
|
|
1037
4112
|
const config = await loadRuntimeConfig(options);
|
|
1038
|
-
|
|
4113
|
+
let runtimeConfig = {
|
|
1039
4114
|
...config,
|
|
1040
4115
|
getProvider,
|
|
1041
4116
|
...frameworkAdapter ? { checkpointer: frameworkAdapter.checkpointer } : {}
|
|
@@ -1046,6 +4121,7 @@ async function streamChat({
|
|
|
1046
4121
|
"streamChat requires selectWorkflow or workflowRegistry to resolve workflows."
|
|
1047
4122
|
);
|
|
1048
4123
|
}
|
|
4124
|
+
runtimeConfig.selectWorkflow = workflowSelector;
|
|
1049
4125
|
const fallbackSessionId = options?.sessionId;
|
|
1050
4126
|
const resolvedSessionId = sessionId || fallbackSessionId || "anonymous-session";
|
|
1051
4127
|
const last = messages[messages.length - 1];
|
|
@@ -1078,6 +4154,7 @@ async function streamChat({
|
|
|
1078
4154
|
sessionId: resolvedSessionId,
|
|
1079
4155
|
config: runtimeConfig,
|
|
1080
4156
|
selectWorkflow: workflowSelector,
|
|
4157
|
+
knownWorkflowIds,
|
|
1081
4158
|
...frameworkAdapter ? { frameworkAdapter } : {},
|
|
1082
4159
|
...defaultWorkflowId ? { defaultWorkflowId } : {},
|
|
1083
4160
|
...applyResumeSelection ? { applyResumeSelection } : {}
|
|
@@ -1103,6 +4180,13 @@ async function streamChat({
|
|
|
1103
4180
|
}
|
|
1104
4181
|
const currentWorkflow = requestedWorkflowId ? baseState.currentWorkflow : headCheckpoint?.state.currentWorkflow ?? baseState.currentWorkflow;
|
|
1105
4182
|
const selectedWorkflow = await workflowSelector(currentWorkflow);
|
|
4183
|
+
runtimeConfig = prepareWorkflowTelemetry({
|
|
4184
|
+
config: runtimeConfig,
|
|
4185
|
+
workflow: selectedWorkflow,
|
|
4186
|
+
runId,
|
|
4187
|
+
sessionId: resolvedSessionId,
|
|
4188
|
+
knownWorkflowIds
|
|
4189
|
+
});
|
|
1106
4190
|
const graph = await (0, import_runtime3.createExecutionGraph)(selectedWorkflow, runtimeConfig);
|
|
1107
4191
|
const initialState = headCheckpoint ? {
|
|
1108
4192
|
...headCheckpoint.state,
|
|
@@ -1115,7 +4199,7 @@ async function streamChat({
|
|
|
1115
4199
|
...runtime.requestedWorkflow ? { requestedWorkflow: runtime.requestedWorkflow } : {},
|
|
1116
4200
|
...runtime.priorMessages ? { priorMessages: runtime.priorMessages } : {}
|
|
1117
4201
|
}
|
|
1118
|
-
} : { ...baseState, currentWorkflow };
|
|
4202
|
+
} : { ...baseState, config: runtimeConfig, currentWorkflow };
|
|
1119
4203
|
const orchestratedStream = await orchestrateGraphStream({
|
|
1120
4204
|
sessionId: resolvedSessionId,
|
|
1121
4205
|
runId,
|
|
@@ -1123,6 +4207,7 @@ async function streamChat({
|
|
|
1123
4207
|
state: initialState,
|
|
1124
4208
|
config: runtimeConfig,
|
|
1125
4209
|
selectWorkflow: workflowSelector,
|
|
4210
|
+
knownWorkflowIds,
|
|
1126
4211
|
...frameworkAdapter ? { frameworkAdapter } : {}
|
|
1127
4212
|
});
|
|
1128
4213
|
return orchestratedStream;
|
|
@@ -1186,7 +4271,7 @@ var hydratePendingGraphCheckpoint = async (frameworkAdapter, request) => {
|
|
|
1186
4271
|
return graphCheckpointId ? { ...request, graphCheckpointId } : request;
|
|
1187
4272
|
};
|
|
1188
4273
|
var clearPendingGraphWrites = async (frameworkAdapter, request) => {
|
|
1189
|
-
if (!request.graphCheckpointId) return;
|
|
4274
|
+
if (request.graphSnapshot || !request.graphCheckpointId) return;
|
|
1190
4275
|
const checkpointer = frameworkAdapter.checkpointer;
|
|
1191
4276
|
if (!checkpointer?.deleteCheckpointWrites) return;
|
|
1192
4277
|
await Promise.all([
|
|
@@ -1236,6 +4321,22 @@ function createAgent(args) {
|
|
|
1236
4321
|
fallbackId: resolvedDefaultWorkflowId ?? "general-chat"
|
|
1237
4322
|
});
|
|
1238
4323
|
})();
|
|
4324
|
+
const registeredWorkflowsPromise = registryPromise.then(
|
|
4325
|
+
(registry) => registry?.list?.()
|
|
4326
|
+
);
|
|
4327
|
+
const projectTopology = async (options) => {
|
|
4328
|
+
const registeredWorkflows = await registeredWorkflowsPromise;
|
|
4329
|
+
if (!registeredWorkflows?.length) return [];
|
|
4330
|
+
const knownWorkflowIds = registeredWorkflows.map((workflow) => workflow.id);
|
|
4331
|
+
return registeredWorkflows.map(
|
|
4332
|
+
(workflow) => projectWorkflowTopology({
|
|
4333
|
+
workflow,
|
|
4334
|
+
environment: options.environment,
|
|
4335
|
+
service: options.service,
|
|
4336
|
+
knownWorkflowIds
|
|
4337
|
+
})
|
|
4338
|
+
);
|
|
4339
|
+
};
|
|
1239
4340
|
const streamChat2 = async (messages, options) => {
|
|
1240
4341
|
const parsedOptions = parseAgentProcessOptions(options);
|
|
1241
4342
|
const registry = await registryPromise;
|
|
@@ -1244,11 +4345,18 @@ function createAgent(args) {
|
|
|
1244
4345
|
"createAgent requires workflows, workflowsDir, or workflowRegistry."
|
|
1245
4346
|
);
|
|
1246
4347
|
}
|
|
4348
|
+
const registeredWorkflows = await registeredWorkflowsPromise.catch(
|
|
4349
|
+
() => void 0
|
|
4350
|
+
);
|
|
4351
|
+
const knownWorkflowIds = registeredWorkflows?.map(
|
|
4352
|
+
(workflow) => workflow.id
|
|
4353
|
+
);
|
|
1247
4354
|
return streamChat({
|
|
1248
4355
|
...resolvedDefaultWorkflowId ? { defaultWorkflowId: resolvedDefaultWorkflowId } : {},
|
|
1249
4356
|
messages,
|
|
1250
4357
|
options: parsedOptions,
|
|
1251
4358
|
workflowRegistry: registry,
|
|
4359
|
+
...knownWorkflowIds ? { knownWorkflowIds } : {},
|
|
1252
4360
|
frameworkAdapter: resolvedFrameworkAdapter,
|
|
1253
4361
|
getProvider: resolvedGetProvider,
|
|
1254
4362
|
loadRuntimeConfig: (runtimeOptions) => ({
|
|
@@ -1265,12 +4373,18 @@ function createAgent(args) {
|
|
|
1265
4373
|
const listCheckpoints = (sessionId) => resolvedFrameworkAdapter.sessionCheckpoints.list(sessionId);
|
|
1266
4374
|
const getCheckpoint = (id) => resolvedFrameworkAdapter.sessionCheckpoints.get(id);
|
|
1267
4375
|
const rollbackTo = async (id) => {
|
|
4376
|
+
const target = await resolvedFrameworkAdapter.sessionCheckpoints.get(id);
|
|
1268
4377
|
const result = await resolvedFrameworkAdapter.sessionCheckpoints.rollbackTo(id);
|
|
1269
4378
|
const activePendingRequests = await Promise.all(
|
|
1270
4379
|
result.activePendingRequests.map(
|
|
1271
4380
|
(request) => hydratePendingGraphCheckpoint(resolvedFrameworkAdapter, request)
|
|
1272
4381
|
)
|
|
1273
4382
|
);
|
|
4383
|
+
restoreWorkflowCallBranch(
|
|
4384
|
+
activePendingRequests,
|
|
4385
|
+
telemetry,
|
|
4386
|
+
target?.runId ?? "unknown"
|
|
4387
|
+
);
|
|
1274
4388
|
await Promise.all(
|
|
1275
4389
|
activePendingRequests.map(
|
|
1276
4390
|
(request) => clearPendingGraphWrites(resolvedFrameworkAdapter, request)
|
|
@@ -1284,9 +4398,25 @@ function createAgent(args) {
|
|
|
1284
4398
|
(request) => resolvedFrameworkAdapter.pendingRequests.save(request)
|
|
1285
4399
|
)
|
|
1286
4400
|
]);
|
|
4401
|
+
if (target) {
|
|
4402
|
+
emitTelemetryEvent({
|
|
4403
|
+
config: { ...telemetry ? { telemetry } : {} },
|
|
4404
|
+
type: "session.rolled_back",
|
|
4405
|
+
correlation: {
|
|
4406
|
+
runId: target.runId,
|
|
4407
|
+
sessionId: target.sessionId,
|
|
4408
|
+
workflowId: target.workflow
|
|
4409
|
+
},
|
|
4410
|
+
payload: {
|
|
4411
|
+
checkpointId: target.id,
|
|
4412
|
+
invalidatedInterruptIds: result.invalidatedInterruptTokens
|
|
4413
|
+
}
|
|
4414
|
+
});
|
|
4415
|
+
}
|
|
1287
4416
|
return { ...result, activePendingRequests };
|
|
1288
4417
|
};
|
|
1289
4418
|
const fork = async (id, options) => {
|
|
4419
|
+
const source = await resolvedFrameworkAdapter.sessionCheckpoints.get(id);
|
|
1290
4420
|
const result = await resolvedFrameworkAdapter.sessionCheckpoints.fork(
|
|
1291
4421
|
id,
|
|
1292
4422
|
options
|
|
@@ -1296,6 +4426,11 @@ function createAgent(args) {
|
|
|
1296
4426
|
(request) => hydratePendingGraphCheckpoint(resolvedFrameworkAdapter, request)
|
|
1297
4427
|
)
|
|
1298
4428
|
);
|
|
4429
|
+
restoreWorkflowCallBranch(
|
|
4430
|
+
activePendingRequests,
|
|
4431
|
+
telemetry,
|
|
4432
|
+
source?.runId ?? "unknown"
|
|
4433
|
+
);
|
|
1299
4434
|
await Promise.all(
|
|
1300
4435
|
activePendingRequests.map(
|
|
1301
4436
|
(request) => clearPendingGraphWrites(resolvedFrameworkAdapter, request)
|
|
@@ -1306,12 +4441,33 @@ function createAgent(args) {
|
|
|
1306
4441
|
(request) => resolvedFrameworkAdapter.pendingRequests.save(request)
|
|
1307
4442
|
)
|
|
1308
4443
|
);
|
|
4444
|
+
emitTelemetryEvent({
|
|
4445
|
+
config: { ...telemetry ? { telemetry } : {} },
|
|
4446
|
+
type: "session.forked",
|
|
4447
|
+
correlation: {
|
|
4448
|
+
runId: result.checkpoint.runId,
|
|
4449
|
+
sessionId: result.checkpoint.sessionId,
|
|
4450
|
+
workflowId: result.checkpoint.workflow
|
|
4451
|
+
},
|
|
4452
|
+
payload: {
|
|
4453
|
+
newSessionId: result.sessionId,
|
|
4454
|
+
parentSessionId: result.parentSessionId,
|
|
4455
|
+
forkedFromCheckpointId: result.forkedFrom
|
|
4456
|
+
}
|
|
4457
|
+
});
|
|
1309
4458
|
return {
|
|
1310
4459
|
...result,
|
|
1311
4460
|
checkpoint: { ...result.checkpoint, activePendingRequests }
|
|
1312
4461
|
};
|
|
1313
4462
|
};
|
|
1314
|
-
return {
|
|
4463
|
+
return {
|
|
4464
|
+
projectTopology,
|
|
4465
|
+
streamChat: streamChat2,
|
|
4466
|
+
listCheckpoints,
|
|
4467
|
+
getCheckpoint,
|
|
4468
|
+
rollbackTo,
|
|
4469
|
+
fork
|
|
4470
|
+
};
|
|
1315
4471
|
}
|
|
1316
4472
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1317
4473
|
0 && (module.exports = {
|
|
@@ -1325,6 +4481,7 @@ function createAgent(args) {
|
|
|
1325
4481
|
parseChatRequestBody,
|
|
1326
4482
|
parseCheckpointRequestBody,
|
|
1327
4483
|
parseResumeMeta,
|
|
4484
|
+
projectWorkflowTopology,
|
|
1328
4485
|
streamChat,
|
|
1329
4486
|
streamChatFromRoute,
|
|
1330
4487
|
transformGraphStreamForUI,
|