@graphrefly/graphrefly 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -180,6 +180,7 @@ __export(index_exports, {
180
180
  gate: () => gate,
181
181
  globToRegExp: () => globToRegExp,
182
182
  graph: () => graph_exports,
183
+ graphspec: () => graphspec_exports,
183
184
  interval: () => interval,
184
185
  isBatching: () => isBatching,
185
186
  isKnownMessageType: () => isKnownMessageType,
@@ -237,6 +238,7 @@ __export(index_exports, {
237
238
  reactiveLog: () => reactiveLog,
238
239
  reactiveMap: () => reactiveMap,
239
240
  reduce: () => reduce,
241
+ reduction: () => reduction_exports,
240
242
  repeat: () => repeat,
241
243
  replay: () => replay,
242
244
  replayWAL: () => replayWAL,
@@ -12861,10 +12863,12 @@ __export(patterns_exports, {
12861
12863
  ai: () => ai_exports,
12862
12864
  cqrs: () => cqrs_exports,
12863
12865
  demoShell: () => demo_shell_exports,
12866
+ graphspec: () => graphspec_exports,
12864
12867
  layout: () => reactive_layout_exports,
12865
12868
  memory: () => memory_exports,
12866
12869
  messaging: () => messaging_exports,
12867
- orchestration: () => orchestration_exports
12870
+ orchestration: () => orchestration_exports,
12871
+ reduction: () => reduction_exports
12868
12872
  });
12869
12873
 
12870
12874
  // src/patterns/ai.ts
@@ -15415,6 +15419,1123 @@ function demoShell(opts) {
15415
15419
  };
15416
15420
  }
15417
15421
 
15422
+ // src/patterns/graphspec.ts
15423
+ var graphspec_exports = {};
15424
+ __export(graphspec_exports, {
15425
+ compileSpec: () => compileSpec,
15426
+ decompileGraph: () => decompileGraph,
15427
+ llmCompose: () => llmCompose,
15428
+ llmRefine: () => llmRefine,
15429
+ specDiff: () => specDiff,
15430
+ validateSpec: () => validateSpec
15431
+ });
15432
+
15433
+ // src/patterns/reduction.ts
15434
+ var reduction_exports = {};
15435
+ __export(reduction_exports, {
15436
+ budgetGate: () => budgetGate,
15437
+ feedback: () => feedback,
15438
+ funnel: () => funnel,
15439
+ scorer: () => scorer,
15440
+ stratify: () => stratify
15441
+ });
15442
+ function baseMeta(kind, meta) {
15443
+ return {
15444
+ reduction: true,
15445
+ reduction_type: kind,
15446
+ ...meta ?? {}
15447
+ };
15448
+ }
15449
+ function stratify(name, source, rules, opts) {
15450
+ const g = new Graph(name, opts);
15451
+ g.add("source", source);
15452
+ const rulesNode = state(rules, {
15453
+ meta: baseMeta("stratify_rules")
15454
+ });
15455
+ g.add("rules", rulesNode);
15456
+ for (const rule of rules) {
15457
+ _addBranch(g, source, rulesNode, rule);
15458
+ }
15459
+ return g;
15460
+ }
15461
+ function _addBranch(graph, source, rulesNode, rule) {
15462
+ const branchName = `branch/${rule.name}`;
15463
+ let pendingDirty = false;
15464
+ const filterNode = node([source, rulesNode], () => void 0, {
15465
+ describeKind: "operator",
15466
+ meta: baseMeta("stratify_branch", { branch: rule.name }),
15467
+ onMessage(msg, depIndex, actions) {
15468
+ if (depIndex !== 0) return false;
15469
+ const t = msg[0];
15470
+ if (t === DATA) {
15471
+ const value = msg[1];
15472
+ const currentRules = rulesNode.get();
15473
+ const currentRule = currentRules.find((r) => r.name === rule.name);
15474
+ if (currentRule && currentRule.classify(value)) {
15475
+ pendingDirty = false;
15476
+ actions.emit(value);
15477
+ } else {
15478
+ if (pendingDirty) {
15479
+ pendingDirty = false;
15480
+ actions.down([[DIRTY], [RESOLVED]]);
15481
+ }
15482
+ }
15483
+ return true;
15484
+ }
15485
+ if (t === DIRTY) {
15486
+ pendingDirty = true;
15487
+ return true;
15488
+ }
15489
+ if (t === RESOLVED) {
15490
+ if (pendingDirty) {
15491
+ pendingDirty = false;
15492
+ actions.down([[DIRTY], [RESOLVED]]);
15493
+ } else {
15494
+ actions.down([[RESOLVED]]);
15495
+ }
15496
+ return true;
15497
+ }
15498
+ if (t === COMPLETE || t === ERROR) {
15499
+ pendingDirty = false;
15500
+ actions.down([msg]);
15501
+ return true;
15502
+ }
15503
+ return false;
15504
+ }
15505
+ });
15506
+ graph.add(branchName, filterNode);
15507
+ graph.connect("source", branchName);
15508
+ if (rule.ops) {
15509
+ const transformed = rule.ops(filterNode);
15510
+ const transformedName = `branch/${rule.name}/out`;
15511
+ graph.add(transformedName, transformed);
15512
+ graph.connect(branchName, transformedName);
15513
+ }
15514
+ }
15515
+ function funnel(name, sources, stages, opts) {
15516
+ if (sources.length === 0) throw new RangeError("funnel requires at least one source");
15517
+ if (stages.length === 0) throw new RangeError("funnel requires at least one stage");
15518
+ const g = new Graph(name, opts);
15519
+ const merged = sources.length === 1 ? sources[0] : merge(...sources);
15520
+ g.add("merged", merged);
15521
+ let prevOutputPath = "merged";
15522
+ for (let i = 0; i < stages.length; i++) {
15523
+ const stage = stages[i];
15524
+ const sub = new Graph(stage.name);
15525
+ stage.build(sub);
15526
+ try {
15527
+ sub.resolve("input");
15528
+ } catch {
15529
+ throw new Error(`funnel stage "${stage.name}" must define an "input" node`);
15530
+ }
15531
+ try {
15532
+ sub.resolve("output");
15533
+ } catch {
15534
+ throw new Error(`funnel stage "${stage.name}" must define an "output" node`);
15535
+ }
15536
+ g.mount(stage.name, sub);
15537
+ const prevNode = g.resolve(prevOutputPath);
15538
+ const stageInputPath = `${stage.name}::input`;
15539
+ const stageInput = g.resolve(stageInputPath);
15540
+ prevNode.subscribe((msgs) => {
15541
+ for (const msg of msgs) {
15542
+ const t = msg[0];
15543
+ if (t === DATA) {
15544
+ stageInput.down([[DATA, msg[1]]]);
15545
+ } else if (t === DIRTY) {
15546
+ stageInput.down([[DIRTY]]);
15547
+ } else if (t === RESOLVED) {
15548
+ stageInput.down([[RESOLVED]]);
15549
+ } else if (t === COMPLETE || t === ERROR) {
15550
+ stageInput.down([msg]);
15551
+ }
15552
+ }
15553
+ });
15554
+ prevOutputPath = `${stage.name}::output`;
15555
+ }
15556
+ return g;
15557
+ }
15558
+ function feedback(graph, condition, reentry, opts) {
15559
+ const maxIter = opts?.maxIterations ?? 10;
15560
+ const counterName = `__feedback_${condition}`;
15561
+ const counter = state(0, {
15562
+ meta: baseMeta("feedback_counter", {
15563
+ maxIterations: maxIter,
15564
+ feedbackFrom: condition,
15565
+ feedbackTo: reentry
15566
+ })
15567
+ });
15568
+ graph.add(counterName, counter);
15569
+ const condNode = graph.resolve(condition);
15570
+ const reentryNode = graph.resolve(reentry);
15571
+ let tornDown = false;
15572
+ let unsubCounter = null;
15573
+ const safeUnsub = () => {
15574
+ if (tornDown) return;
15575
+ tornDown = true;
15576
+ unsub();
15577
+ unsubCounter?.();
15578
+ };
15579
+ const unsub = condNode.subscribe((msgs) => {
15580
+ for (const msg of msgs) {
15581
+ if (msg[0] === DATA) {
15582
+ const currentCount = counter.get();
15583
+ if (currentCount >= maxIter) continue;
15584
+ const condValue = msg[1];
15585
+ if (condValue == null) continue;
15586
+ counter.down([[DATA, currentCount + 1]]);
15587
+ reentryNode.down([[DATA, condValue]]);
15588
+ } else if (msg[0] === COMPLETE || msg[0] === ERROR) {
15589
+ const terminal = msg[0] === ERROR && msg.length > 1 ? [ERROR, msg[1]] : [msg[0]];
15590
+ counter.down([terminal]);
15591
+ safeUnsub();
15592
+ }
15593
+ }
15594
+ });
15595
+ unsubCounter = counter.subscribe((msgs) => {
15596
+ for (const msg of msgs) {
15597
+ if (msg[0] === COMPLETE || msg[0] === ERROR) {
15598
+ safeUnsub();
15599
+ return;
15600
+ }
15601
+ }
15602
+ });
15603
+ return graph;
15604
+ }
15605
+ function budgetGate(source, constraints, opts) {
15606
+ if (constraints.length === 0) throw new RangeError("budgetGate requires at least one constraint");
15607
+ const constraintNodes = constraints.map((c) => c.node);
15608
+ const allDeps = [source, ...constraintNodes];
15609
+ let buffer2 = [];
15610
+ let paused = false;
15611
+ let pendingResolved = false;
15612
+ const lockId = /* @__PURE__ */ Symbol("budget-gate");
15613
+ function checkBudget() {
15614
+ return constraints.every((c) => c.check(c.node.get()));
15615
+ }
15616
+ function flushBuffer(actions) {
15617
+ while (buffer2.length > 0 && checkBudget()) {
15618
+ const item = buffer2.shift();
15619
+ actions.emit(item);
15620
+ }
15621
+ if (buffer2.length === 0 && pendingResolved) {
15622
+ pendingResolved = false;
15623
+ actions.down([[RESOLVED]]);
15624
+ }
15625
+ }
15626
+ return node(allDeps, () => void 0, {
15627
+ ...opts,
15628
+ describeKind: "operator",
15629
+ meta: baseMeta("budget_gate", opts?.meta),
15630
+ onMessage(msg, depIndex, actions) {
15631
+ const t = msg[0];
15632
+ if (depIndex === 0) {
15633
+ if (t === DATA) {
15634
+ if (checkBudget() && buffer2.length === 0) {
15635
+ actions.emit(msg[1]);
15636
+ } else {
15637
+ buffer2.push(msg[1]);
15638
+ if (!paused) {
15639
+ paused = true;
15640
+ actions.up([[PAUSE, lockId]]);
15641
+ }
15642
+ }
15643
+ return true;
15644
+ }
15645
+ if (t === DIRTY) {
15646
+ actions.down([[DIRTY]]);
15647
+ return true;
15648
+ }
15649
+ if (t === RESOLVED) {
15650
+ if (buffer2.length === 0) {
15651
+ actions.down([[RESOLVED]]);
15652
+ } else {
15653
+ pendingResolved = true;
15654
+ }
15655
+ return true;
15656
+ }
15657
+ if (t === COMPLETE || t === ERROR) {
15658
+ for (const item of buffer2) {
15659
+ actions.emit(item);
15660
+ }
15661
+ buffer2 = [];
15662
+ pendingResolved = false;
15663
+ if (paused) {
15664
+ paused = false;
15665
+ actions.up([[RESUME, lockId]]);
15666
+ }
15667
+ actions.down([msg]);
15668
+ return true;
15669
+ }
15670
+ return false;
15671
+ }
15672
+ if (t === DATA || t === RESOLVED) {
15673
+ if (checkBudget() && buffer2.length > 0) {
15674
+ flushBuffer(actions);
15675
+ if (buffer2.length === 0 && paused) {
15676
+ paused = false;
15677
+ actions.up([[RESUME, lockId]]);
15678
+ }
15679
+ } else if (!checkBudget() && !paused && buffer2.length > 0) {
15680
+ paused = true;
15681
+ actions.up([[PAUSE, lockId]]);
15682
+ }
15683
+ return true;
15684
+ }
15685
+ if (t === DIRTY) {
15686
+ return true;
15687
+ }
15688
+ if (t === ERROR) {
15689
+ actions.down([msg]);
15690
+ return true;
15691
+ }
15692
+ if (t === COMPLETE) {
15693
+ return true;
15694
+ }
15695
+ return false;
15696
+ }
15697
+ });
15698
+ }
15699
+ function scorer(sources, weights, opts) {
15700
+ if (sources.length === 0) throw new RangeError("scorer requires at least one source");
15701
+ if (sources.length !== weights.length) {
15702
+ throw new RangeError("scorer requires the same number of sources and weights");
15703
+ }
15704
+ const allDeps = [...sources, ...weights];
15705
+ const n = sources.length;
15706
+ const scoreFns = opts?.scoreFns;
15707
+ return derived(
15708
+ allDeps,
15709
+ (vals) => {
15710
+ const signals = vals.slice(0, n);
15711
+ const weightValues = vals.slice(n);
15712
+ const breakdown = [];
15713
+ let totalScore = 0;
15714
+ for (let i = 0; i < n; i++) {
15715
+ const sig = signals[i] ?? 0;
15716
+ const wt = weightValues[i] ?? 0;
15717
+ const rawScore = scoreFns?.[i] ? scoreFns[i](sig) : sig;
15718
+ const weighted = rawScore * wt;
15719
+ breakdown.push(weighted);
15720
+ totalScore += weighted;
15721
+ }
15722
+ return {
15723
+ value: signals,
15724
+ score: totalScore,
15725
+ breakdown
15726
+ };
15727
+ },
15728
+ {
15729
+ ...opts,
15730
+ describeKind: "derived",
15731
+ meta: baseMeta("scorer", opts?.meta)
15732
+ }
15733
+ );
15734
+ }
15735
+
15736
+ // src/patterns/graphspec.ts
15737
+ var VALID_NODE_TYPES2 = /* @__PURE__ */ new Set([
15738
+ "state",
15739
+ "producer",
15740
+ "derived",
15741
+ "effect",
15742
+ "operator",
15743
+ "template"
15744
+ ]);
15745
+ var INNER_NODE_TYPES = /* @__PURE__ */ new Set(["state", "producer", "derived", "effect", "operator"]);
15746
+ function validateSpec(spec) {
15747
+ const errors = [];
15748
+ if (spec == null || typeof spec !== "object") {
15749
+ return { valid: false, errors: ["GraphSpec must be a non-null object"] };
15750
+ }
15751
+ const s = spec;
15752
+ if (typeof s.name !== "string" || s.name.length === 0) {
15753
+ errors.push("Missing or empty 'name' field");
15754
+ }
15755
+ if (s.nodes == null || typeof s.nodes !== "object" || Array.isArray(s.nodes)) {
15756
+ errors.push("Missing or invalid 'nodes' field (must be an object)");
15757
+ return { valid: false, errors };
15758
+ }
15759
+ const nodeNames = new Set(Object.keys(s.nodes));
15760
+ const nodeTypes = /* @__PURE__ */ new Map();
15761
+ const templateDefs = /* @__PURE__ */ new Map();
15762
+ if (s.templates != null && typeof s.templates === "object" && !Array.isArray(s.templates)) {
15763
+ for (const [tName, tRaw] of Object.entries(s.templates)) {
15764
+ if (tRaw != null && typeof tRaw === "object") {
15765
+ const t = tRaw;
15766
+ templateDefs.set(tName, {
15767
+ params: Array.isArray(t.params) ? t.params : []
15768
+ });
15769
+ }
15770
+ }
15771
+ }
15772
+ if (s.templates != null) {
15773
+ if (typeof s.templates !== "object" || Array.isArray(s.templates)) {
15774
+ errors.push("'templates' must be an object");
15775
+ } else {
15776
+ for (const [tName, tRaw] of Object.entries(s.templates)) {
15777
+ if (tRaw == null || typeof tRaw !== "object") {
15778
+ errors.push(`Template "${tName}": must be an object`);
15779
+ continue;
15780
+ }
15781
+ const t = tRaw;
15782
+ if (!Array.isArray(t.params)) {
15783
+ errors.push(`Template "${tName}": missing 'params' array`);
15784
+ }
15785
+ if (t.nodes == null || typeof t.nodes !== "object" || Array.isArray(t.nodes)) {
15786
+ errors.push(`Template "${tName}": missing or invalid 'nodes' object`);
15787
+ } else {
15788
+ const paramSet = new Set(Array.isArray(t.params) ? t.params : []);
15789
+ const innerNames = new Set(Object.keys(t.nodes));
15790
+ for (const [nName, nRaw] of Object.entries(t.nodes)) {
15791
+ if (nRaw == null || typeof nRaw !== "object") {
15792
+ errors.push(`Template "${tName}" node "${nName}": must be an object`);
15793
+ continue;
15794
+ }
15795
+ const n = nRaw;
15796
+ if (typeof n.type !== "string" || !INNER_NODE_TYPES.has(n.type)) {
15797
+ errors.push(`Template "${tName}" node "${nName}": invalid type`);
15798
+ }
15799
+ if (Array.isArray(n.deps)) {
15800
+ for (const dep of n.deps) {
15801
+ if (!innerNames.has(dep) && !paramSet.has(dep)) {
15802
+ errors.push(
15803
+ `Template "${tName}" node "${nName}": dep "${dep}" is not an inner node or param`
15804
+ );
15805
+ }
15806
+ }
15807
+ }
15808
+ }
15809
+ if (typeof t.output !== "string") {
15810
+ errors.push(`Template "${tName}": missing 'output' string`);
15811
+ } else if (!t.nodes[t.output]) {
15812
+ errors.push(`Template "${tName}": output "${t.output}" is not a declared node`);
15813
+ }
15814
+ }
15815
+ }
15816
+ }
15817
+ }
15818
+ for (const [name, raw] of Object.entries(s.nodes)) {
15819
+ if (raw == null || typeof raw !== "object") {
15820
+ errors.push(`Node "${name}": must be an object`);
15821
+ continue;
15822
+ }
15823
+ const n = raw;
15824
+ if (typeof n.type !== "string" || !VALID_NODE_TYPES2.has(n.type)) {
15825
+ errors.push(
15826
+ `Node "${name}": invalid type "${String(n.type)}" (expected: ${[...VALID_NODE_TYPES2].join(", ")})`
15827
+ );
15828
+ continue;
15829
+ }
15830
+ nodeTypes.set(name, n.type);
15831
+ if (n.type === "template") {
15832
+ if (typeof n.template !== "string" || !templateDefs.has(n.template)) {
15833
+ errors.push(`Node "${name}": template "${String(n.template)}" not found in templates`);
15834
+ } else {
15835
+ if (n.bind == null || typeof n.bind !== "object" || Array.isArray(n.bind)) {
15836
+ errors.push(`Node "${name}": template ref requires 'bind' object`);
15837
+ } else {
15838
+ const tmpl = templateDefs.get(n.template);
15839
+ const bind = n.bind;
15840
+ for (const param of tmpl.params) {
15841
+ if (!(param in bind)) {
15842
+ errors.push(
15843
+ `Node "${name}": template param "${param}" is not bound (template "${n.template}")`
15844
+ );
15845
+ }
15846
+ }
15847
+ for (const [, target] of Object.entries(bind)) {
15848
+ if (typeof target === "string" && !nodeNames.has(target)) {
15849
+ errors.push(
15850
+ `Node "${name}": bind target "${target}" does not reference an existing node`
15851
+ );
15852
+ }
15853
+ }
15854
+ }
15855
+ }
15856
+ } else {
15857
+ if (Array.isArray(n.deps)) {
15858
+ for (const dep of n.deps) {
15859
+ if (dep === name) {
15860
+ errors.push(`Node "${name}": self-referencing dep`);
15861
+ } else if (!nodeNames.has(dep)) {
15862
+ errors.push(`Node "${name}": dep "${dep}" does not reference an existing node`);
15863
+ }
15864
+ }
15865
+ }
15866
+ if ((n.type === "derived" || n.type === "effect" || n.type === "operator") && !Array.isArray(n.deps)) {
15867
+ errors.push(`Node "${name}": ${n.type} node should have a 'deps' array`);
15868
+ }
15869
+ }
15870
+ }
15871
+ if (s.feedback != null) {
15872
+ if (!Array.isArray(s.feedback)) {
15873
+ errors.push("'feedback' must be an array");
15874
+ } else {
15875
+ for (let i = 0; i < s.feedback.length; i++) {
15876
+ const edge = s.feedback[i];
15877
+ if (edge == null || typeof edge !== "object") {
15878
+ errors.push(`Feedback [${i}]: must be an object`);
15879
+ continue;
15880
+ }
15881
+ const e = edge;
15882
+ if (typeof e.from !== "string" || !nodeNames.has(e.from)) {
15883
+ errors.push(
15884
+ `Feedback [${i}]: 'from' "${String(e.from)}" does not reference an existing node`
15885
+ );
15886
+ }
15887
+ if (typeof e.from === "string" && e.from === e.to) {
15888
+ errors.push(`Feedback [${i}]: 'from' and 'to' must be different nodes`);
15889
+ }
15890
+ if (typeof e.to !== "string" || !nodeNames.has(e.to)) {
15891
+ errors.push(
15892
+ `Feedback [${i}]: 'to' "${String(e.to)}" does not reference an existing node`
15893
+ );
15894
+ } else if (typeof e.to === "string" && nodeTypes.get(e.to) !== "state") {
15895
+ errors.push(
15896
+ `Feedback [${i}]: 'to' node "${e.to}" must be a state node (got "${nodeTypes.get(e.to) ?? "unknown"}")`
15897
+ );
15898
+ }
15899
+ }
15900
+ }
15901
+ }
15902
+ return { valid: errors.length === 0, errors };
15903
+ }
15904
+ function compileSpec(spec, opts) {
15905
+ const validation = validateSpec(spec);
15906
+ if (!validation.valid) {
15907
+ throw new Error(`compileSpec: invalid GraphSpec:
15908
+ ${validation.errors.join("\n")}`);
15909
+ }
15910
+ const catalog = opts?.catalog ?? {};
15911
+ const g = new Graph(spec.name);
15912
+ const templates = spec.templates ?? {};
15913
+ const created = /* @__PURE__ */ new Map();
15914
+ const deferred = [];
15915
+ for (const [name, raw] of Object.entries(spec.nodes)) {
15916
+ if (raw.type === "template") continue;
15917
+ const n = raw;
15918
+ if (n.type === "state") {
15919
+ const nd = state(n.initial, {
15920
+ name,
15921
+ meta: n.meta ? { ...n.meta } : void 0
15922
+ });
15923
+ g.add(name, nd);
15924
+ created.set(name, nd);
15925
+ } else if (n.type === "producer") {
15926
+ const sourceFactory = n.source ? catalog.sources?.[n.source] : void 0;
15927
+ const fnFactory = n.fn ? catalog.fns?.[n.fn] : void 0;
15928
+ if (sourceFactory) {
15929
+ const nd = sourceFactory(n.config ?? {});
15930
+ g.add(name, nd);
15931
+ created.set(name, nd);
15932
+ } else if (fnFactory) {
15933
+ const nd = fnFactory([], n.config ?? {});
15934
+ g.add(name, nd);
15935
+ created.set(name, nd);
15936
+ } else {
15937
+ const nd = producer(() => {
15938
+ }, {
15939
+ name,
15940
+ meta: { ...n.meta, _specFn: n.fn, _specSource: n.source }
15941
+ });
15942
+ g.add(name, nd);
15943
+ created.set(name, nd);
15944
+ }
15945
+ } else {
15946
+ deferred.push([name, n]);
15947
+ }
15948
+ }
15949
+ let progressed = true;
15950
+ const pending = new Map(deferred);
15951
+ while (pending.size > 0 && progressed) {
15952
+ progressed = false;
15953
+ for (const [name, n] of [...pending.entries()]) {
15954
+ const deps = n.deps ?? [];
15955
+ if (!deps.every((dep) => created.has(dep))) continue;
15956
+ const resolvedDeps = deps.map((dep) => created.get(dep));
15957
+ const fnFactory = n.fn ? catalog.fns?.[n.fn] : void 0;
15958
+ let nd;
15959
+ if (fnFactory) {
15960
+ nd = fnFactory(resolvedDeps, n.config ?? {});
15961
+ } else if (n.type === "effect") {
15962
+ nd = effect(resolvedDeps, () => {
15963
+ });
15964
+ } else {
15965
+ nd = derived(resolvedDeps, (vals) => vals[0]);
15966
+ }
15967
+ g.add(name, nd);
15968
+ created.set(name, nd);
15969
+ pending.delete(name);
15970
+ progressed = true;
15971
+ }
15972
+ }
15973
+ if (pending.size > 0) {
15974
+ const unresolved = [...pending.keys()].sort().join(", ");
15975
+ throw new Error(`compileSpec: unresolvable deps for nodes: ${unresolved}`);
15976
+ }
15977
+ for (const [name, raw] of Object.entries(spec.nodes)) {
15978
+ if (raw.type !== "template") continue;
15979
+ const ref = raw;
15980
+ const tmpl = templates[ref.template];
15981
+ const sub = new Graph(name);
15982
+ const subCreated = /* @__PURE__ */ new Map();
15983
+ const subDeferred = [];
15984
+ for (const [nName, nSpec] of Object.entries(tmpl.nodes)) {
15985
+ const resolvedDeps = (nSpec.deps ?? []).map((dep) => {
15986
+ if (dep.startsWith("$") && ref.bind[dep]) {
15987
+ return ref.bind[dep];
15988
+ }
15989
+ return dep;
15990
+ });
15991
+ const specWithResolvedDeps = { ...nSpec, deps: resolvedDeps };
15992
+ if (nSpec.type === "state") {
15993
+ const nd = state(nSpec.initial, {
15994
+ name: nName,
15995
+ meta: nSpec.meta ? { ...nSpec.meta } : void 0
15996
+ });
15997
+ sub.add(nName, nd);
15998
+ subCreated.set(nName, nd);
15999
+ } else if (nSpec.type === "producer") {
16000
+ const sourceFactory = nSpec.source ? catalog.sources?.[nSpec.source] : void 0;
16001
+ const fnFactory = nSpec.fn ? catalog.fns?.[nSpec.fn] : void 0;
16002
+ if (sourceFactory) {
16003
+ const nd = sourceFactory(nSpec.config ?? {});
16004
+ sub.add(nName, nd);
16005
+ subCreated.set(nName, nd);
16006
+ } else if (fnFactory) {
16007
+ const nd = fnFactory([], nSpec.config ?? {});
16008
+ sub.add(nName, nd);
16009
+ subCreated.set(nName, nd);
16010
+ } else {
16011
+ const nd = producer(() => {
16012
+ }, {
16013
+ name: nName,
16014
+ meta: { ...nSpec.meta, _specFn: nSpec.fn, _specSource: nSpec.source }
16015
+ });
16016
+ sub.add(nName, nd);
16017
+ subCreated.set(nName, nd);
16018
+ }
16019
+ } else {
16020
+ subDeferred.push([nName, specWithResolvedDeps]);
16021
+ }
16022
+ }
16023
+ let subProgressed = true;
16024
+ const subPending = new Map(subDeferred);
16025
+ while (subPending.size > 0 && subProgressed) {
16026
+ subProgressed = false;
16027
+ for (const [nName, nSpec] of [...subPending.entries()]) {
16028
+ const deps = nSpec.deps ?? [];
16029
+ const allReady = deps.every((dep) => subCreated.has(dep) || created.has(dep));
16030
+ if (!allReady) continue;
16031
+ const resolvedDeps = deps.map((dep) => subCreated.get(dep) ?? created.get(dep));
16032
+ const fnFactory = nSpec.fn ? catalog.fns?.[nSpec.fn] : void 0;
16033
+ let nd;
16034
+ if (fnFactory) {
16035
+ nd = fnFactory(resolvedDeps, nSpec.config ?? {});
16036
+ } else if (nSpec.type === "effect") {
16037
+ nd = effect(resolvedDeps, () => {
16038
+ });
16039
+ } else {
16040
+ nd = derived(resolvedDeps, (vals) => vals[0]);
16041
+ }
16042
+ sub.add(nName, nd);
16043
+ subCreated.set(nName, nd);
16044
+ subPending.delete(nName);
16045
+ subProgressed = true;
16046
+ }
16047
+ }
16048
+ if (subPending.size > 0) {
16049
+ const unresolved = [...subPending.keys()].sort().join(", ");
16050
+ throw new Error(
16051
+ `compileSpec: template "${ref.template}" has unresolvable deps: ${unresolved}`
16052
+ );
16053
+ }
16054
+ g.mount(name, sub);
16055
+ const outputPath = `${name}::${tmpl.output}`;
16056
+ created.set(name, g.resolve(outputPath));
16057
+ try {
16058
+ const outputNode = g.resolve(outputPath);
16059
+ outputNode.meta._templateName?.down([[DATA, ref.template]]);
16060
+ outputNode.meta._templateBind?.down([[DATA, ref.bind]]);
16061
+ } catch {
16062
+ }
16063
+ }
16064
+ for (const [name, raw] of Object.entries(spec.nodes)) {
16065
+ if (raw.type === "template") continue;
16066
+ const n = raw;
16067
+ for (const dep of n.deps ?? []) {
16068
+ try {
16069
+ g.connect(dep, name);
16070
+ } catch (err) {
16071
+ const msg = err instanceof Error ? err.message : "";
16072
+ if (!msg.includes("constructor deps") && !msg.includes("already")) {
16073
+ throw err;
16074
+ }
16075
+ }
16076
+ }
16077
+ }
16078
+ for (const fb of spec.feedback ?? []) {
16079
+ feedback(g, fb.from, fb.to, {
16080
+ maxIterations: fb.maxIterations
16081
+ });
16082
+ }
16083
+ return g;
16084
+ }
16085
+ var INTERNAL_META_KEYS = /* @__PURE__ */ new Set([
16086
+ "reduction",
16087
+ "reduction_type",
16088
+ "_specFn",
16089
+ "_specSource",
16090
+ "_templateName",
16091
+ "_templateBind",
16092
+ "feedbackFrom",
16093
+ "feedbackTo"
16094
+ ]);
16095
+ function decompileGraph(graph) {
16096
+ const desc = graph.describe({ detail: "standard" });
16097
+ const nodes = {};
16098
+ const feedbackEdges = [];
16099
+ const metaSegment = `::${GRAPH_META_SEGMENT}::`;
16100
+ const feedbackCounterPattern = /^__feedback_(.+)$/;
16101
+ const feedbackConditions = /* @__PURE__ */ new Set();
16102
+ for (const path of Object.keys(desc.nodes)) {
16103
+ if (path.includes(metaSegment)) continue;
16104
+ const match = feedbackCounterPattern.exec(path);
16105
+ if (match) {
16106
+ feedbackConditions.add(match[1]);
16107
+ const meta = desc.nodes[path]?.meta;
16108
+ if (meta?.feedbackFrom && meta?.feedbackTo) {
16109
+ feedbackEdges.push({
16110
+ from: meta.feedbackFrom,
16111
+ to: meta.feedbackTo,
16112
+ ...meta.maxIterations ? { maxIterations: meta.maxIterations } : {}
16113
+ });
16114
+ }
16115
+ }
16116
+ }
16117
+ for (const [path, nodeDesc] of Object.entries(desc.nodes)) {
16118
+ if (path.includes(metaSegment)) continue;
16119
+ if (feedbackCounterPattern.test(path)) continue;
16120
+ if (path.includes("::")) continue;
16121
+ const specNode = {
16122
+ type: nodeDesc.type
16123
+ };
16124
+ if (nodeDesc.deps.length > 0) {
16125
+ specNode.deps = nodeDesc.deps.filter((d) => !d.includes("::"));
16126
+ }
16127
+ if (nodeDesc.type === "state" && nodeDesc.value !== void 0) {
16128
+ specNode.initial = nodeDesc.value;
16129
+ }
16130
+ if (nodeDesc.meta && Object.keys(nodeDesc.meta).length > 0) {
16131
+ const meta = {};
16132
+ for (const [k, v] of Object.entries(nodeDesc.meta)) {
16133
+ if (!INTERNAL_META_KEYS.has(k)) meta[k] = v;
16134
+ }
16135
+ if (Object.keys(meta).length > 0) {
16136
+ specNode.meta = meta;
16137
+ }
16138
+ }
16139
+ nodes[path] = specNode;
16140
+ }
16141
+ const templates = {};
16142
+ const templateRefs = {};
16143
+ const metaDetectedSubgraphs = /* @__PURE__ */ new Set();
16144
+ for (const subName of desc.subgraphs) {
16145
+ const prefix = `${subName}::`;
16146
+ for (const [path, nodeDesc] of Object.entries(desc.nodes)) {
16147
+ if (!path.startsWith(prefix)) continue;
16148
+ if (path.includes(metaSegment)) continue;
16149
+ const meta = nodeDesc.meta;
16150
+ if (meta?._templateName && meta?._templateBind) {
16151
+ const templateName = meta._templateName;
16152
+ const bind = meta._templateBind;
16153
+ if (!templates[templateName]) {
16154
+ const tmplNodes = {};
16155
+ const tmplInnerNames = /* @__PURE__ */ new Set();
16156
+ const tmplPrefix = `${subName}::`;
16157
+ for (const [p, nd] of Object.entries(desc.nodes)) {
16158
+ if (!p.startsWith(tmplPrefix) || p.includes(metaSegment)) continue;
16159
+ const localName = p.slice(tmplPrefix.length);
16160
+ if (localName.includes("::")) continue;
16161
+ tmplInnerNames.add(localName);
16162
+ tmplNodes[localName] = {
16163
+ type: nd.type,
16164
+ ...nd.deps.length > 0 ? {
16165
+ deps: nd.deps.map(
16166
+ (d) => d.startsWith(tmplPrefix) ? d.slice(tmplPrefix.length) : d
16167
+ )
16168
+ } : {}
16169
+ };
16170
+ }
16171
+ const tmplParams = [];
16172
+ const tmplParamMap = /* @__PURE__ */ new Map();
16173
+ for (const n of Object.values(tmplNodes)) {
16174
+ for (const dep of n.deps ?? []) {
16175
+ if (!tmplInnerNames.has(dep) && !tmplParamMap.has(dep)) {
16176
+ const param = `$${dep}`;
16177
+ tmplParams.push(param);
16178
+ tmplParamMap.set(dep, param);
16179
+ }
16180
+ }
16181
+ }
16182
+ for (const n of Object.values(tmplNodes)) {
16183
+ if (n.deps) n.deps = n.deps.map((d) => tmplParamMap.get(d) ?? d);
16184
+ }
16185
+ const depended = /* @__PURE__ */ new Set();
16186
+ for (const n of Object.values(tmplNodes)) {
16187
+ for (const dep of n.deps ?? []) {
16188
+ if (tmplInnerNames.has(dep)) depended.add(dep);
16189
+ }
16190
+ }
16191
+ const outputCandidates = [...tmplInnerNames].filter((n) => !depended.has(n));
16192
+ const tmplOutput = outputCandidates[0] ?? [...tmplInnerNames].pop();
16193
+ templates[templateName] = { params: tmplParams, nodes: tmplNodes, output: tmplOutput };
16194
+ }
16195
+ delete nodes[subName];
16196
+ templateRefs[subName] = { type: "template", template: templateName, bind };
16197
+ metaDetectedSubgraphs.add(subName);
16198
+ break;
16199
+ }
16200
+ }
16201
+ }
16202
+ const structureMap = /* @__PURE__ */ new Map();
16203
+ for (const subName of desc.subgraphs) {
16204
+ if (metaDetectedSubgraphs.has(subName)) continue;
16205
+ const subNodes = {};
16206
+ const prefix = `${subName}::`;
16207
+ for (const [path, nodeDesc] of Object.entries(desc.nodes)) {
16208
+ if (path.includes(metaSegment)) continue;
16209
+ if (!path.startsWith(prefix)) continue;
16210
+ const localName = path.slice(prefix.length);
16211
+ if (localName.includes("::")) continue;
16212
+ subNodes[localName] = {
16213
+ type: nodeDesc.type,
16214
+ ...nodeDesc.deps.length > 0 ? {
16215
+ deps: nodeDesc.deps.map((d) => d.startsWith(prefix) ? d.slice(prefix.length) : d)
16216
+ } : {}
16217
+ };
16218
+ }
16219
+ const fingerprint = JSON.stringify(
16220
+ Object.fromEntries(
16221
+ Object.entries(subNodes).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => [k, { type: v.type, deps: v.deps ?? [] }])
16222
+ )
16223
+ );
16224
+ if (!structureMap.has(fingerprint)) {
16225
+ structureMap.set(fingerprint, []);
16226
+ }
16227
+ structureMap.get(fingerprint).push({ name: subName, nodes: subNodes });
16228
+ }
16229
+ for (const [, group] of structureMap) {
16230
+ if (group.length < 2) continue;
16231
+ const templateName = `${group[0].name}_template`;
16232
+ const refNodes = group[0].nodes;
16233
+ const innerNames = new Set(Object.keys(refNodes));
16234
+ const params = [];
16235
+ const baseParamMap = /* @__PURE__ */ new Map();
16236
+ for (const n of Object.values(refNodes)) {
16237
+ for (const dep of n.deps ?? []) {
16238
+ if (!innerNames.has(dep) && !baseParamMap.has(dep)) {
16239
+ const param = `$${dep}`;
16240
+ params.push(param);
16241
+ baseParamMap.set(dep, param);
16242
+ }
16243
+ }
16244
+ }
16245
+ const depended = /* @__PURE__ */ new Set();
16246
+ for (const n of Object.values(refNodes)) {
16247
+ for (const dep of n.deps ?? []) {
16248
+ if (innerNames.has(dep)) depended.add(dep);
16249
+ }
16250
+ }
16251
+ const outputCandidates = [...innerNames].filter((n) => !depended.has(n));
16252
+ const output = outputCandidates[0] ?? [...innerNames].pop();
16253
+ const tmplNodes = {};
16254
+ for (const [nName, nSpec] of Object.entries(refNodes)) {
16255
+ tmplNodes[nName] = {
16256
+ ...nSpec,
16257
+ deps: nSpec.deps?.map((d) => baseParamMap.get(d) ?? d)
16258
+ };
16259
+ }
16260
+ templates[templateName] = { params, nodes: tmplNodes, output };
16261
+ for (const member of group) {
16262
+ delete nodes[member.name];
16263
+ const memberBind = {};
16264
+ const memberInnerNames = new Set(Object.keys(member.nodes));
16265
+ for (const n of Object.values(member.nodes)) {
16266
+ for (const dep of n.deps ?? []) {
16267
+ if (!memberInnerNames.has(dep)) {
16268
+ const param = baseParamMap.get(dep) ?? `$${dep}`;
16269
+ memberBind[param] = dep;
16270
+ }
16271
+ }
16272
+ }
16273
+ templateRefs[member.name] = {
16274
+ type: "template",
16275
+ template: templateName,
16276
+ bind: memberBind
16277
+ };
16278
+ }
16279
+ }
16280
+ const allNodes = {
16281
+ ...nodes,
16282
+ ...templateRefs
16283
+ };
16284
+ const result = { name: desc.name, nodes: allNodes };
16285
+ if (Object.keys(templates).length > 0) result.templates = templates;
16286
+ if (feedbackEdges.length > 0) result.feedback = feedbackEdges;
16287
+ return result;
16288
+ }
16289
+ function specDiff(specA, specB) {
16290
+ const entries = [];
16291
+ if (specA.name !== specB.name) {
16292
+ entries.push({
16293
+ type: "changed",
16294
+ path: "name",
16295
+ detail: `"${specA.name}" \u2192 "${specB.name}"`
16296
+ });
16297
+ }
16298
+ const nodesA = new Set(Object.keys(specA.nodes));
16299
+ const nodesB = new Set(Object.keys(specB.nodes));
16300
+ for (const name of nodesB) {
16301
+ if (!nodesA.has(name)) {
16302
+ const n = specB.nodes[name];
16303
+ entries.push({
16304
+ type: "added",
16305
+ path: `nodes.${name}`,
16306
+ detail: `type: ${n.type}`
16307
+ });
16308
+ }
16309
+ }
16310
+ for (const name of nodesA) {
16311
+ if (!nodesB.has(name)) {
16312
+ entries.push({ type: "removed", path: `nodes.${name}` });
16313
+ }
16314
+ }
16315
+ for (const name of nodesA) {
16316
+ if (!nodesB.has(name)) continue;
16317
+ const a = specA.nodes[name];
16318
+ const b = specB.nodes[name];
16319
+ if (JSON.stringify(a) !== JSON.stringify(b)) {
16320
+ const details = [];
16321
+ if (a.type !== b.type) details.push(`type: ${a.type} \u2192 ${b.type}`);
16322
+ if (JSON.stringify(a.deps) !== JSON.stringify(b.deps)) {
16323
+ details.push("deps changed");
16324
+ }
16325
+ if (a.fn !== b.fn) {
16326
+ details.push(`fn: ${a.fn} \u2192 ${b.fn}`);
16327
+ }
16328
+ if (JSON.stringify(a.config) !== JSON.stringify(b.config)) {
16329
+ details.push("config changed");
16330
+ }
16331
+ entries.push({
16332
+ type: "changed",
16333
+ path: `nodes.${name}`,
16334
+ detail: details.join("; ") || "modified"
16335
+ });
16336
+ }
16337
+ }
16338
+ const tmplA = specA.templates ?? {};
16339
+ const tmplB = specB.templates ?? {};
16340
+ const tmplNamesA = new Set(Object.keys(tmplA));
16341
+ const tmplNamesB = new Set(Object.keys(tmplB));
16342
+ for (const name of tmplNamesB) {
16343
+ if (!tmplNamesA.has(name)) {
16344
+ entries.push({ type: "added", path: `templates.${name}` });
16345
+ }
16346
+ }
16347
+ for (const name of tmplNamesA) {
16348
+ if (!tmplNamesB.has(name)) {
16349
+ entries.push({ type: "removed", path: `templates.${name}` });
16350
+ }
16351
+ }
16352
+ for (const name of tmplNamesA) {
16353
+ if (!tmplNamesB.has(name)) continue;
16354
+ if (JSON.stringify(tmplA[name]) !== JSON.stringify(tmplB[name])) {
16355
+ entries.push({
16356
+ type: "changed",
16357
+ path: `templates.${name}`,
16358
+ detail: "template definition changed"
16359
+ });
16360
+ }
16361
+ }
16362
+ const fbA = specA.feedback ?? [];
16363
+ const fbB = specB.feedback ?? [];
16364
+ const fbKeyA = new Set(fbA.map((e) => `${e.from}->${e.to}`));
16365
+ const fbKeyB = new Set(fbB.map((e) => `${e.from}->${e.to}`));
16366
+ for (const fb of fbB) {
16367
+ const key = `${fb.from}->${fb.to}`;
16368
+ if (!fbKeyA.has(key)) {
16369
+ entries.push({
16370
+ type: "added",
16371
+ path: `feedback.${key}`,
16372
+ detail: `maxIterations: ${fb.maxIterations ?? 10}`
16373
+ });
16374
+ }
16375
+ }
16376
+ for (const fb of fbA) {
16377
+ const key = `${fb.from}->${fb.to}`;
16378
+ if (!fbKeyB.has(key)) {
16379
+ entries.push({ type: "removed", path: `feedback.${key}` });
16380
+ }
16381
+ }
16382
+ for (const fb of fbA) {
16383
+ const key = `${fb.from}->${fb.to}`;
16384
+ const counterpart = fbB.find((b) => b.from === fb.from && b.to === fb.to);
16385
+ if (counterpart && JSON.stringify(fb) !== JSON.stringify(counterpart)) {
16386
+ entries.push({
16387
+ type: "changed",
16388
+ path: `feedback.${key}`,
16389
+ detail: `maxIterations: ${fb.maxIterations ?? 10} \u2192 ${counterpart.maxIterations ?? 10}`
16390
+ });
16391
+ }
16392
+ }
16393
+ const added = entries.filter((e) => e.type === "added").length;
16394
+ const removed = entries.filter((e) => e.type === "removed").length;
16395
+ const changed = entries.filter((e) => e.type === "changed").length;
16396
+ const parts = [];
16397
+ if (added) parts.push(`${added} added`);
16398
+ if (removed) parts.push(`${removed} removed`);
16399
+ if (changed) parts.push(`${changed} changed`);
16400
+ const summary = parts.length > 0 ? parts.join(", ") : "no changes";
16401
+ return { entries, summary };
16402
+ }
16403
+ var LLM_COMPOSE_SYSTEM_PROMPT = `You are a graph architect for GraphReFly, a reactive graph protocol.
16404
+
16405
+ Given a natural-language description, produce a JSON GraphSpec with this structure:
16406
+
16407
+ {
16408
+ "name": "<graph_name>",
16409
+ "nodes": {
16410
+ "<node_name>": {
16411
+ "type": "state" | "derived" | "producer" | "effect" | "operator",
16412
+ "deps": ["<dep_node_name>", ...],
16413
+ "fn": "<catalog_function_name>",
16414
+ "source": "<catalog_source_name>",
16415
+ "config": { ... },
16416
+ "initial": <value>,
16417
+ "meta": { "description": "<purpose>" }
16418
+ },
16419
+ "<template_instance>": {
16420
+ "type": "template",
16421
+ "template": "<template_name>",
16422
+ "bind": { "$param": "node_name" }
16423
+ }
16424
+ },
16425
+ "templates": {
16426
+ "<template_name>": {
16427
+ "params": ["$param1", "$param2"],
16428
+ "nodes": { ... },
16429
+ "output": "<output_node>"
16430
+ }
16431
+ },
16432
+ "feedback": [
16433
+ { "from": "<condition_node>", "to": "<state_node>", "maxIterations": 10 }
16434
+ ]
16435
+ }
16436
+
16437
+ Rules:
16438
+ - "state" nodes hold user/LLM-writable values (knobs). Use "initial" for default values.
16439
+ - "derived" nodes compute from deps using a named "fn".
16440
+ - "effect" nodes produce side effects from deps.
16441
+ - "producer" nodes generate values from a named "source".
16442
+ - Use "templates" when the same subgraph pattern repeats (e.g., per-source resilience).
16443
+ - Use "feedback" for bounded cycles where a derived value writes back to a state node.
16444
+ - meta.description is required for every node.
16445
+ - Return ONLY valid JSON, no markdown fences or commentary.`;
16446
+ function stripFences2(text) {
16447
+ const match = text.match(/^```(?:json)?\s*([\s\S]*?)\s*```[\s\S]*$/);
16448
+ return match ? match[1] : text;
16449
+ }
16450
+ async function llmCompose(problem, adapter, opts) {
16451
+ let systemPrompt = LLM_COMPOSE_SYSTEM_PROMPT;
16452
+ if (opts?.catalogDescription) {
16453
+ systemPrompt += `
16454
+
16455
+ Available catalog:
16456
+ ${opts.catalogDescription}`;
16457
+ }
16458
+ if (opts?.systemPromptExtra) {
16459
+ systemPrompt += `
16460
+
16461
+ ${opts.systemPromptExtra}`;
16462
+ }
16463
+ const messages = [
16464
+ { role: "system", content: systemPrompt },
16465
+ { role: "user", content: problem }
16466
+ ];
16467
+ const rawResult = adapter.invoke(messages, {
16468
+ model: opts?.model,
16469
+ temperature: opts?.temperature ?? 0,
16470
+ maxTokens: opts?.maxTokens
16471
+ });
16472
+ const response = await rawResult;
16473
+ let content = response.content.trim();
16474
+ if (content.startsWith("```")) {
16475
+ content = stripFences2(content);
16476
+ }
16477
+ let parsed;
16478
+ try {
16479
+ parsed = JSON.parse(content);
16480
+ } catch {
16481
+ throw new Error(`llmCompose: LLM response is not valid JSON: ${content.slice(0, 200)}`);
16482
+ }
16483
+ const validation = validateSpec(parsed);
16484
+ if (!validation.valid) {
16485
+ throw new Error(`llmCompose: invalid GraphSpec:
16486
+ ${validation.errors.join("\n")}`);
16487
+ }
16488
+ return parsed;
16489
+ }
16490
+ async function llmRefine(currentSpec, feedback2, adapter, opts) {
16491
+ let systemPrompt = LLM_COMPOSE_SYSTEM_PROMPT;
16492
+ if (opts?.catalogDescription) {
16493
+ systemPrompt += `
16494
+
16495
+ Available catalog:
16496
+ ${opts.catalogDescription}`;
16497
+ }
16498
+ if (opts?.systemPromptExtra) {
16499
+ systemPrompt += `
16500
+
16501
+ ${opts.systemPromptExtra}`;
16502
+ }
16503
+ const messages = [
16504
+ { role: "system", content: systemPrompt },
16505
+ {
16506
+ role: "user",
16507
+ content: `Current GraphSpec:
16508
+ ${JSON.stringify(currentSpec, null, 2)}
16509
+
16510
+ Modification request: ${feedback2}
16511
+
16512
+ Return the complete modified GraphSpec as JSON.`
16513
+ }
16514
+ ];
16515
+ const rawResult = adapter.invoke(messages, {
16516
+ model: opts?.model,
16517
+ temperature: opts?.temperature ?? 0,
16518
+ maxTokens: opts?.maxTokens
16519
+ });
16520
+ const response = await rawResult;
16521
+ let content = response.content.trim();
16522
+ if (content.startsWith("```")) {
16523
+ content = stripFences2(content);
16524
+ }
16525
+ let parsed;
16526
+ try {
16527
+ parsed = JSON.parse(content);
16528
+ } catch {
16529
+ throw new Error(`llmRefine: LLM response is not valid JSON: ${content.slice(0, 200)}`);
16530
+ }
16531
+ const validation = validateSpec(parsed);
16532
+ if (!validation.valid) {
16533
+ throw new Error(`llmRefine: invalid GraphSpec:
16534
+ ${validation.errors.join("\n")}`);
16535
+ }
16536
+ return parsed;
16537
+ }
16538
+
15418
16539
  // src/patterns/messaging.ts
15419
16540
  var messaging_exports = {};
15420
16541
  __export(messaging_exports, {
@@ -15863,7 +16984,7 @@ function registerStep(graph, name, step, depPaths) {
15863
16984
  graph.connect(path, name);
15864
16985
  }
15865
16986
  }
15866
- function baseMeta(kind, meta) {
16987
+ function baseMeta2(kind, meta) {
15867
16988
  return {
15868
16989
  orchestration: true,
15869
16990
  orchestration_type: kind,
@@ -15901,7 +17022,7 @@ function task(graph, name, run, opts) {
15901
17022
  ...nodeOpts,
15902
17023
  name,
15903
17024
  describeKind: "derived",
15904
- meta: baseMeta("task", opts?.meta)
17025
+ meta: baseMeta2("task", opts?.meta)
15905
17026
  }
15906
17027
  );
15907
17028
  registerStep(
@@ -15924,7 +17045,7 @@ function branch(graph, name, source, predicate, opts) {
15924
17045
  ...opts,
15925
17046
  name,
15926
17047
  describeKind: "derived",
15927
- meta: baseMeta("branch", opts?.meta)
17048
+ meta: baseMeta2("branch", opts?.meta)
15928
17049
  }
15929
17050
  );
15930
17051
  registerStep(graph, name, step, src.path ? [src.path] : []);
@@ -15947,7 +17068,7 @@ function gate2(graph, name, source, control, opts) {
15947
17068
  ...opts,
15948
17069
  name,
15949
17070
  describeKind: "operator",
15950
- meta: baseMeta("gate", opts?.meta)
17071
+ meta: baseMeta2("gate", opts?.meta)
15951
17072
  }
15952
17073
  );
15953
17074
  registerStep(
@@ -15975,7 +17096,7 @@ function approval(graph, name, source, approver, opts) {
15975
17096
  ...opts,
15976
17097
  name,
15977
17098
  describeKind: "operator",
15978
- meta: baseMeta("approval", opts?.meta)
17099
+ meta: baseMeta2("approval", opts?.meta)
15979
17100
  }
15980
17101
  );
15981
17102
  registerStep(
@@ -15994,7 +17115,7 @@ function forEach2(graph, name, source, run, opts) {
15994
17115
  name,
15995
17116
  describeKind: "effect",
15996
17117
  completeWhenDepsComplete: false,
15997
- meta: baseMeta("forEach", opts?.meta),
17118
+ meta: baseMeta2("forEach", opts?.meta),
15998
17119
  onMessage(msg, depIndex, actions) {
15999
17120
  if (terminated) return true;
16000
17121
  if (depIndex !== 0) {
@@ -16029,7 +17150,7 @@ function join2(graph, name, deps, opts) {
16029
17150
  ...opts,
16030
17151
  name,
16031
17152
  describeKind: "derived",
16032
- meta: baseMeta("join", opts?.meta)
17153
+ meta: baseMeta2("join", opts?.meta)
16033
17154
  }
16034
17155
  );
16035
17156
  registerStep(
@@ -16060,7 +17181,7 @@ function loop(graph, name, source, iterate, opts) {
16060
17181
  ...opts,
16061
17182
  name,
16062
17183
  describeKind: "derived",
16063
- meta: baseMeta("loop", opts?.meta)
17184
+ meta: baseMeta2("loop", opts?.meta)
16064
17185
  }
16065
17186
  );
16066
17187
  registerStep(
@@ -16085,7 +17206,7 @@ function sensor(graph, name, initial, opts) {
16085
17206
  name,
16086
17207
  initial,
16087
17208
  describeKind: "producer",
16088
- meta: baseMeta("sensor", opts?.meta)
17209
+ meta: baseMeta2("sensor", opts?.meta)
16089
17210
  });
16090
17211
  registerStep(graph, name, source, []);
16091
17212
  return {
@@ -16123,7 +17244,7 @@ function wait(graph, name, source, ms, opts) {
16123
17244
  initial: src.node.get(),
16124
17245
  describeKind: "operator",
16125
17246
  completeWhenDepsComplete: false,
16126
- meta: baseMeta("wait", opts?.meta),
17247
+ meta: baseMeta2("wait", opts?.meta),
16127
17248
  onMessage(msg, depIndex, actions) {
16128
17249
  if (terminated) return true;
16129
17250
  if (depIndex !== 0) {
@@ -16173,7 +17294,7 @@ function onFailure(graph, name, source, recover, opts) {
16173
17294
  name,
16174
17295
  describeKind: "operator",
16175
17296
  completeWhenDepsComplete: false,
16176
- meta: baseMeta("onFailure", opts?.meta),
17297
+ meta: baseMeta2("onFailure", opts?.meta),
16177
17298
  onMessage(msg, _depIndex, actions) {
16178
17299
  if (terminated) return true;
16179
17300
  if (msg[0] === ERROR) {
@@ -16803,6 +17924,7 @@ var version = "0.0.0";
16803
17924
  gate,
16804
17925
  globToRegExp,
16805
17926
  graph,
17927
+ graphspec,
16806
17928
  interval,
16807
17929
  isBatching,
16808
17930
  isKnownMessageType,
@@ -16860,6 +17982,7 @@ var version = "0.0.0";
16860
17982
  reactiveLog,
16861
17983
  reactiveMap,
16862
17984
  reduce,
17985
+ reduction,
16863
17986
  repeat,
16864
17987
  replay,
16865
17988
  replayWAL,