@axiom-lattice/core 2.1.80 → 2.1.82
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-FN4TRQK4.mjs → chunk-SGRFQY3E.mjs} +386 -504
- package/dist/chunk-SGRFQY3E.mjs.map +1 -0
- package/dist/compile-4RFYHUBE.mjs +11 -0
- package/dist/index.d.mts +219 -34
- package/dist/index.d.ts +219 -34
- package/dist/index.js +1725 -1299
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1278 -745
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
- package/dist/chunk-FN4TRQK4.mjs.map +0 -1
- package/dist/compile-SYSKVQHB.mjs +0 -9
- /package/dist/{compile-SYSKVQHB.mjs.map → compile-4RFYHUBE.mjs.map} +0 -0
|
@@ -10,6 +10,300 @@ import {
|
|
|
10
10
|
Annotation
|
|
11
11
|
} from "@langchain/langgraph";
|
|
12
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
|
|
13
307
|
function buildStateAnnotation(fields) {
|
|
14
308
|
const annotations = {};
|
|
15
309
|
if (fields) {
|
|
@@ -296,6 +590,36 @@ function createAgentNode(node, resolveAgent, trackingStore) {
|
|
|
296
590
|
return async (state, config) => {
|
|
297
591
|
const runId = state._runId;
|
|
298
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
|
+
}
|
|
299
623
|
const startedAt = Date.now();
|
|
300
624
|
let stepId;
|
|
301
625
|
if (trackingStore && runId) {
|
|
@@ -309,7 +633,8 @@ function createAgentNode(node, resolveAgent, trackingStore) {
|
|
|
309
633
|
console.log(`[WF][${node.id}] START "${node.name}" | hasSchema=${!!node.output?.schema}`);
|
|
310
634
|
const responseFormat = node.output?.schema;
|
|
311
635
|
console.log(`[WF][${node.id}] resolving agent...`);
|
|
312
|
-
const
|
|
636
|
+
const stepType = node.ask ? "ask" : node.type;
|
|
637
|
+
const client = await resolveAgent(node.ref, responseFormat, stepType);
|
|
313
638
|
console.log(`[WF][${node.id}] agent resolved, building input...`);
|
|
314
639
|
const input = buildInput(
|
|
315
640
|
node.input,
|
|
@@ -317,16 +642,32 @@ function createAgentNode(node, resolveAgent, trackingStore) {
|
|
|
317
642
|
void 0,
|
|
318
643
|
responseFormat ? void 0 : node.output?.schema
|
|
319
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
|
+
}
|
|
320
653
|
const renderedInput = input.messages[0]?.content ?? "";
|
|
321
654
|
console.log(`[WF][${node.id}] === INPUT (full) ===
|
|
322
655
|
${renderedInput}
|
|
323
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
|
+
};
|
|
324
664
|
if (trackingStore && runId) {
|
|
325
665
|
const step = await trackingStore.upsertRunStep({
|
|
326
666
|
runId,
|
|
327
667
|
tenantId,
|
|
328
668
|
stepType: node.type,
|
|
329
669
|
stepName: node.name,
|
|
670
|
+
threadId: subConfig.configurable.thread_id,
|
|
330
671
|
input: { template: node.input?.template, rendered: renderedInput }
|
|
331
672
|
}).catch((e) => {
|
|
332
673
|
console.warn("Failed to upsert run step:", e.message);
|
|
@@ -338,13 +679,6 @@ ${renderedInput}
|
|
|
338
679
|
});
|
|
339
680
|
}
|
|
340
681
|
}
|
|
341
|
-
const subConfig = {
|
|
342
|
-
...config,
|
|
343
|
-
configurable: {
|
|
344
|
-
...config?.configurable ?? {},
|
|
345
|
-
thread_id: `${config?.configurable?.thread_id ?? "default"}:${node.id}`
|
|
346
|
-
}
|
|
347
|
-
};
|
|
348
682
|
let result;
|
|
349
683
|
try {
|
|
350
684
|
console.log(`[WF][${node.id}] invoking agent.invoke()...`);
|
|
@@ -441,157 +775,40 @@ ${JSON.stringify(output, null, 2)}
|
|
|
441
775
|
}
|
|
442
776
|
};
|
|
443
777
|
}
|
|
444
|
-
function
|
|
778
|
+
function createMapNode(node, resolveAgent, trackingStore) {
|
|
445
779
|
return async (state, config) => {
|
|
446
780
|
const runId = state._runId;
|
|
447
781
|
const tenantId = config?.configurable?.tenantId ?? "default";
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
STEP 1: Read the prompt. It contains content to present to the user and a question to ask.
|
|
474
|
-
|
|
475
|
-
STEP 2: Call ask_user_to_clarify. Your question text MUST contain the ACTUAL content from the prompt \u2014 copy-paste the relevant text, data, or facts directly into the question. A question like "Do you approve this recommendation?" is WRONG because the user has no idea what the recommendation says. Instead: "Do you approve this recommendation: [paste the actual recommendation text here]?"
|
|
476
|
-
|
|
477
|
-
STEP 3: The tool will pause. The user will see your questions and respond. You will receive their answers.
|
|
478
|
-
|
|
479
|
-
STEP 4: Based on the user's answers, produce your final output.`
|
|
480
|
-
),
|
|
481
|
-
...input.messages
|
|
482
|
-
];
|
|
483
|
-
const renderedInput = humanContent;
|
|
484
|
-
if (trackingStore && runId) {
|
|
485
|
-
const step = await trackingStore.upsertRunStep({
|
|
486
|
-
runId,
|
|
487
|
-
tenantId,
|
|
488
|
-
stepType: node.type,
|
|
489
|
-
stepName: node.name,
|
|
490
|
-
input: { template: node.input?.template, rendered: renderedInput }
|
|
491
|
-
}).catch((e) => {
|
|
492
|
-
console.warn("Failed to upsert run step:", e.message);
|
|
493
|
-
return null;
|
|
494
|
-
});
|
|
495
|
-
stepId = step?.id;
|
|
496
|
-
if (step && step.status === "interrupted") {
|
|
497
|
-
trackingStore.updateRunStep(runId, step.id, { status: "running" }).catch(() => {
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
const subConfig = {
|
|
502
|
-
...config,
|
|
503
|
-
configurable: {
|
|
504
|
-
...config?.configurable ?? {},
|
|
505
|
-
thread_id: `${config?.configurable?.thread_id ?? "default"}:${node.id}`
|
|
506
|
-
}
|
|
507
|
-
};
|
|
508
|
-
console.log(`[WF][${node.id}] === INPUT (full) ===
|
|
509
|
-
${renderedInput}
|
|
510
|
-
=== END INPUT ===`);
|
|
511
|
-
const result = await invokeWithRetry(
|
|
512
|
-
() => client.invoke(input, subConfig),
|
|
513
|
-
node.config?.maxRetries ?? 0,
|
|
514
|
-
node.config?.retryOn,
|
|
515
|
-
node.config?.timeout
|
|
516
|
-
);
|
|
517
|
-
console.log(`[WF][${node.id}] invoke SUCCESS, extracting output...`);
|
|
518
|
-
const output = extractOutput(result);
|
|
519
|
-
console.log(`[WF][${node.id}] === OUTPUT (full) | key=${node.output?.key} ===
|
|
520
|
-
${JSON.stringify(output, null, 2)}
|
|
521
|
-
=== END OUTPUT ===`);
|
|
522
|
-
const update = { phase: node.id };
|
|
523
|
-
if (node.output?.key) {
|
|
524
|
-
update[node.output.key] = output;
|
|
525
|
-
}
|
|
526
|
-
if (output && typeof output === "object" && !Array.isArray(output)) {
|
|
527
|
-
Object.assign(update, output);
|
|
528
|
-
}
|
|
529
|
-
const subMessages = result.messages;
|
|
530
|
-
if (subMessages && Array.isArray(subMessages)) {
|
|
531
|
-
const aiMessages = subMessages.filter((m) => {
|
|
532
|
-
const t = typeof m._getType === "function" ? m._getType() : m.role ?? m.type;
|
|
533
|
-
return t === "ai";
|
|
534
|
-
});
|
|
535
|
-
if (aiMessages.length > 0) {
|
|
536
|
-
update.messages = aiMessages;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
console.log(`[WF][${node.id}] DONE (${Date.now() - startedAt}ms)`);
|
|
540
|
-
if (trackingStore && runId && stepId) {
|
|
541
|
-
trackingStore.updateRunStep(runId, stepId, {
|
|
542
|
-
status: "completed",
|
|
543
|
-
output,
|
|
544
|
-
completedAt: /* @__PURE__ */ new Date(),
|
|
545
|
-
durationMs: Date.now() - startedAt
|
|
546
|
-
}).catch((e) => {
|
|
547
|
-
console.warn("Failed to update run step:", e.message);
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
return update;
|
|
551
|
-
} catch (err) {
|
|
552
|
-
if (err?.name === "GraphInterrupt") {
|
|
553
|
-
if (trackingStore && runId && stepId) {
|
|
554
|
-
trackingStore.updateRunStep(runId, stepId, {
|
|
555
|
-
status: "interrupted"
|
|
556
|
-
}).catch(() => {
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
if (trackingStore && runId) {
|
|
560
|
-
trackingStore.updateWorkflowRun(runId, {
|
|
561
|
-
status: "interrupted",
|
|
562
|
-
completedAt: null
|
|
563
|
-
}).catch(() => {
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
throw err;
|
|
567
|
-
}
|
|
568
|
-
if (trackingStore && runId) {
|
|
569
|
-
if (stepId) {
|
|
570
|
-
trackingStore.updateRunStep(runId, stepId, {
|
|
571
|
-
status: "failed",
|
|
572
|
-
errorMessage: err.message,
|
|
573
|
-
completedAt: /* @__PURE__ */ new Date(),
|
|
574
|
-
durationMs: Date.now() - startedAt
|
|
575
|
-
}).catch((e) => {
|
|
576
|
-
console.warn("Failed to update run step:", e.message);
|
|
577
|
-
});
|
|
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 };
|
|
578
806
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
completedAt: /* @__PURE__ */ new Date()
|
|
583
|
-
}).catch((e) => {
|
|
584
|
-
console.warn("Failed to finalize WorkflowRun:", e.message);
|
|
585
|
-
});
|
|
807
|
+
} catch (condErr) {
|
|
808
|
+
console.error(`[WF][${node.id}] condition eval error: ${condErr.message}`);
|
|
809
|
+
throw condErr;
|
|
586
810
|
}
|
|
587
|
-
throw err;
|
|
588
811
|
}
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
function createMapNode(node, resolveAgent, trackingStore) {
|
|
592
|
-
return async (state, config) => {
|
|
593
|
-
const runId = state._runId;
|
|
594
|
-
const tenantId = config?.configurable?.tenantId ?? "default";
|
|
595
812
|
const startedAt = Date.now();
|
|
596
813
|
let stepId;
|
|
597
814
|
if (trackingStore && runId) {
|
|
@@ -805,8 +1022,6 @@ function createNodeHandler(node, resolveAgent, trackingStore) {
|
|
|
805
1022
|
switch (node.type) {
|
|
806
1023
|
case "agent":
|
|
807
1024
|
return createAgentNode(node, resolveAgent, trackingStore);
|
|
808
|
-
case "human_feedback":
|
|
809
|
-
return createHumanFeedbackNode(node, resolveAgent, trackingStore);
|
|
810
1025
|
case "map":
|
|
811
1026
|
return createMapNode(node, resolveAgent, trackingStore);
|
|
812
1027
|
case "terminal":
|
|
@@ -825,298 +1040,13 @@ function chunk(arr, size) {
|
|
|
825
1040
|
return result;
|
|
826
1041
|
}
|
|
827
1042
|
|
|
828
|
-
// src/workflow/normalize.ts
|
|
829
|
-
var _counter = 0;
|
|
830
|
-
function uid(prefix) {
|
|
831
|
-
return `${prefix}_${_counter++}`;
|
|
832
|
-
}
|
|
833
|
-
function nodeId(stepId) {
|
|
834
|
-
return `n_${stepId}`;
|
|
835
|
-
}
|
|
836
|
-
function translate(template) {
|
|
837
|
-
return template.replace(/\{\{([^}]+)\}\}/g, (_m, expr) => {
|
|
838
|
-
const t = expr.trim();
|
|
839
|
-
if (t === "item") return "${item}";
|
|
840
|
-
return `\${state.${t}}`;
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
function inferFieldType(id) {
|
|
844
|
-
if (/s$|list|array|items|results/i.test(id)) return { type: "array", default: [] };
|
|
845
|
-
return { type: "string" };
|
|
846
|
-
}
|
|
847
|
-
function expandStep(step) {
|
|
848
|
-
switch (step.type ?? "agent") {
|
|
849
|
-
case "agent":
|
|
850
|
-
return expandAgent(step);
|
|
851
|
-
case "human":
|
|
852
|
-
return expandHuman(step);
|
|
853
|
-
case "condition":
|
|
854
|
-
return expandCondition(step);
|
|
855
|
-
case "map":
|
|
856
|
-
return expandMap(step);
|
|
857
|
-
case "parallel":
|
|
858
|
-
return expandParallel(step);
|
|
859
|
-
case "end":
|
|
860
|
-
return expandEnd(step);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
function validateSchema(schema, stepId) {
|
|
864
|
-
if (schema.type !== "object") {
|
|
865
|
-
throw new Error(
|
|
866
|
-
`Step "${stepId}": schema.type must be "object", got "${String(schema.type)}". Use standard JSON Schema: { "type": "object", "properties": { ... } }`
|
|
867
|
-
);
|
|
868
|
-
}
|
|
869
|
-
if (!schema.properties || typeof schema.properties !== "object") {
|
|
870
|
-
throw new Error(
|
|
871
|
-
`Step "${stepId}": schema must have a "properties" object. Legacy shorthand like { "field": "string" } is not supported. Use: { "type": "object", "properties": { "field": { "type": "string" } } }`
|
|
872
|
-
);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
function expandAgent(s) {
|
|
876
|
-
const sid = s.id || uid("agent");
|
|
877
|
-
const nid = nodeId(sid);
|
|
878
|
-
if (s.schema !== void 0 && s.schema !== null) {
|
|
879
|
-
if (typeof s.schema !== "object") {
|
|
880
|
-
throw new Error(
|
|
881
|
-
`Step "${sid}": schema must be a JSON Schema object, e.g. { "type": "object", "properties": { ... } }. Got ${typeof s.schema} (${JSON.stringify(s.schema)}).`
|
|
882
|
-
);
|
|
883
|
-
}
|
|
884
|
-
validateSchema(s.schema, sid);
|
|
885
|
-
}
|
|
886
|
-
const schema = s.schema && typeof s.schema === "object" ? s.schema : void 0;
|
|
887
|
-
console.log(`[WF EXPAND] agent step id="${sid}" nodeId="${nid}" | hasSchema=${!!schema}`);
|
|
888
|
-
return {
|
|
889
|
-
nodes: [{ id: nid, type: "agent", name: sid, input: { template: translate(s.prompt) }, output: { key: sid, ...schema ? { schema } : {} } }],
|
|
890
|
-
edges: [],
|
|
891
|
-
entryId: nid,
|
|
892
|
-
exitIds: [nid]
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
|
-
function expandHuman(s) {
|
|
896
|
-
const sid = s.id || uid("human");
|
|
897
|
-
const nid = nodeId(sid);
|
|
898
|
-
if (s.schema !== void 0 && s.schema !== null) {
|
|
899
|
-
if (typeof s.schema !== "object") {
|
|
900
|
-
throw new Error(
|
|
901
|
-
`Step "${sid}": schema must be a JSON Schema object, e.g. { "type": "object", "properties": { ... } }. Got ${typeof s.schema} (${JSON.stringify(s.schema)}).`
|
|
902
|
-
);
|
|
903
|
-
}
|
|
904
|
-
validateSchema(s.schema, sid);
|
|
905
|
-
}
|
|
906
|
-
const schema = s.schema && typeof s.schema === "object" ? s.schema : void 0;
|
|
907
|
-
return {
|
|
908
|
-
nodes: [{ id: nid, type: "human_feedback", name: sid, config: { title: s.title ?? sid }, input: { template: translate(s.prompt) }, output: { key: sid, ...schema ? { schema } : {} } }],
|
|
909
|
-
edges: [],
|
|
910
|
-
entryId: nid,
|
|
911
|
-
exitIds: [nid]
|
|
912
|
-
};
|
|
913
|
-
}
|
|
914
|
-
function expandCondition(s) {
|
|
915
|
-
const sid = s.id || uid("cond");
|
|
916
|
-
const nid = nodeId(sid);
|
|
917
|
-
if (s.branches) {
|
|
918
|
-
const branchExps = {};
|
|
919
|
-
for (const [key, steps] of Object.entries(s.branches)) {
|
|
920
|
-
branchExps[key] = expandSteps(Array.isArray(steps) ? steps : [steps]);
|
|
921
|
-
}
|
|
922
|
-
const allNodes = Object.values(branchExps).flatMap((e) => e.nodes);
|
|
923
|
-
const allEdges = Object.values(branchExps).flatMap((e) => e.edges);
|
|
924
|
-
const allExitIds = [...new Set(Object.values(branchExps).flatMap((e) => e.exitIds))];
|
|
925
|
-
const branchEntryIds = Object.fromEntries(
|
|
926
|
-
Object.entries(branchExps).map(([k, v]) => [k, v.entryId])
|
|
927
|
-
);
|
|
928
|
-
return {
|
|
929
|
-
nodes: allNodes,
|
|
930
|
-
edges: allEdges,
|
|
931
|
-
entryId: nid,
|
|
932
|
-
exitIds: allExitIds,
|
|
933
|
-
branchEntryIds
|
|
934
|
-
};
|
|
935
|
-
}
|
|
936
|
-
if (!s.then) {
|
|
937
|
-
throw new Error(`Condition step "${sid}": must have either "then" or "branches"`);
|
|
938
|
-
}
|
|
939
|
-
const thenSteps = Array.isArray(s.then) ? s.then : [s.then];
|
|
940
|
-
const elseSteps = s.else ? Array.isArray(s.else) ? s.else : [s.else] : [];
|
|
941
|
-
const thenExp = expandSteps(thenSteps);
|
|
942
|
-
const elseExp = expandSteps(elseSteps);
|
|
943
|
-
return {
|
|
944
|
-
nodes: [...thenExp.nodes, ...elseExp.nodes],
|
|
945
|
-
edges: [...thenExp.edges, ...elseExp.edges],
|
|
946
|
-
entryId: nid,
|
|
947
|
-
exitIds: [...thenExp.exitIds, ...elseExp.exitIds],
|
|
948
|
-
thenEntryId: thenExp.entryId,
|
|
949
|
-
elseEntryId: elseExp.entryId
|
|
950
|
-
};
|
|
951
|
-
}
|
|
952
|
-
function expandMap(s) {
|
|
953
|
-
const sid = s.id;
|
|
954
|
-
const nid = nodeId(sid);
|
|
955
|
-
const inner = s.each;
|
|
956
|
-
return {
|
|
957
|
-
nodes: [{
|
|
958
|
-
id: nid,
|
|
959
|
-
type: "map",
|
|
960
|
-
name: sid,
|
|
961
|
-
source: `state.${s.source}`,
|
|
962
|
-
itemKey: "item",
|
|
963
|
-
config: { batchSize: s.batch ?? 50, maxConcurrency: s.concurrency ?? 5, innerConcurrency: s.concurrency ?? 5 },
|
|
964
|
-
node: { type: "agent", input: inner.prompt ? { template: translate(inner.prompt) } : void 0, ...inner.schema && typeof inner.schema === "object" ? { schema: inner.schema } : {} },
|
|
965
|
-
...s.reduce ? { reduce: { input: s.reduce.prompt ? { template: translate(s.reduce.prompt) } : void 0, ...s.reduce.schema && typeof s.reduce.schema === "object" ? { schema: s.reduce.schema } : {} } } : {},
|
|
966
|
-
output: { key: sid }
|
|
967
|
-
}],
|
|
968
|
-
edges: [],
|
|
969
|
-
entryId: nid,
|
|
970
|
-
exitIds: [nid]
|
|
971
|
-
};
|
|
972
|
-
}
|
|
973
|
-
function expandParallel(s) {
|
|
974
|
-
const nodes = [];
|
|
975
|
-
const edges = [];
|
|
976
|
-
const entryIds = [];
|
|
977
|
-
const exitIds = [];
|
|
978
|
-
for (const step of s.steps) {
|
|
979
|
-
const exp = expandStep(step);
|
|
980
|
-
nodes.push(...exp.nodes);
|
|
981
|
-
edges.push(...exp.edges);
|
|
982
|
-
entryIds.push(exp.entryId);
|
|
983
|
-
if (exp.exitIds.length > 0) exitIds.push(...exp.exitIds);
|
|
984
|
-
}
|
|
985
|
-
return { nodes, edges, entryId: entryIds[0], exitIds };
|
|
986
|
-
}
|
|
987
|
-
function expandEnd(s) {
|
|
988
|
-
const sid = uid("end");
|
|
989
|
-
const nid = nodeId(sid);
|
|
990
|
-
return { nodes: [{ id: nid, type: "terminal", name: sid, status: s.status ?? "success" }], edges: [], entryId: nid, exitIds: [] };
|
|
991
|
-
}
|
|
992
|
-
function expandSteps(steps, fromStart = false) {
|
|
993
|
-
if (steps.length === 0) return { nodes: [], edges: [], entryId: "END", exitIds: ["END"] };
|
|
994
|
-
const allNodes = [];
|
|
995
|
-
const allEdges = [];
|
|
996
|
-
let prevExitIds = [];
|
|
997
|
-
let firstEntryId = null;
|
|
998
|
-
for (let i = 0; i < steps.length; i++) {
|
|
999
|
-
const exp = expandStep(steps[i]);
|
|
1000
|
-
if (exp.nodes.length > 0) allNodes.push(...exp.nodes);
|
|
1001
|
-
if (exp.edges.length > 0) allEdges.push(...exp.edges);
|
|
1002
|
-
if (firstEntryId === null) firstEntryId = exp.entryId;
|
|
1003
|
-
if (prevExitIds.length > 0) addEdge(steps[i], exp, prevExitIds, allEdges);
|
|
1004
|
-
else if (fromStart && i === 0) addEdge(steps[i], exp, ["START"], allEdges);
|
|
1005
|
-
prevExitIds = exp.exitIds.length > 0 ? exp.exitIds : prevExitIds;
|
|
1006
|
-
}
|
|
1007
|
-
return { nodes: allNodes, edges: allEdges, entryId: firstEntryId || "END", exitIds: prevExitIds };
|
|
1008
|
-
}
|
|
1009
|
-
function toSafeStateExpr(expr) {
|
|
1010
|
-
const match = expr.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)/);
|
|
1011
|
-
if (!match) return `state.${expr}`;
|
|
1012
|
-
const field = match[0];
|
|
1013
|
-
const rest = expr.slice(field.length);
|
|
1014
|
-
return `state["${field}"]${rest}`;
|
|
1015
|
-
}
|
|
1016
|
-
function addEdge(step, exp, fromIds, allEdges) {
|
|
1017
|
-
if (step.type === "condition") {
|
|
1018
|
-
const s = step;
|
|
1019
|
-
if (s.if.includes("{{")) {
|
|
1020
|
-
throw new Error(
|
|
1021
|
-
`Condition step "${s.id || "unnamed"}": the \`if\` field contains "{{". Use a plain state field name or JavaScript expression (e.g. "intent", "score >= 60"). Template markers {{...}} are only for \`prompt\` fields, not \`if\`. Found: "${s.if}"`
|
|
1022
|
-
);
|
|
1023
|
-
}
|
|
1024
|
-
if (s.branches) {
|
|
1025
|
-
const condExp = exp;
|
|
1026
|
-
const exprCode = toSafeStateExpr(s.if);
|
|
1027
|
-
const mapping = {};
|
|
1028
|
-
for (const [key, branchId] of Object.entries(condExp.branchEntryIds)) {
|
|
1029
|
-
if (branchId) mapping[key] = branchId;
|
|
1030
|
-
}
|
|
1031
|
-
for (const fromId of fromIds) allEdges.push({ from: fromId, type: "conditional", rule: { type: "expression", code: exprCode, mapping } });
|
|
1032
|
-
} else {
|
|
1033
|
-
const condExp = exp;
|
|
1034
|
-
const exprCode = `${toSafeStateExpr(s.if)} ? 'then' : 'else'`;
|
|
1035
|
-
const mapping = {};
|
|
1036
|
-
if (condExp.thenEntryId) mapping.then = condExp.thenEntryId;
|
|
1037
|
-
if (condExp.elseEntryId) mapping.else = condExp.elseEntryId;
|
|
1038
|
-
for (const fromId of fromIds) allEdges.push({ from: fromId, type: "conditional", rule: { type: "expression", code: exprCode, mapping } });
|
|
1039
|
-
}
|
|
1040
|
-
} else if (step.type === "parallel") {
|
|
1041
|
-
for (const fromId of fromIds) {
|
|
1042
|
-
for (const sub of step.steps) allEdges.push({ from: fromId, to: expandStep(sub).entryId });
|
|
1043
|
-
}
|
|
1044
|
-
} else {
|
|
1045
|
-
for (const fromId of fromIds) allEdges.push({ from: fromId, to: exp.entryId });
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
function collectIds(steps) {
|
|
1049
|
-
const ids = [];
|
|
1050
|
-
function walk(s) {
|
|
1051
|
-
const id = s.type === "condition" ? s.id || uid("cond") : s.type === "parallel" ? s.id || uid("par") : s.type === "end" ? uid("end") : s.id || s.id || uid("human");
|
|
1052
|
-
if (s.type !== "end" && s.type !== "condition") ids.push(id);
|
|
1053
|
-
if (s.type === "condition") {
|
|
1054
|
-
const c = s;
|
|
1055
|
-
if (c.branches) {
|
|
1056
|
-
for (const steps2 of Object.values(c.branches)) {
|
|
1057
|
-
(Array.isArray(steps2) ? steps2 : [steps2]).forEach(walk);
|
|
1058
|
-
}
|
|
1059
|
-
} else {
|
|
1060
|
-
(Array.isArray(c.then) ? c.then : [c.then]).forEach(walk);
|
|
1061
|
-
if (c.else) (Array.isArray(c.else) ? c.else : [c.else]).forEach(walk);
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
if (s.type === "parallel") s.steps.forEach(walk);
|
|
1065
|
-
}
|
|
1066
|
-
steps.forEach(walk);
|
|
1067
|
-
return [...new Set(ids)];
|
|
1068
|
-
}
|
|
1069
|
-
function collectSourceIds(steps) {
|
|
1070
|
-
const ids = [];
|
|
1071
|
-
function walk(s) {
|
|
1072
|
-
if (s.type === "map") ids.push(s.source);
|
|
1073
|
-
if (s.type === "condition") {
|
|
1074
|
-
const c = s;
|
|
1075
|
-
if (c.branches) {
|
|
1076
|
-
for (const steps2 of Object.values(c.branches)) {
|
|
1077
|
-
(Array.isArray(steps2) ? steps2 : [steps2]).forEach(walk);
|
|
1078
|
-
}
|
|
1079
|
-
} else {
|
|
1080
|
-
(Array.isArray(c.then) ? c.then : [c.then]).forEach(walk);
|
|
1081
|
-
if (c.else) (Array.isArray(c.else) ? c.else : [c.else]).forEach(walk);
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
if (s.type === "parallel") s.steps.forEach(walk);
|
|
1085
|
-
}
|
|
1086
|
-
steps.forEach(walk);
|
|
1087
|
-
return [...new Set(ids)];
|
|
1088
|
-
}
|
|
1089
|
-
function expand(dsl) {
|
|
1090
|
-
_counter = 0;
|
|
1091
|
-
console.log(`[WF EXPAND] expanding workflow "${dsl.name}" | stepCount=${dsl.steps.length}`);
|
|
1092
|
-
const inputNode = {
|
|
1093
|
-
id: "n_input",
|
|
1094
|
-
type: "input",
|
|
1095
|
-
name: "input",
|
|
1096
|
-
output: { key: "input" }
|
|
1097
|
-
};
|
|
1098
|
-
const expanded = expandSteps(dsl.steps, true);
|
|
1099
|
-
const edges = expanded.entryId === "END" ? [{ from: "START", to: "n_input" }] : [
|
|
1100
|
-
...expanded.edges.map((e) => ({ ...e, from: e.from === "START" ? "n_input" : e.from })),
|
|
1101
|
-
{ from: "START", to: "n_input" }
|
|
1102
|
-
];
|
|
1103
|
-
const nodes = [inputNode, ...expanded.nodes];
|
|
1104
|
-
const ids = collectIds(dsl.steps);
|
|
1105
|
-
const sourceIds = collectSourceIds(dsl.steps);
|
|
1106
|
-
const fields = { input: { type: "string" } };
|
|
1107
|
-
for (const id of ids) fields[id] = inferFieldType(id);
|
|
1108
|
-
for (const id of sourceIds) {
|
|
1109
|
-
if (!fields[id]) fields[id] = inferFieldType(id);
|
|
1110
|
-
}
|
|
1111
|
-
const result = { version: "1.0", name: dsl.name, state: { fields }, nodes, edges };
|
|
1112
|
-
console.log(`[WF EXPAND] done | nodeCount=${result.nodes.length} | nodeIds=[${result.nodes.map((n) => `${n.id}:${n.type}`).join(", ")}] | fieldIds=[${Object.keys(fields).join(", ")}]`);
|
|
1113
|
-
return result;
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
1043
|
// src/workflow/compile.ts
|
|
1117
|
-
function compileWorkflow(
|
|
1118
|
-
console.log(`[WF COMPILE] compiling
|
|
1119
|
-
const ir =
|
|
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) {
|
|
1120
1050
|
validateAndThrow(ir);
|
|
1121
1051
|
console.log(`[WF COMPILE] validation passed`);
|
|
1122
1052
|
const StateAnnotation = buildStateAnnotation(ir.state?.fields);
|
|
@@ -1133,21 +1063,13 @@ function compileWorkflow(dsl, resolveAgent, checkpointer, trackingStore) {
|
|
|
1133
1063
|
}
|
|
1134
1064
|
for (const edge of ir.edges) {
|
|
1135
1065
|
const from = edge.from === "START" ? START : edge.from;
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
}
|
|
1140
|
-
const router = buildRouter(edge.rule);
|
|
1141
|
-
builder.addConditionalEdges(from, router, edge.rule.mapping);
|
|
1142
|
-
} else {
|
|
1143
|
-
const targets = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
1144
|
-
for (const to of targets) {
|
|
1145
|
-
builder.addEdge(from, to === "END" ? END : to);
|
|
1146
|
-
}
|
|
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);
|
|
1147
1069
|
}
|
|
1148
1070
|
}
|
|
1149
1071
|
const graph = builder.compile({ checkpointer, name: ir.name });
|
|
1150
|
-
console.log(`[WF COMPILE] graph compiled successfully
|
|
1072
|
+
console.log(`[WF COMPILE] graph compiled successfully`);
|
|
1151
1073
|
return graph;
|
|
1152
1074
|
}
|
|
1153
1075
|
function validateDSL(dsl) {
|
|
@@ -1162,7 +1084,6 @@ function validateDSL(dsl) {
|
|
|
1162
1084
|
const terminalIds = new Set(
|
|
1163
1085
|
dsl.nodes.filter((n) => n.type === "terminal").map((n) => n.id)
|
|
1164
1086
|
);
|
|
1165
|
-
const declaredFields = new Set(Object.keys(dsl.state?.fields ?? {}));
|
|
1166
1087
|
for (const edge of dsl.edges) {
|
|
1167
1088
|
if (edge.from !== "START" && !nodeIds.has(edge.from)) {
|
|
1168
1089
|
errors.push({ type: "error", message: `Edge from "${edge.from}" references unknown node` });
|
|
@@ -1176,24 +1097,12 @@ function validateDSL(dsl) {
|
|
|
1176
1097
|
errors.push({ type: "error", message: `Edge to "${to}" references unknown node` });
|
|
1177
1098
|
}
|
|
1178
1099
|
}
|
|
1179
|
-
if (edge.type === "conditional" && edge.rule) {
|
|
1180
|
-
for (const targetId of Object.values(edge.rule.mapping)) {
|
|
1181
|
-
if (targetId !== "END" && !nodeIds.has(targetId)) {
|
|
1182
|
-
errors.push({ type: "error", message: `Conditional edge mapping references unknown node "${targetId}"` });
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
if (edge.rule.type === "state_field" && edge.rule.field) {
|
|
1186
|
-
if (!declaredFields.has(edge.rule.field)) {
|
|
1187
|
-
errors.push({ type: "warning", message: `Conditional edge reads "${edge.rule.field}" but it is not declared in state.fields` });
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
1100
|
}
|
|
1192
1101
|
for (const node of dsl.nodes) {
|
|
1193
1102
|
if (node.type === "terminal") {
|
|
1194
1103
|
const hasIncoming = dsl.edges.some((e) => {
|
|
1195
1104
|
const targets = Array.isArray(e.to) ? e.to : e.to ? [e.to] : [];
|
|
1196
|
-
return targets.includes(node.id)
|
|
1105
|
+
return targets.includes(node.id);
|
|
1197
1106
|
});
|
|
1198
1107
|
if (!hasIncoming) {
|
|
1199
1108
|
errors.push({ type: "warning", message: `Terminal node "${node.id}" has no incoming edge` });
|
|
@@ -1209,36 +1118,11 @@ function validateAndThrow(dsl) {
|
|
|
1209
1118
|
throw new Error(critical.map((e) => e.message).join("; "));
|
|
1210
1119
|
}
|
|
1211
1120
|
}
|
|
1212
|
-
function buildRouter(rule) {
|
|
1213
|
-
if (rule.type === "state_field") {
|
|
1214
|
-
if (!rule.field) throw new Error("state_field rule requires a field name");
|
|
1215
|
-
return (state) => {
|
|
1216
|
-
const value = resolvePath(state, `state.${rule.field}`);
|
|
1217
|
-
const key = String(value);
|
|
1218
|
-
if (rule.mapping[key] !== void 0) return key;
|
|
1219
|
-
if (rule.mapping["default"] !== void 0) return "default";
|
|
1220
|
-
throw new Error(
|
|
1221
|
-
`Conditional router produced key "${key}" (from field "${rule.field}"), but it is not in mapping: ${JSON.stringify(Object.keys(rule.mapping))}`
|
|
1222
|
-
);
|
|
1223
|
-
};
|
|
1224
|
-
}
|
|
1225
|
-
if (rule.type === "expression") {
|
|
1226
|
-
if (!rule.code) throw new Error("expression rule requires code");
|
|
1227
|
-
const fn = new Function("state", `return ${rule.code}`);
|
|
1228
|
-
return (state) => {
|
|
1229
|
-
const value = fn(state);
|
|
1230
|
-
const key = String(value);
|
|
1231
|
-
if (rule.mapping[key] !== void 0) return key;
|
|
1232
|
-
if (rule.mapping["default"] !== void 0) return "default";
|
|
1233
|
-
throw new Error(
|
|
1234
|
-
`Conditional router produced key "${key}" (from expression "${rule.code}"), but it is not in mapping: ${JSON.stringify(Object.keys(rule.mapping))}`
|
|
1235
|
-
);
|
|
1236
|
-
};
|
|
1237
|
-
}
|
|
1238
|
-
throw new Error(`Unknown rule type: ${rule.type}`);
|
|
1239
|
-
}
|
|
1240
1121
|
|
|
1241
1122
|
export {
|
|
1123
|
+
toJsonSchema,
|
|
1124
|
+
toSafeStateExpr,
|
|
1125
|
+
parseYaml,
|
|
1242
1126
|
buildStateAnnotation,
|
|
1243
1127
|
resolvePath,
|
|
1244
1128
|
renderTemplate,
|
|
@@ -1247,12 +1131,10 @@ export {
|
|
|
1247
1131
|
parallelLimit,
|
|
1248
1132
|
invokeWithRetry,
|
|
1249
1133
|
createAgentNode,
|
|
1250
|
-
createHumanFeedbackNode,
|
|
1251
1134
|
createMapNode,
|
|
1252
|
-
createTerminalNode,
|
|
1253
1135
|
createNodeHandler,
|
|
1254
|
-
expand,
|
|
1255
1136
|
compileWorkflow,
|
|
1137
|
+
compileInternal,
|
|
1256
1138
|
validateDSL
|
|
1257
1139
|
};
|
|
1258
|
-
//# sourceMappingURL=chunk-
|
|
1140
|
+
//# sourceMappingURL=chunk-SGRFQY3E.mjs.map
|