@axiom-lattice/core 2.1.79 → 2.1.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-C5HUS7YV.mjs +331 -0
- package/dist/chunk-C5HUS7YV.mjs.map +1 -0
- package/dist/chunk-SGRFQY3E.mjs +1140 -0
- package/dist/chunk-SGRFQY3E.mjs.map +1 -0
- package/dist/compile-4RFYHUBE.mjs +11 -0
- package/dist/compile-4RFYHUBE.mjs.map +1 -0
- package/dist/index.d.mts +306 -8
- package/dist/index.d.ts +306 -8
- package/dist/index.js +3261 -1026
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1609 -916
- package/dist/index.mjs.map +1 -1
- package/dist/memory_lattice-E66HTTVV.mjs +13 -0
- package/dist/memory_lattice-E66HTTVV.mjs.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,1140 @@
|
|
|
1
|
+
// src/workflow/compile.ts
|
|
2
|
+
import {
|
|
3
|
+
StateGraph,
|
|
4
|
+
START,
|
|
5
|
+
END
|
|
6
|
+
} from "@langchain/langgraph";
|
|
7
|
+
|
|
8
|
+
// src/workflow/utils.ts
|
|
9
|
+
import {
|
|
10
|
+
Annotation
|
|
11
|
+
} from "@langchain/langgraph";
|
|
12
|
+
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
|
|
13
|
+
|
|
14
|
+
// src/workflow/parse-yaml.ts
|
|
15
|
+
import * as yaml from "js-yaml";
|
|
16
|
+
|
|
17
|
+
// src/workflow/schema.ts
|
|
18
|
+
function toJsonSchema(fields) {
|
|
19
|
+
if (!fields || Object.keys(fields).length === 0) return void 0;
|
|
20
|
+
const properties = {};
|
|
21
|
+
const required = [];
|
|
22
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
23
|
+
required.push(key);
|
|
24
|
+
properties[key] = fieldToSchema(value);
|
|
25
|
+
}
|
|
26
|
+
return { type: "object", properties, required };
|
|
27
|
+
}
|
|
28
|
+
function fieldToSchema(value) {
|
|
29
|
+
if (typeof value === "string") return stringTypeToSchema(value);
|
|
30
|
+
if (Array.isArray(value) && value.length > 0 && typeof value[0] === "object" && !Array.isArray(value[0])) {
|
|
31
|
+
const nested = toJsonSchema(value[0]);
|
|
32
|
+
return { type: "array", items: nested ?? { type: "object" } };
|
|
33
|
+
}
|
|
34
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
35
|
+
const nested = toJsonSchema(value);
|
|
36
|
+
return nested ?? { type: "object" };
|
|
37
|
+
}
|
|
38
|
+
return { type: "string" };
|
|
39
|
+
}
|
|
40
|
+
function stringTypeToSchema(type) {
|
|
41
|
+
if (type.endsWith("[]")) {
|
|
42
|
+
const inner = type.slice(0, -2);
|
|
43
|
+
return { type: "array", items: stringTypeToSchema(inner) };
|
|
44
|
+
}
|
|
45
|
+
if (type === "string") return { type: "string" };
|
|
46
|
+
if (type === "number") return { type: "number" };
|
|
47
|
+
if (type === "boolean") return { type: "boolean" };
|
|
48
|
+
return { type };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/workflow/parse-yaml.ts
|
|
52
|
+
function nodeId(label) {
|
|
53
|
+
return `n_${label}`;
|
|
54
|
+
}
|
|
55
|
+
var _parallelSeq = 0;
|
|
56
|
+
function nextParallelLabel() {
|
|
57
|
+
return `parallel_${_parallelSeq++}`;
|
|
58
|
+
}
|
|
59
|
+
function translate(template) {
|
|
60
|
+
return template.replace(/\{\{([^}]+)\}\}/g, (_m, expr) => {
|
|
61
|
+
const t = expr.trim();
|
|
62
|
+
if (t === "item") return "${item}";
|
|
63
|
+
return `\${state.${t}}`;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function toSafeStateExpr(expr) {
|
|
67
|
+
const match = expr.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)/);
|
|
68
|
+
if (!match) return `state.${expr}`;
|
|
69
|
+
const field = match[0];
|
|
70
|
+
const rest = expr.slice(field.length);
|
|
71
|
+
return `state["${field}"]${rest}`;
|
|
72
|
+
}
|
|
73
|
+
function validateExpression(expr) {
|
|
74
|
+
if (/;/.test(expr)) {
|
|
75
|
+
throw new Error(`Condition expression must not contain semicolons: "${expr}"`);
|
|
76
|
+
}
|
|
77
|
+
if (/[{}]/.test(expr)) {
|
|
78
|
+
throw new Error(`Condition expression must not contain braces: "${expr}"`);
|
|
79
|
+
}
|
|
80
|
+
if (/\bfunction\b/.test(expr)) {
|
|
81
|
+
throw new Error(`Condition expression must not contain 'function': "${expr}"`);
|
|
82
|
+
}
|
|
83
|
+
if (/\brequire\b|\bimport\b|\bmodule\b/.test(expr)) {
|
|
84
|
+
throw new Error(`Condition expression must not contain require/import: "${expr}"`);
|
|
85
|
+
}
|
|
86
|
+
if (/\bprocess\b|\bglobal\b|\bglobalThis\b/.test(expr)) {
|
|
87
|
+
throw new Error(`Condition expression must not contain process/global: "${expr}"`);
|
|
88
|
+
}
|
|
89
|
+
if (/\beval\b|\bFunction\b/.test(expr)) {
|
|
90
|
+
throw new Error(`Condition expression must not contain eval/Function: "${expr}"`);
|
|
91
|
+
}
|
|
92
|
+
if (/\bconstructor\b|\b__proto__\b|\bprototype\b/.test(expr)) {
|
|
93
|
+
throw new Error(`Condition expression must not contain prototype access: "${expr}"`);
|
|
94
|
+
}
|
|
95
|
+
if (/\bsetTimeout\b|\bsetInterval\b|\bbuffer\b/i.test(expr)) {
|
|
96
|
+
throw new Error(`Condition expression must not contain timer or buffer references: "${expr}"`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function parseYaml(yamlStr) {
|
|
100
|
+
_parallelSeq = 0;
|
|
101
|
+
let raw;
|
|
102
|
+
try {
|
|
103
|
+
raw = yaml.load(yamlStr);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
const line = e?.mark?.line != null ? ` (line ${e.mark.line + 1})` : "";
|
|
106
|
+
throw new Error(`YAML parse error${line}: ${e.message || e}`);
|
|
107
|
+
}
|
|
108
|
+
if (!raw || typeof raw !== "object" || !Array.isArray(raw.steps)) {
|
|
109
|
+
throw new Error("Workflow YAML must have 'steps' (array)");
|
|
110
|
+
}
|
|
111
|
+
const name = typeof raw.name === "string" ? raw.name : "workflow";
|
|
112
|
+
const wf = {
|
|
113
|
+
name,
|
|
114
|
+
steps: raw.steps.map((s, i) => parseStep(s, i))
|
|
115
|
+
};
|
|
116
|
+
return normalize(wf);
|
|
117
|
+
}
|
|
118
|
+
function parseStep(raw, index) {
|
|
119
|
+
if (raw.parallel !== void 0) {
|
|
120
|
+
if (!Array.isArray(raw.parallel)) {
|
|
121
|
+
throw new Error(`Step at position ${index}: parallel must be an array`);
|
|
122
|
+
}
|
|
123
|
+
const children = raw.parallel.map((c, ci) => {
|
|
124
|
+
const entries2 = Object.entries(c);
|
|
125
|
+
if (entries2.length !== 1) {
|
|
126
|
+
throw new Error(`Parallel child at position ${index}.${ci} must be a single-key mapping`);
|
|
127
|
+
}
|
|
128
|
+
const [label, config2] = entries2[0];
|
|
129
|
+
return {
|
|
130
|
+
label,
|
|
131
|
+
if: config2.if !== void 0 ? String(config2.if) : void 0,
|
|
132
|
+
prompt: String(config2.prompt ?? ""),
|
|
133
|
+
output: config2.output,
|
|
134
|
+
ask: config2.ask === true
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
parallel: children,
|
|
139
|
+
if: raw.if !== void 0 ? String(raw.if) : void 0,
|
|
140
|
+
output: raw.output
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const entries = Object.entries(raw);
|
|
144
|
+
if (entries.length !== 1) {
|
|
145
|
+
throw new Error(`Step at position ${index} must be a single-key mapping`);
|
|
146
|
+
}
|
|
147
|
+
const [key, config] = entries[0];
|
|
148
|
+
const mapConfig = config?.map;
|
|
149
|
+
if (mapConfig && typeof mapConfig === "object") {
|
|
150
|
+
return {
|
|
151
|
+
map: {
|
|
152
|
+
source: mapConfig.source,
|
|
153
|
+
label: key,
|
|
154
|
+
// always use the YAML key as the label
|
|
155
|
+
if: mapConfig.if !== void 0 ? String(mapConfig.if) : void 0,
|
|
156
|
+
each: {
|
|
157
|
+
prompt: String(mapConfig.each?.prompt ?? ""),
|
|
158
|
+
output: mapConfig.each?.output
|
|
159
|
+
},
|
|
160
|
+
output: mapConfig.output,
|
|
161
|
+
batch: mapConfig.batch,
|
|
162
|
+
concurrency: mapConfig.concurrency
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (key === "map") {
|
|
167
|
+
throw new Error(`Map steps must use named format: - <label>: { map: { source, each, ... } }. Anonymous "- map:" is not supported.`);
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
label: key,
|
|
171
|
+
if: config.if !== void 0 ? String(config.if) : void 0,
|
|
172
|
+
prompt: String(config.prompt ?? ""),
|
|
173
|
+
output: config.output,
|
|
174
|
+
ask: config.ask === true
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function normalize(wf) {
|
|
178
|
+
validate(wf);
|
|
179
|
+
for (const step of wf.steps) {
|
|
180
|
+
if ("label" in step) {
|
|
181
|
+
const s = step;
|
|
182
|
+
if (s.if) validateExpression(s.if);
|
|
183
|
+
} else if ("parallel" in step) {
|
|
184
|
+
const pb = step;
|
|
185
|
+
if (pb.if) validateExpression(pb.if);
|
|
186
|
+
for (const child of pb.parallel) {
|
|
187
|
+
if (child.if) validateExpression(child.if);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const nodes = [];
|
|
192
|
+
const edges = [];
|
|
193
|
+
const fields = { input: { type: "string" } };
|
|
194
|
+
nodes.push({ id: "n_input", type: "input", name: "input", output: { key: "input" } });
|
|
195
|
+
edges.push({ from: "START", to: "n_input" });
|
|
196
|
+
let prevNodeId = "n_input";
|
|
197
|
+
for (const step of wf.steps) {
|
|
198
|
+
if ("label" in step) {
|
|
199
|
+
const s = step;
|
|
200
|
+
const nid = nodeId(s.label);
|
|
201
|
+
const schema = toJsonSchema(s.output);
|
|
202
|
+
nodes.push({
|
|
203
|
+
id: nid,
|
|
204
|
+
type: "agent",
|
|
205
|
+
name: s.label,
|
|
206
|
+
input: { template: translate(s.prompt) },
|
|
207
|
+
output: { key: s.label, ...schema ? { schema } : {} },
|
|
208
|
+
ask: s.ask,
|
|
209
|
+
condition: s.if
|
|
210
|
+
});
|
|
211
|
+
fields[s.label] = s.output ? { type: "object" } : { type: "string" };
|
|
212
|
+
addPrevEdges(edges, prevNodeId, nid);
|
|
213
|
+
prevNodeId = nid;
|
|
214
|
+
} else if ("parallel" in step) {
|
|
215
|
+
const pb = step;
|
|
216
|
+
const groupId = nextParallelLabel();
|
|
217
|
+
const groupNodeIds = [];
|
|
218
|
+
for (const child of pb.parallel) {
|
|
219
|
+
const nid = nodeId(child.label);
|
|
220
|
+
const schema = toJsonSchema(child.output);
|
|
221
|
+
nodes.push({
|
|
222
|
+
id: nid,
|
|
223
|
+
type: "agent",
|
|
224
|
+
name: child.label,
|
|
225
|
+
input: { template: translate(child.prompt) },
|
|
226
|
+
output: { key: child.label, ...schema ? { schema } : {} },
|
|
227
|
+
ask: child.ask,
|
|
228
|
+
condition: child.if,
|
|
229
|
+
parallelGroup: groupId
|
|
230
|
+
});
|
|
231
|
+
fields[child.label] = child.output ? { type: "object" } : { type: "string" };
|
|
232
|
+
groupNodeIds.push(nid);
|
|
233
|
+
}
|
|
234
|
+
addPrevEdges(edges, prevNodeId, groupNodeIds);
|
|
235
|
+
prevNodeId = groupNodeIds;
|
|
236
|
+
} else if ("map" in step) {
|
|
237
|
+
const ms = step.map;
|
|
238
|
+
const label = ms.label;
|
|
239
|
+
const nid = nodeId(label);
|
|
240
|
+
const innerSchema = toJsonSchema(ms.each?.output);
|
|
241
|
+
const mapSchema = toJsonSchema(ms.output);
|
|
242
|
+
nodes.push({
|
|
243
|
+
id: nid,
|
|
244
|
+
type: "map",
|
|
245
|
+
name: label,
|
|
246
|
+
source: `state.${ms.source}`,
|
|
247
|
+
itemKey: "item",
|
|
248
|
+
config: {
|
|
249
|
+
batchSize: ms.batch ?? 50,
|
|
250
|
+
maxConcurrency: ms.concurrency ?? 5,
|
|
251
|
+
innerConcurrency: ms.concurrency ?? 5
|
|
252
|
+
},
|
|
253
|
+
node: {
|
|
254
|
+
type: "agent",
|
|
255
|
+
input: ms.each?.prompt ? { template: translate(ms.each.prompt) } : void 0,
|
|
256
|
+
...innerSchema ? { schema: innerSchema } : {}
|
|
257
|
+
},
|
|
258
|
+
output: { key: label, ...mapSchema ? { schema: mapSchema } : {} },
|
|
259
|
+
condition: ms.if
|
|
260
|
+
});
|
|
261
|
+
fields[label] = { type: "array", default: [] };
|
|
262
|
+
addPrevEdges(edges, prevNodeId, nid);
|
|
263
|
+
prevNodeId = nid;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const termNid = nodeId("__end");
|
|
267
|
+
nodes.push({ id: termNid, type: "terminal", name: "__end", status: "success" });
|
|
268
|
+
addPrevEdges(edges, prevNodeId, termNid);
|
|
269
|
+
return { version: "1.0", name: wf.name || "workflow", state: { fields }, nodes, edges };
|
|
270
|
+
}
|
|
271
|
+
function addPrevEdges(edges, fromNodes, toNodes) {
|
|
272
|
+
const fromList = Array.isArray(fromNodes) ? fromNodes : [fromNodes];
|
|
273
|
+
const toList = Array.isArray(toNodes) ? toNodes : [toNodes];
|
|
274
|
+
for (const from of fromList) {
|
|
275
|
+
for (const to of toList) {
|
|
276
|
+
if (!edges.some((e) => e.from === from && e.to === to)) {
|
|
277
|
+
edges.push({ from, to });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function validate(wf) {
|
|
283
|
+
const labels = /* @__PURE__ */ new Set();
|
|
284
|
+
for (const step of wf.steps) {
|
|
285
|
+
if ("label" in step) {
|
|
286
|
+
const s = step;
|
|
287
|
+
if (labels.has(s.label)) throw new Error(`Duplicate step label "${s.label}"`);
|
|
288
|
+
labels.add(s.label);
|
|
289
|
+
} else if ("parallel" in step) {
|
|
290
|
+
const pb = step;
|
|
291
|
+
if (pb.parallel.length === 0) {
|
|
292
|
+
throw new Error("parallel block must contain at least one child step");
|
|
293
|
+
}
|
|
294
|
+
for (const child of pb.parallel) {
|
|
295
|
+
if (labels.has(child.label)) throw new Error(`Duplicate step label "${child.label}"`);
|
|
296
|
+
labels.add(child.label);
|
|
297
|
+
}
|
|
298
|
+
} else if ("map" in step) {
|
|
299
|
+
const ms = step.map;
|
|
300
|
+
if (labels.has(ms.label)) throw new Error(`Duplicate step label "${ms.label}" (map step)`);
|
|
301
|
+
labels.add(ms.label);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/workflow/utils.ts
|
|
307
|
+
function buildStateAnnotation(fields) {
|
|
308
|
+
const annotations = {};
|
|
309
|
+
if (fields) {
|
|
310
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
311
|
+
annotations[key] = Annotation({
|
|
312
|
+
default: () => field.default ?? defaultValueForType(field.type),
|
|
313
|
+
reducer: buildReducer(field.reducer ?? "replace")
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (!annotations["messages"]) {
|
|
318
|
+
annotations["messages"] = Annotation({
|
|
319
|
+
default: () => [],
|
|
320
|
+
reducer: (prev, next) => [...prev, ...next]
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
annotations["phase"] = Annotation({
|
|
324
|
+
default: () => "init",
|
|
325
|
+
reducer: (_prev, next) => next
|
|
326
|
+
});
|
|
327
|
+
if (!annotations["status"]) {
|
|
328
|
+
annotations["status"] = Annotation({
|
|
329
|
+
default: () => "",
|
|
330
|
+
reducer: (_prev, next) => next
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
if (!annotations["_runId"]) {
|
|
334
|
+
annotations["_runId"] = Annotation({
|
|
335
|
+
default: () => void 0,
|
|
336
|
+
reducer: (_prev, next) => next ?? _prev
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
return Annotation.Root(annotations);
|
|
340
|
+
}
|
|
341
|
+
function defaultValueForType(type) {
|
|
342
|
+
switch (type) {
|
|
343
|
+
case "string":
|
|
344
|
+
return "";
|
|
345
|
+
case "number":
|
|
346
|
+
return null;
|
|
347
|
+
case "boolean":
|
|
348
|
+
return false;
|
|
349
|
+
case "object":
|
|
350
|
+
return null;
|
|
351
|
+
case "array":
|
|
352
|
+
return [];
|
|
353
|
+
default:
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function buildReducer(kind) {
|
|
358
|
+
switch (kind) {
|
|
359
|
+
case "append":
|
|
360
|
+
return (prev, next) => {
|
|
361
|
+
const arr = Array.isArray(prev) ? prev : [];
|
|
362
|
+
return arr.concat(Array.isArray(next) ? next : [next]);
|
|
363
|
+
};
|
|
364
|
+
case "merge":
|
|
365
|
+
return (prev, next) => {
|
|
366
|
+
const obj = prev && typeof prev === "object" && !Array.isArray(prev) ? prev : {};
|
|
367
|
+
const merge = next && typeof next === "object" && !Array.isArray(next) ? next : {};
|
|
368
|
+
return { ...obj, ...merge };
|
|
369
|
+
};
|
|
370
|
+
case "replace":
|
|
371
|
+
default:
|
|
372
|
+
return (_prev, next) => next;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function resolvePath(obj, path) {
|
|
376
|
+
const cleanPath = path.startsWith("state.") ? path.slice(6) : path;
|
|
377
|
+
const parts = cleanPath.split(/\.|\[|\]\.?/).filter(Boolean);
|
|
378
|
+
let current = obj;
|
|
379
|
+
for (const part of parts) {
|
|
380
|
+
if (current === null || current === void 0) return void 0;
|
|
381
|
+
if (typeof current === "object") {
|
|
382
|
+
current = current[part];
|
|
383
|
+
} else {
|
|
384
|
+
return void 0;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return current;
|
|
388
|
+
}
|
|
389
|
+
function renderTemplate(template, state, item) {
|
|
390
|
+
return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
|
|
391
|
+
const trimmed = expr.trim();
|
|
392
|
+
if (trimmed.startsWith("state.")) {
|
|
393
|
+
const val = resolvePath(state, trimmed);
|
|
394
|
+
return val === void 0 ? "" : formatValue(val);
|
|
395
|
+
}
|
|
396
|
+
if (trimmed.startsWith("item.") || trimmed === "item") {
|
|
397
|
+
if (!item) return "";
|
|
398
|
+
if (trimmed === "item") {
|
|
399
|
+
if (typeof item === "object" && "item" in item) {
|
|
400
|
+
return formatValue(item.item);
|
|
401
|
+
}
|
|
402
|
+
return formatValue(item);
|
|
403
|
+
}
|
|
404
|
+
const itemPath = trimmed;
|
|
405
|
+
const val = resolvePath({ item }, itemPath);
|
|
406
|
+
return val === void 0 ? "" : formatValue(val);
|
|
407
|
+
}
|
|
408
|
+
if (item && typeof item === "object") {
|
|
409
|
+
const firstSeg = trimmed.split(".")[0];
|
|
410
|
+
if (firstSeg in item) {
|
|
411
|
+
const val = resolvePath({ item }, `item.${trimmed}`);
|
|
412
|
+
return val === void 0 ? "" : formatValue(val);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return `\${${trimmed}}`;
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
function formatValue(val) {
|
|
419
|
+
if (typeof val === "string") return val;
|
|
420
|
+
if (val === null || val === void 0) return "";
|
|
421
|
+
if (Array.isArray(val)) {
|
|
422
|
+
const parts = val.map((item) => {
|
|
423
|
+
const content = item?.content ?? item?.kwargs?.content;
|
|
424
|
+
if (typeof content === "string") return content;
|
|
425
|
+
if (Array.isArray(content)) {
|
|
426
|
+
return content.filter((c) => c?.type === "text").map((c) => String(c.text ?? "")).join("\n");
|
|
427
|
+
}
|
|
428
|
+
if (content && typeof content === "object") return JSON.stringify(content, null, 2);
|
|
429
|
+
if (item && typeof item === "object") return JSON.stringify(item, null, 2);
|
|
430
|
+
return String(item);
|
|
431
|
+
});
|
|
432
|
+
const result = parts.filter(Boolean).join("\n");
|
|
433
|
+
return result || JSON.stringify(val, null, 2);
|
|
434
|
+
}
|
|
435
|
+
return JSON.stringify(val, null, 2);
|
|
436
|
+
}
|
|
437
|
+
function buildInput(input, state, item, outputSchema) {
|
|
438
|
+
let prompt = input?.template ? renderTemplate(input.template, state, item) : "";
|
|
439
|
+
if (outputSchema) {
|
|
440
|
+
const example = schemaToExample(outputSchema);
|
|
441
|
+
const schemaInstruction = `
|
|
442
|
+
|
|
443
|
+
Output must be valid JSON in exactly this shape. Return ONLY the JSON, no other text or markdown:
|
|
444
|
+
${example}`;
|
|
445
|
+
prompt = prompt + schemaInstruction;
|
|
446
|
+
}
|
|
447
|
+
return { messages: [new HumanMessage(prompt)] };
|
|
448
|
+
}
|
|
449
|
+
function schemaToExample(schema) {
|
|
450
|
+
const example = schemaValueToExample(schema);
|
|
451
|
+
return JSON.stringify(example, null, 2);
|
|
452
|
+
}
|
|
453
|
+
function schemaValueToExample(value) {
|
|
454
|
+
if (value === null || value === void 0) return null;
|
|
455
|
+
if (typeof value !== "object") return value;
|
|
456
|
+
const obj = value;
|
|
457
|
+
if (obj.type === "string") return "<string>";
|
|
458
|
+
if (obj.type === "number") return "<number>";
|
|
459
|
+
if (obj.type === "boolean") return "<boolean>";
|
|
460
|
+
if (obj.type === "integer") return "<integer>";
|
|
461
|
+
if (obj.type === "array" && obj.items) {
|
|
462
|
+
return [schemaValueToExample(obj.items)];
|
|
463
|
+
}
|
|
464
|
+
if (obj.type === "object" && obj.properties && typeof obj.properties === "object") {
|
|
465
|
+
const example2 = {};
|
|
466
|
+
for (const [k, v] of Object.entries(obj.properties)) {
|
|
467
|
+
example2[k] = schemaValueToExample(v);
|
|
468
|
+
}
|
|
469
|
+
return example2;
|
|
470
|
+
}
|
|
471
|
+
if (obj.type === "object") return {};
|
|
472
|
+
if (Array.isArray(value)) {
|
|
473
|
+
if (value.length === 1) {
|
|
474
|
+
return [schemaValueToExample(value[0])];
|
|
475
|
+
}
|
|
476
|
+
return value.map(schemaValueToExample);
|
|
477
|
+
}
|
|
478
|
+
const example = {};
|
|
479
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
480
|
+
example[k] = schemaValueToExample(v);
|
|
481
|
+
}
|
|
482
|
+
return example;
|
|
483
|
+
}
|
|
484
|
+
function extractOutput(result) {
|
|
485
|
+
if ("structuredResponse" in result && result.structuredResponse !== void 0) {
|
|
486
|
+
return result.structuredResponse;
|
|
487
|
+
}
|
|
488
|
+
const messages = result.messages;
|
|
489
|
+
if (messages && Array.isArray(messages)) {
|
|
490
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
491
|
+
const msg = messages[i];
|
|
492
|
+
if (msg._getType() === "ai") {
|
|
493
|
+
const content = msg.content;
|
|
494
|
+
if (typeof content === "object" && content !== null && !Array.isArray(content)) {
|
|
495
|
+
return content;
|
|
496
|
+
}
|
|
497
|
+
if (Array.isArray(content)) {
|
|
498
|
+
const textParts = content.filter(
|
|
499
|
+
(c) => typeof c === "object" && c !== null && c.type === "text"
|
|
500
|
+
).map((c) => c.text).join("\n");
|
|
501
|
+
if (textParts) {
|
|
502
|
+
try {
|
|
503
|
+
return JSON.parse(textParts);
|
|
504
|
+
} catch {
|
|
505
|
+
const fenced = extractJsonFromFence(textParts);
|
|
506
|
+
if (fenced !== void 0) return fenced;
|
|
507
|
+
return textParts;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return content;
|
|
511
|
+
}
|
|
512
|
+
if (typeof content === "string") {
|
|
513
|
+
try {
|
|
514
|
+
return JSON.parse(content);
|
|
515
|
+
} catch {
|
|
516
|
+
const fenced = extractJsonFromFence(content);
|
|
517
|
+
if (fenced !== void 0) return fenced;
|
|
518
|
+
return content;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return content;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const { messages: _msgs, ...rest } = result;
|
|
526
|
+
void _msgs;
|
|
527
|
+
return rest;
|
|
528
|
+
}
|
|
529
|
+
function extractJsonFromFence(text) {
|
|
530
|
+
const jsonFence = text.match(/```json\s*\n([\s\S]*?)```/);
|
|
531
|
+
if (jsonFence) {
|
|
532
|
+
try {
|
|
533
|
+
return JSON.parse(jsonFence[1].trim());
|
|
534
|
+
} catch {
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const plainFence = text.match(/```\s*\n([\s\S]*?)```/);
|
|
538
|
+
if (plainFence) {
|
|
539
|
+
try {
|
|
540
|
+
return JSON.parse(plainFence[1].trim());
|
|
541
|
+
} catch {
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return void 0;
|
|
545
|
+
}
|
|
546
|
+
async function parallelLimit(items, limit, fn) {
|
|
547
|
+
const results = new Array(items.length);
|
|
548
|
+
let index = 0;
|
|
549
|
+
async function worker() {
|
|
550
|
+
while (index < items.length) {
|
|
551
|
+
const i = index++;
|
|
552
|
+
results[i] = await fn(items[i], i);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
|
|
556
|
+
return results;
|
|
557
|
+
}
|
|
558
|
+
async function invokeWithRetry(fn, maxRetries, retryOn, timeoutMs) {
|
|
559
|
+
let lastError;
|
|
560
|
+
for (let i = 0; i <= maxRetries; i++) {
|
|
561
|
+
try {
|
|
562
|
+
const promise = fn();
|
|
563
|
+
if (timeoutMs && timeoutMs > 0) {
|
|
564
|
+
return await withTimeout(promise, timeoutMs);
|
|
565
|
+
}
|
|
566
|
+
return await promise;
|
|
567
|
+
} catch (err) {
|
|
568
|
+
if (err?.name === "GraphInterrupt") {
|
|
569
|
+
throw err;
|
|
570
|
+
}
|
|
571
|
+
lastError = err;
|
|
572
|
+
if (i === maxRetries) throw err;
|
|
573
|
+
if (retryOn?.length) {
|
|
574
|
+
const errName = err?.name ?? "";
|
|
575
|
+
if (!retryOn.includes(errName)) throw err;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
throw lastError;
|
|
580
|
+
}
|
|
581
|
+
function withTimeout(promise, ms) {
|
|
582
|
+
return Promise.race([
|
|
583
|
+
promise,
|
|
584
|
+
new Promise(
|
|
585
|
+
(_, reject) => setTimeout(() => reject(new Error(`Operation timed out after ${ms}ms`)), ms)
|
|
586
|
+
)
|
|
587
|
+
]);
|
|
588
|
+
}
|
|
589
|
+
function createAgentNode(node, resolveAgent, trackingStore) {
|
|
590
|
+
return async (state, config) => {
|
|
591
|
+
const runId = state._runId;
|
|
592
|
+
const tenantId = config?.configurable?.tenantId ?? "default";
|
|
593
|
+
if (node.condition) {
|
|
594
|
+
try {
|
|
595
|
+
validateExpression(node.condition);
|
|
596
|
+
const safeExpr = toSafeStateExpr(node.condition);
|
|
597
|
+
const fn = new Function("state", `try { return ${safeExpr}; } catch { return false; }`);
|
|
598
|
+
const shouldRun = fn(state);
|
|
599
|
+
if (!shouldRun) {
|
|
600
|
+
console.log(`[WF][${node.id}] condition false, skipping "${node.name}" (${node.condition})`);
|
|
601
|
+
if (trackingStore && runId) {
|
|
602
|
+
const step = await trackingStore.upsertRunStep({
|
|
603
|
+
runId,
|
|
604
|
+
tenantId,
|
|
605
|
+
stepType: node.type,
|
|
606
|
+
stepName: node.name
|
|
607
|
+
}).catch(() => null);
|
|
608
|
+
if (step) {
|
|
609
|
+
await trackingStore.updateRunStep(runId, step.id, {
|
|
610
|
+
status: "skipped",
|
|
611
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
612
|
+
}).catch(() => {
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return { phase: node.id };
|
|
617
|
+
}
|
|
618
|
+
} catch (condErr) {
|
|
619
|
+
console.error(`[WF][${node.id}] condition eval error: ${condErr.message}`);
|
|
620
|
+
throw condErr;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
const startedAt = Date.now();
|
|
624
|
+
let stepId;
|
|
625
|
+
if (trackingStore && runId) {
|
|
626
|
+
trackingStore.updateWorkflowRun(runId, {
|
|
627
|
+
status: "running",
|
|
628
|
+
completedAt: null
|
|
629
|
+
}).catch(() => {
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
try {
|
|
633
|
+
console.log(`[WF][${node.id}] START "${node.name}" | hasSchema=${!!node.output?.schema}`);
|
|
634
|
+
const responseFormat = node.output?.schema;
|
|
635
|
+
console.log(`[WF][${node.id}] resolving agent...`);
|
|
636
|
+
const stepType = node.ask ? "ask" : node.type;
|
|
637
|
+
const client = await resolveAgent(node.ref, responseFormat, stepType);
|
|
638
|
+
console.log(`[WF][${node.id}] agent resolved, building input...`);
|
|
639
|
+
const input = buildInput(
|
|
640
|
+
node.input,
|
|
641
|
+
state,
|
|
642
|
+
void 0,
|
|
643
|
+
responseFormat ? void 0 : node.output?.schema
|
|
644
|
+
);
|
|
645
|
+
if (node.ask) {
|
|
646
|
+
input.messages = [
|
|
647
|
+
new SystemMessage(
|
|
648
|
+
"Follow this exact procedure. Do NOT skip any step.\n\nSTEP 1: Read the prompt. It contains content to present to the user and a question to ask.\n\nSTEP 2: Call ask_user_to_clarify. Your question text MUST contain the ACTUAL content from the prompt.\n\nSTEP 3: The tool will pause. The user will see your questions and respond. You will receive their answers.\n\nSTEP 4: Based on the user's answers, produce your final output."
|
|
649
|
+
),
|
|
650
|
+
...input.messages
|
|
651
|
+
];
|
|
652
|
+
}
|
|
653
|
+
const renderedInput = input.messages[0]?.content ?? "";
|
|
654
|
+
console.log(`[WF][${node.id}] === INPUT (full) ===
|
|
655
|
+
${renderedInput}
|
|
656
|
+
=== END INPUT ===`);
|
|
657
|
+
const subConfig = {
|
|
658
|
+
...config,
|
|
659
|
+
configurable: {
|
|
660
|
+
...config?.configurable ?? {},
|
|
661
|
+
thread_id: `${config?.configurable?.thread_id ?? "default"}:${node.id}`
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
if (trackingStore && runId) {
|
|
665
|
+
const step = await trackingStore.upsertRunStep({
|
|
666
|
+
runId,
|
|
667
|
+
tenantId,
|
|
668
|
+
stepType: node.type,
|
|
669
|
+
stepName: node.name,
|
|
670
|
+
threadId: subConfig.configurable.thread_id,
|
|
671
|
+
input: { template: node.input?.template, rendered: renderedInput }
|
|
672
|
+
}).catch((e) => {
|
|
673
|
+
console.warn("Failed to upsert run step:", e.message);
|
|
674
|
+
return null;
|
|
675
|
+
});
|
|
676
|
+
stepId = step?.id;
|
|
677
|
+
if (step && step.status === "interrupted") {
|
|
678
|
+
trackingStore.updateRunStep(runId, step.id, { status: "running" }).catch(() => {
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
let result;
|
|
683
|
+
try {
|
|
684
|
+
console.log(`[WF][${node.id}] invoking agent.invoke()...`);
|
|
685
|
+
result = await invokeWithRetry(
|
|
686
|
+
() => client.invoke(input, subConfig),
|
|
687
|
+
node.config?.maxRetries ?? 0,
|
|
688
|
+
node.config?.retryOn,
|
|
689
|
+
node.config?.timeout
|
|
690
|
+
);
|
|
691
|
+
console.log(`[WF][${node.id}] invoke SUCCESS, extracting output...`);
|
|
692
|
+
} catch (err) {
|
|
693
|
+
if (err?.name === "GraphInterrupt") {
|
|
694
|
+
if (trackingStore && runId && stepId) {
|
|
695
|
+
trackingStore.updateRunStep(runId, stepId, { status: "interrupted" }).catch(() => {
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
if (trackingStore && runId) {
|
|
699
|
+
trackingStore.updateWorkflowRun(runId, { status: "interrupted", completedAt: null }).catch(() => {
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
throw err;
|
|
703
|
+
}
|
|
704
|
+
const errMsg = err?.message ?? "";
|
|
705
|
+
console.log(`[WF][${node.id}] invoke FAILED: ${errMsg}`);
|
|
706
|
+
if (responseFormat && (errMsg.includes("tool_choice") || errMsg.includes("thinking") || errMsg.includes("reasoning") || errMsg.includes("InvalidParameter"))) {
|
|
707
|
+
console.log(`[WF][${node.id}] attempting fallback (text-based schema injection)...`);
|
|
708
|
+
const fallbackClient = await resolveAgent(node.ref, void 0, node.type);
|
|
709
|
+
const fallbackInput = buildInput(node.input, state, void 0, responseFormat);
|
|
710
|
+
result = await invokeWithRetry(
|
|
711
|
+
() => fallbackClient.invoke(fallbackInput, subConfig),
|
|
712
|
+
node.config?.maxRetries ?? 0,
|
|
713
|
+
node.config?.retryOn,
|
|
714
|
+
node.config?.timeout
|
|
715
|
+
);
|
|
716
|
+
console.log(`[WF][${node.id}] fallback invoke SUCCESS`);
|
|
717
|
+
} else {
|
|
718
|
+
throw err;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
const output = extractOutput(result);
|
|
722
|
+
console.log(`[WF][${node.id}] === OUTPUT (full) | key=${node.output?.key} ===
|
|
723
|
+
${JSON.stringify(output, null, 2)}
|
|
724
|
+
=== END OUTPUT ===`);
|
|
725
|
+
const update = { phase: node.id };
|
|
726
|
+
if (node.output?.key) {
|
|
727
|
+
update[node.output.key] = output;
|
|
728
|
+
}
|
|
729
|
+
const subMessages = result.messages;
|
|
730
|
+
if (subMessages && Array.isArray(subMessages)) {
|
|
731
|
+
const aiMessages = subMessages.filter((m) => {
|
|
732
|
+
const t = typeof m._getType === "function" ? m._getType() : m.role ?? m.type;
|
|
733
|
+
return t === "ai";
|
|
734
|
+
});
|
|
735
|
+
if (aiMessages.length > 0) {
|
|
736
|
+
update.messages = aiMessages;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
console.log(`[WF][${node.id}] DONE (${Date.now() - startedAt}ms)`);
|
|
740
|
+
if (trackingStore && runId && stepId) {
|
|
741
|
+
trackingStore.updateRunStep(runId, stepId, {
|
|
742
|
+
status: "completed",
|
|
743
|
+
output,
|
|
744
|
+
completedAt: /* @__PURE__ */ new Date(),
|
|
745
|
+
durationMs: Date.now() - startedAt
|
|
746
|
+
}).catch((e) => {
|
|
747
|
+
console.warn("Failed to update run step:", e.message);
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
return update;
|
|
751
|
+
} catch (err) {
|
|
752
|
+
if (err?.name === "GraphInterrupt") {
|
|
753
|
+
throw err;
|
|
754
|
+
}
|
|
755
|
+
if (trackingStore && runId) {
|
|
756
|
+
if (stepId) {
|
|
757
|
+
trackingStore.updateRunStep(runId, stepId, {
|
|
758
|
+
status: "failed",
|
|
759
|
+
errorMessage: err.message,
|
|
760
|
+
completedAt: /* @__PURE__ */ new Date(),
|
|
761
|
+
durationMs: Date.now() - startedAt
|
|
762
|
+
}).catch((e) => {
|
|
763
|
+
console.warn("Failed to update run step:", e.message);
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
trackingStore.updateWorkflowRun(runId, {
|
|
767
|
+
status: "failed",
|
|
768
|
+
errorMessage: err.message,
|
|
769
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
770
|
+
}).catch((e) => {
|
|
771
|
+
console.warn("Failed to finalize WorkflowRun:", e.message);
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
throw err;
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function createMapNode(node, resolveAgent, trackingStore) {
|
|
779
|
+
return async (state, config) => {
|
|
780
|
+
const runId = state._runId;
|
|
781
|
+
const tenantId = config?.configurable?.tenantId ?? "default";
|
|
782
|
+
if (node.condition) {
|
|
783
|
+
try {
|
|
784
|
+
validateExpression(node.condition);
|
|
785
|
+
const safeExpr = toSafeStateExpr(node.condition);
|
|
786
|
+
const fn = new Function("state", `try { return ${safeExpr}; } catch { return false; }`);
|
|
787
|
+
const shouldRun = fn(state);
|
|
788
|
+
if (!shouldRun) {
|
|
789
|
+
console.log(`[WF][${node.id}] condition false, skipping map "${node.name}" (${node.condition})`);
|
|
790
|
+
if (trackingStore && runId) {
|
|
791
|
+
const step = await trackingStore.upsertRunStep({
|
|
792
|
+
runId,
|
|
793
|
+
tenantId,
|
|
794
|
+
stepType: node.type,
|
|
795
|
+
stepName: node.name
|
|
796
|
+
}).catch(() => null);
|
|
797
|
+
if (step) {
|
|
798
|
+
await trackingStore.updateRunStep(runId, step.id, {
|
|
799
|
+
status: "skipped",
|
|
800
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
801
|
+
}).catch(() => {
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return { phase: node.id };
|
|
806
|
+
}
|
|
807
|
+
} catch (condErr) {
|
|
808
|
+
console.error(`[WF][${node.id}] condition eval error: ${condErr.message}`);
|
|
809
|
+
throw condErr;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
const startedAt = Date.now();
|
|
813
|
+
let stepId;
|
|
814
|
+
if (trackingStore && runId) {
|
|
815
|
+
trackingStore.updateWorkflowRun(runId, {
|
|
816
|
+
status: "running",
|
|
817
|
+
completedAt: null
|
|
818
|
+
}).catch(() => {
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
try {
|
|
822
|
+
const items = resolvePath(state, node.source);
|
|
823
|
+
if (trackingStore && runId) {
|
|
824
|
+
const step = await trackingStore.upsertRunStep({
|
|
825
|
+
runId,
|
|
826
|
+
tenantId,
|
|
827
|
+
stepType: node.type,
|
|
828
|
+
stepName: node.name,
|
|
829
|
+
input: { source: node.source, itemCount: Array.isArray(items) ? items.length : 0 }
|
|
830
|
+
}).catch((e) => {
|
|
831
|
+
console.warn("Failed to upsert run step:", e.message);
|
|
832
|
+
return null;
|
|
833
|
+
});
|
|
834
|
+
stepId = step?.id;
|
|
835
|
+
if (step && step.status === "interrupted") {
|
|
836
|
+
trackingStore.updateRunStep(runId, step.id, { status: "running" }).catch(() => {
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (!Array.isArray(items)) {
|
|
841
|
+
throw new Error(
|
|
842
|
+
`Map source "${node.source}" resolved to ${typeof items}, expected array`
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
const batchSize = node.config?.batchSize ?? 50;
|
|
846
|
+
const maxConcurrency = node.config?.maxConcurrency ?? 10;
|
|
847
|
+
const innerConcurrency = node.config?.innerConcurrency ?? 5;
|
|
848
|
+
const itemKey = node.itemKey ?? "item";
|
|
849
|
+
const innerClient = await resolveAgent(node.node.ref, node.node.schema, node.type);
|
|
850
|
+
const batches = chunk(items, batchSize);
|
|
851
|
+
const batchResults = await parallelLimit(batches, maxConcurrency, async (batch) => {
|
|
852
|
+
const itemResults = await parallelLimit(batch, innerConcurrency, async (item, itemIdx) => {
|
|
853
|
+
const itemCtx = { [itemKey]: item };
|
|
854
|
+
const input = buildInput(node.node.input, state, itemCtx);
|
|
855
|
+
const subConfig = {
|
|
856
|
+
...config,
|
|
857
|
+
configurable: {
|
|
858
|
+
...config?.configurable ?? {},
|
|
859
|
+
thread_id: `${config?.configurable?.thread_id ?? "default"}:${node.id}:item:${itemIdx}`
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
const result = await invokeWithRetry(
|
|
863
|
+
() => innerClient.invoke(input, subConfig),
|
|
864
|
+
node.config?.maxRetries ?? 0,
|
|
865
|
+
node.config?.retryOn,
|
|
866
|
+
node.config?.timeout
|
|
867
|
+
);
|
|
868
|
+
return extractOutput(result);
|
|
869
|
+
});
|
|
870
|
+
return itemResults;
|
|
871
|
+
});
|
|
872
|
+
const allResults = batchResults.flat();
|
|
873
|
+
let finalOutput = allResults;
|
|
874
|
+
if (node.reduce) {
|
|
875
|
+
const reduceClient = await resolveAgent(node.reduce.ref, node.reduce.schema, node.type);
|
|
876
|
+
const tempResultsKey = node.output?.key ?? `${node.id}_results`;
|
|
877
|
+
const tempState = { ...state, [tempResultsKey]: allResults };
|
|
878
|
+
const reduceInput = buildInput(node.reduce.input, tempState);
|
|
879
|
+
const reduceResult = await invokeWithRetry(
|
|
880
|
+
() => reduceClient.invoke(reduceInput, {
|
|
881
|
+
...config,
|
|
882
|
+
configurable: {
|
|
883
|
+
...config?.configurable ?? {},
|
|
884
|
+
thread_id: `${config?.configurable?.thread_id ?? "default"}:${node.id}:reduce`
|
|
885
|
+
}
|
|
886
|
+
}),
|
|
887
|
+
node.config?.maxRetries ?? 0,
|
|
888
|
+
node.config?.retryOn,
|
|
889
|
+
node.config?.timeout
|
|
890
|
+
);
|
|
891
|
+
finalOutput = extractOutput(reduceResult);
|
|
892
|
+
}
|
|
893
|
+
const update = { phase: node.id };
|
|
894
|
+
if (node.output?.key) {
|
|
895
|
+
update[node.output.key] = finalOutput;
|
|
896
|
+
}
|
|
897
|
+
if (trackingStore && runId && stepId) {
|
|
898
|
+
trackingStore.updateRunStep(runId, stepId, {
|
|
899
|
+
status: "completed",
|
|
900
|
+
output: finalOutput,
|
|
901
|
+
completedAt: /* @__PURE__ */ new Date(),
|
|
902
|
+
durationMs: Date.now() - startedAt
|
|
903
|
+
}).catch((e) => {
|
|
904
|
+
console.warn("Failed to update run step:", e.message);
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
return update;
|
|
908
|
+
} catch (err) {
|
|
909
|
+
if (err?.name === "GraphInterrupt") {
|
|
910
|
+
if (trackingStore && runId && stepId) {
|
|
911
|
+
trackingStore.updateRunStep(runId, stepId, { status: "interrupted" }).catch(() => {
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
if (trackingStore && runId) {
|
|
915
|
+
trackingStore.updateWorkflowRun(runId, { status: "interrupted", completedAt: null }).catch(() => {
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
throw err;
|
|
919
|
+
}
|
|
920
|
+
console.error(`[WF][${node.id}] FAILED after ${Date.now() - startedAt}ms:`, err.stack || err.message);
|
|
921
|
+
if (trackingStore && runId) {
|
|
922
|
+
if (stepId) {
|
|
923
|
+
trackingStore.updateRunStep(runId, stepId, {
|
|
924
|
+
status: "failed",
|
|
925
|
+
errorMessage: err.message,
|
|
926
|
+
completedAt: /* @__PURE__ */ new Date(),
|
|
927
|
+
durationMs: Date.now() - startedAt
|
|
928
|
+
}).catch(() => {
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
trackingStore.updateWorkflowRun(runId, {
|
|
932
|
+
status: "failed",
|
|
933
|
+
errorMessage: err.message,
|
|
934
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
935
|
+
}).catch(() => {
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
throw err;
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
function createInputNode(node, trackingStore) {
|
|
943
|
+
return async (state, config) => {
|
|
944
|
+
const tenantId = config?.configurable?.tenantId ?? "default";
|
|
945
|
+
const threadId = config?.configurable?.thread_id ?? "default";
|
|
946
|
+
const assistantId = config?.configurable?.assistantId || config?.configurable?.assistant_id || "unknown";
|
|
947
|
+
const messages = state.messages;
|
|
948
|
+
const humanMsg = messages?.find((m) => m._getType() === "human");
|
|
949
|
+
const inputText = typeof humanMsg?.content === "string" ? humanMsg.content : JSON.stringify(humanMsg?.content ?? "");
|
|
950
|
+
console.log(`[WF][n_input] input captured: "${inputText.slice(0, 200)}"`);
|
|
951
|
+
const update = { phase: node.id };
|
|
952
|
+
if (node.output?.key) update[node.output.key] = inputText;
|
|
953
|
+
if (trackingStore && !state._runId) {
|
|
954
|
+
try {
|
|
955
|
+
const run = await trackingStore.createWorkflowRun({
|
|
956
|
+
tenantId,
|
|
957
|
+
assistantId,
|
|
958
|
+
threadId,
|
|
959
|
+
topologyEdges: [],
|
|
960
|
+
metadata: { workflowName: node.name }
|
|
961
|
+
});
|
|
962
|
+
update._runId = run.id;
|
|
963
|
+
await trackingStore.createRunStep({
|
|
964
|
+
runId: run.id,
|
|
965
|
+
tenantId,
|
|
966
|
+
stepType: node.type,
|
|
967
|
+
stepName: node.name,
|
|
968
|
+
input: { rendered: inputText }
|
|
969
|
+
}).catch((e) => {
|
|
970
|
+
console.warn("Failed to create input run step:", e.message);
|
|
971
|
+
});
|
|
972
|
+
} catch (e) {
|
|
973
|
+
console.warn("Failed to create workflow run:", e.message);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return update;
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
function terminalStatusToRunStatus(status) {
|
|
980
|
+
switch (status) {
|
|
981
|
+
case "failed":
|
|
982
|
+
return "failed";
|
|
983
|
+
case "cancelled":
|
|
984
|
+
return "cancelled";
|
|
985
|
+
case "success":
|
|
986
|
+
default:
|
|
987
|
+
return "completed";
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
function createTerminalNode(node, trackingStore) {
|
|
991
|
+
const runStatus = terminalStatusToRunStatus(node.status);
|
|
992
|
+
return async (state) => {
|
|
993
|
+
const runId = state._runId;
|
|
994
|
+
if (trackingStore && runId) {
|
|
995
|
+
try {
|
|
996
|
+
await trackingStore.updateWorkflowRun(runId, {
|
|
997
|
+
status: runStatus,
|
|
998
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
999
|
+
});
|
|
1000
|
+
} catch (e) {
|
|
1001
|
+
console.warn(`[WF][terminal] Failed to finalize WorkflowRun "${runId}":`, e.message);
|
|
1002
|
+
}
|
|
1003
|
+
try {
|
|
1004
|
+
await trackingStore.createRunStep({
|
|
1005
|
+
runId,
|
|
1006
|
+
tenantId: state._tenantId ?? "default",
|
|
1007
|
+
stepType: "terminal",
|
|
1008
|
+
stepName: node.name,
|
|
1009
|
+
input: { status: node.status }
|
|
1010
|
+
});
|
|
1011
|
+
} catch (e) {
|
|
1012
|
+
console.warn(`[WF][terminal] Failed to create terminal RunStep:`, e.message);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
return {
|
|
1016
|
+
status: node.status,
|
|
1017
|
+
phase: node.id
|
|
1018
|
+
};
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
function createNodeHandler(node, resolveAgent, trackingStore) {
|
|
1022
|
+
switch (node.type) {
|
|
1023
|
+
case "agent":
|
|
1024
|
+
return createAgentNode(node, resolveAgent, trackingStore);
|
|
1025
|
+
case "map":
|
|
1026
|
+
return createMapNode(node, resolveAgent, trackingStore);
|
|
1027
|
+
case "terminal":
|
|
1028
|
+
return createTerminalNode(node, trackingStore);
|
|
1029
|
+
case "input":
|
|
1030
|
+
return createInputNode(node, trackingStore);
|
|
1031
|
+
default:
|
|
1032
|
+
throw new Error(`Unknown node type: ${node.type}`);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
function chunk(arr, size) {
|
|
1036
|
+
const result = [];
|
|
1037
|
+
for (let i = 0; i < arr.length; i += size) {
|
|
1038
|
+
result.push(arr.slice(i, i + size));
|
|
1039
|
+
}
|
|
1040
|
+
return result;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// src/workflow/compile.ts
|
|
1044
|
+
function compileWorkflow(yamlStr, resolveAgent, checkpointer, trackingStore) {
|
|
1045
|
+
console.log(`[WF COMPILE] compiling YAML workflow`);
|
|
1046
|
+
const ir = parseYaml(yamlStr);
|
|
1047
|
+
return compileInternal(ir, resolveAgent, checkpointer, trackingStore);
|
|
1048
|
+
}
|
|
1049
|
+
function compileInternal(ir, resolveAgent, checkpointer, trackingStore) {
|
|
1050
|
+
validateAndThrow(ir);
|
|
1051
|
+
console.log(`[WF COMPILE] validation passed`);
|
|
1052
|
+
const StateAnnotation = buildStateAnnotation(ir.state?.fields);
|
|
1053
|
+
const builder = new StateGraph(StateAnnotation);
|
|
1054
|
+
for (const node of ir.nodes) {
|
|
1055
|
+
console.log(`[WF COMPILE] registering node: id=${node.id} type=${node.type} name=${node.name}`);
|
|
1056
|
+
const handler = createNodeHandler(node, resolveAgent, trackingStore);
|
|
1057
|
+
builder.addNode(node.id, handler);
|
|
1058
|
+
}
|
|
1059
|
+
for (const node of ir.nodes) {
|
|
1060
|
+
if (node.type === "terminal") {
|
|
1061
|
+
builder.addEdge(node.id, END);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
for (const edge of ir.edges) {
|
|
1065
|
+
const from = edge.from === "START" ? START : edge.from;
|
|
1066
|
+
const targets = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
1067
|
+
for (const to of targets) {
|
|
1068
|
+
builder.addEdge(from, to === "END" ? END : to);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
const graph = builder.compile({ checkpointer, name: ir.name });
|
|
1072
|
+
console.log(`[WF COMPILE] graph compiled successfully`);
|
|
1073
|
+
return graph;
|
|
1074
|
+
}
|
|
1075
|
+
function validateDSL(dsl) {
|
|
1076
|
+
const errors = [];
|
|
1077
|
+
const nodeIds = /* @__PURE__ */ new Set();
|
|
1078
|
+
for (const node of dsl.nodes) {
|
|
1079
|
+
if (nodeIds.has(node.id)) {
|
|
1080
|
+
errors.push({ type: "error", message: `Duplicate node id "${node.id}"` });
|
|
1081
|
+
}
|
|
1082
|
+
nodeIds.add(node.id);
|
|
1083
|
+
}
|
|
1084
|
+
const terminalIds = new Set(
|
|
1085
|
+
dsl.nodes.filter((n) => n.type === "terminal").map((n) => n.id)
|
|
1086
|
+
);
|
|
1087
|
+
for (const edge of dsl.edges) {
|
|
1088
|
+
if (edge.from !== "START" && !nodeIds.has(edge.from)) {
|
|
1089
|
+
errors.push({ type: "error", message: `Edge from "${edge.from}" references unknown node` });
|
|
1090
|
+
}
|
|
1091
|
+
if (edge.from !== "START" && terminalIds.has(edge.from)) {
|
|
1092
|
+
errors.push({ type: "error", message: `Terminal node "${edge.from}" cannot have outgoing edges` });
|
|
1093
|
+
}
|
|
1094
|
+
const targets = Array.isArray(edge.to) ? edge.to : edge.to ? [edge.to] : [];
|
|
1095
|
+
for (const to of targets) {
|
|
1096
|
+
if (to !== "END" && !nodeIds.has(to)) {
|
|
1097
|
+
errors.push({ type: "error", message: `Edge to "${to}" references unknown node` });
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
for (const node of dsl.nodes) {
|
|
1102
|
+
if (node.type === "terminal") {
|
|
1103
|
+
const hasIncoming = dsl.edges.some((e) => {
|
|
1104
|
+
const targets = Array.isArray(e.to) ? e.to : e.to ? [e.to] : [];
|
|
1105
|
+
return targets.includes(node.id);
|
|
1106
|
+
});
|
|
1107
|
+
if (!hasIncoming) {
|
|
1108
|
+
errors.push({ type: "warning", message: `Terminal node "${node.id}" has no incoming edge` });
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
return errors;
|
|
1113
|
+
}
|
|
1114
|
+
function validateAndThrow(dsl) {
|
|
1115
|
+
const errors = validateDSL(dsl);
|
|
1116
|
+
const critical = errors.filter((e) => e.type === "error");
|
|
1117
|
+
if (critical.length > 0) {
|
|
1118
|
+
throw new Error(critical.map((e) => e.message).join("; "));
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
export {
|
|
1123
|
+
toJsonSchema,
|
|
1124
|
+
toSafeStateExpr,
|
|
1125
|
+
parseYaml,
|
|
1126
|
+
buildStateAnnotation,
|
|
1127
|
+
resolvePath,
|
|
1128
|
+
renderTemplate,
|
|
1129
|
+
buildInput,
|
|
1130
|
+
extractOutput,
|
|
1131
|
+
parallelLimit,
|
|
1132
|
+
invokeWithRetry,
|
|
1133
|
+
createAgentNode,
|
|
1134
|
+
createMapNode,
|
|
1135
|
+
createNodeHandler,
|
|
1136
|
+
compileWorkflow,
|
|
1137
|
+
compileInternal,
|
|
1138
|
+
validateDSL
|
|
1139
|
+
};
|
|
1140
|
+
//# sourceMappingURL=chunk-SGRFQY3E.mjs.map
|