@axiom-lattice/core 2.1.80 → 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.
@@ -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 client = await resolveAgent(node.ref, responseFormat, node.type);
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 createHumanFeedbackNode(node, resolveAgent, trackingStore) {
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
- const startedAt = Date.now();
449
- let stepId;
450
- if (trackingStore && runId) {
451
- trackingStore.updateWorkflowRun(runId, {
452
- status: "running",
453
- completedAt: null
454
- }).catch(() => {
455
- });
456
- }
457
- try {
458
- console.log(`[WF][${node.id}] HUMAN_FEEDBACK START "${node.config?.title || node.name}"`);
459
- const responseFormat = node.output?.schema;
460
- const client = await resolveAgent(void 0, responseFormat, node.type);
461
- console.log(`[WF][${node.id}] agent resolved, building input...`);
462
- const input = buildInput(
463
- node.input,
464
- state,
465
- void 0,
466
- responseFormat ? void 0 : node.output?.schema
467
- );
468
- const humanContent = input.messages[0]?.content ?? "";
469
- input.messages = [
470
- new SystemMessage(
471
- `Follow this exact procedure. Do NOT skip any step.
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
- trackingStore.updateWorkflowRun(runId, {
580
- status: "failed",
581
- errorMessage: err.message,
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(dsl, resolveAgent, checkpointer, trackingStore) {
1118
- console.log(`[WF COMPILE] compiling "${dsl.name}" | stepCount=${dsl.steps.length}`);
1119
- const ir = expand(dsl);
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
- if (edge.type === "conditional") {
1137
- if (!edge.rule) {
1138
- throw new Error(`Conditional edge from "${edge.from}" has no rule`);
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, ready to run`);
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) || e.type === "conditional" && e.rule && Object.values(e.rule.mapping).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-FN4TRQK4.mjs.map
1140
+ //# sourceMappingURL=chunk-SGRFQY3E.mjs.map