@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.
- package/LICENSE +202 -202
- package/NOTICE +14 -14
- package/README.md +63 -63
- package/dist/api/deployment.d.ts +67 -0
- package/dist/api/deployment.d.ts.map +1 -0
- package/dist/api/deployment.js +6 -0
- package/dist/api/deployment.js.map +1 -0
- package/dist/api/engine.d.ts +744 -0
- package/dist/api/engine.d.ts.map +1 -0
- package/dist/api/engine.js +6 -0
- package/dist/api/engine.js.map +1 -0
- package/dist/api/index.d.ts +4 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/workflow.d.ts +17 -1
- package/dist/api/workflow.d.ts.map +1 -1
- package/dist/channel/Channel.d.ts +1 -1
- package/dist/channel/Channel.d.ts.map +1 -1
- package/dist/channel/Channel.js +1 -1
- package/dist/channel/Channel.js.map +1 -1
- package/dist/channel/ChannelDefinition.d.ts.map +1 -1
- package/dist/channel/ChannelDefinition.js +22 -0
- package/dist/channel/ChannelDefinition.js.map +1 -1
- package/dist/channel/serialization.d.ts.map +1 -1
- package/dist/channel/serialization.js +6 -0
- package/dist/channel/serialization.js.map +1 -1
- package/dist/deploy/index.d.ts +5 -1
- package/dist/deploy/index.d.ts.map +1 -1
- package/dist/deploy/index.js +2 -1
- package/dist/deploy/index.js.map +1 -1
- package/dist/deploy/inputs.d.ts +26 -0
- package/dist/deploy/inputs.d.ts.map +1 -0
- package/dist/deploy/inputs.js +12 -0
- package/dist/deploy/inputs.js.map +1 -0
- package/dist/deploy/requirements.d.ts +24 -0
- package/dist/deploy/requirements.d.ts.map +1 -1
- package/dist/deploy/requirements.js +61 -0
- package/dist/deploy/requirements.js.map +1 -1
- package/dist/deploy/spec.d.ts +32 -0
- package/dist/deploy/spec.d.ts.map +1 -0
- package/dist/deploy/spec.js +343 -0
- package/dist/deploy/spec.js.map +1 -0
- package/dist/node/OutputNode.js +2 -2
- package/dist/node/OutputNode.js.map +1 -1
- package/package.json +2 -2
- package/src/api/deployment.ts +67 -0
- package/src/api/engine.ts +744 -0
- package/src/api/index.ts +26 -11
- package/src/api/workflow.ts +17 -1
- package/src/channel/Channel.ts +11 -11
- package/src/channel/ChannelDefinition.ts +98 -76
- package/src/channel/index.ts +6 -6
- package/src/channel/serialization.ts +74 -68
- package/src/deploy/index.ts +14 -1
- package/src/deploy/inputs.ts +41 -0
- package/src/deploy/requirements.test.ts +61 -61
- package/src/deploy/requirements.ts +156 -41
- package/src/deploy/spec.test.ts +226 -0
- package/src/deploy/spec.ts +379 -0
- package/src/diagnostics/__fixtures__/diagnosticFixtures.ts +158 -158
- package/src/diagnostics/diagnostics.test.ts +878 -878
- package/src/diagnostics/diagnostics.ts +936 -936
- package/src/diagnostics/index.ts +11 -11
- package/src/edge/Edge.ts +23 -23
- package/src/edge/EdgeDefinition.ts +45 -45
- package/src/edge/EdgeType.ts +19 -19
- package/src/edge/index.ts +8 -8
- package/src/edge/serialization.ts +83 -83
- package/src/expression/index.ts +4 -4
- package/src/expression/parser.ts +362 -362
- package/src/expression/types.ts +30 -30
- package/src/function/FunctionDeclaration.ts +54 -54
- package/src/function/index.ts +3 -3
- package/src/function/serialization.ts +40 -40
- package/src/globals.d.ts +9 -9
- package/src/id/index.ts +8 -8
- package/src/index.ts +22 -22
- package/src/memory/Memory.ts +15 -15
- package/src/memory/MemoryDefinition.ts +16 -16
- package/src/memory/MemoryFileDefinition.ts +37 -37
- package/src/memory/MemoryRegistry.ts +35 -35
- package/src/memory/VectorDatabaseDefinition.ts +21 -21
- package/src/memory/index.ts +8 -8
- package/src/memory/serialization.ts +47 -47
- package/src/migration/index.ts +4 -4
- package/src/migration/migrate.test.ts +44 -44
- package/src/migration/migrate.ts +58 -58
- package/src/migration/migrations.ts +24 -24
- package/src/migration/version.ts +9 -9
- package/src/model/LLMModelDefinition.ts +12 -12
- package/src/model/Model.ts +39 -39
- package/src/model/ModelDefinition.ts +15 -15
- package/src/model/ModelRegistry.ts +33 -33
- package/src/model/index.ts +7 -7
- package/src/model/serialization.ts +30 -30
- package/src/node/AgentNode.ts +82 -82
- package/src/node/DataNode.ts +41 -41
- package/src/node/FunctionNode.ts +76 -76
- package/src/node/InputNode.ts +185 -185
- package/src/node/LogicNode.ts +33 -33
- package/src/node/MqttNode.ts +127 -127
- package/src/node/Node.ts +61 -61
- package/src/node/NodeDefinition.ts +37 -37
- package/src/node/NodeRegistry.ts +85 -85
- package/src/node/OutputNode.ts +87 -87
- package/src/node/ToolNode.ts +32 -32
- package/src/node/TriggerNode.ts +272 -272
- package/src/node/constants.ts +16 -16
- package/src/node/index.ts +26 -26
- package/src/node/methods.ts +278 -278
- package/src/node/serialization.ts +544 -544
- package/src/parameter/OutputParameter.ts +68 -68
- package/src/parameter/Parameter.ts +243 -243
- package/src/parameter/index.ts +33 -33
- package/src/variable/Variable.ts +10 -10
- package/src/variable/index.ts +16 -16
- package/src/variable/operations.ts +106 -106
- package/src/workflow/Workflow.ts +41 -41
- package/src/workflow/index.ts +3 -3
- package/src/workflow/serialization.test.ts +240 -240
- 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
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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"];
|
package/src/api/workflow.ts
CHANGED
|
@@ -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
|
};
|
package/src/channel/Channel.ts
CHANGED
|
@@ -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
|
+
};
|
package/src/channel/index.ts
CHANGED
|
@@ -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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
case "
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
+
}
|
package/src/deploy/index.ts
CHANGED
|
@@ -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
|
+
});
|