@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,11 @@
|
|
|
1
|
+
export type { DiagnosticSeverity, DiagnosticCategory, Diagnostic, CanvasValidationResult, ValidationResult } from "./diagnostics";
|
|
2
|
+
export {
|
|
3
|
+
computeNodeDiagnostics,
|
|
4
|
+
computeEdgeDiagnostics,
|
|
5
|
+
validateChannel,
|
|
6
|
+
validateMemory,
|
|
7
|
+
validateModel,
|
|
8
|
+
validateFunction,
|
|
9
|
+
validateFunctionOutputs,
|
|
10
|
+
validateWorkflowState,
|
|
11
|
+
} from "./diagnostics";
|
package/src/edge/Edge.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Expression } from "../api";
|
|
2
|
+
|
|
3
|
+
export interface EdgeData extends Record<string, unknown> {
|
|
4
|
+
prompt?: Expression; // agentTask and agentDelegate
|
|
5
|
+
description?: string; // agentChoice and agentDelegate
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Full domain edge entity held on a {@link Canvas}: connectivity topology plus
|
|
10
|
+
* the optional {@link EdgeData} payload. Low-level core fns that read only
|
|
11
|
+
* connectivity accept it directly, and the editor's React Flow `Edge[]` is
|
|
12
|
+
* structurally assignable
|
|
13
|
+
* without an adapter (core stays free of `@xyflow/react`).
|
|
14
|
+
*/
|
|
15
|
+
export interface Edge {
|
|
16
|
+
id: string;
|
|
17
|
+
type?: string;
|
|
18
|
+
source: string;
|
|
19
|
+
sourceHandle?: string | null;
|
|
20
|
+
target: string;
|
|
21
|
+
targetHandle?: string | null;
|
|
22
|
+
data?: EdgeData;
|
|
23
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Parameter } from "../parameter";
|
|
2
|
+
import type { EdgeType } from "./EdgeType";
|
|
3
|
+
|
|
4
|
+
export interface EdgeDefinition {
|
|
5
|
+
label: string;
|
|
6
|
+
description: string;
|
|
7
|
+
parameters: Parameter[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const EDGE_DEFINITIONS: Record<EdgeType, EdgeDefinition> = {
|
|
11
|
+
agentTask: {
|
|
12
|
+
label: "Task",
|
|
13
|
+
description: "Assigns a task to an agent with a prompt",
|
|
14
|
+
parameters: [{ id: "prompt", label: "Prompt", description: "", type: "expression", expressionType: "string", default: { expression: "", references: [], dataType: "string" } }],
|
|
15
|
+
},
|
|
16
|
+
agentChoice: {
|
|
17
|
+
label: "Choice",
|
|
18
|
+
description: "A path the agent can choose based on its description",
|
|
19
|
+
parameters: [
|
|
20
|
+
{ id: "description", label: "Description", description: "", type: "string", default: "When should the agent take this path?" },
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
agentDelegate: {
|
|
24
|
+
label: "Delegation",
|
|
25
|
+
description: "Lets an agent hand off control to another agent",
|
|
26
|
+
parameters: [
|
|
27
|
+
{ id: "description", label: "Description", description: "", type: "string", default: "When should the agent take this path?" },
|
|
28
|
+
{ id: "prompt", label: "Prompt", description: "", type: "expression", expressionType: "string", default: { expression: "", references: [], dataType: "string" } },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
control: {
|
|
32
|
+
label: "Control",
|
|
33
|
+
description: "Sequential control flow between nodes",
|
|
34
|
+
parameters: [],
|
|
35
|
+
},
|
|
36
|
+
tool: {
|
|
37
|
+
label: "Tool",
|
|
38
|
+
description: "Tool connection between nodes",
|
|
39
|
+
parameters: [],
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function getEdgeDefinition(type: EdgeType): EdgeDefinition {
|
|
44
|
+
return EDGE_DEFINITIONS[type];
|
|
45
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Edge taxonomy: a plain control/tool edge, or one of the three agent-specific
|
|
2
|
+
// control-flow refinements resolved at connection time from source/target context.
|
|
3
|
+
// Node port handles themselves only carry `control` or `tool`. Tool edges have no
|
|
4
|
+
// agent-specific variant — every tool edge points at an agent by construction.
|
|
5
|
+
export type EdgeType = "control" | "tool" | "agentTask" | "agentChoice" | "agentDelegate";
|
|
6
|
+
|
|
7
|
+
// All types that behave like control flow (horizontal bezier, side ports)
|
|
8
|
+
export type ControlFlowType = "control" | "agentTask" | "agentChoice" | "agentDelegate";
|
|
9
|
+
|
|
10
|
+
// All types that behave like tool connections (vertical bezier, top/bottom ports)
|
|
11
|
+
export type ToolFlowType = "tool";
|
|
12
|
+
|
|
13
|
+
export function isControlFlow(type: EdgeType): type is ControlFlowType {
|
|
14
|
+
return type === "control" || type === "agentTask" || type === "agentChoice" || type === "agentDelegate";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isToolFlow(type: EdgeType): type is ToolFlowType {
|
|
18
|
+
return type === "tool";
|
|
19
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Public surface of the edge module. The base type (EdgeData) lives in
|
|
2
|
+
// ./Edge; this file is a barrel only. Mirrors channel/memory/model.
|
|
3
|
+
|
|
4
|
+
export type { EdgeData, Edge } from "./Edge";
|
|
5
|
+
export { type EdgeDefinition, getEdgeDefinition, EDGE_DEFINITIONS } from "./EdgeDefinition";
|
|
6
|
+
export { type EdgeType, type ControlFlowType, type ToolFlowType, isControlFlow, isToolFlow } from "./EdgeType";
|
|
7
|
+
export { serialize, deserialize } from "./serialization";
|
|
8
|
+
export type { ApiEdge } from "./serialization";
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Schemas } from "../api";
|
|
2
|
+
import type { Edge, EdgeData } from "./Edge";
|
|
3
|
+
import type { EdgeType } from "./EdgeType";
|
|
4
|
+
|
|
5
|
+
export type ApiEdge = Schemas["Edge"];
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Serialize a domain {@link Edge} to the api `Edge`. The `id` is preserved
|
|
9
|
+
* (the api requires it). Edge-type-conditional metadata (`prompt`,
|
|
10
|
+
* `description`) is reattached from the edge's `data` payload.
|
|
11
|
+
*/
|
|
12
|
+
export function serialize(edge: Edge): ApiEdge {
|
|
13
|
+
const sourceHandle = edge.sourceHandle || "";
|
|
14
|
+
const targetHandle = edge.targetHandle || "";
|
|
15
|
+
const edgeType = edge.type as EdgeType | undefined;
|
|
16
|
+
const from = { nodeId: edge.source, port: sourceHandle };
|
|
17
|
+
const to = { nodeId: edge.target, port: targetHandle };
|
|
18
|
+
|
|
19
|
+
switch (edgeType) {
|
|
20
|
+
case "agentTask":
|
|
21
|
+
return {
|
|
22
|
+
id: edge.id,
|
|
23
|
+
type: "agentTask",
|
|
24
|
+
from,
|
|
25
|
+
to,
|
|
26
|
+
prompt: (edge.data?.prompt as Schemas["Expression"]) ?? { expression: "", references: [], dataType: "string" },
|
|
27
|
+
};
|
|
28
|
+
case "agentChoice":
|
|
29
|
+
return {
|
|
30
|
+
id: edge.id,
|
|
31
|
+
type: "agentChoice",
|
|
32
|
+
from,
|
|
33
|
+
to,
|
|
34
|
+
...(edge.data?.description ? { description: edge.data.description as string } : {}),
|
|
35
|
+
};
|
|
36
|
+
case "agentDelegate":
|
|
37
|
+
return {
|
|
38
|
+
id: edge.id,
|
|
39
|
+
type: "agentDelegate",
|
|
40
|
+
from,
|
|
41
|
+
to,
|
|
42
|
+
...(edge.data?.prompt ? { prompt: edge.data.prompt as Schemas["Expression"] } : {}),
|
|
43
|
+
...(edge.data?.description ? { description: edge.data.description as string } : {}),
|
|
44
|
+
};
|
|
45
|
+
case "control":
|
|
46
|
+
return { id: edge.id, type: "control", from, to };
|
|
47
|
+
case "tool":
|
|
48
|
+
return { id: edge.id, type: "tool", from, to };
|
|
49
|
+
default:
|
|
50
|
+
return {
|
|
51
|
+
id: edge.id,
|
|
52
|
+
type: sourceHandle.startsWith("ctrl") || targetHandle.startsWith("ctrl") ? "control" : "tool",
|
|
53
|
+
from,
|
|
54
|
+
to,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Convert an api `Edge` into a domain {@link Edge}. The api's `id` is preserved
|
|
61
|
+
* verbatim (earlier code synthesized `e${index+1}`, breaking roundtrip
|
|
62
|
+
* identity). Edge-type-conditional metadata (`prompt` on agentTask/agentDelegate;
|
|
63
|
+
* `description` on agentChoice/agentDelegate) is folded into `data` as
|
|
64
|
+
* {@link EdgeData}.
|
|
65
|
+
*/
|
|
66
|
+
export function deserialize(apiEdge: ApiEdge): Edge {
|
|
67
|
+
let data: EdgeData | undefined;
|
|
68
|
+
if ((apiEdge.type === "agentTask" || apiEdge.type === "agentDelegate") && apiEdge.prompt) {
|
|
69
|
+
data = { ...data, prompt: apiEdge.prompt };
|
|
70
|
+
}
|
|
71
|
+
if ((apiEdge.type === "agentChoice" || apiEdge.type === "agentDelegate") && apiEdge.description) {
|
|
72
|
+
data = { ...data, description: apiEdge.description };
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
id: apiEdge.id,
|
|
76
|
+
type: apiEdge.type,
|
|
77
|
+
source: apiEdge.from.nodeId,
|
|
78
|
+
sourceHandle: apiEdge.from.port,
|
|
79
|
+
target: apiEdge.to.nodeId,
|
|
80
|
+
targetHandle: apiEdge.to.port,
|
|
81
|
+
...(data ? { data } : {}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import jsep from "jsep";
|
|
2
|
+
import type { DataType } from "../api";
|
|
3
|
+
import { ResolvedExpr } from "./types";
|
|
4
|
+
|
|
5
|
+
export interface ParseResult {
|
|
6
|
+
isValid: boolean;
|
|
7
|
+
inferredType: DataType | null;
|
|
8
|
+
errors: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Map of placeholder names to their types (populated from variables)
|
|
12
|
+
type TypeContext = Map<string, DataType>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse and validate an expression against C-style type rules.
|
|
16
|
+
* Replaces ${} placeholders with temporary identifiers, parses with jsep,
|
|
17
|
+
* infers types from the AST, and validates against the expected type.
|
|
18
|
+
*/
|
|
19
|
+
export function parseExpression(expr: ResolvedExpr): ParseResult {
|
|
20
|
+
// Handle early returns
|
|
21
|
+
if (!expr.expression.trim()) {
|
|
22
|
+
return { isValid: false, inferredType: null, errors: ["Expression is empty"] };
|
|
23
|
+
}
|
|
24
|
+
if (expr.variables.some((v) => v === null)) {
|
|
25
|
+
return { isValid: false, inferredType: null, errors: ["Expression contains stale variable references"] };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const errors: string[] = [];
|
|
29
|
+
|
|
30
|
+
// 1. Build type context from variables
|
|
31
|
+
const typeContext: TypeContext = new Map();
|
|
32
|
+
|
|
33
|
+
// Replace ${} placeholders with temp identifiers for jsep
|
|
34
|
+
let parsableExpr = expr.expression;
|
|
35
|
+
|
|
36
|
+
// Count placeholders in expression
|
|
37
|
+
const placeholderCount = (expr.expression.match(/\$\{\}/g) || []).length;
|
|
38
|
+
|
|
39
|
+
if (placeholderCount !== expr.variables.length) {
|
|
40
|
+
errors.push(`Placeholder count (${placeholderCount}) doesn't match reference count (${expr.variables.length})`);
|
|
41
|
+
return { isValid: false, inferredType: null, errors };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
expr.variables.forEach((variable, i) => {
|
|
45
|
+
const placeholder = `__var${i}__`;
|
|
46
|
+
parsableExpr = parsableExpr.replace("${}", placeholder);
|
|
47
|
+
|
|
48
|
+
if (variable) {
|
|
49
|
+
typeContext.set(placeholder, variable.dataType);
|
|
50
|
+
} else {
|
|
51
|
+
// Stale reference - variable was deleted
|
|
52
|
+
errors.push(`Referenced variable at position ${i + 1} not found`);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// If we have stale/null variables, don't proceed with parsing
|
|
57
|
+
if (errors.length > 0) {
|
|
58
|
+
return { isValid: false, inferredType: null, errors };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 2. For string-type expressions, auto-wrap bare text as string literals
|
|
62
|
+
// so users don't need to type quotes. E.g. `hello __var0__` → `"hello " + __var0__`
|
|
63
|
+
if (expr.expectedType === "string") {
|
|
64
|
+
parsableExpr = wrapStringTemplate(parsableExpr);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 3. Parse with jsep
|
|
68
|
+
let ast: jsep.Expression;
|
|
69
|
+
try {
|
|
70
|
+
ast = jsep(parsableExpr);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
73
|
+
return { isValid: false, inferredType: null, errors: [`Parse error: ${message}`] };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 4. Infer type by walking AST
|
|
77
|
+
const inferredType = inferType(ast, typeContext, errors);
|
|
78
|
+
|
|
79
|
+
// 5. Check against expected type
|
|
80
|
+
if (inferredType && expr.expectedType !== inferredType) {
|
|
81
|
+
// Allow implicit conversions (int → float, etc.)
|
|
82
|
+
if (!isCompatible(inferredType, expr.expectedType)) {
|
|
83
|
+
errors.push(`Type mismatch: expression evaluates to '${inferredType}', expected '${expr.expectedType}'`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
isValid: errors.length === 0 && inferredType !== null,
|
|
89
|
+
inferredType,
|
|
90
|
+
errors,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Recursively infer the type of an AST node
|
|
96
|
+
*/
|
|
97
|
+
function inferType(node: jsep.Expression, ctx: TypeContext, errors: string[]): DataType | null {
|
|
98
|
+
switch (node.type) {
|
|
99
|
+
case "Literal":
|
|
100
|
+
return inferLiteralType(node as jsep.Literal);
|
|
101
|
+
|
|
102
|
+
case "Identifier": {
|
|
103
|
+
const name = (node as jsep.Identifier).name;
|
|
104
|
+
const type = ctx.get(name);
|
|
105
|
+
if (!type) {
|
|
106
|
+
// Check if it's a placeholder variable that's missing
|
|
107
|
+
if (name.startsWith("__var") && name.endsWith("__")) {
|
|
108
|
+
errors.push(`Missing type for variable reference`);
|
|
109
|
+
} else {
|
|
110
|
+
errors.push(`Unknown identifier: '${name}'`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return type ?? null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
case "BinaryExpression":
|
|
117
|
+
return inferBinaryType(node as jsep.BinaryExpression, ctx, errors);
|
|
118
|
+
|
|
119
|
+
case "UnaryExpression":
|
|
120
|
+
return inferUnaryType(node as jsep.UnaryExpression, ctx, errors);
|
|
121
|
+
|
|
122
|
+
case "ConditionalExpression": {
|
|
123
|
+
// Ternary: condition ? consequent : alternate
|
|
124
|
+
const cond = node as jsep.ConditionalExpression;
|
|
125
|
+
const condType = inferType(cond.test, ctx, errors);
|
|
126
|
+
if (condType !== "bool") {
|
|
127
|
+
errors.push("Ternary condition must be boolean");
|
|
128
|
+
}
|
|
129
|
+
const consType = inferType(cond.consequent, ctx, errors);
|
|
130
|
+
const altType = inferType(cond.alternate, ctx, errors);
|
|
131
|
+
if (consType && altType && consType !== altType) {
|
|
132
|
+
// Allow numeric promotion
|
|
133
|
+
if (isNumeric(consType) && isNumeric(altType)) {
|
|
134
|
+
return "float";
|
|
135
|
+
}
|
|
136
|
+
errors.push(`Ternary branches must have same type (got '${consType}' and '${altType}')`);
|
|
137
|
+
}
|
|
138
|
+
return consType;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
case "MemberExpression": {
|
|
142
|
+
// For now, we don't support member expressions in C code generation
|
|
143
|
+
errors.push("Member expressions (e.g., obj.property) are not supported");
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
case "CallExpression": {
|
|
148
|
+
return inferCallType(node as jsep.CallExpression, ctx, errors);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
default:
|
|
152
|
+
errors.push(`Unsupported expression type: ${node.type}`);
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Infer the type of a literal value
|
|
159
|
+
*/
|
|
160
|
+
function inferLiteralType(node: jsep.Literal): DataType {
|
|
161
|
+
const value = node.value;
|
|
162
|
+
|
|
163
|
+
if (typeof value === "boolean") return "bool";
|
|
164
|
+
|
|
165
|
+
if (typeof value === "number") {
|
|
166
|
+
// Check if it's an integer or float
|
|
167
|
+
return Number.isInteger(value) ? "int" : "float";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (typeof value === "string") return "string";
|
|
171
|
+
|
|
172
|
+
// null/undefined - default to int
|
|
173
|
+
return "int";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Infer the result type of a binary operation
|
|
178
|
+
*/
|
|
179
|
+
function inferBinaryType(node: jsep.BinaryExpression, ctx: TypeContext, errors: string[]): DataType | null {
|
|
180
|
+
const leftType = inferType(node.left, ctx, errors);
|
|
181
|
+
const rightType = inferType(node.right, ctx, errors);
|
|
182
|
+
const op = node.operator;
|
|
183
|
+
|
|
184
|
+
// If either operand failed to type, propagate null
|
|
185
|
+
if (!leftType || !rightType) return null;
|
|
186
|
+
|
|
187
|
+
// String concatenation: string + any → string
|
|
188
|
+
if (op === "+" && (leftType === "string" || rightType === "string")) {
|
|
189
|
+
return "string";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Arithmetic operators: int/float → int/float (promote to float if either is float)
|
|
193
|
+
if (["+", "-", "*", "/"].includes(op)) {
|
|
194
|
+
if (!isNumeric(leftType) || !isNumeric(rightType)) {
|
|
195
|
+
errors.push(`Operator '${op}' requires numeric operands (got '${leftType}' and '${rightType}')`);
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
return leftType === "float" || rightType === "float" ? "float" : "int";
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Modulo: int only
|
|
202
|
+
if (op === "%") {
|
|
203
|
+
if (leftType !== "int" || rightType !== "int") {
|
|
204
|
+
errors.push(`Operator '%' requires integer operands (got '${leftType}' and '${rightType}')`);
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
return "int";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Comparison operators: numeric → bool
|
|
211
|
+
if (["<", ">", "<=", ">="].includes(op)) {
|
|
212
|
+
if (!isNumeric(leftType) || !isNumeric(rightType)) {
|
|
213
|
+
errors.push(`Operator '${op}' requires numeric operands (got '${leftType}' and '${rightType}')`);
|
|
214
|
+
}
|
|
215
|
+
return "bool";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Equality operators: any → bool (but types should match)
|
|
219
|
+
if (["==", "!="].includes(op)) {
|
|
220
|
+
if (leftType !== rightType && !(isNumeric(leftType) && isNumeric(rightType))) {
|
|
221
|
+
errors.push(`Comparing incompatible types: '${leftType}' and '${rightType}'`);
|
|
222
|
+
}
|
|
223
|
+
return "bool";
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Logical operators: bool → bool
|
|
227
|
+
if (["&&", "||"].includes(op)) {
|
|
228
|
+
if (leftType !== "bool" || rightType !== "bool") {
|
|
229
|
+
errors.push(`Operator '${op}' requires boolean operands (got '${leftType}' and '${rightType}')`);
|
|
230
|
+
}
|
|
231
|
+
return "bool";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Bitwise operators: int → int
|
|
235
|
+
if (["&", "|", "^", "<<", ">>"].includes(op)) {
|
|
236
|
+
if (leftType !== "int" || rightType !== "int") {
|
|
237
|
+
errors.push(`Bitwise operator '${op}' requires integer operands (got '${leftType}' and '${rightType}')`);
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
return "int";
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
errors.push(`Unknown operator: '${op}'`);
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Infer the result type of a unary operation
|
|
249
|
+
*/
|
|
250
|
+
function inferUnaryType(node: jsep.UnaryExpression, ctx: TypeContext, errors: string[]): DataType | null {
|
|
251
|
+
const argType = inferType(node.argument, ctx, errors);
|
|
252
|
+
|
|
253
|
+
if (!argType) return null;
|
|
254
|
+
|
|
255
|
+
// Logical NOT: bool → bool
|
|
256
|
+
if (node.operator === "!") {
|
|
257
|
+
if (argType !== "bool") {
|
|
258
|
+
errors.push(`Logical NOT '!' requires boolean operand (got '${argType}')`);
|
|
259
|
+
}
|
|
260
|
+
return "bool";
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Unary plus/minus: numeric → same type
|
|
264
|
+
if (node.operator === "-" || node.operator === "+") {
|
|
265
|
+
if (!isNumeric(argType)) {
|
|
266
|
+
errors.push(`Unary '${node.operator}' requires numeric operand (got '${argType}')`);
|
|
267
|
+
}
|
|
268
|
+
return argType;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Bitwise NOT: int → int
|
|
272
|
+
if (node.operator === "~") {
|
|
273
|
+
if (argType !== "int") {
|
|
274
|
+
errors.push(`Bitwise NOT '~' requires integer operand (got '${argType}')`);
|
|
275
|
+
}
|
|
276
|
+
return "int";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
errors.push(`Unknown unary operator: '${node.operator}'`);
|
|
280
|
+
return argType;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Cast functions: expression syntax uses int(), float(), bool(), str()
|
|
284
|
+
* which the code generator translates to C casts / conversion calls.
|
|
285
|
+
*/
|
|
286
|
+
const CAST_FUNCTIONS: Record<string, DataType> = {
|
|
287
|
+
int: "int",
|
|
288
|
+
float: "float",
|
|
289
|
+
bool: "bool",
|
|
290
|
+
str: "string",
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Infer the result type of a cast function call, e.g. int(expr), str(expr).
|
|
295
|
+
* Any type can be cast to any other type — it's the user's explicit intent.
|
|
296
|
+
*/
|
|
297
|
+
function inferCallType(node: jsep.CallExpression, ctx: TypeContext, errors: string[]): DataType | null {
|
|
298
|
+
const callee = node.callee;
|
|
299
|
+
if (callee.type !== "Identifier") {
|
|
300
|
+
errors.push("Only cast functions are supported (int, float, bool, str)");
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const name = (callee as jsep.Identifier).name;
|
|
305
|
+
const targetType = CAST_FUNCTIONS[name];
|
|
306
|
+
if (!targetType) {
|
|
307
|
+
errors.push(`Unknown function '${name}'. Available cast functions: int(), float(), bool(), str()`);
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (node.arguments.length !== 1) {
|
|
312
|
+
errors.push(`${name}() expects exactly 1 argument, got ${node.arguments.length}`);
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Validate the argument expression (ensure it type-checks). Length checked as 1 above.
|
|
317
|
+
inferType(node.arguments[0]!, ctx, errors);
|
|
318
|
+
|
|
319
|
+
return targetType;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Check if a type is numeric (int or float)
|
|
324
|
+
*/
|
|
325
|
+
function isNumeric(type: DataType | null): boolean {
|
|
326
|
+
return type === "int" || type === "float";
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Check if a type can be implicitly converted to another.
|
|
331
|
+
* - int ↔ float: both directions (truncation for float→int, common in embedded)
|
|
332
|
+
* - any → string: allowed (format strings)
|
|
333
|
+
* - bool is strict: requires explicit cast to/from bool
|
|
334
|
+
*/
|
|
335
|
+
function isCompatible(from: DataType, to: DataType): boolean {
|
|
336
|
+
if (from === to) return true;
|
|
337
|
+
if (isNumeric(from) && isNumeric(to)) return true;
|
|
338
|
+
if (to === "string") return true;
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Wrap bare text in a string-type expression into quoted string literals for jsep.
|
|
344
|
+
* Splits by variable placeholders (__varN__), quotes text segments, joins with +.
|
|
345
|
+
* E.g. `hello __var0__` → `"hello " + __var0__`
|
|
346
|
+
*/
|
|
347
|
+
function wrapStringTemplate(expr: string): string {
|
|
348
|
+
// Split by __varN__ placeholders but keep them as delimiters
|
|
349
|
+
const parts = expr.split(/(__var\d+__)/);
|
|
350
|
+
const segments: string[] = [];
|
|
351
|
+
|
|
352
|
+
for (const part of parts) {
|
|
353
|
+
if (/^__var\d+__$/.test(part)) {
|
|
354
|
+
segments.push(part);
|
|
355
|
+
} else if (part !== "") {
|
|
356
|
+
const escaped = part.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
357
|
+
segments.push(`"${escaped}"`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return segments.join(" + ");
|
|
362
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { NodeOutput } from "../node";
|
|
2
|
+
import type { DataType, Expression } from "../api";
|
|
3
|
+
import type { Variable } from "../variable";
|
|
4
|
+
import { refToLookupKey } from "../variable";
|
|
5
|
+
|
|
6
|
+
// ResolvedExpr represents an expression with its variable references resolved to runtime variables
|
|
7
|
+
export interface ResolvedExpr {
|
|
8
|
+
expression: string; // The expression string with variable references, e.g. "${} + ${}"
|
|
9
|
+
variables: (NodeOutput | null)[]; // List of variables used in the expression (null if stale/invalid)
|
|
10
|
+
expectedType: DataType; // The expected data type of the expression result
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Resolve an expression by converting variable references to runtime variables
|
|
14
|
+
export function resolveExpression(apiExpr: Expression, availableVars: Record<string, Variable>): ResolvedExpr {
|
|
15
|
+
return {
|
|
16
|
+
expression: apiExpr.expression,
|
|
17
|
+
expectedType: apiExpr.dataType,
|
|
18
|
+
variables: apiExpr.references.map((ref) => {
|
|
19
|
+
if (!ref.varId) return null;
|
|
20
|
+
const key = refToLookupKey(ref);
|
|
21
|
+
const v = availableVars[key];
|
|
22
|
+
return v ? { name: v.name, dataType: v.dataType } : null;
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Check if a value is an expression (has references array)
|
|
28
|
+
export function isExpression(value: unknown): value is Expression {
|
|
29
|
+
return typeof value === "object" && value !== null && "expression" in value && "references" in value;
|
|
30
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Expression, Schemas } from "../api";
|
|
2
|
+
import type { ApiVariable } from "../variable";
|
|
3
|
+
import type { DataType } from "../parameter";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The flat call-site signature of a function: identity + version + ports, with no
|
|
7
|
+
* expressions (the contract's `FunctionInfo`, used both inside `Function` on the wire
|
|
8
|
+
* and here in the domain). It is the snapshot a {@link FunctionCallNode} caches — to
|
|
9
|
+
* detect drift against the live declaration and render its ports without a registry
|
|
10
|
+
* lookup — and the shape {@link buildFunctionNodeDef} consumes.
|
|
11
|
+
*/
|
|
12
|
+
export type FunctionInfo = Schemas["FunctionInfo"];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A function output: its declaration (`uid`/`name`/`dataType`) bundled with the
|
|
16
|
+
* `expression` that produces it (evaluated in callee scope at the function's end).
|
|
17
|
+
* Acts as a supertype of {@link ApiVariable}.
|
|
18
|
+
*/
|
|
19
|
+
export interface OutputAssignment {
|
|
20
|
+
uid: string;
|
|
21
|
+
name: string;
|
|
22
|
+
dataType: DataType;
|
|
23
|
+
expression: Expression;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The domain function declaration: a signature with its outputs bundled
|
|
28
|
+
* (declaration + assignment per output).
|
|
29
|
+
* Separate from the function body (which is a canvas of nodes, edges, variables).
|
|
30
|
+
*/
|
|
31
|
+
export interface FunctionDeclaration {
|
|
32
|
+
id: string;
|
|
33
|
+
version: number;
|
|
34
|
+
name: string;
|
|
35
|
+
arguments: ApiVariable[];
|
|
36
|
+
outputs: OutputAssignment[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Project a declaration to the flat call-site signature, dropping the expressions
|
|
41
|
+
* (the caller has no business storing the callee's internals). Used only when
|
|
42
|
+
* crossing from declaration to a call-site snapshot — dropping or migrating a
|
|
43
|
+
* `FunctionCall` node — and by serialization. Never used to represent a function
|
|
44
|
+
* within the domain.
|
|
45
|
+
*/
|
|
46
|
+
export function toFunctionInfo(fn: FunctionDeclaration): FunctionInfo {
|
|
47
|
+
return {
|
|
48
|
+
id: fn.id,
|
|
49
|
+
version: fn.version,
|
|
50
|
+
name: fn.name,
|
|
51
|
+
arguments: fn.arguments,
|
|
52
|
+
returns: fn.outputs.map(({ uid, name, dataType }) => ({ uid, name, dataType })),
|
|
53
|
+
};
|
|
54
|
+
}
|