@particle-academy/fancy-flow 0.13.0 → 0.15.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 (42) hide show
  1. package/README.md +48 -0
  2. package/dist/{capabilities-D-V9Rxv1.d.cts → capabilities-BEbLqnJU.d.cts} +1 -1
  3. package/dist/{capabilities-B6r1Snfj.d.ts → capabilities-CI3gw0C7.d.ts} +1 -1
  4. package/dist/chunk-UEOE6B52.js +39 -0
  5. package/dist/chunk-UEOE6B52.js.map +1 -0
  6. package/dist/{chunk-WEZJFMLV.js → chunk-ZQBWPYTI.js} +142 -19
  7. package/dist/chunk-ZQBWPYTI.js.map +1 -0
  8. package/dist/engine.cjs +299 -0
  9. package/dist/engine.cjs.map +1 -1
  10. package/dist/engine.d.cts +190 -27
  11. package/dist/engine.d.ts +190 -27
  12. package/dist/engine.js +258 -2
  13. package/dist/engine.js.map +1 -1
  14. package/dist/index.cjs +181 -16
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +7 -6
  17. package/dist/index.d.ts +7 -6
  18. package/dist/index.js +3 -2
  19. package/dist/index.js.map +1 -1
  20. package/dist/llm/vercel-ai.d.cts +2 -2
  21. package/dist/llm/vercel-ai.d.ts +2 -2
  22. package/dist/pause-9iT4tCEV.d.cts +96 -0
  23. package/dist/pause-9iT4tCEV.d.ts +96 -0
  24. package/dist/registry/index.d.cts +8 -7
  25. package/dist/registry/index.d.ts +8 -7
  26. package/dist/registry.cjs +183 -17
  27. package/dist/registry.cjs.map +1 -1
  28. package/dist/registry.js +2 -1
  29. package/dist/run-flow-ChBi6q-W.d.ts +35 -0
  30. package/dist/run-flow-VKm57Hcj.d.cts +35 -0
  31. package/dist/runtime/index.d.cts +3 -3
  32. package/dist/runtime/index.d.ts +3 -3
  33. package/dist/schema/index.d.cts +1 -1
  34. package/dist/schema/index.d.ts +1 -1
  35. package/dist/{types-H60tQAxr.d.cts → types-D_jOR3Z2.d.cts} +12 -1
  36. package/dist/{types-CnnKPnpt.d.ts → types-NerkPtHS.d.ts} +12 -1
  37. package/dist/{types-fc0fFKkf.d.cts → types-sOmpCitB.d.cts} +1 -1
  38. package/dist/{types-fc0fFKkf.d.ts → types-sOmpCitB.d.ts} +1 -1
  39. package/dist/ux.d.cts +3 -2
  40. package/dist/ux.d.ts +3 -2
  41. package/package.json +1 -1
  42. package/dist/chunk-WEZJFMLV.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -222,7 +222,7 @@ function getWorkflowResolver() {
222
222
  return workflowResolver;
223
223
  }
224
224
 
225
- // src/registry/llm-branch.ts
225
+ // src/registry/llm-router.ts
226
226
  function declaredRoutes(config) {
227
227
  const raw = config.routes;
228
228
  if (!Array.isArray(raw)) return [];
@@ -232,11 +232,11 @@ function resolveFallbackPort(routes, fallbackEnabled) {
232
232
  if (fallbackEnabled) return "fallback";
233
233
  return routes[0]?.port ?? "out";
234
234
  }
235
- var llmBranchExecutor = async (ctx) => {
235
+ var llmRouterExecutor = async (ctx) => {
236
236
  const config = ctx.node.data?.config ?? {};
237
237
  const routes = declaredRoutes(config);
238
238
  if (routes.length === 0) {
239
- ctx.abort("llm_branch has no routes configured");
239
+ ctx.abort("llm_router has no routes configured");
240
240
  }
241
241
  const client = getLlmClient();
242
242
  {
@@ -262,7 +262,7 @@ var llmBranchExecutor = async (ctx) => {
262
262
  type: "log",
263
263
  nodeId: ctx.node.id,
264
264
  level: "warn",
265
- message: `llm_branch: model returned "${port || "(nothing)"}", which is not a declared route. Routing to "${safe}".`
265
+ message: `llm_router: model returned "${port || "(nothing)"}", which is not a declared route. Routing to "${safe}".`
266
266
  });
267
267
  reason = reason ?? `unrecognised route "${port}"`;
268
268
  port = safe;
@@ -602,6 +602,7 @@ var KINDS = [
602
602
  {
603
603
  name: "@particle-academy/user_input",
604
604
  aliases: ["user_input", "@fancy/user_input"],
605
+ pausesForHuman: "input",
605
606
  category: "human",
606
607
  label: "User Input",
607
608
  description: "Pause the flow until the user submits the configured form.",
@@ -643,6 +644,7 @@ var KINDS = [
643
644
  {
644
645
  name: "@particle-academy/rich_user_input",
645
646
  aliases: ["rich_user_input", "@fancy/rich_user_input"],
647
+ pausesForHuman: "input",
646
648
  category: "human",
647
649
  label: "Rich User Input",
648
650
  description: "Pause the flow on a fully authored page \u2014 content, required reading, multi-section forms.",
@@ -700,10 +702,13 @@ var KINDS = [
700
702
  description: "Streaming adds a second port so a parent can show progress instead of a spinner."
701
703
  },
702
704
  {
703
- type: "json",
705
+ type: "keyvalue",
704
706
  key: "inputs",
705
707
  label: "Input mapping",
706
- description: "Entry-point inputs for the child run. Omit to pass this node's inputs straight through."
708
+ description: "Values handed to the child's entry points. Omit to pass this node's inputs straight through.",
709
+ keyLabel: "Name",
710
+ valueLabel: "Value",
711
+ addLabel: "Add input"
707
712
  },
708
713
  {
709
714
  type: "number",
@@ -727,7 +732,57 @@ var KINDS = [
727
732
  inputs: [{ id: "in" }],
728
733
  outputs: [{ id: "true", label: "true" }, { id: "false", label: "false" }],
729
734
  configSchema: [
730
- { type: "expression", key: "condition", label: "Condition", example: "{{ $json.active }}", required: true }
735
+ {
736
+ type: "select",
737
+ key: "match",
738
+ label: "Match",
739
+ default: "all",
740
+ options: [
741
+ { value: "all", label: "All conditions (AND)" },
742
+ { value: "any", label: "Any condition (OR)" }
743
+ ]
744
+ },
745
+ {
746
+ type: "repeater",
747
+ key: "conditions",
748
+ label: "Conditions",
749
+ description: "Routes to `true` when these match, otherwise `false`.",
750
+ titleKey: "left",
751
+ addLabel: "Add condition",
752
+ minItems: 1,
753
+ fields: [
754
+ { type: "expression", key: "left", label: "Value", example: "{{ $json.status }}", required: true },
755
+ {
756
+ type: "select",
757
+ key: "operator",
758
+ label: "Is",
759
+ default: "eq",
760
+ options: [
761
+ { value: "eq", label: "equal to" },
762
+ { value: "neq", label: "not equal to" },
763
+ { value: "contains", label: "contains" },
764
+ { value: "not_contains", label: "does not contain" },
765
+ { value: "gt", label: "greater than" },
766
+ { value: "gte", label: "greater than or equal to" },
767
+ { value: "lt", label: "less than" },
768
+ { value: "lte", label: "less than or equal to" },
769
+ { value: "truthy", label: "true" },
770
+ { value: "falsy", label: "false" },
771
+ { value: "empty", label: "empty" },
772
+ { value: "not_empty", label: "not empty" }
773
+ ]
774
+ },
775
+ { type: "text", key: "right", label: "Compared to", placeholder: "active" }
776
+ ],
777
+ default: [{ left: "", operator: "eq", right: "" }]
778
+ },
779
+ {
780
+ type: "expression",
781
+ key: "condition",
782
+ label: "Raw expression (advanced)",
783
+ example: "{{ $json.active && $json.score > 10 }}",
784
+ description: "Escape hatch for logic the builder can't express. Overrides the conditions above when set."
785
+ }
731
786
  ]
732
787
  },
733
788
  {
@@ -817,12 +872,35 @@ var KINDS = [
817
872
  description: "Reshape data with an expression.",
818
873
  icon: "\u0192",
819
874
  configSchema: [
875
+ {
876
+ type: "select",
877
+ key: "mode",
878
+ label: "Build the output",
879
+ default: "fields",
880
+ options: [
881
+ { value: "fields", label: "Field by field" },
882
+ { value: "expression", label: "One expression" }
883
+ ]
884
+ },
885
+ {
886
+ type: "repeater",
887
+ key: "fields",
888
+ label: "Output fields",
889
+ description: "Each row becomes a key on the result.",
890
+ titleKey: "key",
891
+ addLabel: "Add field",
892
+ fields: [
893
+ { type: "text", key: "key", label: "Key", required: true, placeholder: "name" },
894
+ { type: "expression", key: "value", label: "Value", example: "{{ $json.first }}", required: true }
895
+ ],
896
+ default: [{ key: "", value: "" }]
897
+ },
820
898
  {
821
899
  type: "expression",
822
900
  key: "expression",
823
- label: "Expression",
901
+ label: "Expression (advanced)",
824
902
  example: "{{ { id: $json.id, name: $json.first + ' ' + $json.last } }}",
825
- required: true
903
+ description: "Used when the mode above is set to one expression."
826
904
  }
827
905
  ]
828
906
  },
@@ -872,7 +950,15 @@ var KINDS = [
872
950
  },
873
951
  { type: "text", key: "table", label: "Table / collection", required: true },
874
952
  { type: "text", key: "key", label: "Key" },
875
- { type: "json", key: "where", label: "Where (JSON)", description: "For query/list operations." },
953
+ {
954
+ type: "keyvalue",
955
+ key: "where",
956
+ label: "Where",
957
+ description: "Field/value pairs to match. For query and list operations.",
958
+ keyLabel: "Field",
959
+ valueLabel: "Equals",
960
+ addLabel: "Add filter"
961
+ },
876
962
  { type: "expression", key: "value", label: "Value (set only)", example: "{{ $json }}" },
877
963
  { type: "credential", key: "store", label: "Data store", credentialType: "data_store" }
878
964
  ]
@@ -914,13 +1000,32 @@ var KINDS = [
914
1000
  { type: "expression", key: "prompt", label: "User prompt", example: "{{ $json.question }}", required: true },
915
1001
  { type: "number", key: "temperature", label: "Temperature", min: 0, max: 2, step: 0.1, default: 0.7 },
916
1002
  { type: "number", key: "max_tokens", label: "Max tokens", min: 1, max: 8192, default: 1024 },
917
- { type: "json", key: "tools", label: "Tools (JSON)", description: "Optional Anthropic-style tool definitions." },
1003
+ {
1004
+ type: "repeater",
1005
+ key: "tools",
1006
+ label: "Tools",
1007
+ description: "Tools the model may call.",
1008
+ titleKey: "name",
1009
+ addLabel: "Add tool",
1010
+ fields: [
1011
+ { type: "text", key: "name", label: "Name", required: true, placeholder: "search_index" },
1012
+ { type: "text", key: "description", label: "When to use it" },
1013
+ {
1014
+ type: "json",
1015
+ key: "input_schema",
1016
+ label: "Input schema",
1017
+ description: "JSON Schema for the tool's arguments."
1018
+ }
1019
+ ]
1020
+ },
918
1021
  { type: "credential", key: "credential", label: "API credential", credentialType: "llm_credential" }
919
1022
  ]
920
1023
  },
921
1024
  {
922
- name: "@particle-academy/llm_branch",
923
- aliases: ["llm_branch", "@fancy/llm_branch"],
1025
+ name: "@particle-academy/llm_router",
1026
+ // Every id this node has ever shipped under keeps resolving — MOIC's saved
1027
+ // flows carry the bare `llm_branch`.
1028
+ aliases: ["llm_router", "llm_branch", "@fancy/llm_branch", "@fancy/llm_router"],
924
1029
  category: "ai",
925
1030
  label: "LLM Router",
926
1031
  description: "Let a model choose which route the flow takes.",
@@ -932,7 +1037,7 @@ var KINDS = [
932
1037
  // A shuttle, not an engine: it carries the routes out to whatever LLM
933
1038
  // client the host registered and carries the choice back. No provider SDK
934
1039
  // reaches core, so this stays a builtin without adding a dependency.
935
- executor: llmBranchExecutor,
1040
+ executor: llmRouterExecutor,
936
1041
  configSchema: [
937
1042
  {
938
1043
  type: "textarea",
@@ -1029,7 +1134,17 @@ var KINDS = [
1029
1134
  configSchema: [
1030
1135
  ...HTTP_METHODS,
1031
1136
  { type: "text", key: "url", label: "URL", placeholder: "https://api.example.com/...", required: true },
1032
- { type: "json", key: "headers", label: "Headers", default: { "content-type": "application/json" } },
1137
+ {
1138
+ type: "keyvalue",
1139
+ key: "headers",
1140
+ label: "Headers",
1141
+ keyLabel: "Header",
1142
+ valueLabel: "Value",
1143
+ keyPlaceholder: "content-type",
1144
+ valuePlaceholder: "application/json",
1145
+ addLabel: "Add header",
1146
+ default: { "content-type": "application/json" }
1147
+ },
1033
1148
  { type: "json", key: "body", label: "Body" },
1034
1149
  { type: "credential", key: "auth", label: "Auth", credentialType: "api_credential" }
1035
1150
  ]
@@ -1043,7 +1158,14 @@ var KINDS = [
1043
1158
  icon: "\u2197",
1044
1159
  configSchema: [
1045
1160
  { type: "text", key: "url", label: "URL", required: true },
1046
- { type: "json", key: "headers", label: "Headers" },
1161
+ {
1162
+ type: "keyvalue",
1163
+ key: "headers",
1164
+ label: "Headers",
1165
+ keyLabel: "Header",
1166
+ valueLabel: "Value",
1167
+ addLabel: "Add header"
1168
+ },
1047
1169
  { type: "expression", key: "payload", label: "Payload", required: true, example: "{{ $json }}" }
1048
1170
  ]
1049
1171
  },
@@ -1051,6 +1173,7 @@ var KINDS = [
1051
1173
  {
1052
1174
  name: "@particle-academy/human_approval",
1053
1175
  aliases: ["human_approval", "@fancy/human_approval"],
1176
+ pausesForHuman: "approval",
1054
1177
  category: "human",
1055
1178
  label: "Human Approval",
1056
1179
  description: "Pause until a human approves or denies.",
@@ -10988,6 +11111,42 @@ function importWorkflow(schema, options = {}) {
10988
11111
  function workflowToBlob(schema) {
10989
11112
  return new Blob([JSON.stringify(schema, null, 2)], { type: "application/json" });
10990
11113
  }
11114
+
11115
+ // src/registry/pause.ts
11116
+ var PAUSE_PREFIX = "fancy-flow:pause:";
11117
+ var LEGACY_PAUSE_PREFIXES = [
11118
+ ["awaiting-approval:", "approval"],
11119
+ ["awaiting-input:", "input"]
11120
+ ];
11121
+ function encodePause(signal) {
11122
+ const { nodeId, awaiting, detail } = signal;
11123
+ return PAUSE_PREFIX + JSON.stringify(detail === void 0 ? { nodeId, awaiting } : { nodeId, awaiting, detail });
11124
+ }
11125
+ function decodePause(reason) {
11126
+ if (typeof reason !== "string") return null;
11127
+ if (reason.startsWith(PAUSE_PREFIX)) {
11128
+ const body = reason.slice(PAUSE_PREFIX.length);
11129
+ try {
11130
+ const parsed = JSON.parse(body);
11131
+ if (typeof parsed?.nodeId !== "string" || typeof parsed?.awaiting !== "string") return null;
11132
+ return "detail" in parsed ? { nodeId: parsed.nodeId, awaiting: parsed.awaiting, detail: parsed.detail } : { nodeId: parsed.nodeId, awaiting: parsed.awaiting };
11133
+ } catch {
11134
+ return null;
11135
+ }
11136
+ }
11137
+ for (const [prefix, awaiting] of LEGACY_PAUSE_PREFIXES) {
11138
+ if (reason.startsWith(prefix)) {
11139
+ return { nodeId: reason.slice(prefix.length), awaiting };
11140
+ }
11141
+ }
11142
+ return null;
11143
+ }
11144
+ function isPause(reason) {
11145
+ return decodePause(reason) !== null;
11146
+ }
11147
+ function pauseForHuman(ctx, awaiting, detail) {
11148
+ return ctx.abort(encodePause({ nodeId: ctx.node.id, awaiting, detail }));
11149
+ }
10991
11150
  function RegistryNodeInner(props) {
10992
11151
  const kindName = props.data.kind ?? props.type;
10993
11152
  const kind = ReactExports.useMemo(() => getNodeKind(kindName), [kindName]);
@@ -11639,12 +11798,14 @@ exports.FlowCanvas = FlowCanvas;
11639
11798
  exports.FlowEditor = FlowEditor;
11640
11799
  exports.FlowRunControls = FlowRunControls;
11641
11800
  exports.FlowRunFeed = FlowRunFeed;
11801
+ exports.LEGACY_PAUSE_PREFIXES = LEGACY_PAUSE_PREFIXES;
11642
11802
  exports.NodeConfigPanel = NodeConfigPanel;
11643
11803
  exports.NodePalette = NodePalette;
11644
11804
  exports.NodePort = NodePort;
11645
11805
  exports.NodeShell = NodeShell;
11646
11806
  exports.NoteNode = NoteNode;
11647
11807
  exports.OutputNode = OutputNode2;
11808
+ exports.PAUSE_PREFIX = PAUSE_PREFIX;
11648
11809
  exports.RegistryNode = RegistryNode;
11649
11810
  exports.RichInputPreview = RichInputPreview;
11650
11811
  exports.SubgraphNode = SubgraphNode;
@@ -11654,18 +11815,22 @@ exports.WORKFLOW_SCHEMA_VERSION = WORKFLOW_SCHEMA_VERSION;
11654
11815
  exports.applyStatusesToNodes = applyStatusesToNodes;
11655
11816
  exports.buildNodeTypes = buildNodeTypes;
11656
11817
  exports.categoryAccent = categoryAccent;
11818
+ exports.decodePause = decodePause;
11657
11819
  exports.defaultConfigFor = defaultConfigFor;
11658
11820
  exports.defaultNodeTypes = defaultNodeTypes;
11659
11821
  exports.defineNode = defineNode;
11822
+ exports.encodePause = encodePause;
11660
11823
  exports.exportWorkflow = exportWorkflow;
11661
11824
  exports.getNodeKind = getNodeKind;
11662
11825
  exports.getRichInputAdapter = getRichInputAdapter;
11663
11826
  exports.importWorkflow = importWorkflow;
11827
+ exports.isPause = isPause;
11664
11828
  exports.isRichInputEnabled = isRichInputEnabled;
11665
11829
  exports.listNodeKinds = listNodeKinds;
11666
11830
  exports.onNodeKindsChanged = onNodeKindsChanged;
11667
11831
  exports.onRichInputAdapterChanged = onRichInputAdapterChanged;
11668
11832
  exports.paletteDropHandlers = paletteDropHandlers;
11833
+ exports.pauseForHuman = pauseForHuman;
11669
11834
  exports.registerBuiltinKinds = registerBuiltinKinds;
11670
11835
  exports.registerNodeKind = registerNodeKind;
11671
11836
  exports.registerRichInputAdapter = registerRichInputAdapter;