@foresthubai/workflow-core 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +14 -0
- package/README.md +63 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +2 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/workflow.d.ts +607 -0
- package/dist/api/workflow.d.ts.map +1 -0
- package/dist/api/workflow.js +6 -0
- package/dist/api/workflow.js.map +1 -0
- package/dist/channel/Channel.d.ts +10 -0
- package/dist/channel/Channel.d.ts.map +1 -0
- package/dist/channel/Channel.js +2 -0
- package/dist/channel/Channel.js.map +1 -0
- package/dist/channel/ChannelDefinition.d.ts +27 -0
- package/dist/channel/ChannelDefinition.d.ts.map +1 -0
- package/dist/channel/ChannelDefinition.js +50 -0
- package/dist/channel/ChannelDefinition.js.map +1 -0
- package/dist/channel/index.d.ts +7 -0
- package/dist/channel/index.d.ts.map +1 -0
- package/dist/channel/index.js +4 -0
- package/dist/channel/index.js.map +1 -0
- package/dist/channel/serialization.d.ts +17 -0
- package/dist/channel/serialization.d.ts.map +1 -0
- package/dist/channel/serialization.js +63 -0
- package/dist/channel/serialization.js.map +1 -0
- package/dist/deploy/index.d.ts +2 -0
- package/dist/deploy/index.d.ts.map +1 -0
- package/dist/deploy/index.js +2 -0
- package/dist/deploy/index.js.map +1 -0
- package/dist/deploy/requirements.d.ts +17 -0
- package/dist/deploy/requirements.d.ts.map +1 -0
- package/dist/deploy/requirements.js +41 -0
- package/dist/deploy/requirements.js.map +1 -0
- package/dist/diagnostics/__fixtures__/diagnosticFixtures.d.ts +28 -0
- package/dist/diagnostics/__fixtures__/diagnosticFixtures.d.ts.map +1 -0
- package/dist/diagnostics/__fixtures__/diagnosticFixtures.js +125 -0
- package/dist/diagnostics/__fixtures__/diagnosticFixtures.js.map +1 -0
- package/dist/diagnostics/diagnostics.d.ts +128 -0
- package/dist/diagnostics/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics/diagnostics.js +783 -0
- package/dist/diagnostics/diagnostics.js.map +1 -0
- package/dist/diagnostics/index.d.ts +3 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +2 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/edge/Edge.d.ts +22 -0
- package/dist/edge/Edge.d.ts.map +1 -0
- package/dist/edge/Edge.js +2 -0
- package/dist/edge/Edge.js.map +1 -0
- package/dist/edge/EdgeDefinition.d.ts +10 -0
- package/dist/edge/EdgeDefinition.d.ts.map +1 -0
- package/dist/edge/EdgeDefinition.js +36 -0
- package/dist/edge/EdgeDefinition.js.map +1 -0
- package/dist/edge/EdgeType.d.ts +6 -0
- package/dist/edge/EdgeType.d.ts.map +1 -0
- package/dist/edge/EdgeType.js +7 -0
- package/dist/edge/EdgeType.js.map +1 -0
- package/dist/edge/index.d.ts +6 -0
- package/dist/edge/index.d.ts.map +1 -0
- package/dist/edge/index.js +6 -0
- package/dist/edge/index.js.map +1 -0
- package/dist/edge/serialization.d.ts +18 -0
- package/dist/edge/serialization.d.ts.map +1 -0
- package/dist/edge/serialization.js +76 -0
- package/dist/edge/serialization.js.map +1 -0
- package/dist/expression/index.d.ts +5 -0
- package/dist/expression/index.d.ts.map +1 -0
- package/dist/expression/index.js +3 -0
- package/dist/expression/index.js.map +1 -0
- package/dist/expression/parser.d.ts +14 -0
- package/dist/expression/parser.d.ts.map +1 -0
- package/dist/expression/parser.js +309 -0
- package/dist/expression/parser.js.map +1 -0
- package/dist/expression/types.d.ts +11 -0
- package/dist/expression/types.d.ts.map +1 -0
- package/dist/expression/types.js +20 -0
- package/dist/expression/types.js.map +1 -0
- package/dist/function/FunctionDeclaration.d.ts +43 -0
- package/dist/function/FunctionDeclaration.d.ts.map +1 -0
- package/dist/function/FunctionDeclaration.js +17 -0
- package/dist/function/FunctionDeclaration.js.map +1 -0
- package/dist/function/index.d.ts +4 -0
- package/dist/function/index.d.ts.map +1 -0
- package/dist/function/index.js +3 -0
- package/dist/function/index.js.map +1 -0
- package/dist/function/serialization.d.ts +20 -0
- package/dist/function/serialization.d.ts.map +1 -0
- package/dist/function/serialization.js +26 -0
- package/dist/function/serialization.js.map +1 -0
- package/dist/id/index.d.ts +7 -0
- package/dist/id/index.d.ts.map +1 -0
- package/dist/id/index.js +9 -0
- package/dist/id/index.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/Memory.d.ts +12 -0
- package/dist/memory/Memory.d.ts.map +1 -0
- package/dist/memory/Memory.js +2 -0
- package/dist/memory/Memory.js.map +1 -0
- package/dist/memory/MemoryDefinition.d.ts +16 -0
- package/dist/memory/MemoryDefinition.d.ts.map +1 -0
- package/dist/memory/MemoryDefinition.js +2 -0
- package/dist/memory/MemoryDefinition.js.map +1 -0
- package/dist/memory/MemoryFileDefinition.d.ts +8 -0
- package/dist/memory/MemoryFileDefinition.d.ts.map +1 -0
- package/dist/memory/MemoryFileDefinition.js +36 -0
- package/dist/memory/MemoryFileDefinition.js.map +1 -0
- package/dist/memory/MemoryRegistry.d.ts +17 -0
- package/dist/memory/MemoryRegistry.d.ts.map +1 -0
- package/dist/memory/MemoryRegistry.js +29 -0
- package/dist/memory/MemoryRegistry.js.map +1 -0
- package/dist/memory/VectorDatabaseDefinition.d.ts +7 -0
- package/dist/memory/VectorDatabaseDefinition.d.ts.map +1 -0
- package/dist/memory/VectorDatabaseDefinition.js +20 -0
- package/dist/memory/VectorDatabaseDefinition.js.map +1 -0
- package/dist/memory/index.d.ts +9 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +6 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/serialization.d.ts +11 -0
- package/dist/memory/serialization.d.ts.map +1 -0
- package/dist/memory/serialization.js +44 -0
- package/dist/memory/serialization.js.map +1 -0
- package/dist/migration/index.d.ts +5 -0
- package/dist/migration/index.d.ts.map +1 -0
- package/dist/migration/index.js +4 -0
- package/dist/migration/index.js.map +1 -0
- package/dist/migration/migrate.d.ts +11 -0
- package/dist/migration/migrate.d.ts.map +1 -0
- package/dist/migration/migrate.js +52 -0
- package/dist/migration/migrate.js.map +1 -0
- package/dist/migration/migrations.d.ts +22 -0
- package/dist/migration/migrations.d.ts.map +1 -0
- package/dist/migration/migrations.js +10 -0
- package/dist/migration/migrations.js.map +1 -0
- package/dist/migration/version.d.ts +9 -0
- package/dist/migration/version.d.ts.map +1 -0
- package/dist/migration/version.js +9 -0
- package/dist/migration/version.js.map +1 -0
- package/dist/model/LLMModelDefinition.d.ts +7 -0
- package/dist/model/LLMModelDefinition.d.ts.map +1 -0
- package/dist/model/LLMModelDefinition.js +11 -0
- package/dist/model/LLMModelDefinition.js.map +1 -0
- package/dist/model/Model.d.ts +21 -0
- package/dist/model/Model.d.ts.map +1 -0
- package/dist/model/Model.js +15 -0
- package/dist/model/Model.js.map +1 -0
- package/dist/model/ModelDefinition.d.ts +15 -0
- package/dist/model/ModelDefinition.d.ts.map +1 -0
- package/dist/model/ModelDefinition.js +2 -0
- package/dist/model/ModelDefinition.js.map +1 -0
- package/dist/model/ModelRegistry.d.ts +17 -0
- package/dist/model/ModelRegistry.d.ts.map +1 -0
- package/dist/model/ModelRegistry.js +27 -0
- package/dist/model/ModelRegistry.js.map +1 -0
- package/dist/model/index.d.ts +8 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/index.js +5 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/serialization.d.ts +8 -0
- package/dist/model/serialization.d.ts.map +1 -0
- package/dist/model/serialization.js +25 -0
- package/dist/model/serialization.js.map +1 -0
- package/dist/node/AgentNode.d.ts +21 -0
- package/dist/node/AgentNode.d.ts.map +1 -0
- package/dist/node/AgentNode.js +60 -0
- package/dist/node/AgentNode.js.map +1 -0
- package/dist/node/DataNode.d.ts +14 -0
- package/dist/node/DataNode.d.ts.map +1 -0
- package/dist/node/DataNode.js +26 -0
- package/dist/node/DataNode.js.map +1 -0
- package/dist/node/FunctionNode.d.ts +24 -0
- package/dist/node/FunctionNode.d.ts.map +1 -0
- package/dist/node/FunctionNode.js +44 -0
- package/dist/node/FunctionNode.js.map +1 -0
- package/dist/node/InputNode.d.ts +46 -0
- package/dist/node/InputNode.d.ts.map +1 -0
- package/dist/node/InputNode.js +133 -0
- package/dist/node/InputNode.js.map +1 -0
- package/dist/node/LogicNode.d.ts +13 -0
- package/dist/node/LogicNode.d.ts.map +1 -0
- package/dist/node/LogicNode.js +19 -0
- package/dist/node/LogicNode.js.map +1 -0
- package/dist/node/MqttNode.d.ts +27 -0
- package/dist/node/MqttNode.d.ts.map +1 -0
- package/dist/node/MqttNode.js +96 -0
- package/dist/node/MqttNode.js.map +1 -0
- package/dist/node/Node.d.ts +49 -0
- package/dist/node/Node.d.ts.map +1 -0
- package/dist/node/Node.js +9 -0
- package/dist/node/Node.js.map +1 -0
- package/dist/node/NodeDefinition.d.ts +30 -0
- package/dist/node/NodeDefinition.d.ts.map +1 -0
- package/dist/node/NodeDefinition.js +2 -0
- package/dist/node/NodeDefinition.js.map +1 -0
- package/dist/node/NodeRegistry.d.ts +19 -0
- package/dist/node/NodeRegistry.d.ts.map +1 -0
- package/dist/node/NodeRegistry.js +65 -0
- package/dist/node/NodeRegistry.js.map +1 -0
- package/dist/node/OutputNode.d.ts +23 -0
- package/dist/node/OutputNode.d.ts.map +1 -0
- package/dist/node/OutputNode.js +62 -0
- package/dist/node/OutputNode.js.map +1 -0
- package/dist/node/ToolNode.d.ts +12 -0
- package/dist/node/ToolNode.d.ts.map +1 -0
- package/dist/node/ToolNode.js +18 -0
- package/dist/node/ToolNode.js.map +1 -0
- package/dist/node/TriggerNode.d.ts +67 -0
- package/dist/node/TriggerNode.d.ts.map +1 -0
- package/dist/node/TriggerNode.js +172 -0
- package/dist/node/TriggerNode.js.map +1 -0
- package/dist/node/constants.d.ts +16 -0
- package/dist/node/constants.d.ts.map +1 -0
- package/dist/node/constants.js +18 -0
- package/dist/node/constants.js.map +1 -0
- package/dist/node/index.d.ts +12 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +9 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/methods.d.ts +73 -0
- package/dist/node/methods.d.ts.map +1 -0
- package/dist/node/methods.js +261 -0
- package/dist/node/methods.js.map +1 -0
- package/dist/node/serialization.d.ts +25 -0
- package/dist/node/serialization.d.ts.map +1 -0
- package/dist/node/serialization.js +525 -0
- package/dist/node/serialization.js.map +1 -0
- package/dist/parameter/OutputParameter.d.ts +69 -0
- package/dist/parameter/OutputParameter.d.ts.map +1 -0
- package/dist/parameter/OutputParameter.js +6 -0
- package/dist/parameter/OutputParameter.js.map +1 -0
- package/dist/parameter/Parameter.d.ts +152 -0
- package/dist/parameter/Parameter.d.ts.map +1 -0
- package/dist/parameter/Parameter.js +86 -0
- package/dist/parameter/Parameter.js.map +1 -0
- package/dist/parameter/index.d.ts +5 -0
- package/dist/parameter/index.d.ts.map +1 -0
- package/dist/parameter/index.js +3 -0
- package/dist/parameter/index.js.map +1 -0
- package/dist/variable/Variable.d.ts +25 -0
- package/dist/variable/Variable.d.ts.map +1 -0
- package/dist/variable/Variable.js +2 -0
- package/dist/variable/Variable.js.map +1 -0
- package/dist/variable/index.d.ts +3 -0
- package/dist/variable/index.d.ts.map +1 -0
- package/dist/variable/index.js +5 -0
- package/dist/variable/index.js.map +1 -0
- package/dist/variable/operations.d.ts +37 -0
- package/dist/variable/operations.d.ts.map +1 -0
- package/dist/variable/operations.js +88 -0
- package/dist/variable/operations.js.map +1 -0
- package/dist/workflow/Workflow.d.ts +38 -0
- package/dist/workflow/Workflow.d.ts.map +1 -0
- package/dist/workflow/Workflow.js +8 -0
- package/dist/workflow/Workflow.js.map +1 -0
- package/dist/workflow/index.d.ts +4 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +3 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/serialization.d.ts +43 -0
- package/dist/workflow/serialization.d.ts.map +1 -0
- package/dist/workflow/serialization.js +215 -0
- package/dist/workflow/serialization.js.map +1 -0
- package/package.json +105 -0
- package/src/api/index.ts +11 -0
- package/src/api/workflow.ts +607 -0
- package/src/channel/Channel.ts +11 -0
- package/src/channel/ChannelDefinition.ts +76 -0
- package/src/channel/index.ts +6 -0
- package/src/channel/serialization.ts +68 -0
- package/src/deploy/index.ts +1 -0
- package/src/deploy/requirements.test.ts +61 -0
- package/src/deploy/requirements.ts +41 -0
- package/src/diagnostics/__fixtures__/diagnosticFixtures.ts +158 -0
- package/src/diagnostics/diagnostics.test.ts +878 -0
- package/src/diagnostics/diagnostics.ts +936 -0
- package/src/diagnostics/index.ts +11 -0
- package/src/edge/Edge.ts +23 -0
- package/src/edge/EdgeDefinition.ts +45 -0
- package/src/edge/EdgeType.ts +19 -0
- package/src/edge/index.ts +8 -0
- package/src/edge/serialization.ts +83 -0
- package/src/expression/index.ts +4 -0
- package/src/expression/parser.ts +362 -0
- package/src/expression/types.ts +30 -0
- package/src/function/FunctionDeclaration.ts +54 -0
- package/src/function/index.ts +3 -0
- package/src/function/serialization.ts +40 -0
- package/src/globals.d.ts +9 -0
- package/src/id/index.ts +8 -0
- package/src/index.ts +22 -0
- package/src/memory/Memory.ts +15 -0
- package/src/memory/MemoryDefinition.ts +16 -0
- package/src/memory/MemoryFileDefinition.ts +37 -0
- package/src/memory/MemoryRegistry.ts +35 -0
- package/src/memory/VectorDatabaseDefinition.ts +21 -0
- package/src/memory/index.ts +8 -0
- package/src/memory/serialization.ts +47 -0
- package/src/migration/index.ts +4 -0
- package/src/migration/migrate.test.ts +44 -0
- package/src/migration/migrate.ts +58 -0
- package/src/migration/migrations.ts +24 -0
- package/src/migration/version.ts +9 -0
- package/src/model/LLMModelDefinition.ts +12 -0
- package/src/model/Model.ts +39 -0
- package/src/model/ModelDefinition.ts +15 -0
- package/src/model/ModelRegistry.ts +33 -0
- package/src/model/index.ts +7 -0
- package/src/model/serialization.ts +30 -0
- package/src/node/AgentNode.ts +82 -0
- package/src/node/DataNode.ts +41 -0
- package/src/node/FunctionNode.ts +76 -0
- package/src/node/InputNode.ts +185 -0
- package/src/node/LogicNode.ts +33 -0
- package/src/node/MqttNode.ts +127 -0
- package/src/node/Node.ts +61 -0
- package/src/node/NodeDefinition.ts +37 -0
- package/src/node/NodeRegistry.ts +85 -0
- package/src/node/OutputNode.ts +87 -0
- package/src/node/ToolNode.ts +32 -0
- package/src/node/TriggerNode.ts +272 -0
- package/src/node/constants.ts +16 -0
- package/src/node/index.ts +26 -0
- package/src/node/methods.ts +278 -0
- package/src/node/serialization.ts +544 -0
- package/src/parameter/OutputParameter.ts +68 -0
- package/src/parameter/Parameter.ts +243 -0
- package/src/parameter/index.ts +33 -0
- package/src/variable/Variable.ts +10 -0
- package/src/variable/index.ts +16 -0
- package/src/variable/operations.ts +106 -0
- package/src/workflow/Workflow.ts +41 -0
- package/src/workflow/index.ts +3 -0
- package/src/workflow/serialization.test.ts +240 -0
- package/src/workflow/serialization.ts +242 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { serialize, deserialize, computeVariablesFromNodes, buildCanvasVariables } from "./serialization";
|
|
3
|
+
import { MAIN_CANVAS_ID, type Workflow, type Canvas } from "./Workflow";
|
|
4
|
+
import type { Schemas } from "../api";
|
|
5
|
+
import type { NodeData } from "../node";
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Reverse roundtrip: api JSON → deserialize → serialize → deep-equal JSON
|
|
9
|
+
//
|
|
10
|
+
// This direction is the strongest invariant — deserialize is the function
|
|
11
|
+
// that reconstructs derivable state (variable records); if it gets the rest
|
|
12
|
+
// right, re-serialize must produce the same JSON back. Tests every code path
|
|
13
|
+
// in deserialize + serialize without separately constructing WorkflowState.
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
const empty: Schemas["Workflow"] = {
|
|
17
|
+
schemaVersion: 1,
|
|
18
|
+
nodes: [],
|
|
19
|
+
edges: [],
|
|
20
|
+
functions: [],
|
|
21
|
+
declaredVariables: [],
|
|
22
|
+
channels: [],
|
|
23
|
+
memory: [],
|
|
24
|
+
models: [],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const mainOnly: Schemas["Workflow"] = {
|
|
28
|
+
schemaVersion: 1,
|
|
29
|
+
nodes: [
|
|
30
|
+
{
|
|
31
|
+
id: "n1",
|
|
32
|
+
type: "Agent",
|
|
33
|
+
position: { x: 100, y: 200 },
|
|
34
|
+
label: "My Agent",
|
|
35
|
+
arguments: {
|
|
36
|
+
name: "agent-1",
|
|
37
|
+
model: "claude-opus-4-7",
|
|
38
|
+
instructions: "be helpful",
|
|
39
|
+
outputDeclarations: [],
|
|
40
|
+
memoryRefs: [],
|
|
41
|
+
answer: { active: true, mode: "emit", name: "answer" },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
edges: [],
|
|
46
|
+
functions: [],
|
|
47
|
+
declaredVariables: [
|
|
48
|
+
{ uid: "d1", name: "counter", dataType: "int", initialValue: 0 },
|
|
49
|
+
],
|
|
50
|
+
channels: [],
|
|
51
|
+
memory: [],
|
|
52
|
+
models: [],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const allEdgeTypes: Schemas["Workflow"] = {
|
|
56
|
+
schemaVersion: 1,
|
|
57
|
+
nodes: [],
|
|
58
|
+
edges: [
|
|
59
|
+
{ id: "edge-control", type: "control", from: { nodeId: "a", port: "out" }, to: { nodeId: "b", port: "in" } },
|
|
60
|
+
{ id: "edge-tool", type: "tool", from: { nodeId: "a", port: "tool-out" }, to: { nodeId: "b", port: "tool-in" } },
|
|
61
|
+
{
|
|
62
|
+
id: "edge-task",
|
|
63
|
+
type: "agentTask",
|
|
64
|
+
from: { nodeId: "agent", port: "tool" },
|
|
65
|
+
to: { nodeId: "task", port: "trigger" },
|
|
66
|
+
prompt: { expression: '"summarize"', references: [], dataType: "string" },
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "edge-choice",
|
|
70
|
+
type: "agentChoice",
|
|
71
|
+
from: { nodeId: "agent", port: "choice" },
|
|
72
|
+
to: { nodeId: "branchA", port: "in" },
|
|
73
|
+
description: "when the user asks about weather",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "edge-delegate",
|
|
77
|
+
type: "agentDelegate",
|
|
78
|
+
from: { nodeId: "agent", port: "delegate" },
|
|
79
|
+
to: { nodeId: "sub", port: "in" },
|
|
80
|
+
prompt: { expression: '"continue"', references: [], dataType: "string" },
|
|
81
|
+
description: "delegate everything else",
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
functions: [],
|
|
85
|
+
declaredVariables: [],
|
|
86
|
+
channels: [],
|
|
87
|
+
memory: [],
|
|
88
|
+
models: [],
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const withFunctionCanvas: Schemas["Workflow"] = {
|
|
92
|
+
schemaVersion: 1,
|
|
93
|
+
nodes: [
|
|
94
|
+
{
|
|
95
|
+
id: "fcall",
|
|
96
|
+
type: "FunctionCall",
|
|
97
|
+
position: { x: 0, y: 0 },
|
|
98
|
+
// The wire stores only the reference; the signature is resolved from
|
|
99
|
+
// `functions[]` and the snapshot rebuilt on deserialize.
|
|
100
|
+
functionId: "fn-uuid",
|
|
101
|
+
arguments: {
|
|
102
|
+
inputBindings: {
|
|
103
|
+
"arg-x": { expression: "1", references: [], dataType: "int" },
|
|
104
|
+
"arg-y": { expression: "2", references: [], dataType: "int" },
|
|
105
|
+
},
|
|
106
|
+
outputBindings: {
|
|
107
|
+
"ret-sum": { active: true, mode: "emit", name: "result" },
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
edges: [],
|
|
113
|
+
functions: [
|
|
114
|
+
{
|
|
115
|
+
functionInfo: {
|
|
116
|
+
id: "fn-uuid",
|
|
117
|
+
version: 1,
|
|
118
|
+
name: "add",
|
|
119
|
+
arguments: [
|
|
120
|
+
{ uid: "arg-x", name: "x", dataType: "int" },
|
|
121
|
+
{ uid: "arg-y", name: "y", dataType: "int" },
|
|
122
|
+
],
|
|
123
|
+
returns: [{ uid: "ret-sum", name: "sum", dataType: "int" }],
|
|
124
|
+
},
|
|
125
|
+
outputAssignments: {
|
|
126
|
+
"ret-sum": { expression: "x + y", references: [
|
|
127
|
+
{ srcId: "fnarg", varId: "arg-x" },
|
|
128
|
+
{ srcId: "fnarg", varId: "arg-y" },
|
|
129
|
+
], dataType: "int" },
|
|
130
|
+
},
|
|
131
|
+
nodes: [],
|
|
132
|
+
edges: [],
|
|
133
|
+
declaredVariables: [],
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
declaredVariables: [],
|
|
137
|
+
channels: [],
|
|
138
|
+
memory: [],
|
|
139
|
+
models: [],
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
describe("workflowSerialization — reverse roundtrip (JSON → deserialize → serialize)", () => {
|
|
143
|
+
it("empty workflow", () => {
|
|
144
|
+
expect(serialize(deserialize(empty))).toEqual(empty);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("main canvas with declared variables and one node", () => {
|
|
148
|
+
expect(serialize(deserialize(mainOnly))).toEqual(mainOnly);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("preserves all edge type variants verbatim, including ids", () => {
|
|
152
|
+
const out = serialize(deserialize(allEdgeTypes));
|
|
153
|
+
expect(out).toEqual(allEdgeTypes);
|
|
154
|
+
// Specific guardrail: each edge id is preserved (the bug we fixed).
|
|
155
|
+
const ids = out.edges.map((e) => e.id).sort();
|
|
156
|
+
expect(ids).toEqual(["edge-choice", "edge-control", "edge-delegate", "edge-task", "edge-tool"]);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("function canvas with fnargs, output assignments, and a FunctionCall on main", () => {
|
|
160
|
+
const out = serialize(deserialize(withFunctionCanvas));
|
|
161
|
+
expect(out).toEqual(withFunctionCanvas);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// ============================================================================
|
|
166
|
+
// Forward roundtrip: WorkflowState → serialize → deserialize → equality
|
|
167
|
+
//
|
|
168
|
+
// Variables are reconstructed from nodes + functionInfo + declared on every
|
|
169
|
+
// deserialize. To get exact equality, build fixtures whose `variables` field
|
|
170
|
+
// matches what reconstruction would produce (i.e., let buildCanvasVariables
|
|
171
|
+
// produce the expected value for us).
|
|
172
|
+
// ============================================================================
|
|
173
|
+
|
|
174
|
+
function makeMainCanvas(nodes: Canvas["nodes"] = [], edges: Canvas["edges"] = [], declared: Schemas["Variable"][] = []): Canvas {
|
|
175
|
+
return {
|
|
176
|
+
nodes,
|
|
177
|
+
edges,
|
|
178
|
+
variables: buildCanvasVariables(nodes, [], declared),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
describe("workflowSerialization — forward roundtrip (state → serialize → deserialize)", () => {
|
|
183
|
+
it("empty state roundtrips to a state with an empty main canvas", () => {
|
|
184
|
+
const state: Workflow = { canvases: { [MAIN_CANVAS_ID]: makeMainCanvas() }, functions: {}, channels: {}, memory: {}, models: {} };
|
|
185
|
+
expect(deserialize(serialize(state))).toEqual(state);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("preserves edge ids across serialize/deserialize", () => {
|
|
189
|
+
const edges: Canvas["edges"] = [
|
|
190
|
+
{ id: "stable-id-A", type: "control", source: "n1", sourceHandle: "out", target: "n2", targetHandle: "in" },
|
|
191
|
+
{ id: "stable-id-B", type: "tool", source: "n1", sourceHandle: "tool", target: "n3", targetHandle: "in" },
|
|
192
|
+
];
|
|
193
|
+
const state: Workflow = { canvases: { [MAIN_CANVAS_ID]: makeMainCanvas([], edges) }, functions: {}, channels: {}, memory: {}, models: {} };
|
|
194
|
+
const roundTripped = deserialize(serialize(state));
|
|
195
|
+
expect(roundTripped.canvases[MAIN_CANVAS_ID]!.edges.map((e) => e.id)).toEqual(["stable-id-A", "stable-id-B"]);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// ============================================================================
|
|
200
|
+
// Variable reconstruction guardrails
|
|
201
|
+
// ============================================================================
|
|
202
|
+
|
|
203
|
+
describe("buildCanvasVariables", () => {
|
|
204
|
+
it("reconstructs node-output variables from nodes via getNodeOutput", () => {
|
|
205
|
+
const nodes: NodeData[] = [
|
|
206
|
+
{
|
|
207
|
+
id: "agent1",
|
|
208
|
+
type: "Agent",
|
|
209
|
+
arguments: {
|
|
210
|
+
name: "a",
|
|
211
|
+
model: "claude-opus-4-7",
|
|
212
|
+
instructions: "",
|
|
213
|
+
maxTurns: undefined,
|
|
214
|
+
outputDeclarations: [],
|
|
215
|
+
memoryRefs: [],
|
|
216
|
+
answer: { active: true, mode: "emit", name: "answer" },
|
|
217
|
+
},
|
|
218
|
+
} as NodeData,
|
|
219
|
+
];
|
|
220
|
+
const vars = computeVariablesFromNodes(nodes);
|
|
221
|
+
// Agent emits at least one output (the answer); key is "<nodeId>:<outputId>".
|
|
222
|
+
const keys = Object.keys(vars);
|
|
223
|
+
expect(keys.length).toBeGreaterThan(0);
|
|
224
|
+
expect(keys.every((k) => k.startsWith("agent1:"))).toBe(true);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("merges declared + fnarg + node-output into disjoint key namespaces", () => {
|
|
228
|
+
const declared: Schemas["Variable"][] = [{ uid: "d1", name: "x", dataType: "int" }];
|
|
229
|
+
const fnInfo = {
|
|
230
|
+
id: "fn",
|
|
231
|
+
version: 1,
|
|
232
|
+
name: "f",
|
|
233
|
+
arguments: [{ uid: "a1", name: "arg1", dataType: "int" as const }],
|
|
234
|
+
returns: [],
|
|
235
|
+
};
|
|
236
|
+
const merged = buildCanvasVariables([], fnInfo.arguments, declared);
|
|
237
|
+
expect(merged["declared:d1"]).toMatchObject({ kind: "declared", uid: "d1" });
|
|
238
|
+
expect(merged["fnarg:a1"]).toMatchObject({ kind: "fnarg", uid: "a1" });
|
|
239
|
+
});
|
|
240
|
+
});
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// Pure conversion between the api format (Schemas["Workflow"])
|
|
2
|
+
// and the domain shape (Workflow).
|
|
3
|
+
// Two producers feed serialize: the editor reads its live stores into
|
|
4
|
+
// a Workflow literal; the CLI calls deserialize on parsed JSON.
|
|
5
|
+
|
|
6
|
+
import type { Schemas } from "../api";
|
|
7
|
+
import type { NodeData } from "../node";
|
|
8
|
+
import type { FunctionDeclaration, FunctionInfo } from "../function";
|
|
9
|
+
import { serialize as serializeFunction, deserialize as deserializeFunction } from "../function";
|
|
10
|
+
import { getNodeOutput, isNodeUsedAsTool } from "../node/methods";
|
|
11
|
+
import { ALL_CHANNEL_TYPES, type ApiChannel, type Channel, type ChannelType } from "../channel";
|
|
12
|
+
import { serialize as serializeChannel, deserialize as deserializeChannel } from "../channel";
|
|
13
|
+
import type { Memory } from "../memory";
|
|
14
|
+
import { serialize as serializeMemory, deserialize as deserializeMemory } from "../memory";
|
|
15
|
+
import type { Model } from "../model";
|
|
16
|
+
import { serialize as serializeModel, deserialize as deserializeModel } from "../model";
|
|
17
|
+
import { serialize as serializeNode, deserialize as deserializeNode } from "../node";
|
|
18
|
+
import { serialize as serializeEdge, deserialize as deserializeEdge } from "../edge";
|
|
19
|
+
import type { Variable, NodeOutputVariable } from "../variable";
|
|
20
|
+
import { declaredVarKey, fnargKey, nodeOutputVarKey, ensureUids } from "../variable";
|
|
21
|
+
import { MAIN_CANVAS_ID, type Workflow, type Canvas } from "./Workflow";
|
|
22
|
+
import { CURRENT_SCHEMA_VERSION } from "../migration";
|
|
23
|
+
|
|
24
|
+
const KNOWN_CHANNEL_TYPES = new Set<ChannelType>(ALL_CHANNEL_TYPES);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Serialize domain → api Workflow. Multi-canvas
|
|
28
|
+
* mapping: the `main` canvas's nodes/edges/declaredVariables land at the
|
|
29
|
+
* root of `Workflow`; every other canvas becomes a `Function` entry in
|
|
30
|
+
* `Workflow.functions[]`, carrying its own functionInfo + outputAssignments.
|
|
31
|
+
*/
|
|
32
|
+
export function serialize(state: Workflow): Schemas["Workflow"] {
|
|
33
|
+
const mainCanvas = state.canvases[MAIN_CANVAS_ID];
|
|
34
|
+
if (!mainCanvas) {
|
|
35
|
+
throw new Error("Main canvas missing");
|
|
36
|
+
}
|
|
37
|
+
const mainNodes = mainCanvas.nodes.map((n) => serializeNode(n, isNodeUsedAsTool(n.id, n, mainCanvas.edges)));
|
|
38
|
+
const mainEdges = mainCanvas.edges.map(serializeEdge);
|
|
39
|
+
const mainDeclared = extractDeclaredVariables(mainCanvas.variables);
|
|
40
|
+
|
|
41
|
+
// Each declaration + its body canvas (joined by id) becomes one wire Function.
|
|
42
|
+
// The declaration splits into functionInfo + outputAssignments via serializeFunction.
|
|
43
|
+
const functions: Schemas["Function"][] = [];
|
|
44
|
+
for (const [id, decl] of Object.entries(state.functions)) {
|
|
45
|
+
const body = state.canvases[id];
|
|
46
|
+
if (!body) {
|
|
47
|
+
throw new Error(`[workflow-core] function ${id} has no body canvas — cannot serialize`);
|
|
48
|
+
}
|
|
49
|
+
const { functionInfo, outputAssignments } = serializeFunction(decl);
|
|
50
|
+
functions.push({
|
|
51
|
+
functionInfo,
|
|
52
|
+
outputAssignments,
|
|
53
|
+
nodes: body.nodes.map((n) => serializeNode(n, isNodeUsedAsTool(n.id, n, body.edges))),
|
|
54
|
+
edges: body.edges.map(serializeEdge),
|
|
55
|
+
declaredVariables: extractDeclaredVariables(body.variables),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const channels = Object.values(state.channels).map(serializeChannel);
|
|
60
|
+
const memory = Object.values(state.memory).map(serializeMemory);
|
|
61
|
+
const models = Object.values(state.models).map(serializeModel);
|
|
62
|
+
return {
|
|
63
|
+
schemaVersion: CURRENT_SCHEMA_VERSION,
|
|
64
|
+
nodes: mainNodes,
|
|
65
|
+
edges: mainEdges,
|
|
66
|
+
functions,
|
|
67
|
+
declaredVariables: mainDeclared,
|
|
68
|
+
channels,
|
|
69
|
+
memory,
|
|
70
|
+
models,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Deserialize api → domain Workflow. Variable
|
|
76
|
+
* records are reconstructed per canvas (declared from the api; fnarg
|
|
77
|
+
* from `functionInfo.arguments`; node-output via {@link computeVariablesFromNodes})
|
|
78
|
+
* since the api intentionally carries only `declaredVariables` to avoid
|
|
79
|
+
* redundancy.
|
|
80
|
+
*
|
|
81
|
+
* Each node is a flat {@link Node} (domain `NodeData` + `position`);
|
|
82
|
+
* its `type` is the domain node type (e.g. "Agent"). Workflow-builder projects
|
|
83
|
+
* that into a React Flow display type during store hydration — editor-only.
|
|
84
|
+
*/
|
|
85
|
+
export function deserialize(workflow: Schemas["Workflow"]): Workflow {
|
|
86
|
+
const canvases: Record<string, Canvas> = {};
|
|
87
|
+
const functions: Record<string, FunctionDeclaration> = {};
|
|
88
|
+
|
|
89
|
+
// Pre-build the function-info table so FunctionCall nodes — on the main canvas or
|
|
90
|
+
// in any function body — can rebuild their signature snapshot from the wire's
|
|
91
|
+
// `functionId`. (The wire stores only the reference; the engine likewise resolves
|
|
92
|
+
// by id, so the snapshot is editor-side state reconstructed here.)
|
|
93
|
+
const functionInfos: Record<string, FunctionInfo> = {};
|
|
94
|
+
for (const fn of workflow.functions ?? []) {
|
|
95
|
+
functionInfos[fn.functionInfo.id] = {
|
|
96
|
+
...fn.functionInfo,
|
|
97
|
+
arguments: ensureUids(fn.functionInfo.arguments),
|
|
98
|
+
returns: ensureUids(fn.functionInfo.returns),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const resolveFunctionInfo = (id: string): FunctionInfo | undefined => functionInfos[id];
|
|
102
|
+
|
|
103
|
+
// Main canvas: data at the api root. No function declaration.
|
|
104
|
+
const mainNodes = workflow.nodes.map((n) => deserializeNode(n, resolveFunctionInfo));
|
|
105
|
+
const mainEdges = workflow.edges.map(deserializeEdge);
|
|
106
|
+
const mainDeclared = workflow.declaredVariables ?? [];
|
|
107
|
+
canvases[MAIN_CANVAS_ID] = {
|
|
108
|
+
nodes: mainNodes,
|
|
109
|
+
edges: mainEdges,
|
|
110
|
+
variables: buildCanvasVariables(mainNodes, [], mainDeclared),
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Each wire Function splits into a project-scoped declaration (functions[id]) and
|
|
114
|
+
// its body canvas (canvases[id]), joined by the function id.
|
|
115
|
+
for (const fn of workflow.functions ?? []) {
|
|
116
|
+
const functionInfo = functionInfos[fn.functionInfo.id]!;
|
|
117
|
+
const decl = deserializeFunction(functionInfo, fn.outputAssignments ?? {});
|
|
118
|
+
functions[decl.id] = decl;
|
|
119
|
+
const fnNodes = fn.nodes.map((n) => deserializeNode(n, resolveFunctionInfo));
|
|
120
|
+
const fnEdges = fn.edges.map(deserializeEdge);
|
|
121
|
+
canvases[decl.id] = {
|
|
122
|
+
nodes: fnNodes,
|
|
123
|
+
edges: fnEdges,
|
|
124
|
+
variables: buildCanvasVariables(fnNodes, decl.arguments, fn.declaredVariables ?? []),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const channels: Record<string, Channel> = {};
|
|
129
|
+
for (const c of workflow.channels ?? []) {
|
|
130
|
+
if (!KNOWN_CHANNEL_TYPES.has(c.type as ChannelType)) continue;
|
|
131
|
+
const instance = deserializeChannel(c as ApiChannel);
|
|
132
|
+
channels[instance.id] = instance;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const memory: Record<string, Memory> = {};
|
|
136
|
+
for (const m of workflow.memory ?? []) {
|
|
137
|
+
const instance = deserializeMemory(m);
|
|
138
|
+
memory[instance.id] = instance;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const models: Record<string, Model> = {};
|
|
142
|
+
for (const m of workflow.models ?? []) {
|
|
143
|
+
const instance = deserializeModel(m);
|
|
144
|
+
models[instance.id] = instance;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
canvases,
|
|
149
|
+
functions,
|
|
150
|
+
channels,
|
|
151
|
+
memory,
|
|
152
|
+
models,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ============================================================================
|
|
157
|
+
// Variable reconstruction
|
|
158
|
+
// ============================================================================
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Derive node-output variables (`{kind: "node", nodeId, outputId, ...}`)
|
|
162
|
+
* from an array of node instances. Calls {@link getNodeOutput} to inspect
|
|
163
|
+
* each node's declared outputs.
|
|
164
|
+
*
|
|
165
|
+
* Ported from workflow-builder's canvasStore to take NodeData[] directly
|
|
166
|
+
* (NodeData carries id at the top level — no React Flow wrapper needed).
|
|
167
|
+
*/
|
|
168
|
+
export function computeVariablesFromNodes(nodes: NodeData[]): Record<string, NodeOutputVariable> {
|
|
169
|
+
const out: Record<string, NodeOutputVariable> = {};
|
|
170
|
+
for (const node of nodes) {
|
|
171
|
+
for (const [outputId, variable] of Object.entries(getNodeOutput(node))) {
|
|
172
|
+
out[nodeOutputVarKey(node.id, outputId)] = {
|
|
173
|
+
kind: "node",
|
|
174
|
+
nodeId: node.id,
|
|
175
|
+
outputId,
|
|
176
|
+
name: variable.name,
|
|
177
|
+
dataType: variable.dataType,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Merge the three variable sources into a single per-canvas record:
|
|
186
|
+
* declared (from api)
|
|
187
|
+
* + fnarg (derived from the function's `arguments` — empty for the main canvas)
|
|
188
|
+
* + nodeOutput (derived from nodes via {@link computeVariablesFromNodes})
|
|
189
|
+
*
|
|
190
|
+
* Disjoint key namespaces (`declared:`, `fnarg:`, `<nodeId>:<outputId>`) so
|
|
191
|
+
* merge order is irrelevant.
|
|
192
|
+
*/
|
|
193
|
+
export function buildCanvasVariables(
|
|
194
|
+
nodes: NodeData[],
|
|
195
|
+
fnArgs: readonly Schemas["Variable"][],
|
|
196
|
+
declaredVariables: readonly Schemas["Variable"][],
|
|
197
|
+
): Record<string, Variable> {
|
|
198
|
+
const variables: Record<string, Variable> = computeVariablesFromNodes(nodes);
|
|
199
|
+
for (const arg of fnArgs) {
|
|
200
|
+
variables[fnargKey(arg.uid)] = {
|
|
201
|
+
kind: "fnarg",
|
|
202
|
+
uid: arg.uid,
|
|
203
|
+
name: arg.name,
|
|
204
|
+
dataType: arg.dataType,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
for (const dv of declaredVariables) {
|
|
208
|
+
variables[declaredVarKey(dv.uid)] = {
|
|
209
|
+
kind: "declared",
|
|
210
|
+
uid: dv.uid,
|
|
211
|
+
name: dv.name,
|
|
212
|
+
dataType: dv.dataType,
|
|
213
|
+
...(dv.initialValue !== undefined ? { initialValue: dv.initialValue } : {}),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return variables;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ============================================================================
|
|
220
|
+
// Helpers — declared variables
|
|
221
|
+
// ============================================================================
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Filter a canvas's variables down to the declared-kind entries (the only
|
|
225
|
+
* variety the api persists). Node-output and fnarg variables are
|
|
226
|
+
* reconstructed on deserialize from nodes + functionInfo.
|
|
227
|
+
*/
|
|
228
|
+
function extractDeclaredVariables(variables: Record<string, Variable>): Schemas["Variable"][] {
|
|
229
|
+
const result: Schemas["Variable"][] = [];
|
|
230
|
+
for (const v of Object.values(variables)) {
|
|
231
|
+
if (v.kind === "declared") {
|
|
232
|
+
result.push({
|
|
233
|
+
uid: v.uid,
|
|
234
|
+
name: v.name,
|
|
235
|
+
dataType: v.dataType,
|
|
236
|
+
...(v.initialValue !== undefined ? { initialValue: v.initialValue } : {}),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
|