@foresthubai/workflow-core 0.4.3 → 0.4.4

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 (120) hide show
  1. package/LICENSE +202 -202
  2. package/NOTICE +14 -14
  3. package/README.md +63 -63
  4. package/dist/api/deployment.d.ts +67 -0
  5. package/dist/api/deployment.d.ts.map +1 -0
  6. package/dist/api/deployment.js +6 -0
  7. package/dist/api/deployment.js.map +1 -0
  8. package/dist/api/engine.d.ts +744 -0
  9. package/dist/api/engine.d.ts.map +1 -0
  10. package/dist/api/engine.js +6 -0
  11. package/dist/api/engine.js.map +1 -0
  12. package/dist/api/index.d.ts +4 -0
  13. package/dist/api/index.d.ts.map +1 -1
  14. package/dist/api/workflow.d.ts +17 -1
  15. package/dist/api/workflow.d.ts.map +1 -1
  16. package/dist/channel/Channel.d.ts +1 -1
  17. package/dist/channel/Channel.d.ts.map +1 -1
  18. package/dist/channel/Channel.js +1 -1
  19. package/dist/channel/Channel.js.map +1 -1
  20. package/dist/channel/ChannelDefinition.d.ts.map +1 -1
  21. package/dist/channel/ChannelDefinition.js +22 -0
  22. package/dist/channel/ChannelDefinition.js.map +1 -1
  23. package/dist/channel/serialization.d.ts.map +1 -1
  24. package/dist/channel/serialization.js +6 -0
  25. package/dist/channel/serialization.js.map +1 -1
  26. package/dist/deploy/index.d.ts +5 -1
  27. package/dist/deploy/index.d.ts.map +1 -1
  28. package/dist/deploy/index.js +2 -1
  29. package/dist/deploy/index.js.map +1 -1
  30. package/dist/deploy/inputs.d.ts +26 -0
  31. package/dist/deploy/inputs.d.ts.map +1 -0
  32. package/dist/deploy/inputs.js +12 -0
  33. package/dist/deploy/inputs.js.map +1 -0
  34. package/dist/deploy/requirements.d.ts +24 -0
  35. package/dist/deploy/requirements.d.ts.map +1 -1
  36. package/dist/deploy/requirements.js +61 -0
  37. package/dist/deploy/requirements.js.map +1 -1
  38. package/dist/deploy/spec.d.ts +32 -0
  39. package/dist/deploy/spec.d.ts.map +1 -0
  40. package/dist/deploy/spec.js +343 -0
  41. package/dist/deploy/spec.js.map +1 -0
  42. package/dist/node/OutputNode.js +2 -2
  43. package/dist/node/OutputNode.js.map +1 -1
  44. package/package.json +2 -2
  45. package/src/api/deployment.ts +67 -0
  46. package/src/api/engine.ts +744 -0
  47. package/src/api/index.ts +26 -11
  48. package/src/api/workflow.ts +17 -1
  49. package/src/channel/Channel.ts +11 -11
  50. package/src/channel/ChannelDefinition.ts +98 -76
  51. package/src/channel/index.ts +6 -6
  52. package/src/channel/serialization.ts +74 -68
  53. package/src/deploy/index.ts +14 -1
  54. package/src/deploy/inputs.ts +41 -0
  55. package/src/deploy/requirements.test.ts +61 -61
  56. package/src/deploy/requirements.ts +156 -41
  57. package/src/deploy/spec.test.ts +226 -0
  58. package/src/deploy/spec.ts +379 -0
  59. package/src/diagnostics/__fixtures__/diagnosticFixtures.ts +158 -158
  60. package/src/diagnostics/diagnostics.test.ts +878 -878
  61. package/src/diagnostics/diagnostics.ts +936 -936
  62. package/src/diagnostics/index.ts +11 -11
  63. package/src/edge/Edge.ts +23 -23
  64. package/src/edge/EdgeDefinition.ts +45 -45
  65. package/src/edge/EdgeType.ts +19 -19
  66. package/src/edge/index.ts +8 -8
  67. package/src/edge/serialization.ts +83 -83
  68. package/src/expression/index.ts +4 -4
  69. package/src/expression/parser.ts +362 -362
  70. package/src/expression/types.ts +30 -30
  71. package/src/function/FunctionDeclaration.ts +54 -54
  72. package/src/function/index.ts +3 -3
  73. package/src/function/serialization.ts +40 -40
  74. package/src/globals.d.ts +9 -9
  75. package/src/id/index.ts +8 -8
  76. package/src/index.ts +22 -22
  77. package/src/memory/Memory.ts +15 -15
  78. package/src/memory/MemoryDefinition.ts +16 -16
  79. package/src/memory/MemoryFileDefinition.ts +37 -37
  80. package/src/memory/MemoryRegistry.ts +35 -35
  81. package/src/memory/VectorDatabaseDefinition.ts +21 -21
  82. package/src/memory/index.ts +8 -8
  83. package/src/memory/serialization.ts +47 -47
  84. package/src/migration/index.ts +4 -4
  85. package/src/migration/migrate.test.ts +44 -44
  86. package/src/migration/migrate.ts +58 -58
  87. package/src/migration/migrations.ts +24 -24
  88. package/src/migration/version.ts +9 -9
  89. package/src/model/LLMModelDefinition.ts +12 -12
  90. package/src/model/Model.ts +39 -39
  91. package/src/model/ModelDefinition.ts +15 -15
  92. package/src/model/ModelRegistry.ts +33 -33
  93. package/src/model/index.ts +7 -7
  94. package/src/model/serialization.ts +30 -30
  95. package/src/node/AgentNode.ts +82 -82
  96. package/src/node/DataNode.ts +41 -41
  97. package/src/node/FunctionNode.ts +76 -76
  98. package/src/node/InputNode.ts +185 -185
  99. package/src/node/LogicNode.ts +33 -33
  100. package/src/node/MqttNode.ts +127 -127
  101. package/src/node/Node.ts +61 -61
  102. package/src/node/NodeDefinition.ts +37 -37
  103. package/src/node/NodeRegistry.ts +85 -85
  104. package/src/node/OutputNode.ts +87 -87
  105. package/src/node/ToolNode.ts +32 -32
  106. package/src/node/TriggerNode.ts +272 -272
  107. package/src/node/constants.ts +16 -16
  108. package/src/node/index.ts +26 -26
  109. package/src/node/methods.ts +278 -278
  110. package/src/node/serialization.ts +544 -544
  111. package/src/parameter/OutputParameter.ts +68 -68
  112. package/src/parameter/Parameter.ts +243 -243
  113. package/src/parameter/index.ts +33 -33
  114. package/src/variable/Variable.ts +10 -10
  115. package/src/variable/index.ts +16 -16
  116. package/src/variable/operations.ts +106 -106
  117. package/src/workflow/Workflow.ts +41 -41
  118. package/src/workflow/index.ts +3 -3
  119. package/src/workflow/serialization.test.ts +240 -240
  120. package/src/workflow/serialization.ts +242 -242
package/src/api/index.ts CHANGED
@@ -1,11 +1,26 @@
1
- export type { components } from "./workflow";
2
-
3
- import type { components } from "./workflow";
4
- export type Schemas = components["schemas"];
5
-
6
- // Api-layer type aliases used across the domain. They live here (not in any
7
- // single domain module) so modules don't cross-import them; consumers pull
8
- // them from the package root (`@foresthubai/workflow-core`).
9
- export type DataType = Schemas["DataType"];
10
- export type Reference = Schemas["Reference"];
11
- export type Expression = Schemas["Expression"];
1
+ export type { components } from "./workflow";
2
+
3
+ import type { components } from "./workflow";
4
+ export type Schemas = components["schemas"];
5
+
6
+ // Deployment-spec api layer, generated from contract/deployment.yaml. Its
7
+ // DeployComponent.config is an opaque object the spec transports rendered config
8
+ // as bytes and no longer $refs engine.yaml — so the engine's own config types live
9
+ // in EngineSchemas below, not here. Kept under a distinct name so it never collides
10
+ // with the workflow Schemas above.
11
+ import type { components as deploymentComponents } from "./deployment";
12
+ export type DeploymentSchemas = deploymentComponents["schemas"];
13
+
14
+ // Engine wire/config api layer, generated from contract/engine.yaml. The deploy
15
+ // resolver (src/deploy/) PRODUCES an EngineConfig (workflow + mapping + external
16
+ // resources + device manifest) and freezes it into a DeployComponent's opaque
17
+ // config, so it types that construction against these.
18
+ import type { components as engineComponents } from "./engine";
19
+ export type EngineSchemas = engineComponents["schemas"];
20
+
21
+ // Api-layer type aliases used across the domain. They live here (not in any
22
+ // single domain module) so modules don't cross-import them; consumers pull
23
+ // them from the package root (`@foresthubai/workflow-core`).
24
+ export type DataType = Schemas["DataType"];
25
+ export type Reference = Schemas["Reference"];
26
+ export type Expression = Schemas["Expression"];
@@ -519,7 +519,7 @@ export interface components {
519
519
  output: components["schemas"]["OutputBinding"];
520
520
  };
521
521
  };
522
- Channel: components["schemas"]["GPIOINChannel"] | components["schemas"]["GPIOOUTChannel"] | components["schemas"]["ADCChannel"] | components["schemas"]["PWMChannel"] | components["schemas"]["DACChannel"] | components["schemas"]["UARTChannel"] | components["schemas"]["MQTTChannel"];
522
+ Channel: components["schemas"]["GPIOINChannel"] | components["schemas"]["GPIOOUTChannel"] | components["schemas"]["ADCChannel"] | components["schemas"]["PWMChannel"] | components["schemas"]["DACChannel"] | components["schemas"]["UARTChannel"] | components["schemas"]["MQTTChannel"] | components["schemas"]["LOGChannel"];
523
523
  GPIOINChannel: {
524
524
  /**
525
525
  * @description discriminator enum property added by openapi-typescript
@@ -594,6 +594,22 @@ export interface components {
594
594
  /** @description Topic this channel publishes to / subscribes on. The engine wraps it with the bound broker's prefix at runtime. */
595
595
  topic: string;
596
596
  };
597
+ LOGChannel: {
598
+ /**
599
+ * @description discriminator enum property added by openapi-typescript
600
+ * @enum {string}
601
+ */
602
+ type: "LOG";
603
+ id: string;
604
+ label: string;
605
+ /**
606
+ * @description Severity the engine records messages written to this channel at.
607
+ * @enum {string}
608
+ */
609
+ level: "debug" | "info" | "warn" | "error";
610
+ /** @description Optional category stamped on each line so the backend can group workflow-emitted logs apart from engine diagnostics. */
611
+ tag?: string;
612
+ };
597
613
  /** @enum {string} */
598
614
  ModelCapability: "chat" | "embedding" | "function_call" | "vision" | "fine_tuning" | "reasoning" | "classification" | "code";
599
615
  };
@@ -1,11 +1,11 @@
1
- export type ChannelType = "GPIOIN" | "GPIOOUT" | "ADC" | "PWM" | "DAC" | "UART" | "MQTT";
2
-
3
- export const ALL_CHANNEL_TYPES: ChannelType[] = ["GPIOIN", "GPIOOUT", "ADC", "PWM", "DAC", "UART", "MQTT"];
4
-
5
- /** Interface for a channel instance in the workflow */
6
- export interface Channel {
7
- id: string;
8
- label: string;
9
- type: ChannelType;
10
- arguments: Record<string, unknown>;
11
- }
1
+ export type ChannelType = "GPIOIN" | "GPIOOUT" | "ADC" | "PWM" | "DAC" | "UART" | "MQTT" | "LOG";
2
+
3
+ export const ALL_CHANNEL_TYPES: ChannelType[] = ["GPIOIN", "GPIOOUT", "ADC", "PWM", "DAC", "UART", "MQTT", "LOG"];
4
+
5
+ /** Interface for a channel instance in the workflow */
6
+ export interface Channel {
7
+ id: string;
8
+ label: string;
9
+ type: ChannelType;
10
+ arguments: Record<string, unknown>;
11
+ }
@@ -1,76 +1,98 @@
1
- import type { Parameter } from "../parameter";
2
- import { ALL_CHANNEL_TYPES } from "./Channel";
3
-
4
- /**
5
- * Single union definition for all Channel variants — deliberately NOT the
6
- * per-type registry pattern used by Node/Memory/Model (`*Definition` +
7
- * `*Registry`, one definition object per type).
8
- *
9
- * The reason is in-place `type` switching that preserves shared parameter state.
10
- * Because `type` is a parameter and all variants share one `arguments` bag,
11
- * changing a channel's type keeps the same instance (id, label) and retains any
12
- * entered values that are still valid for the new type: the parameter +
13
- * `activationRules` machinery just re-gates which fields show, and `serialize`
14
- * (via `pruneArguments`) drops the now-inactive ones. A per-type registry
15
- * would make each variant its own definition, so switching type would mean
16
- * delete-and-recreate — losing id/label and every shared value the user entered.
17
- * (It also lets channels add from one "Add Channel" button with type as a field,
18
- * rather than an add-button per type. Node/Memory/Model instead treat `type` as
19
- * an immutable registry key chosen at creation.)
20
- *
21
- * The same could be achieved with a UI-layer "type selector" over a
22
- * ChannelRegistry; this union trades cross-family consistency for that
23
- * shared-state-preserving switch.
24
- */
25
- export interface ChannelDefinition {
26
- parameters: Parameter[];
27
- }
28
-
29
- export const CHANNEL_DEFINITION: ChannelDefinition = {
30
- parameters: [
31
- {
32
- id: "type",
33
- label: "Type",
34
- description: "Channel type",
35
- type: "selection",
36
- default: "GPIOIN",
37
- options: ALL_CHANNEL_TYPES.map((t) => ({ value: t, label: t })),
38
- },
39
- {
40
- id: "bias",
41
- label: "Bias",
42
- description: "Pin bias configuration",
43
- type: "selection",
44
- default: "none",
45
- options: [
46
- { value: "none", label: "None" },
47
- { value: "pullup", label: "Pull-up" },
48
- { value: "pulldown", label: "Pull-down" },
49
- ],
50
- activationRules: [{ type: "parameterIn", parameterId: "type", values: ["GPIOIN"] }],
51
- },
52
- {
53
- id: "debounceMs",
54
- label: "Debounce (ms)",
55
- description: "Debounce window in milliseconds",
56
- type: "int",
57
- default: 50,
58
- activationRules: [{ type: "parameterIn", parameterId: "type", values: ["GPIOIN"] }],
59
- },
60
- {
61
- id: "frequency",
62
- label: "Frequency (Hz)",
63
- description: "PWM frequency in Hz",
64
- type: "int",
65
- default: 1000,
66
- activationRules: [{ type: "parameterIn", parameterId: "type", values: ["PWM"] }],
67
- },
68
- {
69
- id: "topic",
70
- label: "Topic",
71
- description: "MQTT topic this channel publishes to / subscribes on (e.g. sensors/temperature)",
72
- type: "string",
73
- activationRules: [{ type: "parameterIn", parameterId: "type", values: ["MQTT"] }],
74
- },
75
- ],
76
- };
1
+ import type { Parameter } from "../parameter";
2
+ import { ALL_CHANNEL_TYPES } from "./Channel";
3
+
4
+ /**
5
+ * Single union definition for all Channel variants — deliberately NOT the
6
+ * per-type registry pattern used by Node/Memory/Model (`*Definition` +
7
+ * `*Registry`, one definition object per type).
8
+ *
9
+ * The reason is in-place `type` switching that preserves shared parameter state.
10
+ * Because `type` is a parameter and all variants share one `arguments` bag,
11
+ * changing a channel's type keeps the same instance (id, label) and retains any
12
+ * entered values that are still valid for the new type: the parameter +
13
+ * `activationRules` machinery just re-gates which fields show, and `serialize`
14
+ * (via `pruneArguments`) drops the now-inactive ones. A per-type registry
15
+ * would make each variant its own definition, so switching type would mean
16
+ * delete-and-recreate — losing id/label and every shared value the user entered.
17
+ * (It also lets channels add from one "Add Channel" button with type as a field,
18
+ * rather than an add-button per type. Node/Memory/Model instead treat `type` as
19
+ * an immutable registry key chosen at creation.)
20
+ *
21
+ * The same could be achieved with a UI-layer "type selector" over a
22
+ * ChannelRegistry; this union trades cross-family consistency for that
23
+ * shared-state-preserving switch.
24
+ */
25
+ export interface ChannelDefinition {
26
+ parameters: Parameter[];
27
+ }
28
+
29
+ export const CHANNEL_DEFINITION: ChannelDefinition = {
30
+ parameters: [
31
+ {
32
+ id: "type",
33
+ label: "Type",
34
+ description: "Channel type",
35
+ type: "selection",
36
+ default: "GPIOIN",
37
+ options: ALL_CHANNEL_TYPES.map((t) => ({ value: t, label: t })),
38
+ },
39
+ {
40
+ id: "bias",
41
+ label: "Bias",
42
+ description: "Pin bias configuration",
43
+ type: "selection",
44
+ default: "none",
45
+ options: [
46
+ { value: "none", label: "None" },
47
+ { value: "pullup", label: "Pull-up" },
48
+ { value: "pulldown", label: "Pull-down" },
49
+ ],
50
+ activationRules: [{ type: "parameterIn", parameterId: "type", values: ["GPIOIN"] }],
51
+ },
52
+ {
53
+ id: "debounceMs",
54
+ label: "Debounce (ms)",
55
+ description: "Debounce window in milliseconds",
56
+ type: "int",
57
+ default: 50,
58
+ activationRules: [{ type: "parameterIn", parameterId: "type", values: ["GPIOIN"] }],
59
+ },
60
+ {
61
+ id: "frequency",
62
+ label: "Frequency (Hz)",
63
+ description: "PWM frequency in Hz",
64
+ type: "int",
65
+ default: 1000,
66
+ activationRules: [{ type: "parameterIn", parameterId: "type", values: ["PWM"] }],
67
+ },
68
+ {
69
+ id: "topic",
70
+ label: "Topic",
71
+ description: "MQTT topic this channel publishes to / subscribes on (e.g. sensors/temperature)",
72
+ type: "string",
73
+ activationRules: [{ type: "parameterIn", parameterId: "type", values: ["MQTT"] }],
74
+ },
75
+ {
76
+ id: "level",
77
+ label: "Level",
78
+ description: "Severity the engine records messages written to this channel at",
79
+ type: "selection",
80
+ default: "info",
81
+ options: [
82
+ { value: "debug", label: "Debug" },
83
+ { value: "info", label: "Info" },
84
+ { value: "warn", label: "Warn" },
85
+ { value: "error", label: "Error" },
86
+ ],
87
+ activationRules: [{ type: "parameterIn", parameterId: "type", values: ["LOG"] }],
88
+ },
89
+ {
90
+ id: "tag",
91
+ label: "Tag",
92
+ description: "Optional category stamped on each line so the backend can group workflow-emitted logs",
93
+ type: "string",
94
+ optional: true,
95
+ activationRules: [{ type: "parameterIn", parameterId: "type", values: ["LOG"] }],
96
+ },
97
+ ],
98
+ };
@@ -1,6 +1,6 @@
1
- export type { ChannelType, Channel } from "./Channel";
2
- export { ALL_CHANNEL_TYPES } from "./Channel";
3
- export type { ChannelDefinition } from "./ChannelDefinition";
4
- export { CHANNEL_DEFINITION } from "./ChannelDefinition";
5
- export { serialize, deserialize } from "./serialization";
6
- export type { ApiChannel } from "./serialization";
1
+ export type { ChannelType, Channel } from "./Channel";
2
+ export { ALL_CHANNEL_TYPES } from "./Channel";
3
+ export type { ChannelDefinition } from "./ChannelDefinition";
4
+ export { CHANNEL_DEFINITION } from "./ChannelDefinition";
5
+ export { serialize, deserialize } from "./serialization";
6
+ export type { ApiChannel } from "./serialization";
@@ -1,68 +1,74 @@
1
- import type { Schemas } from "../api";
2
- import { pruneArguments } from "../parameter";
3
- import { CHANNEL_DEFINITION } from "./ChannelDefinition";
4
- import type { Channel } from "./Channel";
5
-
6
- export type ApiChannel = Schemas["Channel"];
7
-
8
- /**
9
- * Serialize a domain Channel to the API discriminated-union shape.
10
- *
11
- * The domain store retains inactive parameters (non-destructive type switching),
12
- * so this is the boundary that drops them. The `type` discriminator must be in
13
- * the args record so `parameterIn` rules can evaluate — otherwise every gated
14
- * field (bias/debounceMs/frequency) is stripped as "inactive". Physical
15
- * addressing (GPIO line, ADC/PWM/DAC channel) is not here — it's a deploy
16
- * binding, not workflow state.
17
- */
18
- export function serialize(ch: Channel): ApiChannel {
19
- const { id, label, type } = ch;
20
- const args: Record<string, unknown> = { ...ch.arguments, type };
21
- pruneArguments(args, CHANNEL_DEFINITION.parameters);
22
- switch (type) {
23
- case "GPIOIN":
24
- return {
25
- type,
26
- id,
27
- label,
28
- bias: args.bias as Schemas["GPIOINChannel"]["bias"],
29
- debounceMs: args.debounceMs as number,
30
- };
31
- case "GPIOOUT":
32
- return { type, id, label };
33
- case "ADC":
34
- return { type, id, label };
35
- case "PWM":
36
- return { type, id, label, frequency: args.frequency as number };
37
- case "DAC":
38
- return { type, id, label };
39
- case "UART":
40
- return { type, id, label };
41
- case "MQTT":
42
- return { type, id, label, topic: args.topic as string };
43
- }
44
- }
45
-
46
- /** Convert an API Channel into a domain Channel. */
47
- export function deserialize(api: ApiChannel): Channel {
48
- const { id, label, type } = api;
49
- const args: Record<string, unknown> = {};
50
- switch (type) {
51
- case "GPIOIN":
52
- args.bias = api.bias;
53
- args.debounceMs = api.debounceMs;
54
- break;
55
- case "PWM":
56
- args.frequency = api.frequency;
57
- break;
58
- case "MQTT":
59
- args.topic = api.topic;
60
- break;
61
- case "GPIOOUT":
62
- case "ADC":
63
- case "DAC":
64
- case "UART":
65
- break;
66
- }
67
- return { id, label, type, arguments: args };
68
- }
1
+ import type { Schemas } from "../api";
2
+ import { pruneArguments } from "../parameter";
3
+ import { CHANNEL_DEFINITION } from "./ChannelDefinition";
4
+ import type { Channel } from "./Channel";
5
+
6
+ export type ApiChannel = Schemas["Channel"];
7
+
8
+ /**
9
+ * Serialize a domain Channel to the API discriminated-union shape.
10
+ *
11
+ * The domain store retains inactive parameters (non-destructive type switching),
12
+ * so this is the boundary that drops them. The `type` discriminator must be in
13
+ * the args record so `parameterIn` rules can evaluate — otherwise every gated
14
+ * field (bias/debounceMs/frequency) is stripped as "inactive". Physical
15
+ * addressing (GPIO line, ADC/PWM/DAC channel) is not here — it's a deploy
16
+ * binding, not workflow state.
17
+ */
18
+ export function serialize(ch: Channel): ApiChannel {
19
+ const { id, label, type } = ch;
20
+ const args: Record<string, unknown> = { ...ch.arguments, type };
21
+ pruneArguments(args, CHANNEL_DEFINITION.parameters);
22
+ switch (type) {
23
+ case "GPIOIN":
24
+ return {
25
+ type,
26
+ id,
27
+ label,
28
+ bias: args.bias as Schemas["GPIOINChannel"]["bias"],
29
+ debounceMs: args.debounceMs as number,
30
+ };
31
+ case "GPIOOUT":
32
+ return { type, id, label };
33
+ case "ADC":
34
+ return { type, id, label };
35
+ case "PWM":
36
+ return { type, id, label, frequency: args.frequency as number };
37
+ case "DAC":
38
+ return { type, id, label };
39
+ case "UART":
40
+ return { type, id, label };
41
+ case "MQTT":
42
+ return { type, id, label, topic: args.topic as string };
43
+ case "LOG":
44
+ return { type, id, label, level: args.level as Schemas["LOGChannel"]["level"], tag: args.tag as string | undefined };
45
+ }
46
+ }
47
+
48
+ /** Convert an API Channel into a domain Channel. */
49
+ export function deserialize(api: ApiChannel): Channel {
50
+ const { id, label, type } = api;
51
+ const args: Record<string, unknown> = {};
52
+ switch (type) {
53
+ case "GPIOIN":
54
+ args.bias = api.bias;
55
+ args.debounceMs = api.debounceMs;
56
+ break;
57
+ case "PWM":
58
+ args.frequency = api.frequency;
59
+ break;
60
+ case "MQTT":
61
+ args.topic = api.topic;
62
+ break;
63
+ case "LOG":
64
+ args.level = api.level;
65
+ args.tag = api.tag;
66
+ break;
67
+ case "GPIOOUT":
68
+ case "ADC":
69
+ case "DAC":
70
+ case "UART":
71
+ break;
72
+ }
73
+ return { id, label, type, arguments: args };
74
+ }
@@ -1 +1,14 @@
1
- export { getReferencedCatalogModelIds } from "./requirements";
1
+ export { getReferencedCatalogModelIds, deriveRequirements } from "./requirements";
2
+ export type { DeployRequirements, HardwareChannel, MqttChannel, CustomModel, HardwareFamily } from "./requirements";
3
+ export type { DeploymentInputs, HardwareBinding, MqttBinding, ModelBinding } from "./inputs";
4
+ export {
5
+ buildDeploymentSpec,
6
+ assertDeployable,
7
+ hardwareConflicts,
8
+ familyMismatches,
9
+ hardwareAddressKey,
10
+ hardwareAddressLabel,
11
+ ggufNameError,
12
+ sidecarServiceName,
13
+ } from "./spec";
14
+ export type { DeploymentSpecMeta, DeploymentSpecResult, ResourceSecret, ResourceSecrets } from "./spec";
@@ -0,0 +1,41 @@
1
+ // Operator-supplied bindings: the per-deploy values that bind a workflow's
2
+ // logical resource ids to a concrete environment. Shared by every spec
3
+ // producer (the CLI prompts/--values, the FE deploy forms) — how the values are
4
+ // collected differs per renderer, but the shape they resolve to is one thing.
5
+ //
6
+ // Secrets policy: provider API keys and the web-search key are NOT here — they
7
+ // are device/operator-scoped engine env, injected at render time, never frozen
8
+ // into a versioned spec. Only deployment-scoped resource creds (broker, custom
9
+ // endpoint) live in these bindings, because EngineConfig.externalResources needs
10
+ // them resolved inline.
11
+
12
+ // One hardware channel's physical address. `index` = sub-address (addressable
13
+ // families: GPIO line, ADC/DAC/PWM channel); `baud` = serial only.
14
+ export interface HardwareBinding {
15
+ chipOrDevice: string;
16
+ index?: number;
17
+ baud?: number;
18
+ }
19
+
20
+ // One MQTT channel's broker connection.
21
+ export interface MqttBinding {
22
+ brokerUrl: string;
23
+ username?: string;
24
+ password?: string;
25
+ }
26
+
27
+ // One custom model's runtime location. `device` = a llama-server sidecar on this
28
+ // controller (the engine reaches it over the container network by service name);
29
+ // `network` = an inference endpoint the operator runs elsewhere. The endpoint
30
+ // serves the model under its workflow id — no upstream-name aliasing yet.
31
+ export type ModelBinding =
32
+ | { location: "device"; modelFile: string; port?: number; ctxSize?: number }
33
+ | { location: "network"; url: string; apiKey?: string };
34
+
35
+ // The complete set of bindings a deploy supplies, keyed by workflow logical id
36
+ // (channel id / model id). Empty for any resource kind the workflow doesn't use.
37
+ export interface DeploymentInputs {
38
+ hardware: Record<string, HardwareBinding>;
39
+ mqtt: Record<string, MqttBinding>;
40
+ models: Record<string, ModelBinding>;
41
+ }
@@ -1,61 +1,61 @@
1
- import { describe, it, expect } from "vitest";
2
- import { getReferencedCatalogModelIds } from "./requirements";
3
- import { MAIN_CANVAS_ID, type Workflow, type Canvas } from "../workflow";
4
- import type { Node } from "../node";
5
- import type { Model } from "../model";
6
-
7
- // Minimal Agent node referencing `model`. Cast through the union — only id/type/
8
- // arguments.model matter to the walk.
9
- function agent(id: string, model: string): Node {
10
- return {
11
- id,
12
- type: "Agent",
13
- position: { x: 0, y: 0 },
14
- arguments: {
15
- name: id,
16
- model,
17
- instructions: "",
18
- outputDeclarations: [],
19
- memoryRefs: [],
20
- answer: { active: true, mode: "emit", name: "answer" },
21
- },
22
- } as Node;
23
- }
24
-
25
- function canvas(nodes: Node[]): Canvas {
26
- return { nodes, edges: [], variables: {} };
27
- }
28
-
29
- const customModel: Model = { id: "custom-llm", label: "Custom", type: "LLMModel", arguments: {} };
30
-
31
- function workflow(canvases: Workflow["canvases"], models: Record<string, Model> = {}): Workflow {
32
- return { canvases, functions: {}, channels: {}, memory: {}, models };
33
- }
34
-
35
- describe("getReferencedCatalogModelIds", () => {
36
- it("returns catalog ids (referenced but not declared), excluding declared customs", () => {
37
- const wf = workflow(
38
- { [MAIN_CANVAS_ID]: canvas([agent("n1", "claude-opus-4-7"), agent("n2", "custom-llm")]) },
39
- { "custom-llm": customModel },
40
- );
41
- expect(getReferencedCatalogModelIds(wf)).toEqual(["claude-opus-4-7"]);
42
- });
43
-
44
- it("ignores unset model references", () => {
45
- const wf = workflow({ [MAIN_CANVAS_ID]: canvas([agent("n1", "")]) });
46
- expect(getReferencedCatalogModelIds(wf)).toEqual([]);
47
- });
48
-
49
- it("dedupes across nodes and walks every canvas (main + function bodies)", () => {
50
- const wf = workflow({
51
- [MAIN_CANVAS_ID]: canvas([agent("n1", "claude-opus-4-7"), agent("n2", "claude-opus-4-7")]),
52
- fnBody: canvas([agent("n3", "gemini-2")]),
53
- });
54
- expect(getReferencedCatalogModelIds(wf).sort()).toEqual(["claude-opus-4-7", "gemini-2"]);
55
- });
56
-
57
- it("returns nothing when every referenced model is declared", () => {
58
- const wf = workflow({ [MAIN_CANVAS_ID]: canvas([agent("n1", "custom-llm")]) }, { "custom-llm": customModel });
59
- expect(getReferencedCatalogModelIds(wf)).toEqual([]);
60
- });
61
- });
1
+ import { describe, it, expect } from "vitest";
2
+ import { getReferencedCatalogModelIds } from "./requirements";
3
+ import { MAIN_CANVAS_ID, type Workflow, type Canvas } from "../workflow";
4
+ import type { Node } from "../node";
5
+ import type { Model } from "../model";
6
+
7
+ // Minimal Agent node referencing `model`. Cast through the union — only id/type/
8
+ // arguments.model matter to the walk.
9
+ function agent(id: string, model: string): Node {
10
+ return {
11
+ id,
12
+ type: "Agent",
13
+ position: { x: 0, y: 0 },
14
+ arguments: {
15
+ name: id,
16
+ model,
17
+ instructions: "",
18
+ outputDeclarations: [],
19
+ memoryRefs: [],
20
+ answer: { active: true, mode: "emit", name: "answer" },
21
+ },
22
+ } as Node;
23
+ }
24
+
25
+ function canvas(nodes: Node[]): Canvas {
26
+ return { nodes, edges: [], variables: {} };
27
+ }
28
+
29
+ const customModel: Model = { id: "custom-llm", label: "Custom", type: "LLMModel", arguments: {} };
30
+
31
+ function workflow(canvases: Workflow["canvases"], models: Record<string, Model> = {}): Workflow {
32
+ return { canvases, functions: {}, channels: {}, memory: {}, models };
33
+ }
34
+
35
+ describe("getReferencedCatalogModelIds", () => {
36
+ it("returns catalog ids (referenced but not declared), excluding declared customs", () => {
37
+ const wf = workflow(
38
+ { [MAIN_CANVAS_ID]: canvas([agent("n1", "claude-opus-4-7"), agent("n2", "custom-llm")]) },
39
+ { "custom-llm": customModel },
40
+ );
41
+ expect(getReferencedCatalogModelIds(wf)).toEqual(["claude-opus-4-7"]);
42
+ });
43
+
44
+ it("ignores unset model references", () => {
45
+ const wf = workflow({ [MAIN_CANVAS_ID]: canvas([agent("n1", "")]) });
46
+ expect(getReferencedCatalogModelIds(wf)).toEqual([]);
47
+ });
48
+
49
+ it("dedupes across nodes and walks every canvas (main + function bodies)", () => {
50
+ const wf = workflow({
51
+ [MAIN_CANVAS_ID]: canvas([agent("n1", "claude-opus-4-7"), agent("n2", "claude-opus-4-7")]),
52
+ fnBody: canvas([agent("n3", "gemini-2")]),
53
+ });
54
+ expect(getReferencedCatalogModelIds(wf).sort()).toEqual(["claude-opus-4-7", "gemini-2"]);
55
+ });
56
+
57
+ it("returns nothing when every referenced model is declared", () => {
58
+ const wf = workflow({ [MAIN_CANVAS_ID]: canvas([agent("n1", "custom-llm")]) }, { "custom-llm": customModel });
59
+ expect(getReferencedCatalogModelIds(wf)).toEqual([]);
60
+ });
61
+ });