@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.js CHANGED
@@ -145,14 +145,6 @@ import {
145
145
  import {
146
146
  core_exports
147
147
  } from "./chunk-E7OH6ZAZ.js";
148
- import {
149
- JsonCodec,
150
- createDagCborCodec,
151
- createDagCborZstdCodec,
152
- graph_exports,
153
- negotiateCodec,
154
- replayWAL
155
- } from "./chunk-A2AJJOSJ.js";
156
148
  import {
157
149
  cached,
158
150
  createWatermarkController,
@@ -189,6 +181,14 @@ import {
189
181
  import {
190
182
  ResettableTimer
191
183
  } from "./chunk-WZ2Z2CRV.js";
184
+ import {
185
+ JsonCodec,
186
+ createDagCborCodec,
187
+ createDagCborZstdCodec,
188
+ graph_exports,
189
+ negotiateCodec,
190
+ replayWAL
191
+ } from "./chunk-A2AJJOSJ.js";
192
192
  import {
193
193
  analyzeAndMeasure,
194
194
  computeLineBreaks,
@@ -1045,10 +1045,12 @@ __export(patterns_exports, {
1045
1045
  ai: () => ai_exports,
1046
1046
  cqrs: () => cqrs_exports,
1047
1047
  demoShell: () => demo_shell_exports,
1048
+ graphspec: () => graphspec_exports,
1048
1049
  layout: () => reactive_layout_exports,
1049
1050
  memory: () => memory_exports,
1050
1051
  messaging: () => messaging_exports,
1051
- orchestration: () => orchestration_exports
1052
+ orchestration: () => orchestration_exports,
1053
+ reduction: () => reduction_exports
1052
1054
  });
1053
1055
 
1054
1056
  // src/patterns/ai.ts
@@ -3023,6 +3025,1123 @@ function demoShell(opts) {
3023
3025
  };
3024
3026
  }
3025
3027
 
3028
+ // src/patterns/graphspec.ts
3029
+ var graphspec_exports = {};
3030
+ __export(graphspec_exports, {
3031
+ compileSpec: () => compileSpec,
3032
+ decompileGraph: () => decompileGraph,
3033
+ llmCompose: () => llmCompose,
3034
+ llmRefine: () => llmRefine,
3035
+ specDiff: () => specDiff,
3036
+ validateSpec: () => validateSpec
3037
+ });
3038
+
3039
+ // src/patterns/reduction.ts
3040
+ var reduction_exports = {};
3041
+ __export(reduction_exports, {
3042
+ budgetGate: () => budgetGate,
3043
+ feedback: () => feedback,
3044
+ funnel: () => funnel,
3045
+ scorer: () => scorer,
3046
+ stratify: () => stratify
3047
+ });
3048
+ function baseMeta(kind, meta) {
3049
+ return {
3050
+ reduction: true,
3051
+ reduction_type: kind,
3052
+ ...meta ?? {}
3053
+ };
3054
+ }
3055
+ function stratify(name, source, rules, opts) {
3056
+ const g = new Graph(name, opts);
3057
+ g.add("source", source);
3058
+ const rulesNode = state(rules, {
3059
+ meta: baseMeta("stratify_rules")
3060
+ });
3061
+ g.add("rules", rulesNode);
3062
+ for (const rule of rules) {
3063
+ _addBranch(g, source, rulesNode, rule);
3064
+ }
3065
+ return g;
3066
+ }
3067
+ function _addBranch(graph, source, rulesNode, rule) {
3068
+ const branchName = `branch/${rule.name}`;
3069
+ let pendingDirty = false;
3070
+ const filterNode = node([source, rulesNode], () => void 0, {
3071
+ describeKind: "operator",
3072
+ meta: baseMeta("stratify_branch", { branch: rule.name }),
3073
+ onMessage(msg, depIndex, actions) {
3074
+ if (depIndex !== 0) return false;
3075
+ const t = msg[0];
3076
+ if (t === DATA) {
3077
+ const value = msg[1];
3078
+ const currentRules = rulesNode.get();
3079
+ const currentRule = currentRules.find((r) => r.name === rule.name);
3080
+ if (currentRule && currentRule.classify(value)) {
3081
+ pendingDirty = false;
3082
+ actions.emit(value);
3083
+ } else {
3084
+ if (pendingDirty) {
3085
+ pendingDirty = false;
3086
+ actions.down([[DIRTY], [RESOLVED]]);
3087
+ }
3088
+ }
3089
+ return true;
3090
+ }
3091
+ if (t === DIRTY) {
3092
+ pendingDirty = true;
3093
+ return true;
3094
+ }
3095
+ if (t === RESOLVED) {
3096
+ if (pendingDirty) {
3097
+ pendingDirty = false;
3098
+ actions.down([[DIRTY], [RESOLVED]]);
3099
+ } else {
3100
+ actions.down([[RESOLVED]]);
3101
+ }
3102
+ return true;
3103
+ }
3104
+ if (t === COMPLETE || t === ERROR) {
3105
+ pendingDirty = false;
3106
+ actions.down([msg]);
3107
+ return true;
3108
+ }
3109
+ return false;
3110
+ }
3111
+ });
3112
+ graph.add(branchName, filterNode);
3113
+ graph.connect("source", branchName);
3114
+ if (rule.ops) {
3115
+ const transformed = rule.ops(filterNode);
3116
+ const transformedName = `branch/${rule.name}/out`;
3117
+ graph.add(transformedName, transformed);
3118
+ graph.connect(branchName, transformedName);
3119
+ }
3120
+ }
3121
+ function funnel(name, sources, stages, opts) {
3122
+ if (sources.length === 0) throw new RangeError("funnel requires at least one source");
3123
+ if (stages.length === 0) throw new RangeError("funnel requires at least one stage");
3124
+ const g = new Graph(name, opts);
3125
+ const merged = sources.length === 1 ? sources[0] : merge(...sources);
3126
+ g.add("merged", merged);
3127
+ let prevOutputPath = "merged";
3128
+ for (let i = 0; i < stages.length; i++) {
3129
+ const stage = stages[i];
3130
+ const sub = new Graph(stage.name);
3131
+ stage.build(sub);
3132
+ try {
3133
+ sub.resolve("input");
3134
+ } catch {
3135
+ throw new Error(`funnel stage "${stage.name}" must define an "input" node`);
3136
+ }
3137
+ try {
3138
+ sub.resolve("output");
3139
+ } catch {
3140
+ throw new Error(`funnel stage "${stage.name}" must define an "output" node`);
3141
+ }
3142
+ g.mount(stage.name, sub);
3143
+ const prevNode = g.resolve(prevOutputPath);
3144
+ const stageInputPath = `${stage.name}::input`;
3145
+ const stageInput = g.resolve(stageInputPath);
3146
+ prevNode.subscribe((msgs) => {
3147
+ for (const msg of msgs) {
3148
+ const t = msg[0];
3149
+ if (t === DATA) {
3150
+ stageInput.down([[DATA, msg[1]]]);
3151
+ } else if (t === DIRTY) {
3152
+ stageInput.down([[DIRTY]]);
3153
+ } else if (t === RESOLVED) {
3154
+ stageInput.down([[RESOLVED]]);
3155
+ } else if (t === COMPLETE || t === ERROR) {
3156
+ stageInput.down([msg]);
3157
+ }
3158
+ }
3159
+ });
3160
+ prevOutputPath = `${stage.name}::output`;
3161
+ }
3162
+ return g;
3163
+ }
3164
+ function feedback(graph, condition, reentry, opts) {
3165
+ const maxIter = opts?.maxIterations ?? 10;
3166
+ const counterName = `__feedback_${condition}`;
3167
+ const counter = state(0, {
3168
+ meta: baseMeta("feedback_counter", {
3169
+ maxIterations: maxIter,
3170
+ feedbackFrom: condition,
3171
+ feedbackTo: reentry
3172
+ })
3173
+ });
3174
+ graph.add(counterName, counter);
3175
+ const condNode = graph.resolve(condition);
3176
+ const reentryNode = graph.resolve(reentry);
3177
+ let tornDown = false;
3178
+ let unsubCounter = null;
3179
+ const safeUnsub = () => {
3180
+ if (tornDown) return;
3181
+ tornDown = true;
3182
+ unsub();
3183
+ unsubCounter?.();
3184
+ };
3185
+ const unsub = condNode.subscribe((msgs) => {
3186
+ for (const msg of msgs) {
3187
+ if (msg[0] === DATA) {
3188
+ const currentCount = counter.get();
3189
+ if (currentCount >= maxIter) continue;
3190
+ const condValue = msg[1];
3191
+ if (condValue == null) continue;
3192
+ counter.down([[DATA, currentCount + 1]]);
3193
+ reentryNode.down([[DATA, condValue]]);
3194
+ } else if (msg[0] === COMPLETE || msg[0] === ERROR) {
3195
+ const terminal = msg[0] === ERROR && msg.length > 1 ? [ERROR, msg[1]] : [msg[0]];
3196
+ counter.down([terminal]);
3197
+ safeUnsub();
3198
+ }
3199
+ }
3200
+ });
3201
+ unsubCounter = counter.subscribe((msgs) => {
3202
+ for (const msg of msgs) {
3203
+ if (msg[0] === COMPLETE || msg[0] === ERROR) {
3204
+ safeUnsub();
3205
+ return;
3206
+ }
3207
+ }
3208
+ });
3209
+ return graph;
3210
+ }
3211
+ function budgetGate(source, constraints, opts) {
3212
+ if (constraints.length === 0) throw new RangeError("budgetGate requires at least one constraint");
3213
+ const constraintNodes = constraints.map((c) => c.node);
3214
+ const allDeps = [source, ...constraintNodes];
3215
+ let buffer2 = [];
3216
+ let paused = false;
3217
+ let pendingResolved = false;
3218
+ const lockId = /* @__PURE__ */ Symbol("budget-gate");
3219
+ function checkBudget() {
3220
+ return constraints.every((c) => c.check(c.node.get()));
3221
+ }
3222
+ function flushBuffer(actions) {
3223
+ while (buffer2.length > 0 && checkBudget()) {
3224
+ const item = buffer2.shift();
3225
+ actions.emit(item);
3226
+ }
3227
+ if (buffer2.length === 0 && pendingResolved) {
3228
+ pendingResolved = false;
3229
+ actions.down([[RESOLVED]]);
3230
+ }
3231
+ }
3232
+ return node(allDeps, () => void 0, {
3233
+ ...opts,
3234
+ describeKind: "operator",
3235
+ meta: baseMeta("budget_gate", opts?.meta),
3236
+ onMessage(msg, depIndex, actions) {
3237
+ const t = msg[0];
3238
+ if (depIndex === 0) {
3239
+ if (t === DATA) {
3240
+ if (checkBudget() && buffer2.length === 0) {
3241
+ actions.emit(msg[1]);
3242
+ } else {
3243
+ buffer2.push(msg[1]);
3244
+ if (!paused) {
3245
+ paused = true;
3246
+ actions.up([[PAUSE, lockId]]);
3247
+ }
3248
+ }
3249
+ return true;
3250
+ }
3251
+ if (t === DIRTY) {
3252
+ actions.down([[DIRTY]]);
3253
+ return true;
3254
+ }
3255
+ if (t === RESOLVED) {
3256
+ if (buffer2.length === 0) {
3257
+ actions.down([[RESOLVED]]);
3258
+ } else {
3259
+ pendingResolved = true;
3260
+ }
3261
+ return true;
3262
+ }
3263
+ if (t === COMPLETE || t === ERROR) {
3264
+ for (const item of buffer2) {
3265
+ actions.emit(item);
3266
+ }
3267
+ buffer2 = [];
3268
+ pendingResolved = false;
3269
+ if (paused) {
3270
+ paused = false;
3271
+ actions.up([[RESUME, lockId]]);
3272
+ }
3273
+ actions.down([msg]);
3274
+ return true;
3275
+ }
3276
+ return false;
3277
+ }
3278
+ if (t === DATA || t === RESOLVED) {
3279
+ if (checkBudget() && buffer2.length > 0) {
3280
+ flushBuffer(actions);
3281
+ if (buffer2.length === 0 && paused) {
3282
+ paused = false;
3283
+ actions.up([[RESUME, lockId]]);
3284
+ }
3285
+ } else if (!checkBudget() && !paused && buffer2.length > 0) {
3286
+ paused = true;
3287
+ actions.up([[PAUSE, lockId]]);
3288
+ }
3289
+ return true;
3290
+ }
3291
+ if (t === DIRTY) {
3292
+ return true;
3293
+ }
3294
+ if (t === ERROR) {
3295
+ actions.down([msg]);
3296
+ return true;
3297
+ }
3298
+ if (t === COMPLETE) {
3299
+ return true;
3300
+ }
3301
+ return false;
3302
+ }
3303
+ });
3304
+ }
3305
+ function scorer(sources, weights, opts) {
3306
+ if (sources.length === 0) throw new RangeError("scorer requires at least one source");
3307
+ if (sources.length !== weights.length) {
3308
+ throw new RangeError("scorer requires the same number of sources and weights");
3309
+ }
3310
+ const allDeps = [...sources, ...weights];
3311
+ const n = sources.length;
3312
+ const scoreFns = opts?.scoreFns;
3313
+ return derived(
3314
+ allDeps,
3315
+ (vals) => {
3316
+ const signals = vals.slice(0, n);
3317
+ const weightValues = vals.slice(n);
3318
+ const breakdown = [];
3319
+ let totalScore = 0;
3320
+ for (let i = 0; i < n; i++) {
3321
+ const sig = signals[i] ?? 0;
3322
+ const wt = weightValues[i] ?? 0;
3323
+ const rawScore = scoreFns?.[i] ? scoreFns[i](sig) : sig;
3324
+ const weighted = rawScore * wt;
3325
+ breakdown.push(weighted);
3326
+ totalScore += weighted;
3327
+ }
3328
+ return {
3329
+ value: signals,
3330
+ score: totalScore,
3331
+ breakdown
3332
+ };
3333
+ },
3334
+ {
3335
+ ...opts,
3336
+ describeKind: "derived",
3337
+ meta: baseMeta("scorer", opts?.meta)
3338
+ }
3339
+ );
3340
+ }
3341
+
3342
+ // src/patterns/graphspec.ts
3343
+ var VALID_NODE_TYPES2 = /* @__PURE__ */ new Set([
3344
+ "state",
3345
+ "producer",
3346
+ "derived",
3347
+ "effect",
3348
+ "operator",
3349
+ "template"
3350
+ ]);
3351
+ var INNER_NODE_TYPES = /* @__PURE__ */ new Set(["state", "producer", "derived", "effect", "operator"]);
3352
+ function validateSpec(spec) {
3353
+ const errors = [];
3354
+ if (spec == null || typeof spec !== "object") {
3355
+ return { valid: false, errors: ["GraphSpec must be a non-null object"] };
3356
+ }
3357
+ const s = spec;
3358
+ if (typeof s.name !== "string" || s.name.length === 0) {
3359
+ errors.push("Missing or empty 'name' field");
3360
+ }
3361
+ if (s.nodes == null || typeof s.nodes !== "object" || Array.isArray(s.nodes)) {
3362
+ errors.push("Missing or invalid 'nodes' field (must be an object)");
3363
+ return { valid: false, errors };
3364
+ }
3365
+ const nodeNames = new Set(Object.keys(s.nodes));
3366
+ const nodeTypes = /* @__PURE__ */ new Map();
3367
+ const templateDefs = /* @__PURE__ */ new Map();
3368
+ if (s.templates != null && typeof s.templates === "object" && !Array.isArray(s.templates)) {
3369
+ for (const [tName, tRaw] of Object.entries(s.templates)) {
3370
+ if (tRaw != null && typeof tRaw === "object") {
3371
+ const t = tRaw;
3372
+ templateDefs.set(tName, {
3373
+ params: Array.isArray(t.params) ? t.params : []
3374
+ });
3375
+ }
3376
+ }
3377
+ }
3378
+ if (s.templates != null) {
3379
+ if (typeof s.templates !== "object" || Array.isArray(s.templates)) {
3380
+ errors.push("'templates' must be an object");
3381
+ } else {
3382
+ for (const [tName, tRaw] of Object.entries(s.templates)) {
3383
+ if (tRaw == null || typeof tRaw !== "object") {
3384
+ errors.push(`Template "${tName}": must be an object`);
3385
+ continue;
3386
+ }
3387
+ const t = tRaw;
3388
+ if (!Array.isArray(t.params)) {
3389
+ errors.push(`Template "${tName}": missing 'params' array`);
3390
+ }
3391
+ if (t.nodes == null || typeof t.nodes !== "object" || Array.isArray(t.nodes)) {
3392
+ errors.push(`Template "${tName}": missing or invalid 'nodes' object`);
3393
+ } else {
3394
+ const paramSet = new Set(Array.isArray(t.params) ? t.params : []);
3395
+ const innerNames = new Set(Object.keys(t.nodes));
3396
+ for (const [nName, nRaw] of Object.entries(t.nodes)) {
3397
+ if (nRaw == null || typeof nRaw !== "object") {
3398
+ errors.push(`Template "${tName}" node "${nName}": must be an object`);
3399
+ continue;
3400
+ }
3401
+ const n = nRaw;
3402
+ if (typeof n.type !== "string" || !INNER_NODE_TYPES.has(n.type)) {
3403
+ errors.push(`Template "${tName}" node "${nName}": invalid type`);
3404
+ }
3405
+ if (Array.isArray(n.deps)) {
3406
+ for (const dep of n.deps) {
3407
+ if (!innerNames.has(dep) && !paramSet.has(dep)) {
3408
+ errors.push(
3409
+ `Template "${tName}" node "${nName}": dep "${dep}" is not an inner node or param`
3410
+ );
3411
+ }
3412
+ }
3413
+ }
3414
+ }
3415
+ if (typeof t.output !== "string") {
3416
+ errors.push(`Template "${tName}": missing 'output' string`);
3417
+ } else if (!t.nodes[t.output]) {
3418
+ errors.push(`Template "${tName}": output "${t.output}" is not a declared node`);
3419
+ }
3420
+ }
3421
+ }
3422
+ }
3423
+ }
3424
+ for (const [name, raw] of Object.entries(s.nodes)) {
3425
+ if (raw == null || typeof raw !== "object") {
3426
+ errors.push(`Node "${name}": must be an object`);
3427
+ continue;
3428
+ }
3429
+ const n = raw;
3430
+ if (typeof n.type !== "string" || !VALID_NODE_TYPES2.has(n.type)) {
3431
+ errors.push(
3432
+ `Node "${name}": invalid type "${String(n.type)}" (expected: ${[...VALID_NODE_TYPES2].join(", ")})`
3433
+ );
3434
+ continue;
3435
+ }
3436
+ nodeTypes.set(name, n.type);
3437
+ if (n.type === "template") {
3438
+ if (typeof n.template !== "string" || !templateDefs.has(n.template)) {
3439
+ errors.push(`Node "${name}": template "${String(n.template)}" not found in templates`);
3440
+ } else {
3441
+ if (n.bind == null || typeof n.bind !== "object" || Array.isArray(n.bind)) {
3442
+ errors.push(`Node "${name}": template ref requires 'bind' object`);
3443
+ } else {
3444
+ const tmpl = templateDefs.get(n.template);
3445
+ const bind = n.bind;
3446
+ for (const param of tmpl.params) {
3447
+ if (!(param in bind)) {
3448
+ errors.push(
3449
+ `Node "${name}": template param "${param}" is not bound (template "${n.template}")`
3450
+ );
3451
+ }
3452
+ }
3453
+ for (const [, target] of Object.entries(bind)) {
3454
+ if (typeof target === "string" && !nodeNames.has(target)) {
3455
+ errors.push(
3456
+ `Node "${name}": bind target "${target}" does not reference an existing node`
3457
+ );
3458
+ }
3459
+ }
3460
+ }
3461
+ }
3462
+ } else {
3463
+ if (Array.isArray(n.deps)) {
3464
+ for (const dep of n.deps) {
3465
+ if (dep === name) {
3466
+ errors.push(`Node "${name}": self-referencing dep`);
3467
+ } else if (!nodeNames.has(dep)) {
3468
+ errors.push(`Node "${name}": dep "${dep}" does not reference an existing node`);
3469
+ }
3470
+ }
3471
+ }
3472
+ if ((n.type === "derived" || n.type === "effect" || n.type === "operator") && !Array.isArray(n.deps)) {
3473
+ errors.push(`Node "${name}": ${n.type} node should have a 'deps' array`);
3474
+ }
3475
+ }
3476
+ }
3477
+ if (s.feedback != null) {
3478
+ if (!Array.isArray(s.feedback)) {
3479
+ errors.push("'feedback' must be an array");
3480
+ } else {
3481
+ for (let i = 0; i < s.feedback.length; i++) {
3482
+ const edge = s.feedback[i];
3483
+ if (edge == null || typeof edge !== "object") {
3484
+ errors.push(`Feedback [${i}]: must be an object`);
3485
+ continue;
3486
+ }
3487
+ const e = edge;
3488
+ if (typeof e.from !== "string" || !nodeNames.has(e.from)) {
3489
+ errors.push(
3490
+ `Feedback [${i}]: 'from' "${String(e.from)}" does not reference an existing node`
3491
+ );
3492
+ }
3493
+ if (typeof e.from === "string" && e.from === e.to) {
3494
+ errors.push(`Feedback [${i}]: 'from' and 'to' must be different nodes`);
3495
+ }
3496
+ if (typeof e.to !== "string" || !nodeNames.has(e.to)) {
3497
+ errors.push(
3498
+ `Feedback [${i}]: 'to' "${String(e.to)}" does not reference an existing node`
3499
+ );
3500
+ } else if (typeof e.to === "string" && nodeTypes.get(e.to) !== "state") {
3501
+ errors.push(
3502
+ `Feedback [${i}]: 'to' node "${e.to}" must be a state node (got "${nodeTypes.get(e.to) ?? "unknown"}")`
3503
+ );
3504
+ }
3505
+ }
3506
+ }
3507
+ }
3508
+ return { valid: errors.length === 0, errors };
3509
+ }
3510
+ function compileSpec(spec, opts) {
3511
+ const validation = validateSpec(spec);
3512
+ if (!validation.valid) {
3513
+ throw new Error(`compileSpec: invalid GraphSpec:
3514
+ ${validation.errors.join("\n")}`);
3515
+ }
3516
+ const catalog = opts?.catalog ?? {};
3517
+ const g = new Graph(spec.name);
3518
+ const templates = spec.templates ?? {};
3519
+ const created = /* @__PURE__ */ new Map();
3520
+ const deferred = [];
3521
+ for (const [name, raw] of Object.entries(spec.nodes)) {
3522
+ if (raw.type === "template") continue;
3523
+ const n = raw;
3524
+ if (n.type === "state") {
3525
+ const nd = state(n.initial, {
3526
+ name,
3527
+ meta: n.meta ? { ...n.meta } : void 0
3528
+ });
3529
+ g.add(name, nd);
3530
+ created.set(name, nd);
3531
+ } else if (n.type === "producer") {
3532
+ const sourceFactory = n.source ? catalog.sources?.[n.source] : void 0;
3533
+ const fnFactory = n.fn ? catalog.fns?.[n.fn] : void 0;
3534
+ if (sourceFactory) {
3535
+ const nd = sourceFactory(n.config ?? {});
3536
+ g.add(name, nd);
3537
+ created.set(name, nd);
3538
+ } else if (fnFactory) {
3539
+ const nd = fnFactory([], n.config ?? {});
3540
+ g.add(name, nd);
3541
+ created.set(name, nd);
3542
+ } else {
3543
+ const nd = producer(() => {
3544
+ }, {
3545
+ name,
3546
+ meta: { ...n.meta, _specFn: n.fn, _specSource: n.source }
3547
+ });
3548
+ g.add(name, nd);
3549
+ created.set(name, nd);
3550
+ }
3551
+ } else {
3552
+ deferred.push([name, n]);
3553
+ }
3554
+ }
3555
+ let progressed = true;
3556
+ const pending = new Map(deferred);
3557
+ while (pending.size > 0 && progressed) {
3558
+ progressed = false;
3559
+ for (const [name, n] of [...pending.entries()]) {
3560
+ const deps = n.deps ?? [];
3561
+ if (!deps.every((dep) => created.has(dep))) continue;
3562
+ const resolvedDeps = deps.map((dep) => created.get(dep));
3563
+ const fnFactory = n.fn ? catalog.fns?.[n.fn] : void 0;
3564
+ let nd;
3565
+ if (fnFactory) {
3566
+ nd = fnFactory(resolvedDeps, n.config ?? {});
3567
+ } else if (n.type === "effect") {
3568
+ nd = effect(resolvedDeps, () => {
3569
+ });
3570
+ } else {
3571
+ nd = derived(resolvedDeps, (vals) => vals[0]);
3572
+ }
3573
+ g.add(name, nd);
3574
+ created.set(name, nd);
3575
+ pending.delete(name);
3576
+ progressed = true;
3577
+ }
3578
+ }
3579
+ if (pending.size > 0) {
3580
+ const unresolved = [...pending.keys()].sort().join(", ");
3581
+ throw new Error(`compileSpec: unresolvable deps for nodes: ${unresolved}`);
3582
+ }
3583
+ for (const [name, raw] of Object.entries(spec.nodes)) {
3584
+ if (raw.type !== "template") continue;
3585
+ const ref = raw;
3586
+ const tmpl = templates[ref.template];
3587
+ const sub = new Graph(name);
3588
+ const subCreated = /* @__PURE__ */ new Map();
3589
+ const subDeferred = [];
3590
+ for (const [nName, nSpec] of Object.entries(tmpl.nodes)) {
3591
+ const resolvedDeps = (nSpec.deps ?? []).map((dep) => {
3592
+ if (dep.startsWith("$") && ref.bind[dep]) {
3593
+ return ref.bind[dep];
3594
+ }
3595
+ return dep;
3596
+ });
3597
+ const specWithResolvedDeps = { ...nSpec, deps: resolvedDeps };
3598
+ if (nSpec.type === "state") {
3599
+ const nd = state(nSpec.initial, {
3600
+ name: nName,
3601
+ meta: nSpec.meta ? { ...nSpec.meta } : void 0
3602
+ });
3603
+ sub.add(nName, nd);
3604
+ subCreated.set(nName, nd);
3605
+ } else if (nSpec.type === "producer") {
3606
+ const sourceFactory = nSpec.source ? catalog.sources?.[nSpec.source] : void 0;
3607
+ const fnFactory = nSpec.fn ? catalog.fns?.[nSpec.fn] : void 0;
3608
+ if (sourceFactory) {
3609
+ const nd = sourceFactory(nSpec.config ?? {});
3610
+ sub.add(nName, nd);
3611
+ subCreated.set(nName, nd);
3612
+ } else if (fnFactory) {
3613
+ const nd = fnFactory([], nSpec.config ?? {});
3614
+ sub.add(nName, nd);
3615
+ subCreated.set(nName, nd);
3616
+ } else {
3617
+ const nd = producer(() => {
3618
+ }, {
3619
+ name: nName,
3620
+ meta: { ...nSpec.meta, _specFn: nSpec.fn, _specSource: nSpec.source }
3621
+ });
3622
+ sub.add(nName, nd);
3623
+ subCreated.set(nName, nd);
3624
+ }
3625
+ } else {
3626
+ subDeferred.push([nName, specWithResolvedDeps]);
3627
+ }
3628
+ }
3629
+ let subProgressed = true;
3630
+ const subPending = new Map(subDeferred);
3631
+ while (subPending.size > 0 && subProgressed) {
3632
+ subProgressed = false;
3633
+ for (const [nName, nSpec] of [...subPending.entries()]) {
3634
+ const deps = nSpec.deps ?? [];
3635
+ const allReady = deps.every((dep) => subCreated.has(dep) || created.has(dep));
3636
+ if (!allReady) continue;
3637
+ const resolvedDeps = deps.map((dep) => subCreated.get(dep) ?? created.get(dep));
3638
+ const fnFactory = nSpec.fn ? catalog.fns?.[nSpec.fn] : void 0;
3639
+ let nd;
3640
+ if (fnFactory) {
3641
+ nd = fnFactory(resolvedDeps, nSpec.config ?? {});
3642
+ } else if (nSpec.type === "effect") {
3643
+ nd = effect(resolvedDeps, () => {
3644
+ });
3645
+ } else {
3646
+ nd = derived(resolvedDeps, (vals) => vals[0]);
3647
+ }
3648
+ sub.add(nName, nd);
3649
+ subCreated.set(nName, nd);
3650
+ subPending.delete(nName);
3651
+ subProgressed = true;
3652
+ }
3653
+ }
3654
+ if (subPending.size > 0) {
3655
+ const unresolved = [...subPending.keys()].sort().join(", ");
3656
+ throw new Error(
3657
+ `compileSpec: template "${ref.template}" has unresolvable deps: ${unresolved}`
3658
+ );
3659
+ }
3660
+ g.mount(name, sub);
3661
+ const outputPath = `${name}::${tmpl.output}`;
3662
+ created.set(name, g.resolve(outputPath));
3663
+ try {
3664
+ const outputNode = g.resolve(outputPath);
3665
+ outputNode.meta._templateName?.down([[DATA, ref.template]]);
3666
+ outputNode.meta._templateBind?.down([[DATA, ref.bind]]);
3667
+ } catch {
3668
+ }
3669
+ }
3670
+ for (const [name, raw] of Object.entries(spec.nodes)) {
3671
+ if (raw.type === "template") continue;
3672
+ const n = raw;
3673
+ for (const dep of n.deps ?? []) {
3674
+ try {
3675
+ g.connect(dep, name);
3676
+ } catch (err) {
3677
+ const msg = err instanceof Error ? err.message : "";
3678
+ if (!msg.includes("constructor deps") && !msg.includes("already")) {
3679
+ throw err;
3680
+ }
3681
+ }
3682
+ }
3683
+ }
3684
+ for (const fb of spec.feedback ?? []) {
3685
+ feedback(g, fb.from, fb.to, {
3686
+ maxIterations: fb.maxIterations
3687
+ });
3688
+ }
3689
+ return g;
3690
+ }
3691
+ var INTERNAL_META_KEYS = /* @__PURE__ */ new Set([
3692
+ "reduction",
3693
+ "reduction_type",
3694
+ "_specFn",
3695
+ "_specSource",
3696
+ "_templateName",
3697
+ "_templateBind",
3698
+ "feedbackFrom",
3699
+ "feedbackTo"
3700
+ ]);
3701
+ function decompileGraph(graph) {
3702
+ const desc = graph.describe({ detail: "standard" });
3703
+ const nodes = {};
3704
+ const feedbackEdges = [];
3705
+ const metaSegment = `::${GRAPH_META_SEGMENT}::`;
3706
+ const feedbackCounterPattern = /^__feedback_(.+)$/;
3707
+ const feedbackConditions = /* @__PURE__ */ new Set();
3708
+ for (const path of Object.keys(desc.nodes)) {
3709
+ if (path.includes(metaSegment)) continue;
3710
+ const match = feedbackCounterPattern.exec(path);
3711
+ if (match) {
3712
+ feedbackConditions.add(match[1]);
3713
+ const meta = desc.nodes[path]?.meta;
3714
+ if (meta?.feedbackFrom && meta?.feedbackTo) {
3715
+ feedbackEdges.push({
3716
+ from: meta.feedbackFrom,
3717
+ to: meta.feedbackTo,
3718
+ ...meta.maxIterations ? { maxIterations: meta.maxIterations } : {}
3719
+ });
3720
+ }
3721
+ }
3722
+ }
3723
+ for (const [path, nodeDesc] of Object.entries(desc.nodes)) {
3724
+ if (path.includes(metaSegment)) continue;
3725
+ if (feedbackCounterPattern.test(path)) continue;
3726
+ if (path.includes("::")) continue;
3727
+ const specNode = {
3728
+ type: nodeDesc.type
3729
+ };
3730
+ if (nodeDesc.deps.length > 0) {
3731
+ specNode.deps = nodeDesc.deps.filter((d) => !d.includes("::"));
3732
+ }
3733
+ if (nodeDesc.type === "state" && nodeDesc.value !== void 0) {
3734
+ specNode.initial = nodeDesc.value;
3735
+ }
3736
+ if (nodeDesc.meta && Object.keys(nodeDesc.meta).length > 0) {
3737
+ const meta = {};
3738
+ for (const [k, v] of Object.entries(nodeDesc.meta)) {
3739
+ if (!INTERNAL_META_KEYS.has(k)) meta[k] = v;
3740
+ }
3741
+ if (Object.keys(meta).length > 0) {
3742
+ specNode.meta = meta;
3743
+ }
3744
+ }
3745
+ nodes[path] = specNode;
3746
+ }
3747
+ const templates = {};
3748
+ const templateRefs = {};
3749
+ const metaDetectedSubgraphs = /* @__PURE__ */ new Set();
3750
+ for (const subName of desc.subgraphs) {
3751
+ const prefix = `${subName}::`;
3752
+ for (const [path, nodeDesc] of Object.entries(desc.nodes)) {
3753
+ if (!path.startsWith(prefix)) continue;
3754
+ if (path.includes(metaSegment)) continue;
3755
+ const meta = nodeDesc.meta;
3756
+ if (meta?._templateName && meta?._templateBind) {
3757
+ const templateName = meta._templateName;
3758
+ const bind = meta._templateBind;
3759
+ if (!templates[templateName]) {
3760
+ const tmplNodes = {};
3761
+ const tmplInnerNames = /* @__PURE__ */ new Set();
3762
+ const tmplPrefix = `${subName}::`;
3763
+ for (const [p, nd] of Object.entries(desc.nodes)) {
3764
+ if (!p.startsWith(tmplPrefix) || p.includes(metaSegment)) continue;
3765
+ const localName = p.slice(tmplPrefix.length);
3766
+ if (localName.includes("::")) continue;
3767
+ tmplInnerNames.add(localName);
3768
+ tmplNodes[localName] = {
3769
+ type: nd.type,
3770
+ ...nd.deps.length > 0 ? {
3771
+ deps: nd.deps.map(
3772
+ (d) => d.startsWith(tmplPrefix) ? d.slice(tmplPrefix.length) : d
3773
+ )
3774
+ } : {}
3775
+ };
3776
+ }
3777
+ const tmplParams = [];
3778
+ const tmplParamMap = /* @__PURE__ */ new Map();
3779
+ for (const n of Object.values(tmplNodes)) {
3780
+ for (const dep of n.deps ?? []) {
3781
+ if (!tmplInnerNames.has(dep) && !tmplParamMap.has(dep)) {
3782
+ const param = `$${dep}`;
3783
+ tmplParams.push(param);
3784
+ tmplParamMap.set(dep, param);
3785
+ }
3786
+ }
3787
+ }
3788
+ for (const n of Object.values(tmplNodes)) {
3789
+ if (n.deps) n.deps = n.deps.map((d) => tmplParamMap.get(d) ?? d);
3790
+ }
3791
+ const depended = /* @__PURE__ */ new Set();
3792
+ for (const n of Object.values(tmplNodes)) {
3793
+ for (const dep of n.deps ?? []) {
3794
+ if (tmplInnerNames.has(dep)) depended.add(dep);
3795
+ }
3796
+ }
3797
+ const outputCandidates = [...tmplInnerNames].filter((n) => !depended.has(n));
3798
+ const tmplOutput = outputCandidates[0] ?? [...tmplInnerNames].pop();
3799
+ templates[templateName] = { params: tmplParams, nodes: tmplNodes, output: tmplOutput };
3800
+ }
3801
+ delete nodes[subName];
3802
+ templateRefs[subName] = { type: "template", template: templateName, bind };
3803
+ metaDetectedSubgraphs.add(subName);
3804
+ break;
3805
+ }
3806
+ }
3807
+ }
3808
+ const structureMap = /* @__PURE__ */ new Map();
3809
+ for (const subName of desc.subgraphs) {
3810
+ if (metaDetectedSubgraphs.has(subName)) continue;
3811
+ const subNodes = {};
3812
+ const prefix = `${subName}::`;
3813
+ for (const [path, nodeDesc] of Object.entries(desc.nodes)) {
3814
+ if (path.includes(metaSegment)) continue;
3815
+ if (!path.startsWith(prefix)) continue;
3816
+ const localName = path.slice(prefix.length);
3817
+ if (localName.includes("::")) continue;
3818
+ subNodes[localName] = {
3819
+ type: nodeDesc.type,
3820
+ ...nodeDesc.deps.length > 0 ? {
3821
+ deps: nodeDesc.deps.map((d) => d.startsWith(prefix) ? d.slice(prefix.length) : d)
3822
+ } : {}
3823
+ };
3824
+ }
3825
+ const fingerprint = JSON.stringify(
3826
+ Object.fromEntries(
3827
+ Object.entries(subNodes).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => [k, { type: v.type, deps: v.deps ?? [] }])
3828
+ )
3829
+ );
3830
+ if (!structureMap.has(fingerprint)) {
3831
+ structureMap.set(fingerprint, []);
3832
+ }
3833
+ structureMap.get(fingerprint).push({ name: subName, nodes: subNodes });
3834
+ }
3835
+ for (const [, group] of structureMap) {
3836
+ if (group.length < 2) continue;
3837
+ const templateName = `${group[0].name}_template`;
3838
+ const refNodes = group[0].nodes;
3839
+ const innerNames = new Set(Object.keys(refNodes));
3840
+ const params = [];
3841
+ const baseParamMap = /* @__PURE__ */ new Map();
3842
+ for (const n of Object.values(refNodes)) {
3843
+ for (const dep of n.deps ?? []) {
3844
+ if (!innerNames.has(dep) && !baseParamMap.has(dep)) {
3845
+ const param = `$${dep}`;
3846
+ params.push(param);
3847
+ baseParamMap.set(dep, param);
3848
+ }
3849
+ }
3850
+ }
3851
+ const depended = /* @__PURE__ */ new Set();
3852
+ for (const n of Object.values(refNodes)) {
3853
+ for (const dep of n.deps ?? []) {
3854
+ if (innerNames.has(dep)) depended.add(dep);
3855
+ }
3856
+ }
3857
+ const outputCandidates = [...innerNames].filter((n) => !depended.has(n));
3858
+ const output = outputCandidates[0] ?? [...innerNames].pop();
3859
+ const tmplNodes = {};
3860
+ for (const [nName, nSpec] of Object.entries(refNodes)) {
3861
+ tmplNodes[nName] = {
3862
+ ...nSpec,
3863
+ deps: nSpec.deps?.map((d) => baseParamMap.get(d) ?? d)
3864
+ };
3865
+ }
3866
+ templates[templateName] = { params, nodes: tmplNodes, output };
3867
+ for (const member of group) {
3868
+ delete nodes[member.name];
3869
+ const memberBind = {};
3870
+ const memberInnerNames = new Set(Object.keys(member.nodes));
3871
+ for (const n of Object.values(member.nodes)) {
3872
+ for (const dep of n.deps ?? []) {
3873
+ if (!memberInnerNames.has(dep)) {
3874
+ const param = baseParamMap.get(dep) ?? `$${dep}`;
3875
+ memberBind[param] = dep;
3876
+ }
3877
+ }
3878
+ }
3879
+ templateRefs[member.name] = {
3880
+ type: "template",
3881
+ template: templateName,
3882
+ bind: memberBind
3883
+ };
3884
+ }
3885
+ }
3886
+ const allNodes = {
3887
+ ...nodes,
3888
+ ...templateRefs
3889
+ };
3890
+ const result = { name: desc.name, nodes: allNodes };
3891
+ if (Object.keys(templates).length > 0) result.templates = templates;
3892
+ if (feedbackEdges.length > 0) result.feedback = feedbackEdges;
3893
+ return result;
3894
+ }
3895
+ function specDiff(specA, specB) {
3896
+ const entries = [];
3897
+ if (specA.name !== specB.name) {
3898
+ entries.push({
3899
+ type: "changed",
3900
+ path: "name",
3901
+ detail: `"${specA.name}" \u2192 "${specB.name}"`
3902
+ });
3903
+ }
3904
+ const nodesA = new Set(Object.keys(specA.nodes));
3905
+ const nodesB = new Set(Object.keys(specB.nodes));
3906
+ for (const name of nodesB) {
3907
+ if (!nodesA.has(name)) {
3908
+ const n = specB.nodes[name];
3909
+ entries.push({
3910
+ type: "added",
3911
+ path: `nodes.${name}`,
3912
+ detail: `type: ${n.type}`
3913
+ });
3914
+ }
3915
+ }
3916
+ for (const name of nodesA) {
3917
+ if (!nodesB.has(name)) {
3918
+ entries.push({ type: "removed", path: `nodes.${name}` });
3919
+ }
3920
+ }
3921
+ for (const name of nodesA) {
3922
+ if (!nodesB.has(name)) continue;
3923
+ const a = specA.nodes[name];
3924
+ const b = specB.nodes[name];
3925
+ if (JSON.stringify(a) !== JSON.stringify(b)) {
3926
+ const details = [];
3927
+ if (a.type !== b.type) details.push(`type: ${a.type} \u2192 ${b.type}`);
3928
+ if (JSON.stringify(a.deps) !== JSON.stringify(b.deps)) {
3929
+ details.push("deps changed");
3930
+ }
3931
+ if (a.fn !== b.fn) {
3932
+ details.push(`fn: ${a.fn} \u2192 ${b.fn}`);
3933
+ }
3934
+ if (JSON.stringify(a.config) !== JSON.stringify(b.config)) {
3935
+ details.push("config changed");
3936
+ }
3937
+ entries.push({
3938
+ type: "changed",
3939
+ path: `nodes.${name}`,
3940
+ detail: details.join("; ") || "modified"
3941
+ });
3942
+ }
3943
+ }
3944
+ const tmplA = specA.templates ?? {};
3945
+ const tmplB = specB.templates ?? {};
3946
+ const tmplNamesA = new Set(Object.keys(tmplA));
3947
+ const tmplNamesB = new Set(Object.keys(tmplB));
3948
+ for (const name of tmplNamesB) {
3949
+ if (!tmplNamesA.has(name)) {
3950
+ entries.push({ type: "added", path: `templates.${name}` });
3951
+ }
3952
+ }
3953
+ for (const name of tmplNamesA) {
3954
+ if (!tmplNamesB.has(name)) {
3955
+ entries.push({ type: "removed", path: `templates.${name}` });
3956
+ }
3957
+ }
3958
+ for (const name of tmplNamesA) {
3959
+ if (!tmplNamesB.has(name)) continue;
3960
+ if (JSON.stringify(tmplA[name]) !== JSON.stringify(tmplB[name])) {
3961
+ entries.push({
3962
+ type: "changed",
3963
+ path: `templates.${name}`,
3964
+ detail: "template definition changed"
3965
+ });
3966
+ }
3967
+ }
3968
+ const fbA = specA.feedback ?? [];
3969
+ const fbB = specB.feedback ?? [];
3970
+ const fbKeyA = new Set(fbA.map((e) => `${e.from}->${e.to}`));
3971
+ const fbKeyB = new Set(fbB.map((e) => `${e.from}->${e.to}`));
3972
+ for (const fb of fbB) {
3973
+ const key = `${fb.from}->${fb.to}`;
3974
+ if (!fbKeyA.has(key)) {
3975
+ entries.push({
3976
+ type: "added",
3977
+ path: `feedback.${key}`,
3978
+ detail: `maxIterations: ${fb.maxIterations ?? 10}`
3979
+ });
3980
+ }
3981
+ }
3982
+ for (const fb of fbA) {
3983
+ const key = `${fb.from}->${fb.to}`;
3984
+ if (!fbKeyB.has(key)) {
3985
+ entries.push({ type: "removed", path: `feedback.${key}` });
3986
+ }
3987
+ }
3988
+ for (const fb of fbA) {
3989
+ const key = `${fb.from}->${fb.to}`;
3990
+ const counterpart = fbB.find((b) => b.from === fb.from && b.to === fb.to);
3991
+ if (counterpart && JSON.stringify(fb) !== JSON.stringify(counterpart)) {
3992
+ entries.push({
3993
+ type: "changed",
3994
+ path: `feedback.${key}`,
3995
+ detail: `maxIterations: ${fb.maxIterations ?? 10} \u2192 ${counterpart.maxIterations ?? 10}`
3996
+ });
3997
+ }
3998
+ }
3999
+ const added = entries.filter((e) => e.type === "added").length;
4000
+ const removed = entries.filter((e) => e.type === "removed").length;
4001
+ const changed = entries.filter((e) => e.type === "changed").length;
4002
+ const parts = [];
4003
+ if (added) parts.push(`${added} added`);
4004
+ if (removed) parts.push(`${removed} removed`);
4005
+ if (changed) parts.push(`${changed} changed`);
4006
+ const summary = parts.length > 0 ? parts.join(", ") : "no changes";
4007
+ return { entries, summary };
4008
+ }
4009
+ var LLM_COMPOSE_SYSTEM_PROMPT = `You are a graph architect for GraphReFly, a reactive graph protocol.
4010
+
4011
+ Given a natural-language description, produce a JSON GraphSpec with this structure:
4012
+
4013
+ {
4014
+ "name": "<graph_name>",
4015
+ "nodes": {
4016
+ "<node_name>": {
4017
+ "type": "state" | "derived" | "producer" | "effect" | "operator",
4018
+ "deps": ["<dep_node_name>", ...],
4019
+ "fn": "<catalog_function_name>",
4020
+ "source": "<catalog_source_name>",
4021
+ "config": { ... },
4022
+ "initial": <value>,
4023
+ "meta": { "description": "<purpose>" }
4024
+ },
4025
+ "<template_instance>": {
4026
+ "type": "template",
4027
+ "template": "<template_name>",
4028
+ "bind": { "$param": "node_name" }
4029
+ }
4030
+ },
4031
+ "templates": {
4032
+ "<template_name>": {
4033
+ "params": ["$param1", "$param2"],
4034
+ "nodes": { ... },
4035
+ "output": "<output_node>"
4036
+ }
4037
+ },
4038
+ "feedback": [
4039
+ { "from": "<condition_node>", "to": "<state_node>", "maxIterations": 10 }
4040
+ ]
4041
+ }
4042
+
4043
+ Rules:
4044
+ - "state" nodes hold user/LLM-writable values (knobs). Use "initial" for default values.
4045
+ - "derived" nodes compute from deps using a named "fn".
4046
+ - "effect" nodes produce side effects from deps.
4047
+ - "producer" nodes generate values from a named "source".
4048
+ - Use "templates" when the same subgraph pattern repeats (e.g., per-source resilience).
4049
+ - Use "feedback" for bounded cycles where a derived value writes back to a state node.
4050
+ - meta.description is required for every node.
4051
+ - Return ONLY valid JSON, no markdown fences or commentary.`;
4052
+ function stripFences2(text) {
4053
+ const match = text.match(/^```(?:json)?\s*([\s\S]*?)\s*```[\s\S]*$/);
4054
+ return match ? match[1] : text;
4055
+ }
4056
+ async function llmCompose(problem, adapter, opts) {
4057
+ let systemPrompt = LLM_COMPOSE_SYSTEM_PROMPT;
4058
+ if (opts?.catalogDescription) {
4059
+ systemPrompt += `
4060
+
4061
+ Available catalog:
4062
+ ${opts.catalogDescription}`;
4063
+ }
4064
+ if (opts?.systemPromptExtra) {
4065
+ systemPrompt += `
4066
+
4067
+ ${opts.systemPromptExtra}`;
4068
+ }
4069
+ const messages = [
4070
+ { role: "system", content: systemPrompt },
4071
+ { role: "user", content: problem }
4072
+ ];
4073
+ const rawResult = adapter.invoke(messages, {
4074
+ model: opts?.model,
4075
+ temperature: opts?.temperature ?? 0,
4076
+ maxTokens: opts?.maxTokens
4077
+ });
4078
+ const response = await rawResult;
4079
+ let content = response.content.trim();
4080
+ if (content.startsWith("```")) {
4081
+ content = stripFences2(content);
4082
+ }
4083
+ let parsed;
4084
+ try {
4085
+ parsed = JSON.parse(content);
4086
+ } catch {
4087
+ throw new Error(`llmCompose: LLM response is not valid JSON: ${content.slice(0, 200)}`);
4088
+ }
4089
+ const validation = validateSpec(parsed);
4090
+ if (!validation.valid) {
4091
+ throw new Error(`llmCompose: invalid GraphSpec:
4092
+ ${validation.errors.join("\n")}`);
4093
+ }
4094
+ return parsed;
4095
+ }
4096
+ async function llmRefine(currentSpec, feedback2, adapter, opts) {
4097
+ let systemPrompt = LLM_COMPOSE_SYSTEM_PROMPT;
4098
+ if (opts?.catalogDescription) {
4099
+ systemPrompt += `
4100
+
4101
+ Available catalog:
4102
+ ${opts.catalogDescription}`;
4103
+ }
4104
+ if (opts?.systemPromptExtra) {
4105
+ systemPrompt += `
4106
+
4107
+ ${opts.systemPromptExtra}`;
4108
+ }
4109
+ const messages = [
4110
+ { role: "system", content: systemPrompt },
4111
+ {
4112
+ role: "user",
4113
+ content: `Current GraphSpec:
4114
+ ${JSON.stringify(currentSpec, null, 2)}
4115
+
4116
+ Modification request: ${feedback2}
4117
+
4118
+ Return the complete modified GraphSpec as JSON.`
4119
+ }
4120
+ ];
4121
+ const rawResult = adapter.invoke(messages, {
4122
+ model: opts?.model,
4123
+ temperature: opts?.temperature ?? 0,
4124
+ maxTokens: opts?.maxTokens
4125
+ });
4126
+ const response = await rawResult;
4127
+ let content = response.content.trim();
4128
+ if (content.startsWith("```")) {
4129
+ content = stripFences2(content);
4130
+ }
4131
+ let parsed;
4132
+ try {
4133
+ parsed = JSON.parse(content);
4134
+ } catch {
4135
+ throw new Error(`llmRefine: LLM response is not valid JSON: ${content.slice(0, 200)}`);
4136
+ }
4137
+ const validation = validateSpec(parsed);
4138
+ if (!validation.valid) {
4139
+ throw new Error(`llmRefine: invalid GraphSpec:
4140
+ ${validation.errors.join("\n")}`);
4141
+ }
4142
+ return parsed;
4143
+ }
4144
+
3026
4145
  // src/patterns/messaging.ts
3027
4146
  var messaging_exports = {};
3028
4147
  __export(messaging_exports, {
@@ -3471,7 +4590,7 @@ function registerStep(graph, name, step, depPaths) {
3471
4590
  graph.connect(path, name);
3472
4591
  }
3473
4592
  }
3474
- function baseMeta(kind, meta) {
4593
+ function baseMeta2(kind, meta) {
3475
4594
  return {
3476
4595
  orchestration: true,
3477
4596
  orchestration_type: kind,
@@ -3509,7 +4628,7 @@ function task(graph, name, run, opts) {
3509
4628
  ...nodeOpts,
3510
4629
  name,
3511
4630
  describeKind: "derived",
3512
- meta: baseMeta("task", opts?.meta)
4631
+ meta: baseMeta2("task", opts?.meta)
3513
4632
  }
3514
4633
  );
3515
4634
  registerStep(
@@ -3532,7 +4651,7 @@ function branch(graph, name, source, predicate, opts) {
3532
4651
  ...opts,
3533
4652
  name,
3534
4653
  describeKind: "derived",
3535
- meta: baseMeta("branch", opts?.meta)
4654
+ meta: baseMeta2("branch", opts?.meta)
3536
4655
  }
3537
4656
  );
3538
4657
  registerStep(graph, name, step, src.path ? [src.path] : []);
@@ -3555,7 +4674,7 @@ function gate2(graph, name, source, control, opts) {
3555
4674
  ...opts,
3556
4675
  name,
3557
4676
  describeKind: "operator",
3558
- meta: baseMeta("gate", opts?.meta)
4677
+ meta: baseMeta2("gate", opts?.meta)
3559
4678
  }
3560
4679
  );
3561
4680
  registerStep(
@@ -3583,7 +4702,7 @@ function approval(graph, name, source, approver, opts) {
3583
4702
  ...opts,
3584
4703
  name,
3585
4704
  describeKind: "operator",
3586
- meta: baseMeta("approval", opts?.meta)
4705
+ meta: baseMeta2("approval", opts?.meta)
3587
4706
  }
3588
4707
  );
3589
4708
  registerStep(
@@ -3602,7 +4721,7 @@ function forEach2(graph, name, source, run, opts) {
3602
4721
  name,
3603
4722
  describeKind: "effect",
3604
4723
  completeWhenDepsComplete: false,
3605
- meta: baseMeta("forEach", opts?.meta),
4724
+ meta: baseMeta2("forEach", opts?.meta),
3606
4725
  onMessage(msg, depIndex, actions) {
3607
4726
  if (terminated) return true;
3608
4727
  if (depIndex !== 0) {
@@ -3637,7 +4756,7 @@ function join(graph, name, deps, opts) {
3637
4756
  ...opts,
3638
4757
  name,
3639
4758
  describeKind: "derived",
3640
- meta: baseMeta("join", opts?.meta)
4759
+ meta: baseMeta2("join", opts?.meta)
3641
4760
  }
3642
4761
  );
3643
4762
  registerStep(
@@ -3668,7 +4787,7 @@ function loop(graph, name, source, iterate, opts) {
3668
4787
  ...opts,
3669
4788
  name,
3670
4789
  describeKind: "derived",
3671
- meta: baseMeta("loop", opts?.meta)
4790
+ meta: baseMeta2("loop", opts?.meta)
3672
4791
  }
3673
4792
  );
3674
4793
  registerStep(
@@ -3693,7 +4812,7 @@ function sensor(graph, name, initial, opts) {
3693
4812
  name,
3694
4813
  initial,
3695
4814
  describeKind: "producer",
3696
- meta: baseMeta("sensor", opts?.meta)
4815
+ meta: baseMeta2("sensor", opts?.meta)
3697
4816
  });
3698
4817
  registerStep(graph, name, source, []);
3699
4818
  return {
@@ -3731,7 +4850,7 @@ function wait(graph, name, source, ms, opts) {
3731
4850
  initial: src.node.get(),
3732
4851
  describeKind: "operator",
3733
4852
  completeWhenDepsComplete: false,
3734
- meta: baseMeta("wait", opts?.meta),
4853
+ meta: baseMeta2("wait", opts?.meta),
3735
4854
  onMessage(msg, depIndex, actions) {
3736
4855
  if (terminated) return true;
3737
4856
  if (depIndex !== 0) {
@@ -3781,7 +4900,7 @@ function onFailure(graph, name, source, recover, opts) {
3781
4900
  name,
3782
4901
  describeKind: "operator",
3783
4902
  completeWhenDepsComplete: false,
3784
- meta: baseMeta("onFailure", opts?.meta),
4903
+ meta: baseMeta2("onFailure", opts?.meta),
3785
4904
  onMessage(msg, _depIndex, actions) {
3786
4905
  if (terminated) return true;
3787
4906
  if (msg[0] === ERROR) {
@@ -3920,6 +5039,7 @@ export {
3920
5039
  gate,
3921
5040
  globToRegExp,
3922
5041
  graph_exports as graph,
5042
+ graphspec_exports as graphspec,
3923
5043
  interval,
3924
5044
  isBatching,
3925
5045
  isKnownMessageType,
@@ -3977,6 +5097,7 @@ export {
3977
5097
  reactiveLog,
3978
5098
  reactiveMap,
3979
5099
  reduce,
5100
+ reduction_exports as reduction,
3980
5101
  repeat,
3981
5102
  replay,
3982
5103
  replayWAL,