@foresthubai/workflow-core 0.4.6 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/camera.d.ts +254 -0
- package/dist/api/camera.d.ts.map +1 -0
- package/dist/api/camera.js +6 -0
- package/dist/api/camera.js.map +1 -0
- package/dist/api/engine.d.ts +89 -10
- package/dist/api/engine.d.ts.map +1 -1
- package/dist/api/index.d.ts +0 -4
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/workflow.d.ts +61 -4
- 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 +16 -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/bindingRequirements.d.ts +4 -0
- package/dist/deploy/bindingRequirements.d.ts.map +1 -0
- package/dist/deploy/bindingRequirements.js +59 -0
- package/dist/deploy/bindingRequirements.js.map +1 -0
- package/dist/deploy/constants.d.ts +8 -0
- package/dist/deploy/constants.d.ts.map +1 -0
- package/dist/deploy/constants.js +26 -0
- package/dist/deploy/constants.js.map +1 -0
- package/dist/deploy/index.d.ts +3 -5
- package/dist/deploy/index.d.ts.map +1 -1
- package/dist/deploy/index.js +10 -2
- package/dist/deploy/index.js.map +1 -1
- package/dist/deploy/inputs.d.ts +30 -3
- package/dist/deploy/inputs.d.ts.map +1 -1
- package/dist/deploy/requirements.d.ts +4 -36
- package/dist/deploy/requirements.d.ts.map +1 -1
- package/dist/deploy/requirements.js +64 -76
- package/dist/deploy/requirements.js.map +1 -1
- package/dist/deploy/spec.d.ts +10 -4
- package/dist/deploy/spec.d.ts.map +1 -1
- package/dist/deploy/spec.js +233 -63
- package/dist/deploy/spec.js.map +1 -1
- package/dist/expression/parser.js +10 -1
- package/dist/expression/parser.js.map +1 -1
- package/dist/model/MLModelDefinition.d.ts +8 -0
- package/dist/model/MLModelDefinition.d.ts.map +1 -0
- package/dist/model/MLModelDefinition.js +14 -0
- package/dist/model/MLModelDefinition.js.map +1 -0
- package/dist/model/Model.d.ts +2 -1
- package/dist/model/Model.d.ts.map +1 -1
- package/dist/model/Model.js +1 -1
- package/dist/model/Model.js.map +1 -1
- package/dist/model/ModelRegistry.d.ts.map +1 -1
- package/dist/model/ModelRegistry.js +2 -0
- package/dist/model/ModelRegistry.js.map +1 -1
- package/dist/model/index.d.ts +1 -0
- package/dist/model/index.d.ts.map +1 -1
- package/dist/model/index.js +1 -0
- package/dist/model/index.js.map +1 -1
- package/dist/model/serialization.d.ts.map +1 -1
- package/dist/model/serialization.js +5 -0
- package/dist/model/serialization.js.map +1 -1
- package/dist/node/AINode.d.ts +31 -0
- package/dist/node/AINode.d.ts.map +1 -0
- package/dist/node/AINode.js +85 -0
- package/dist/node/AINode.js.map +1 -0
- package/dist/node/InputNode.d.ts +10 -2
- package/dist/node/InputNode.d.ts.map +1 -1
- package/dist/node/InputNode.js +16 -0
- package/dist/node/InputNode.js.map +1 -1
- package/dist/node/MLInferenceNode.d.ts +15 -0
- package/dist/node/MLInferenceNode.d.ts.map +1 -0
- package/dist/node/MLInferenceNode.js +26 -0
- package/dist/node/MLInferenceNode.js.map +1 -0
- package/dist/node/Node.d.ts +3 -3
- package/dist/node/Node.d.ts.map +1 -1
- package/dist/node/NodeRegistry.d.ts.map +1 -1
- package/dist/node/NodeRegistry.js +4 -2
- package/dist/node/NodeRegistry.js.map +1 -1
- package/dist/node/methods.d.ts.map +1 -1
- package/dist/node/methods.js +6 -0
- package/dist/node/methods.js.map +1 -1
- package/dist/node/serialization.d.ts.map +1 -1
- package/dist/node/serialization.js +42 -0
- package/dist/node/serialization.js.map +1 -1
- package/package.json +2 -2
- package/src/api/index.ts +0 -15
- package/src/api/workflow.ts +61 -4
- package/src/channel/Channel.ts +2 -2
- package/src/channel/ChannelDefinition.ts +16 -0
- package/src/channel/serialization.ts +6 -0
- package/src/deploy/constants.ts +30 -0
- package/src/deploy/index.ts +14 -17
- package/src/deploy/requirements.test.ts +165 -64
- package/src/deploy/requirements.ts +122 -159
- package/src/diagnostics/diagnostics.test.ts +41 -23
- package/src/expression/parser.ts +9 -1
- package/src/model/MLModelDefinition.ts +16 -0
- package/src/model/Model.ts +6 -2
- package/src/model/ModelRegistry.ts +2 -0
- package/src/model/index.ts +1 -0
- package/src/model/serialization.ts +5 -0
- package/src/node/{AgentNode.ts → AINode.ts} +124 -85
- package/src/node/InputNode.ts +28 -2
- package/src/node/Node.ts +3 -3
- package/src/node/NodeRegistry.ts +4 -2
- package/src/node/methods.ts +6 -0
- package/src/node/serialization.ts +44 -2
- package/src/workflow/serialization.test.ts +53 -0
- package/src/api/deployment.ts +0 -74
- package/src/api/engine.ts +0 -744
- package/src/deploy/inputs.ts +0 -44
- package/src/deploy/spec.test.ts +0 -227
- package/src/deploy/spec.ts +0 -397
|
@@ -8,7 +8,7 @@ import type { FunctionDeclaration } from "../function";
|
|
|
8
8
|
import { NodeCategory } from "../node";
|
|
9
9
|
import { SetVariableNodeDefinition } from "../node/DataNode";
|
|
10
10
|
import { RetrieverNodeDefinition } from "../node/InputNode";
|
|
11
|
-
import { AgentNodeDefinition } from "../node/
|
|
11
|
+
import { AgentNodeDefinition } from "../node/AINode";
|
|
12
12
|
import { OnThresholdNodeDefinition } from "../node/TriggerNode";
|
|
13
13
|
import { WebSearchToolNodeDefinition } from "../node/ToolNode";
|
|
14
14
|
import {
|
|
@@ -101,7 +101,7 @@ describe("computeNodeDiagnostics — expression validation", () => {
|
|
|
101
101
|
const exprDef = makeNodeDef({
|
|
102
102
|
category: NodeCategory.Input,
|
|
103
103
|
parameters: [
|
|
104
|
-
{ id: "val", label: "Value", description: "", type: "expression", expressionType: "int" },
|
|
104
|
+
{ id: "val", label: "Value", description: "", type: "expression", expressionType: "int", default: makeExpression("0", "int") },
|
|
105
105
|
],
|
|
106
106
|
});
|
|
107
107
|
|
|
@@ -114,8 +114,8 @@ describe("computeNodeDiagnostics — expression validation", () => {
|
|
|
114
114
|
});
|
|
115
115
|
const exprDiags = diagsOfCategory(diags, "invalid-expression");
|
|
116
116
|
expect(exprDiags).toHaveLength(1);
|
|
117
|
-
expect(exprDiags[0]
|
|
118
|
-
expect(exprDiags[0]
|
|
117
|
+
expect(exprDiags[0]?.paramId).toBe("val");
|
|
118
|
+
expect(exprDiags[0]?.message).toContain("Invalid expression");
|
|
119
119
|
});
|
|
120
120
|
|
|
121
121
|
it("accepts a well-formed expression matching declared type", () => {
|
|
@@ -138,7 +138,7 @@ describe("computeNodeDiagnostics — expression validation", () => {
|
|
|
138
138
|
});
|
|
139
139
|
const exprDiags = diagsOfCategory(diags, "invalid-expression");
|
|
140
140
|
expect(exprDiags).toHaveLength(1);
|
|
141
|
-
expect(exprDiags[0]
|
|
141
|
+
expect(exprDiags[0]?.message).toMatch(/Type mismatch|expected/i);
|
|
142
142
|
});
|
|
143
143
|
|
|
144
144
|
it("reactivity: changing a referenced variable's type invalidates a previously-valid expression", () => {
|
|
@@ -171,7 +171,24 @@ describe("computeNodeDiagnostics — expression validation", () => {
|
|
|
171
171
|
expect(diagsOfCategory(before, "invalid-expression")).toHaveLength(0);
|
|
172
172
|
const afterExprDiags = diagsOfCategory(after, "invalid-expression");
|
|
173
173
|
expect(afterExprDiags).toHaveLength(1);
|
|
174
|
-
expect(afterExprDiags[0]
|
|
174
|
+
expect(afterExprDiags[0]?.paramId).toBe("val");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("flags an image variable used in a string expression", () => {
|
|
178
|
+
// A CameraCapture frame (image) interpolated into a text expression has no
|
|
179
|
+
// string form; it must be rejected, not silently emptied at runtime.
|
|
180
|
+
const stringDef = makeNodeDef({
|
|
181
|
+
category: NodeCategory.Input,
|
|
182
|
+
parameters: [{ id: "val", label: "Value", description: "", type: "expression", expressionType: "string", default: makeExpression("0", "string") }],
|
|
183
|
+
});
|
|
184
|
+
const diags = computeNodeDiagnostics({
|
|
185
|
+
...baseOpts({
|
|
186
|
+
nodeDefinition: stringDef,
|
|
187
|
+
nodeData: makeNode("OnStartup", { val: makeExpression("frame is ${}", "string", [makeDeclaredRef("v1")]) }),
|
|
188
|
+
}),
|
|
189
|
+
availableVariables: makeAvailableVars([makeDeclaredVar({ uid: "v1", dataType: "image" })]),
|
|
190
|
+
});
|
|
191
|
+
expect(diagsOfCategory(diags, "invalid-expression")).toHaveLength(1);
|
|
175
192
|
});
|
|
176
193
|
|
|
177
194
|
it("skips validation on inactive expression parameters", () => {
|
|
@@ -185,6 +202,7 @@ describe("computeNodeDiagnostics — expression validation", () => {
|
|
|
185
202
|
description: "",
|
|
186
203
|
type: "expression",
|
|
187
204
|
expressionType: "int",
|
|
205
|
+
default: makeExpression("0", "int"),
|
|
188
206
|
activationRules: [{ type: "parameterIn", parameterId: "mode", values: ["on"] }],
|
|
189
207
|
},
|
|
190
208
|
],
|
|
@@ -219,13 +237,13 @@ describe("computeNodeDiagnostics — missing required parameters", () => {
|
|
|
219
237
|
});
|
|
220
238
|
const missing = diagsOfCategory(diags, "missing-required-param");
|
|
221
239
|
expect(missing).toHaveLength(1);
|
|
222
|
-
expect(missing[0]
|
|
240
|
+
expect(missing[0]?.paramId).toBe("name");
|
|
223
241
|
});
|
|
224
242
|
|
|
225
243
|
it("flags required expression param that is empty", () => {
|
|
226
244
|
const def = makeNodeDef({
|
|
227
245
|
category: NodeCategory.Input,
|
|
228
|
-
parameters: [{ id: "expr", label: "Expr", description: "", type: "expression", expressionType: "int" }],
|
|
246
|
+
parameters: [{ id: "expr", label: "Expr", description: "", type: "expression", expressionType: "int", default: makeExpression("0", "int") }],
|
|
229
247
|
});
|
|
230
248
|
const diags = computeNodeDiagnostics({
|
|
231
249
|
...baseOpts({
|
|
@@ -319,8 +337,8 @@ describe("computeNodeDiagnostics — variableSelect validation", () => {
|
|
|
319
337
|
});
|
|
320
338
|
const refDiags = diagsOfCategory(diags, "invalid-reference");
|
|
321
339
|
expect(refDiags).toHaveLength(1);
|
|
322
|
-
expect(refDiags[0]
|
|
323
|
-
expect(refDiags[0]
|
|
340
|
+
expect(refDiags[0]?.message).toMatch(/deleted variable/);
|
|
341
|
+
expect(refDiags[0]?.paramId).toBe("ref");
|
|
324
342
|
});
|
|
325
343
|
|
|
326
344
|
it("accepts a valid variable reference", () => {
|
|
@@ -369,7 +387,7 @@ describe("computeNodeDiagnostics — channelSelect validation", () => {
|
|
|
369
387
|
});
|
|
370
388
|
const refDiags = diagsOfCategory(diags, "invalid-reference");
|
|
371
389
|
expect(refDiags).toHaveLength(1);
|
|
372
|
-
expect(refDiags[0]
|
|
390
|
+
expect(refDiags[0]?.message).toMatch(/deleted channel/);
|
|
373
391
|
});
|
|
374
392
|
|
|
375
393
|
it("flags reference to a channel with incompatible type", () => {
|
|
@@ -382,7 +400,7 @@ describe("computeNodeDiagnostics — channelSelect validation", () => {
|
|
|
382
400
|
});
|
|
383
401
|
const refDiags = diagsOfCategory(diags, "invalid-reference");
|
|
384
402
|
expect(refDiags).toHaveLength(1);
|
|
385
|
-
expect(refDiags[0]
|
|
403
|
+
expect(refDiags[0]?.message).toMatch(/not a compatible channel type/);
|
|
386
404
|
});
|
|
387
405
|
|
|
388
406
|
it("accepts reference to a channel with compatible type", () => {
|
|
@@ -432,7 +450,7 @@ describe("computeNodeDiagnostics — memorySelect validation", () => {
|
|
|
432
450
|
});
|
|
433
451
|
const refDiags = diagsOfCategory(diags, "invalid-reference");
|
|
434
452
|
expect(refDiags).toHaveLength(1);
|
|
435
|
-
expect(refDiags[0]
|
|
453
|
+
expect(refDiags[0]?.message).toMatch(/deleted memory/);
|
|
436
454
|
});
|
|
437
455
|
|
|
438
456
|
it("accepts a memory id present in the store with a compatible type", () => {
|
|
@@ -456,7 +474,7 @@ describe("computeNodeDiagnostics — memorySelect validation", () => {
|
|
|
456
474
|
});
|
|
457
475
|
const refDiags = diagsOfCategory(diags, "invalid-reference");
|
|
458
476
|
expect(refDiags).toHaveLength(1);
|
|
459
|
-
expect(refDiags[0]
|
|
477
|
+
expect(refDiags[0]?.message).toMatch(/not a compatible memory type/);
|
|
460
478
|
});
|
|
461
479
|
|
|
462
480
|
it("does not flag when the memory map is undefined (not provided)", () => {
|
|
@@ -500,14 +518,14 @@ describe("computeNodeDiagnostics — scalar output binding validation", () => {
|
|
|
500
518
|
const diags = computeNodeDiagnostics(retrieverBase({ active: true, mode: "assign", target: { srcId: "", varId: "v1" } }));
|
|
501
519
|
const assignDiags = diagsOfCategory(diags, "assign-type-mismatch");
|
|
502
520
|
expect(assignDiags).toHaveLength(1);
|
|
503
|
-
expect(assignDiags[0]
|
|
521
|
+
expect(assignDiags[0]?.message).toMatch(/no variable selected/);
|
|
504
522
|
});
|
|
505
523
|
|
|
506
524
|
it("flags assign-mode binding targeting a deleted variable", () => {
|
|
507
525
|
const diags = computeNodeDiagnostics(retrieverBase({ active: true, mode: "assign", target: makeDeclaredRef("ghost") }));
|
|
508
526
|
const assignDiags = diagsOfCategory(diags, "assign-type-mismatch");
|
|
509
527
|
expect(assignDiags).toHaveLength(1);
|
|
510
|
-
expect(assignDiags[0]
|
|
528
|
+
expect(assignDiags[0]?.message).toMatch(/deleted variable/);
|
|
511
529
|
});
|
|
512
530
|
|
|
513
531
|
it("flags assign-mode binding with type mismatch", () => {
|
|
@@ -519,7 +537,7 @@ describe("computeNodeDiagnostics — scalar output binding validation", () => {
|
|
|
519
537
|
);
|
|
520
538
|
const assignDiags = diagsOfCategory(diags, "assign-type-mismatch");
|
|
521
539
|
expect(assignDiags).toHaveLength(1);
|
|
522
|
-
expect(assignDiags[0]
|
|
540
|
+
expect(assignDiags[0]?.message).toMatch(/cannot assign/);
|
|
523
541
|
});
|
|
524
542
|
|
|
525
543
|
it("accepts assign-mode binding with matching types", () => {
|
|
@@ -579,8 +597,8 @@ describe("computeNodeDiagnostics — list-output entry validation", () => {
|
|
|
579
597
|
);
|
|
580
598
|
const entry = diagsOfCategory(diags, "assign-type-mismatch");
|
|
581
599
|
expect(entry).toHaveLength(1);
|
|
582
|
-
expect(entry[0]
|
|
583
|
-
expect(entry[0]
|
|
600
|
+
expect(entry[0]?.outputId).toBe("outputDeclarations[0]");
|
|
601
|
+
expect(entry[0]?.message).toMatch(/entry #1 has no variable selected/);
|
|
584
602
|
});
|
|
585
603
|
|
|
586
604
|
it("flags list entry targeting a deleted variable", () => {
|
|
@@ -589,7 +607,7 @@ describe("computeNodeDiagnostics — list-output entry validation", () => {
|
|
|
589
607
|
);
|
|
590
608
|
const entry = diagsOfCategory(diags, "assign-type-mismatch");
|
|
591
609
|
expect(entry).toHaveLength(1);
|
|
592
|
-
expect(entry[0]
|
|
610
|
+
expect(entry[0]?.message).toMatch(/deleted variable/);
|
|
593
611
|
});
|
|
594
612
|
|
|
595
613
|
it("flags list entry with type mismatch", () => {
|
|
@@ -601,7 +619,7 @@ describe("computeNodeDiagnostics — list-output entry validation", () => {
|
|
|
601
619
|
);
|
|
602
620
|
const entry = diagsOfCategory(diags, "assign-type-mismatch");
|
|
603
621
|
expect(entry).toHaveLength(1);
|
|
604
|
-
expect(entry[0]
|
|
622
|
+
expect(entry[0]?.message).toMatch(/cannot assign/);
|
|
605
623
|
});
|
|
606
624
|
|
|
607
625
|
it("accepts a valid list entry", () => {
|
|
@@ -628,7 +646,7 @@ describe("computeNodeDiagnostics — list-output entry validation", () => {
|
|
|
628
646
|
);
|
|
629
647
|
const entry = diagsOfCategory(diags, "assign-type-mismatch");
|
|
630
648
|
expect(entry).toHaveLength(1);
|
|
631
|
-
expect(entry[0]
|
|
649
|
+
expect(entry[0]?.outputId).toBe("outputDeclarations[1]");
|
|
632
650
|
});
|
|
633
651
|
|
|
634
652
|
it("flags duplicate names across the list (both modes)", () => {
|
|
@@ -654,7 +672,7 @@ describe("computeNodeDiagnostics — list-output entry validation", () => {
|
|
|
654
672
|
);
|
|
655
673
|
const missing = diagsOfCategory(diags, "missing-required-param").filter((d) => d.outputId);
|
|
656
674
|
expect(missing).toHaveLength(1);
|
|
657
|
-
expect(missing[0]
|
|
675
|
+
expect(missing[0]?.message).toMatch(/has no name/);
|
|
658
676
|
});
|
|
659
677
|
});
|
|
660
678
|
|
package/src/expression/parser.ts
CHANGED
|
@@ -187,8 +187,13 @@ function inferBinaryType(node: jsep.BinaryExpression, ctx: TypeContext, errors:
|
|
|
187
187
|
// If either operand failed to type, propagate null
|
|
188
188
|
if (!leftType || !rightType) return null;
|
|
189
189
|
|
|
190
|
-
// String concatenation: string + any → string
|
|
190
|
+
// String concatenation: string + any → string. An image has no text form,
|
|
191
|
+
// so it cannot be concatenated even though the other operand is a string.
|
|
191
192
|
if (op === "+" && (leftType === "string" || rightType === "string")) {
|
|
193
|
+
if (leftType === "image" || rightType === "image") {
|
|
194
|
+
errors.push("An image value cannot be used in a string expression");
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
192
197
|
return "string";
|
|
193
198
|
}
|
|
194
199
|
|
|
@@ -338,6 +343,9 @@ function isNumeric(type: DataType | null): boolean {
|
|
|
338
343
|
function isCompatible(from: DataType, to: DataType): boolean {
|
|
339
344
|
if (from === to) return true;
|
|
340
345
|
if (isNumeric(from) && isNumeric(to)) return true;
|
|
346
|
+
// An image is opaque binary with no text form; it is only compatible with an
|
|
347
|
+
// image sink (handled by from === to above), never the string catch-all.
|
|
348
|
+
if (from === "image") return false;
|
|
341
349
|
if (to === "string") return true;
|
|
342
350
|
return false;
|
|
343
351
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) 2026 ForestHub.
|
|
3
|
+
|
|
4
|
+
import type { ModelDefinition } from "./ModelDefinition";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A declared machine-learning model, served by an inference component, that nodes
|
|
8
|
+
* can reference. The component selects the model by id and derives its behaviour
|
|
9
|
+
* from the mounted model bundle, so nothing needs to be configured here.
|
|
10
|
+
*/
|
|
11
|
+
export const MLModelDefinition: ModelDefinition = {
|
|
12
|
+
type: "MLModel",
|
|
13
|
+
label: "ML Model",
|
|
14
|
+
description: "A machine-learning model served by an inference component (e.g. object detection)",
|
|
15
|
+
parameters: [],
|
|
16
|
+
};
|
package/src/model/Model.ts
CHANGED
|
@@ -20,9 +20,9 @@ import type { Schemas } from "../api";
|
|
|
20
20
|
/** A capability a model supports (chat, embedding, vision, ...). From the api. */
|
|
21
21
|
export type ModelCapability = Schemas["ModelCapability"];
|
|
22
22
|
|
|
23
|
-
export type ModelType = "LLMModel";
|
|
23
|
+
export type ModelType = "LLMModel" | "MLModel";
|
|
24
24
|
|
|
25
|
-
export const ALL_MODEL_TYPES: ModelType[] = ["LLMModel"];
|
|
25
|
+
export const ALL_MODEL_TYPES: ModelType[] = ["LLMModel", "MLModel"];
|
|
26
26
|
|
|
27
27
|
export interface Model {
|
|
28
28
|
id: string;
|
|
@@ -39,4 +39,8 @@ export interface ModelInfo {
|
|
|
39
39
|
id: string;
|
|
40
40
|
label: string;
|
|
41
41
|
capabilities: ModelCapability[];
|
|
42
|
+
// Catalog provider that serves this model (e.g. "anthropic"). The deploy
|
|
43
|
+
// resolver reads it to emit one ExternalResources provider entry per distinct
|
|
44
|
+
// provider a workflow's Agent nodes reference. Mirrors llmproxy ModelInfo.provider.
|
|
45
|
+
provider: string;
|
|
42
46
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { ModelType } from "./Model";
|
|
5
5
|
import type { ModelDefinition } from "./ModelDefinition";
|
|
6
6
|
import { LLMModelDefinition } from "./LLMModelDefinition";
|
|
7
|
+
import { MLModelDefinition } from "./MLModelDefinition";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Central registry for declared (custom) model variant definitions (one per
|
|
@@ -16,6 +17,7 @@ class ModelDefinitionRegistry {
|
|
|
16
17
|
initialize() {
|
|
17
18
|
if (this.initialized) return;
|
|
18
19
|
this.register(LLMModelDefinition);
|
|
20
|
+
this.register(MLModelDefinition);
|
|
19
21
|
this.initialized = true;
|
|
20
22
|
}
|
|
21
23
|
|
package/src/model/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type { ModelType, Model, ModelCapability, ModelInfo } from "./Model";
|
|
|
5
5
|
export { ALL_MODEL_TYPES } from "./Model";
|
|
6
6
|
export type { ModelDefinition } from "./ModelDefinition";
|
|
7
7
|
export { LLMModelDefinition } from "./LLMModelDefinition";
|
|
8
|
+
export { MLModelDefinition } from "./MLModelDefinition";
|
|
8
9
|
export { ModelRegistry } from "./ModelRegistry";
|
|
9
10
|
export { serialize, deserialize } from "./serialization";
|
|
10
11
|
export type { ApiModel } from "./serialization";
|
|
@@ -17,6 +17,8 @@ export function serialize(model: Model): ApiModel {
|
|
|
17
17
|
label,
|
|
18
18
|
capabilities: (args.capabilities as ModelCapability[]) ?? ["chat"],
|
|
19
19
|
};
|
|
20
|
+
case "MLModel":
|
|
21
|
+
return { type, id, label };
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -28,6 +30,9 @@ export function deserialize(api: ApiModel): Model {
|
|
|
28
30
|
case "LLMModel":
|
|
29
31
|
args.capabilities = api.capabilities;
|
|
30
32
|
break;
|
|
33
|
+
case "MLModel":
|
|
34
|
+
// No type-specific arguments; the component selects the model by id.
|
|
35
|
+
break;
|
|
31
36
|
}
|
|
32
37
|
return { id, label, type, arguments: args };
|
|
33
38
|
}
|
|
@@ -1,85 +1,124 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// Copyright (c) 2026 ForestHub.
|
|
3
|
-
|
|
4
|
-
import { NodeBase } from "./Node";
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import { NodeCategory } from "./constants";
|
|
8
|
-
import { NodeDefinition } from "./NodeDefinition";
|
|
9
|
-
|
|
10
|
-
export type MemoryRef = Schemas["MemoryRef"];
|
|
11
|
-
|
|
12
|
-
export interface AgentNode extends NodeBase {
|
|
13
|
-
type: "Agent";
|
|
14
|
-
arguments: {
|
|
15
|
-
name: string;
|
|
16
|
-
model: string;
|
|
17
|
-
instructions: string;
|
|
18
|
-
maxTurns: number | undefined;
|
|
19
|
-
outputDeclarations: OutputDeclaration[];
|
|
20
|
-
memoryRefs: MemoryRef[];
|
|
21
|
-
answer: OutputBinding;
|
|
22
|
-
toolDescription?: string;
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
id: "
|
|
64
|
-
label: "
|
|
65
|
-
description: "
|
|
66
|
-
type: "
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) 2026 ForestHub.
|
|
3
|
+
|
|
4
|
+
import { NodeBase } from "./Node";
|
|
5
|
+
import type { Reference, Schemas } from "../api";
|
|
6
|
+
import { OutputBinding, OutputDeclaration } from "../parameter";
|
|
7
|
+
import { NodeCategory } from "./constants";
|
|
8
|
+
import { NodeDefinition } from "./NodeDefinition";
|
|
9
|
+
|
|
10
|
+
export type MemoryRef = Schemas["MemoryRef"];
|
|
11
|
+
|
|
12
|
+
export interface AgentNode extends NodeBase {
|
|
13
|
+
type: "Agent";
|
|
14
|
+
arguments: {
|
|
15
|
+
name: string;
|
|
16
|
+
model: string;
|
|
17
|
+
instructions: string;
|
|
18
|
+
maxTurns: number | undefined;
|
|
19
|
+
outputDeclarations: OutputDeclaration[];
|
|
20
|
+
memoryRefs: MemoryRef[];
|
|
21
|
+
answer: OutputBinding;
|
|
22
|
+
toolDescription?: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// MLInference — runs a declared ML model on an input and returns its result.
|
|
27
|
+
// The model is selected from the workflow's declared MLModels; the input is a
|
|
28
|
+
// reference to a variable whose value is fed to the model, and the result is
|
|
29
|
+
// emitted as a string.
|
|
30
|
+
export interface MLInferenceNode extends NodeBase {
|
|
31
|
+
type: "MLInference";
|
|
32
|
+
arguments: {
|
|
33
|
+
model: string;
|
|
34
|
+
input: Reference | undefined;
|
|
35
|
+
output: OutputBinding;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type AINodeType = "Agent" | "MLInference";
|
|
40
|
+
export type AINode = AgentNode | MLInferenceNode;
|
|
41
|
+
|
|
42
|
+
// Node Definitions
|
|
43
|
+
|
|
44
|
+
export const AgentNodeDefinition: NodeDefinition = {
|
|
45
|
+
type: "Agent",
|
|
46
|
+
label: "LLM Agent",
|
|
47
|
+
category: NodeCategory.AI,
|
|
48
|
+
description: "AI-powered agent for intelligent processing",
|
|
49
|
+
canBranch: true, // Control output may fan out to multiple branches (tool output is always multi-target).
|
|
50
|
+
outputs: [
|
|
51
|
+
{ id: "answer", label: "Answer", type: "static", dataType: "string" },
|
|
52
|
+
{ id: "outputDeclarations", label: "Structured Output", type: "list" },
|
|
53
|
+
],
|
|
54
|
+
parameters: [
|
|
55
|
+
{
|
|
56
|
+
id: "name",
|
|
57
|
+
label: "Name",
|
|
58
|
+
description: "Name of the agent",
|
|
59
|
+
optional: true,
|
|
60
|
+
type: "string",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "model",
|
|
64
|
+
label: "Model",
|
|
65
|
+
description: "AI model to use for the agent",
|
|
66
|
+
type: "modelSelect",
|
|
67
|
+
modelType: ["LLMModel"],
|
|
68
|
+
capabilities: ["chat"],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "instructions",
|
|
72
|
+
label: "Instructions",
|
|
73
|
+
description: "Instructions for the cloud agent that act as system prompt",
|
|
74
|
+
type: "string",
|
|
75
|
+
multiline: true,
|
|
76
|
+
optional: true,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "maxTurns",
|
|
80
|
+
label: "Max Turns",
|
|
81
|
+
description: "Maximum number of agent runner turns",
|
|
82
|
+
type: "int",
|
|
83
|
+
optional: true,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "memoryRefs",
|
|
87
|
+
label: "Memory Files",
|
|
88
|
+
description: "Project memory files this agent can access, with per-file read or read+write mode",
|
|
89
|
+
type: "memory-refs",
|
|
90
|
+
default: [],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "toolDescription",
|
|
94
|
+
label: "Tool Description",
|
|
95
|
+
description: "Description shown to the calling agent when this agent is wired as a tool",
|
|
96
|
+
type: "string",
|
|
97
|
+
multiline: true,
|
|
98
|
+
activationRules: [{ type: "isToolInput" }],
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const MLInferenceNodeDefinition: NodeDefinition = {
|
|
104
|
+
type: "MLInference",
|
|
105
|
+
label: "ML Inference",
|
|
106
|
+
category: NodeCategory.AI,
|
|
107
|
+
description: "Runs a machine-learning model on an input and returns its result",
|
|
108
|
+
outputs: [{ id: "output", label: "Result", type: "static", dataType: "string" }],
|
|
109
|
+
parameters: [
|
|
110
|
+
{
|
|
111
|
+
id: "model",
|
|
112
|
+
label: "Model",
|
|
113
|
+
description: "ML model to run",
|
|
114
|
+
type: "modelSelect",
|
|
115
|
+
modelType: ["MLModel"],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "input",
|
|
119
|
+
label: "Input",
|
|
120
|
+
description: "Variable whose value is fed to the model",
|
|
121
|
+
type: "variableSelect",
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
};
|
package/src/node/InputNode.ts
CHANGED
|
@@ -48,8 +48,17 @@ export interface WebFetchNode extends NodeBase {
|
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
export
|
|
51
|
+
// CameraCapture - grabs a single still image from a camera channel
|
|
52
|
+
export interface CameraCaptureNode extends NodeBase {
|
|
53
|
+
type: "CameraCapture";
|
|
54
|
+
arguments: {
|
|
55
|
+
cameraReference: string | undefined;
|
|
56
|
+
output: OutputBinding;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type InputNodeType = "ReadPin" | "SerialRead" | "Retriever" | "WebFetch" | "CameraCapture";
|
|
61
|
+
export type InputNode = ReadPinNode | SerialReadNode | RetrieverNode | WebFetchNode | CameraCaptureNode;
|
|
53
62
|
|
|
54
63
|
// Node Definitions
|
|
55
64
|
|
|
@@ -186,3 +195,20 @@ export const SerialReadNodeDefinition: NodeDefinition = {
|
|
|
186
195
|
},
|
|
187
196
|
],
|
|
188
197
|
};
|
|
198
|
+
|
|
199
|
+
export const CameraCaptureNodeDefinition: NodeDefinition = {
|
|
200
|
+
type: "CameraCapture",
|
|
201
|
+
label: "Camera Capture",
|
|
202
|
+
category: NodeCategory.Input,
|
|
203
|
+
description: "Capture a still image from a camera",
|
|
204
|
+
outputs: [{ id: "output", label: "Image", type: "static", dataType: "image" }],
|
|
205
|
+
parameters: [
|
|
206
|
+
{
|
|
207
|
+
id: "cameraReference",
|
|
208
|
+
label: "Camera",
|
|
209
|
+
description: "Camera to capture from",
|
|
210
|
+
type: "channelSelect",
|
|
211
|
+
channelType: ["CAMERA"],
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
};
|
package/src/node/Node.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import type { DataType } from "../api";
|
|
13
13
|
import { InputNode, InputNodeType } from "./InputNode";
|
|
14
14
|
import { OutputNode, OutputNodeType } from "./OutputNode";
|
|
15
|
-
import {
|
|
15
|
+
import { AINode, AINodeType } from "./AINode";
|
|
16
16
|
import { LogicNode, LogicNodeType } from "./LogicNode";
|
|
17
17
|
import { DataNode, DataNodeType } from "./DataNode";
|
|
18
18
|
import { TriggerNode, TriggerNodeType } from "./TriggerNode";
|
|
@@ -24,7 +24,7 @@ export type NodeOutput = { name: string; dataType: DataType };
|
|
|
24
24
|
export type NodeType =
|
|
25
25
|
| InputNodeType
|
|
26
26
|
| OutputNodeType
|
|
27
|
-
|
|
|
27
|
+
| AINodeType
|
|
28
28
|
| LogicNodeType
|
|
29
29
|
| DataNodeType
|
|
30
30
|
| TriggerNodeType
|
|
@@ -37,7 +37,7 @@ export type NodeType =
|
|
|
37
37
|
* It is a union type of all specific node types, each with their own typed parameters.
|
|
38
38
|
* Use this type when you need strong typing for a specific node.
|
|
39
39
|
*/
|
|
40
|
-
export type NodeData = InputNode | OutputNode |
|
|
40
|
+
export type NodeData = InputNode | OutputNode | AINode | LogicNode | DataNode | TriggerNode | ToolNode | FunctionCallNode | MqttNode;
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Full domain node entity held on a {@link Canvas}: the {@link NodeData}
|
package/src/node/NodeRegistry.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) 2026 ForestHub.
|
|
3
3
|
|
|
4
|
-
import { ReadPinNodeDefinition, SerialReadNodeDefinition, RetrieverNodeDefinition, WebFetchNodeDefinition } from "./InputNode";
|
|
5
|
-
import { AgentNodeDefinition } from "./
|
|
4
|
+
import { ReadPinNodeDefinition, SerialReadNodeDefinition, RetrieverNodeDefinition, WebFetchNodeDefinition, CameraCaptureNodeDefinition } from "./InputNode";
|
|
5
|
+
import { AgentNodeDefinition, MLInferenceNodeDefinition } from "./AINode";
|
|
6
6
|
import {
|
|
7
7
|
DelayNodeDefinition,
|
|
8
8
|
TickerNodeDefinition,
|
|
@@ -38,6 +38,7 @@ class NodeDefinitionRegistry {
|
|
|
38
38
|
this.register(WritePinNodeDefinition);
|
|
39
39
|
this.register(SerialWriteNodeDefinition);
|
|
40
40
|
this.register(AgentNodeDefinition);
|
|
41
|
+
this.register(MLInferenceNodeDefinition);
|
|
41
42
|
this.register(IfNodeDefinition);
|
|
42
43
|
this.register(SetVariableNodeDefinition);
|
|
43
44
|
// Register trigger nodes
|
|
@@ -54,6 +55,7 @@ class NodeDefinitionRegistry {
|
|
|
54
55
|
// Register input tool nodes
|
|
55
56
|
this.register(RetrieverNodeDefinition);
|
|
56
57
|
this.register(WebFetchNodeDefinition);
|
|
58
|
+
this.register(CameraCaptureNodeDefinition);
|
|
57
59
|
// Register MQTT nodes
|
|
58
60
|
this.register(MqttPublishNodeDefinition);
|
|
59
61
|
this.register(OnMqttMessageNodeDefinition);
|
package/src/node/methods.ts
CHANGED
|
@@ -66,6 +66,7 @@ export function getPorts(node: NodeData): PortDefinitions {
|
|
|
66
66
|
case "WritePin":
|
|
67
67
|
case "SerialWrite":
|
|
68
68
|
case "MqttPublish":
|
|
69
|
+
case "CameraCapture":
|
|
69
70
|
return {
|
|
70
71
|
input: [{ id: "ctrl", type: "control" }],
|
|
71
72
|
output: [{ id: "ctrl", type: "control" }],
|
|
@@ -132,6 +133,11 @@ export function getPorts(node: NodeData): PortDefinitions {
|
|
|
132
133
|
input: [{ id: "ctrl", type: "control" }],
|
|
133
134
|
output: [{ id: "ctrl", type: "control" }],
|
|
134
135
|
};
|
|
136
|
+
case "MLInference":
|
|
137
|
+
return {
|
|
138
|
+
input: [{ id: "ctrl", type: "control" }],
|
|
139
|
+
output: [{ id: "ctrl", type: "control" }],
|
|
140
|
+
};
|
|
135
141
|
case "WebSearchTool":
|
|
136
142
|
return { input: [{ id: "tool", type: "tool", label: "As Tool" }], output: [] };
|
|
137
143
|
}
|