@particle-academy/fancy-flow 0.11.0 → 0.12.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.
Files changed (37) hide show
  1. package/dist/{chunk-YNOI7ONN.js → chunk-IK5DS5JP.js} +258 -51
  2. package/dist/chunk-IK5DS5JP.js.map +1 -0
  3. package/dist/{chunk-2NQT2A3I.js → chunk-L4AX73Q6.js} +5 -4
  4. package/dist/chunk-L4AX73Q6.js.map +1 -0
  5. package/dist/{chunk-3GMBLHTM.js → chunk-MFMRTRPO.js} +3 -3
  6. package/dist/{chunk-3GMBLHTM.js.map → chunk-MFMRTRPO.js.map} +1 -1
  7. package/dist/engine.cjs +3 -2
  8. package/dist/engine.cjs.map +1 -1
  9. package/dist/engine.d.cts +4 -2
  10. package/dist/engine.d.ts +4 -2
  11. package/dist/engine.js +1 -1
  12. package/dist/index.cjs +396 -215
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +4 -4
  15. package/dist/index.d.ts +4 -4
  16. package/dist/index.js +5 -5
  17. package/dist/registry/index.d.cts +132 -4
  18. package/dist/registry/index.d.ts +132 -4
  19. package/dist/registry.cjs +455 -93
  20. package/dist/registry.cjs.map +1 -1
  21. package/dist/registry.js +2 -1
  22. package/dist/runtime/index.d.cts +1 -1
  23. package/dist/runtime/index.d.ts +1 -1
  24. package/dist/runtime.cjs +3 -2
  25. package/dist/runtime.cjs.map +1 -1
  26. package/dist/runtime.js +2 -2
  27. package/dist/schema/index.d.cts +1 -1
  28. package/dist/schema/index.d.ts +1 -1
  29. package/dist/{types-BS3Gwnkq.d.cts → types-Dg2REAz5.d.cts} +6 -0
  30. package/dist/{types-BS3Gwnkq.d.ts → types-Dg2REAz5.d.ts} +6 -0
  31. package/dist/{types-BwLr5tZV.d.cts → types-DnQ4TEHN.d.cts} +1 -1
  32. package/dist/{types-CsEe3EQl.d.ts → types-DtXOwXjA.d.ts} +1 -1
  33. package/dist/ux.d.cts +2 -2
  34. package/dist/ux.d.ts +2 -2
  35. package/package.json +1 -1
  36. package/dist/chunk-2NQT2A3I.js.map +0 -1
  37. package/dist/chunk-YNOI7ONN.js.map +0 -1
@@ -1,10 +1,162 @@
1
1
  import { Handle, Position } from './chunk-NF6NPY5N.js';
2
+ import { runFlow } from './chunk-L4AX73Q6.js';
2
3
  import { resolveNodePorts, nodeConfig } from './chunk-TITD5W4Y.js';
3
4
  import { getNodeKind, categoryAccent, registerNodeKind, listNodeKinds, kindIds } from './chunk-U5F22BHV.js';
4
5
  import { RichInputPreview } from './chunk-F5RPRB7A.js';
5
6
  import { memo, useMemo, createElement } from 'react';
6
7
  import { jsxs, jsx } from 'react/jsx-runtime';
7
8
 
9
+ // src/registry/capabilities.ts
10
+ var llmClient = null;
11
+ function registerLlmClient(client) {
12
+ llmClient = client;
13
+ return () => {
14
+ if (llmClient === client) llmClient = null;
15
+ };
16
+ }
17
+ function getLlmClient() {
18
+ return llmClient;
19
+ }
20
+ var workflowResolver = null;
21
+ function registerWorkflowResolver(resolver) {
22
+ workflowResolver = resolver;
23
+ return () => {
24
+ if (workflowResolver === resolver) workflowResolver = null;
25
+ };
26
+ }
27
+ function getWorkflowResolver() {
28
+ return workflowResolver;
29
+ }
30
+ function capabilityStatus() {
31
+ let documentReady = false;
32
+ try {
33
+ documentReady = Boolean(globalThis.__fancyFlowDocumentAdapter);
34
+ } catch {
35
+ documentReady = false;
36
+ }
37
+ return {
38
+ llm: llmClient !== null,
39
+ workflow_resolver: workflowResolver !== null,
40
+ document: documentReady
41
+ };
42
+ }
43
+
44
+ // src/registry/subflow.ts
45
+ var DEFAULT_MAX_DEPTH = 8;
46
+ function subflowMode(config) {
47
+ const mode = config.mode;
48
+ return mode === "stream" || mode === "both" ? mode : "output";
49
+ }
50
+ function subflowPorts(config) {
51
+ const mode = subflowMode(config);
52
+ const ports = [{ id: "out", label: "result" }];
53
+ if (mode === "stream" || mode === "both") ports.unshift({ id: "stream", label: "stream" });
54
+ return ports;
55
+ }
56
+ var subflowExecutor = async (ctx) => {
57
+ const config = ctx.node.data?.config ?? {};
58
+ const ref = String(config.workflow ?? "").trim();
59
+ if (!ref) ctx.abort("subflow has no workflow reference configured");
60
+ const resolver = getWorkflowResolver();
61
+ if (!resolver) {
62
+ ctx.abort(
63
+ "No workflow resolver registered. Call registerWorkflowResolver() so subflow can find the workflow it references."
64
+ );
65
+ }
66
+ const maxDepth = Number.isFinite(config.maxDepth) ? Number(config.maxDepth) : DEFAULT_MAX_DEPTH;
67
+ const depth = ctx.depth ?? 0;
68
+ if (depth + 1 > maxDepth) {
69
+ ctx.abort(
70
+ `subflow depth limit reached (${maxDepth}) at "${ref}" \u2014 a workflow is referencing itself, directly or through a chain.`
71
+ );
72
+ }
73
+ const child = await resolver(ref);
74
+ if (!child) ctx.abort(`subflow could not resolve workflow "${ref}"`);
75
+ const mode = subflowMode(config);
76
+ const streaming = mode === "stream" || mode === "both";
77
+ const forward = (event) => {
78
+ if (!streaming) return;
79
+ const detail = event.type === "node-status" ? `${event.nodeId} ${event.status}` : event.type === "run-end" ? `finished (${event.ok ? "ok" : "failed"})` : event.type;
80
+ ctx.emit({
81
+ type: "log",
82
+ nodeId: ctx.node.id,
83
+ level: "info",
84
+ message: `[${ref}] ${detail}`
85
+ });
86
+ };
87
+ const result = await runFlow(
88
+ child,
89
+ config.executors ?? {},
90
+ forward,
91
+ {
92
+ initialInputs: config.inputs ?? {
93
+ // With no explicit mapping, hand the parent's inputs to the child's
94
+ // entry points — the obvious default, and it makes the simple case
95
+ // require no configuration at all.
96
+ __parent: ctx.inputs
97
+ },
98
+ depth: depth + 1
99
+ }
100
+ );
101
+ if (!result.ok) {
102
+ ctx.abort(`subflow "${ref}" failed: ${result.error ?? "unknown error"}`);
103
+ }
104
+ if (mode === "stream") {
105
+ return { __port: "stream", value: result.outputs };
106
+ }
107
+ if (mode === "both") {
108
+ return result.outputs;
109
+ }
110
+ return { __port: "out", value: result.outputs };
111
+ };
112
+
113
+ // src/registry/llm-branch.ts
114
+ function declaredRoutes(config) {
115
+ const raw = config.routes;
116
+ if (!Array.isArray(raw)) return [];
117
+ return raw.map((r) => ({ port: String(r?.port ?? "").trim(), description: r?.description })).filter((r) => r.port !== "");
118
+ }
119
+ function resolveFallbackPort(routes, fallbackEnabled) {
120
+ if (fallbackEnabled) return "fallback";
121
+ return routes[0]?.port ?? "out";
122
+ }
123
+ var llmBranchExecutor = async (ctx) => {
124
+ const config = ctx.node.data?.config ?? {};
125
+ const routes = declaredRoutes(config);
126
+ if (routes.length === 0) {
127
+ ctx.abort("llm_branch has no routes configured");
128
+ }
129
+ const client = getLlmClient();
130
+ if (!client) {
131
+ ctx.abort(
132
+ "No LLM client registered. Call registerLlmClient() with your provider adapter \u2014 fancy-flow ships the routing, not the model call."
133
+ );
134
+ }
135
+ const fallbackEnabled = config.fallback !== false;
136
+ const choice = await client.chooseRoute({
137
+ system: typeof config.system === "string" ? config.system : void 0,
138
+ prompt: String(config.prompt ?? ctx.inputs ?? ""),
139
+ routes,
140
+ provider: typeof config.provider === "string" ? config.provider : void 0,
141
+ model: typeof config.model === "string" ? config.model : void 0,
142
+ credential: typeof config.credential === "string" ? config.credential : void 0
143
+ });
144
+ const offered = new Set(routes.map((r) => r.port));
145
+ let port = choice?.port ?? "";
146
+ let reason = choice?.reason;
147
+ if (!offered.has(port)) {
148
+ const safe = resolveFallbackPort(routes, fallbackEnabled);
149
+ ctx.emit({
150
+ type: "log",
151
+ nodeId: ctx.node.id,
152
+ level: "warn",
153
+ message: `llm_branch: model returned "${port || "(nothing)"}", which is not a declared route. Routing to "${safe}".`
154
+ });
155
+ reason = reason ?? `unrecognised route "${port}"`;
156
+ port = safe;
157
+ }
158
+ return { __port: port, value: { route: port, reason, input: ctx.inputs } };
159
+ };
8
160
  function RegistryNodeInner(props) {
9
161
  const kindName = props.data.kind ?? props.type;
10
162
  const kind = useMemo(() => getNodeKind(kindName), [kindName]);
@@ -163,8 +315,8 @@ var HTTP_METHODS = [
163
315
  var KINDS = [
164
316
  // ───────────── Triggers ─────────────
165
317
  {
166
- name: "@fancy/manual_trigger",
167
- aliases: ["manual_trigger"],
318
+ name: "@particle-academy/manual_trigger",
319
+ aliases: ["manual_trigger", "@fancy/manual_trigger"],
168
320
  category: "trigger",
169
321
  label: "Manual",
170
322
  description: "Entry point fired when the user clicks Run.",
@@ -173,8 +325,8 @@ var KINDS = [
173
325
  outputs: [{ id: "out" }]
174
326
  },
175
327
  {
176
- name: "@fancy/webhook_trigger",
177
- aliases: ["webhook_trigger"],
328
+ name: "@particle-academy/webhook_trigger",
329
+ aliases: ["webhook_trigger", "@fancy/webhook_trigger"],
178
330
  category: "trigger",
179
331
  label: "Webhook",
180
332
  description: "Triggered by an inbound HTTP request to a host-provided URL.",
@@ -191,8 +343,8 @@ var KINDS = [
191
343
  ]
192
344
  },
193
345
  {
194
- name: "@fancy/schedule_trigger",
195
- aliases: ["schedule_trigger"],
346
+ name: "@particle-academy/schedule_trigger",
347
+ aliases: ["schedule_trigger", "@fancy/schedule_trigger"],
196
348
  category: "trigger",
197
349
  label: "Schedule",
198
350
  description: "Fires on a cron schedule (host-implemented).",
@@ -212,8 +364,8 @@ var KINDS = [
212
364
  ]
213
365
  },
214
366
  {
215
- name: "@fancy/user_input",
216
- aliases: ["user_input"],
367
+ name: "@particle-academy/user_input",
368
+ aliases: ["user_input", "@fancy/user_input"],
217
369
  category: "human",
218
370
  label: "User Input",
219
371
  description: "Pause the flow until the user submits the configured form.",
@@ -253,8 +405,8 @@ var KINDS = [
253
405
  ]
254
406
  },
255
407
  {
256
- name: "@fancy/rich_user_input",
257
- aliases: ["rich_user_input"],
408
+ name: "@particle-academy/rich_user_input",
409
+ aliases: ["rich_user_input", "@fancy/rich_user_input"],
258
410
  category: "human",
259
411
  label: "Rich User Input",
260
412
  description: "Pause the flow on a fully authored page \u2014 content, required reading, multi-section forms.",
@@ -277,10 +429,61 @@ var KINDS = [
277
429
  // what the person hitting this step will actually see.
278
430
  renderBody: (ctx) => createElement(RichInputPreview, { config: ctx.config ?? {} })
279
431
  },
432
+ {
433
+ name: "@particle-academy/subflow",
434
+ aliases: ["subflow", "@fancy/subflow"],
435
+ category: "logic",
436
+ label: "SubFlow",
437
+ description: "Run another workflow and bring its result \u2014 or its live progress \u2014 back into this one.",
438
+ icon: "\u29C9",
439
+ inputs: [{ id: "in" }],
440
+ // The stream port only exists when something actually streams.
441
+ outputs: (config) => subflowPorts(config ?? {}),
442
+ // Core, not marketplace: it runs a child graph through this same engine and
443
+ // needs nothing from outside except where workflows live.
444
+ executor: subflowExecutor,
445
+ configSchema: [
446
+ {
447
+ type: "text",
448
+ key: "workflow",
449
+ label: "Workflow",
450
+ required: true,
451
+ placeholder: "onboarding-v2",
452
+ description: "Reference resolved by the host's registerWorkflowResolver()."
453
+ },
454
+ {
455
+ type: "select",
456
+ key: "mode",
457
+ label: "Return",
458
+ default: "output",
459
+ options: [
460
+ { value: "output", label: "Output when it finishes" },
461
+ { value: "stream", label: "Stream progress as it runs" },
462
+ { value: "both", label: "Both \u2014 stream, then output" }
463
+ ],
464
+ description: "Streaming adds a second port so a parent can show progress instead of a spinner."
465
+ },
466
+ {
467
+ type: "json",
468
+ key: "inputs",
469
+ label: "Input mapping",
470
+ description: "Entry-point inputs for the child run. Omit to pass this node's inputs straight through."
471
+ },
472
+ {
473
+ type: "number",
474
+ key: "maxDepth",
475
+ label: "Max nesting depth",
476
+ default: DEFAULT_MAX_DEPTH,
477
+ min: 1,
478
+ max: 32,
479
+ description: "Guards against a workflow referencing itself."
480
+ }
481
+ ]
482
+ },
280
483
  // ───────────── Logic ─────────────
281
484
  {
282
- name: "@fancy/branch",
283
- aliases: ["branch"],
485
+ name: "@particle-academy/branch",
486
+ aliases: ["branch", "@fancy/branch"],
284
487
  category: "logic",
285
488
  label: "Branch",
286
489
  description: "Multi-way branch on a condition or value.",
@@ -292,8 +495,8 @@ var KINDS = [
292
495
  ]
293
496
  },
294
497
  {
295
- name: "@fancy/switch_case",
296
- aliases: ["switch_case"],
498
+ name: "@particle-academy/switch_case",
499
+ aliases: ["switch_case", "@fancy/switch_case"],
297
500
  category: "logic",
298
501
  label: "Switch",
299
502
  description: "Route to one of N labelled outputs based on a key.",
@@ -320,8 +523,8 @@ var KINDS = [
320
523
  ]
321
524
  },
322
525
  {
323
- name: "@fancy/for_each",
324
- aliases: ["for_each"],
526
+ name: "@particle-academy/for_each",
527
+ aliases: ["for_each", "@fancy/for_each"],
325
528
  category: "logic",
326
529
  label: "For Each",
327
530
  description: "Iterate over a list, emitting each item on `item`.",
@@ -334,8 +537,8 @@ var KINDS = [
334
537
  ]
335
538
  },
336
539
  {
337
- name: "@fancy/merge",
338
- aliases: ["merge"],
540
+ name: "@particle-academy/merge",
541
+ aliases: ["merge", "@fancy/merge"],
339
542
  category: "logic",
340
543
  label: "Merge",
341
544
  description: "Combine multiple inputs into one object or array.",
@@ -353,8 +556,8 @@ var KINDS = [
353
556
  ]
354
557
  },
355
558
  {
356
- name: "@fancy/wait",
357
- aliases: ["wait"],
559
+ name: "@particle-academy/wait",
560
+ aliases: ["wait", "@fancy/wait"],
358
561
  category: "logic",
359
562
  label: "Wait",
360
563
  description: "Sleep or wait for an external event.",
@@ -371,8 +574,8 @@ var KINDS = [
371
574
  ]
372
575
  },
373
576
  {
374
- name: "@fancy/transform",
375
- aliases: ["transform"],
577
+ name: "@particle-academy/transform",
578
+ aliases: ["transform", "@fancy/transform"],
376
579
  category: "logic",
377
580
  label: "Transform",
378
581
  description: "Reshape data with an expression.",
@@ -389,8 +592,8 @@ var KINDS = [
389
592
  },
390
593
  // ───────────── Data ─────────────
391
594
  {
392
- name: "@fancy/memory_store",
393
- aliases: ["memory_store"],
595
+ name: "@particle-academy/memory_store",
596
+ aliases: ["memory_store", "@fancy/memory_store"],
394
597
  category: "data",
395
598
  label: "Memory Store",
396
599
  description: "Read or write per-conversation memory.",
@@ -410,8 +613,8 @@ var KINDS = [
410
613
  ]
411
614
  },
412
615
  {
413
- name: "@fancy/data_store",
414
- aliases: ["data_store"],
616
+ name: "@particle-academy/data_store",
617
+ aliases: ["data_store", "@fancy/data_store"],
415
618
  category: "data",
416
619
  label: "Data Store",
417
620
  description: "Key-value or table read/write against a host store.",
@@ -439,8 +642,8 @@ var KINDS = [
439
642
  ]
440
643
  },
441
644
  {
442
- name: "@fancy/variable",
443
- aliases: ["variable"],
645
+ name: "@particle-academy/variable",
646
+ aliases: ["variable", "@fancy/variable"],
444
647
  category: "data",
445
648
  label: "Variable",
446
649
  description: "Workflow-scoped value used by other nodes.",
@@ -452,8 +655,8 @@ var KINDS = [
452
655
  },
453
656
  // ───────────── AI ─────────────
454
657
  {
455
- name: "@fancy/llm_call",
456
- aliases: ["llm_call"],
658
+ name: "@particle-academy/llm_call",
659
+ aliases: ["llm_call", "@fancy/llm_call"],
457
660
  category: "ai",
458
661
  label: "LLM Call",
459
662
  description: "Send a prompt + context to a model and receive a response.",
@@ -480,8 +683,8 @@ var KINDS = [
480
683
  ]
481
684
  },
482
685
  {
483
- name: "@fancy/llm_branch",
484
- aliases: ["llm_branch"],
686
+ name: "@particle-academy/llm_branch",
687
+ aliases: ["llm_branch", "@fancy/llm_branch"],
485
688
  category: "ai",
486
689
  label: "LLM Router",
487
690
  description: "Let a model choose which route the flow takes.",
@@ -490,6 +693,10 @@ var KINDS = [
490
693
  // Each declared route is a port. The executor returns `{ __port: id }`
491
694
  // (or `Port.only(id)` on the PHP runtime) to pick one.
492
695
  outputs: (config) => routePorts(config?.routes, config?.fallback),
696
+ // A shuttle, not an engine: it carries the routes out to whatever LLM
697
+ // client the host registered and carries the choice back. No provider SDK
698
+ // reaches core, so this stays a builtin without adding a dependency.
699
+ executor: llmBranchExecutor,
493
700
  configSchema: [
494
701
  {
495
702
  type: "textarea",
@@ -551,8 +758,8 @@ var KINDS = [
551
758
  ]
552
759
  },
553
760
  {
554
- name: "@fancy/tool_use",
555
- aliases: ["tool_use"],
761
+ name: "@particle-academy/tool_use",
762
+ aliases: ["tool_use", "@fancy/tool_use"],
556
763
  category: "ai",
557
764
  label: "Tool Use",
558
765
  description: "Hand control to a host-registered tool by name.",
@@ -563,8 +770,8 @@ var KINDS = [
563
770
  ]
564
771
  },
565
772
  {
566
- name: "@fancy/embed_search",
567
- aliases: ["embed_search"],
773
+ name: "@particle-academy/embed_search",
774
+ aliases: ["embed_search", "@fancy/embed_search"],
568
775
  category: "ai",
569
776
  label: "Embed & Search",
570
777
  description: "Embed a query and search a vector store.",
@@ -577,8 +784,8 @@ var KINDS = [
577
784
  },
578
785
  // ───────────── IO ─────────────
579
786
  {
580
- name: "@fancy/api_request",
581
- aliases: ["api_request"],
787
+ name: "@particle-academy/api_request",
788
+ aliases: ["api_request", "@fancy/api_request"],
582
789
  category: "io",
583
790
  label: "API Request",
584
791
  description: "HTTP request to any URL.",
@@ -592,8 +799,8 @@ var KINDS = [
592
799
  ]
593
800
  },
594
801
  {
595
- name: "@fancy/webhook_out",
596
- aliases: ["webhook_out"],
802
+ name: "@particle-academy/webhook_out",
803
+ aliases: ["webhook_out", "@fancy/webhook_out"],
597
804
  category: "io",
598
805
  label: "Send Webhook",
599
806
  description: "POST a payload to a configured URL.",
@@ -606,8 +813,8 @@ var KINDS = [
606
813
  },
607
814
  // ───────────── Human ─────────────
608
815
  {
609
- name: "@fancy/human_approval",
610
- aliases: ["human_approval"],
816
+ name: "@particle-academy/human_approval",
817
+ aliases: ["human_approval", "@fancy/human_approval"],
611
818
  category: "human",
612
819
  label: "Human Approval",
613
820
  description: "Pause until a human approves or denies.",
@@ -621,8 +828,8 @@ var KINDS = [
621
828
  ]
622
829
  },
623
830
  {
624
- name: "@fancy/notify",
625
- aliases: ["notify"],
831
+ name: "@particle-academy/notify",
832
+ aliases: ["notify", "@fancy/notify"],
626
833
  category: "human",
627
834
  label: "Notify",
628
835
  description: "Send a message via Slack / email / SMS / etc.",
@@ -646,8 +853,8 @@ var KINDS = [
646
853
  },
647
854
  // ───────────── Output ─────────────
648
855
  {
649
- name: "@fancy/output",
650
- aliases: ["output"],
856
+ name: "@particle-academy/output",
857
+ aliases: ["output", "@fancy/output"],
651
858
  category: "output",
652
859
  label: "Output",
653
860
  description: "Terminal node \u2014 captures the workflow's result.",
@@ -656,8 +863,8 @@ var KINDS = [
656
863
  outputs: []
657
864
  },
658
865
  {
659
- name: "@fancy/log",
660
- aliases: ["log"],
866
+ name: "@particle-academy/log",
867
+ aliases: ["log", "@fancy/log"],
661
868
  category: "output",
662
869
  label: "Log",
663
870
  description: "Send to the run feed.",
@@ -690,6 +897,6 @@ function buildNodeTypes() {
690
897
  return map;
691
898
  }
692
899
 
693
- export { BUILTIN_KINDS, RegistryNode, buildNodeTypes, registerBuiltinKinds };
694
- //# sourceMappingURL=chunk-YNOI7ONN.js.map
695
- //# sourceMappingURL=chunk-YNOI7ONN.js.map
900
+ export { BUILTIN_KINDS, DEFAULT_MAX_DEPTH, RegistryNode, buildNodeTypes, capabilityStatus, declaredRoutes, getLlmClient, getWorkflowResolver, llmBranchExecutor, registerBuiltinKinds, registerLlmClient, registerWorkflowResolver, resolveFallbackPort, subflowExecutor, subflowMode, subflowPorts };
901
+ //# sourceMappingURL=chunk-IK5DS5JP.js.map
902
+ //# sourceMappingURL=chunk-IK5DS5JP.js.map