@dypai-ai/workflow-core 0.1.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/fixtures/capability-catalog.json +126 -0
- package/fixtures/legacy-create-booking.yaml +40 -0
- package/package.json +40 -0
- package/src/adapters/adapters.test.ts +168 -0
- package/src/adapters/engineBinding.ts +35 -0
- package/src/adapters/flowDefinitionToIr.ts +141 -0
- package/src/adapters/irToWorkflowCodeV2.ts +293 -0
- package/src/adapters/legacyYamlToIr.ts +340 -0
- package/src/adapters/placeholderToRef.ts +74 -0
- package/src/adapters/refToLegacyPlaceholder.ts +33 -0
- package/src/adapters/sqlBuilders.ts +81 -0
- package/src/adapters/triggers.ts +86 -0
- package/src/adapters/types.ts +15 -0
- package/src/adapters/workflowCodeTypes.ts +45 -0
- package/src/capabilities/agentBrief.ts +42 -0
- package/src/capabilities/capabilities.test.ts +126 -0
- package/src/capabilities/capabilityRegistry.ts +112 -0
- package/src/capabilities/catalogSchema.ts +14 -0
- package/src/capabilities/fromCatalog.ts +30 -0
- package/src/capabilities/index.ts +35 -0
- package/src/capabilities/types.ts +57 -0
- package/src/fixtures/createBooking.flow.ts +64 -0
- package/src/fixtures/createBooking.ir.ts +103 -0
- package/src/fixtures/listBookings.ir.ts +61 -0
- package/src/index.ts +172 -0
- package/src/ir/refs.ts +103 -0
- package/src/ir/schema.ts +149 -0
- package/src/ir/sourceMap.ts +59 -0
- package/src/ir/types.ts +147 -0
- package/src/ir/validate.test.ts +181 -0
- package/src/ir/validate.ts +365 -0
- package/src/registry/defineNode.ts +19 -0
- package/src/registry/nodeRegistry.ts +87 -0
- package/src/registry/nodes/dypaiDb.ts +164 -0
- package/src/registry/nodes/dypaiEmail.ts +57 -0
- package/src/registry/nodes/dypaiFlow.ts +25 -0
- package/src/registry/nodes/legacyWorkflow.ts +27 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "capability-catalog.v1",
|
|
3
|
+
"engine_version": "test-fixture",
|
|
4
|
+
"content_hash": "fixture-capability-catalog",
|
|
5
|
+
"groups": [
|
|
6
|
+
{
|
|
7
|
+
"id": "dypai.db",
|
|
8
|
+
"namespace": "dypai",
|
|
9
|
+
"resource": "db",
|
|
10
|
+
"version": 1,
|
|
11
|
+
"label": "DYPAI Database",
|
|
12
|
+
"category": "database",
|
|
13
|
+
"docs": "Declarative and SQL database operations backed by Postgres.",
|
|
14
|
+
"operations": [
|
|
15
|
+
{
|
|
16
|
+
"id": "dypai.db.query",
|
|
17
|
+
"group_id": "dypai.db",
|
|
18
|
+
"operation": "query",
|
|
19
|
+
"name": "Run SQL query",
|
|
20
|
+
"authoring_signature": "dypai.db.query({ sql, params? })",
|
|
21
|
+
"input_schema": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"required": ["sql"],
|
|
24
|
+
"properties": {
|
|
25
|
+
"sql": { "type": "string" },
|
|
26
|
+
"params": { "type": "object" }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"side_effects": ["db.read"],
|
|
30
|
+
"engine_binding": {
|
|
31
|
+
"node_type": "dypai_database",
|
|
32
|
+
"operation": "query",
|
|
33
|
+
"adapter": "dypai.db"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "dypai.db.insert",
|
|
38
|
+
"group_id": "dypai.db",
|
|
39
|
+
"operation": "insert",
|
|
40
|
+
"name": "Insert row",
|
|
41
|
+
"authoring_signature": "dypai.db.insert({ table, values, returning? })",
|
|
42
|
+
"input_schema": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"required": ["table", "values"],
|
|
45
|
+
"properties": {
|
|
46
|
+
"table": { "type": "string" },
|
|
47
|
+
"values": { "type": "object" },
|
|
48
|
+
"returning": { "type": "array", "items": { "type": "string" } }
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"side_effects": ["db.write"],
|
|
52
|
+
"engine_binding": {
|
|
53
|
+
"node_type": "dypai_database",
|
|
54
|
+
"operation": "query",
|
|
55
|
+
"adapter": "dypai.db"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "stripe.checkout",
|
|
62
|
+
"namespace": "stripe",
|
|
63
|
+
"resource": "checkout",
|
|
64
|
+
"version": 1,
|
|
65
|
+
"label": "Stripe Checkout",
|
|
66
|
+
"category": "payments",
|
|
67
|
+
"provider": "Stripe",
|
|
68
|
+
"operations": [
|
|
69
|
+
{
|
|
70
|
+
"id": "stripe.checkout.createSession",
|
|
71
|
+
"group_id": "stripe.checkout",
|
|
72
|
+
"operation": "createSession",
|
|
73
|
+
"name": "Create checkout session",
|
|
74
|
+
"authoring_signature": "stripe.checkout.createSession({ mode, price_id, success_url, cancel_url, metadata? })",
|
|
75
|
+
"input_schema": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"required": ["mode", "price_id", "success_url", "cancel_url"],
|
|
78
|
+
"properties": {
|
|
79
|
+
"mode": { "type": "string" },
|
|
80
|
+
"price_id": { "type": "string" },
|
|
81
|
+
"success_url": { "type": "string" },
|
|
82
|
+
"cancel_url": { "type": "string" },
|
|
83
|
+
"metadata": { "type": "object" }
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"required_credentials": ["stripe"],
|
|
87
|
+
"engine_binding": {
|
|
88
|
+
"node_type": "stripe",
|
|
89
|
+
"operation": "stripe_create_checkout_session"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "google.sheets",
|
|
96
|
+
"namespace": "google",
|
|
97
|
+
"resource": "sheets",
|
|
98
|
+
"version": 1,
|
|
99
|
+
"label": "Google Sheets",
|
|
100
|
+
"category": "data",
|
|
101
|
+
"operations": [
|
|
102
|
+
{
|
|
103
|
+
"id": "google.sheets.readRows",
|
|
104
|
+
"group_id": "google.sheets",
|
|
105
|
+
"operation": "readRows",
|
|
106
|
+
"name": "Read rows",
|
|
107
|
+
"authoring_signature": "google.sheets.readRows({ spreadsheet_id, sheet_name?, range? })",
|
|
108
|
+
"input_schema": {
|
|
109
|
+
"type": "object",
|
|
110
|
+
"required": ["spreadsheet_id"],
|
|
111
|
+
"properties": {
|
|
112
|
+
"spreadsheet_id": { "type": "string" },
|
|
113
|
+
"sheet_name": { "type": "string" },
|
|
114
|
+
"range": { "type": "string" }
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"required_credentials": ["google_sheets"],
|
|
118
|
+
"engine_binding": {
|
|
119
|
+
"node_type": "google_sheets",
|
|
120
|
+
"operation": "read_rows"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: create-booking
|
|
2
|
+
method: POST
|
|
3
|
+
trigger:
|
|
4
|
+
http_api:
|
|
5
|
+
auth_mode: public
|
|
6
|
+
input:
|
|
7
|
+
type: object
|
|
8
|
+
required: [name, email, date]
|
|
9
|
+
additionalProperties: false
|
|
10
|
+
properties:
|
|
11
|
+
name:
|
|
12
|
+
type: string
|
|
13
|
+
email:
|
|
14
|
+
type: string
|
|
15
|
+
format: email
|
|
16
|
+
date:
|
|
17
|
+
type: string
|
|
18
|
+
format: date
|
|
19
|
+
output:
|
|
20
|
+
type: object
|
|
21
|
+
additionalProperties: false
|
|
22
|
+
properties:
|
|
23
|
+
id:
|
|
24
|
+
type: string
|
|
25
|
+
name:
|
|
26
|
+
type: string
|
|
27
|
+
email:
|
|
28
|
+
type: string
|
|
29
|
+
date:
|
|
30
|
+
type: string
|
|
31
|
+
response_cardinality: single
|
|
32
|
+
workflow:
|
|
33
|
+
nodes:
|
|
34
|
+
- id: booking
|
|
35
|
+
type: dypai_database
|
|
36
|
+
operation: query
|
|
37
|
+
query: |
|
|
38
|
+
INSERT INTO public.bookings (name, email, date)
|
|
39
|
+
VALUES (${input.name}, ${input.email}, ${input.date})
|
|
40
|
+
RETURNING id, name, email, date
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dypai-ai/workflow-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Workflow IR, adapters, and validation for DYPAI Flow endpoint authoring",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/DYPAI-SOLUTIONS/dypai.git"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://dypai.ai",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"dypai",
|
|
17
|
+
"workflow",
|
|
18
|
+
"flow",
|
|
19
|
+
"ir"
|
|
20
|
+
],
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./src/index.ts"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"src/",
|
|
26
|
+
"fixtures/",
|
|
27
|
+
"tsconfig.json"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"test": "bun test",
|
|
31
|
+
"typecheck": "bunx tsc --noEmit"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"yaml": "^2.6.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/bun": "^1.1.14",
|
|
38
|
+
"typescript": "^5.7.2"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { describe, expect, test } from "bun:test";
|
|
5
|
+
import { flowDefinitionToIr } from "./flowDefinitionToIr";
|
|
6
|
+
import { irToWorkflowCodeV2, stableStringifyWorkflowCode } from "./irToWorkflowCodeV2";
|
|
7
|
+
import { legacyYamlToIr } from "./legacyYamlToIr";
|
|
8
|
+
import { placeholderToRef } from "./placeholderToRef";
|
|
9
|
+
import { createBookingFlowDefinition } from "../fixtures/createBooking.flow";
|
|
10
|
+
import { createBookingWorkflowIR } from "../fixtures/createBooking.ir";
|
|
11
|
+
import { listBookingsWorkflowIR } from "../fixtures/listBookings.ir";
|
|
12
|
+
import { validateWorkflowIR } from "../ir/validate";
|
|
13
|
+
|
|
14
|
+
const fixturesDir = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "fixtures");
|
|
15
|
+
|
|
16
|
+
describe("legacyYamlToIr", () => {
|
|
17
|
+
test("imports simple CRUD YAML into WorkflowIR with YAML source maps", () => {
|
|
18
|
+
const yamlText = readFileSync(join(fixturesDir, "legacy-create-booking.yaml"), "utf-8");
|
|
19
|
+
const imported = legacyYamlToIr(yamlText, { file: "dypai/endpoints/create-booking.yaml" });
|
|
20
|
+
|
|
21
|
+
expect(imported.ok).toBe(true);
|
|
22
|
+
expect(imported.value?.name).toBe("create-booking");
|
|
23
|
+
expect(imported.value?.source?.kind).toBe("legacy-yaml");
|
|
24
|
+
expect(imported.value?.steps.some((step) => step.node === "dypai.db" && step.operation === "query")).toBe(true);
|
|
25
|
+
|
|
26
|
+
const validated = validateWorkflowIR(imported.value);
|
|
27
|
+
expect(validated.ok).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("flags mutation+query anti-pattern during import", () => {
|
|
31
|
+
const yamlText = `
|
|
32
|
+
name: bad-create
|
|
33
|
+
method: POST
|
|
34
|
+
trigger:
|
|
35
|
+
http_api:
|
|
36
|
+
auth_mode: public
|
|
37
|
+
input:
|
|
38
|
+
type: object
|
|
39
|
+
properties: {}
|
|
40
|
+
output:
|
|
41
|
+
type: object
|
|
42
|
+
properties:
|
|
43
|
+
id:
|
|
44
|
+
type: string
|
|
45
|
+
workflow:
|
|
46
|
+
nodes:
|
|
47
|
+
- id: create
|
|
48
|
+
type: dypai_database
|
|
49
|
+
operation: mutation
|
|
50
|
+
query: INSERT INTO public.items (name) VALUES (\${input.name}) RETURNING id
|
|
51
|
+
`;
|
|
52
|
+
const imported = legacyYamlToIr(yamlText, { file: "dypai/endpoints/bad-create.yaml" });
|
|
53
|
+
expect(imported.ok).toBe(false);
|
|
54
|
+
expect(imported.diagnostics.some((item) => item.rule === "legacy_mutation_with_query")).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("flowDefinitionToIr", () => {
|
|
59
|
+
test("compiles FlowDefinition fixture to valid WorkflowIR", () => {
|
|
60
|
+
const workflow = flowDefinitionToIr(createBookingFlowDefinition);
|
|
61
|
+
const validated = validateWorkflowIR(workflow);
|
|
62
|
+
expect(validated.ok).toBe(true);
|
|
63
|
+
expect(workflow.steps.some((step) => step.node === "dypai.db" && step.operation === "insert")).toBe(true);
|
|
64
|
+
expect(workflow.responseMap.booking.kind).toBe("step");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("irToWorkflowCodeV2", () => {
|
|
69
|
+
test("adapts create-booking IR without invalid engine patterns", () => {
|
|
70
|
+
const adapted = irToWorkflowCodeV2(createBookingWorkflowIR);
|
|
71
|
+
expect(adapted.ok).toBe(true);
|
|
72
|
+
expect(adapted.value?.schema_version).toBe("2.0");
|
|
73
|
+
|
|
74
|
+
const databaseNode = adapted.value?.nodes.find((node) => node.id === "booking");
|
|
75
|
+
expect(databaseNode?.node_type).toBe("dypai_database");
|
|
76
|
+
expect(databaseNode?.parameters.operation).toBe("query");
|
|
77
|
+
expect(databaseNode?.parameters.query).toContain("INSERT INTO public.bookings");
|
|
78
|
+
expect(databaseNode?.parameters.operation).not.toBe("mutation");
|
|
79
|
+
|
|
80
|
+
const composeNode = adapted.value?.nodes.find((node) => node.is_return);
|
|
81
|
+
expect(composeNode?.node_type).toBe("set_fields");
|
|
82
|
+
expect(composeNode?.parameters.operation).toBe("compose");
|
|
83
|
+
expect(composeNode?.parameters.fields).toEqual({
|
|
84
|
+
booking: "${vars.booking.row}",
|
|
85
|
+
});
|
|
86
|
+
expect(adapted.value?.edges.some((edge) => edge.target === "respond")).toBe(false);
|
|
87
|
+
expect(adapted.value?.edges).toEqual([
|
|
88
|
+
{ source: "booking", target: "__response" },
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("adapts list-bookings IR with jwt trigger metadata", () => {
|
|
93
|
+
expect(validateWorkflowIR(listBookingsWorkflowIR).ok).toBe(true);
|
|
94
|
+
const adapted = irToWorkflowCodeV2(listBookingsWorkflowIR);
|
|
95
|
+
expect(adapted.ok).toBe(true);
|
|
96
|
+
expect(adapted.value?.metadata?.response_cardinality).toBe("many");
|
|
97
|
+
expect(adapted.value?.execution_config.triggers.http_api).toMatchObject({
|
|
98
|
+
enabled: true,
|
|
99
|
+
auth_mode: "jwt",
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const listNode = adapted.value?.nodes.find((node) => node.id === "bookings");
|
|
103
|
+
expect(String(listNode?.parameters.query)).toContain("SELECT * FROM public.bookings");
|
|
104
|
+
expect(String(listNode?.parameters.query)).toContain("${current_user_id}");
|
|
105
|
+
expect(String(listNode?.parameters.query)).toContain("LIMIT ${input.limit}");
|
|
106
|
+
expect(String(listNode?.parameters.query)).toContain("OFFSET ${input.offset}");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("generates deterministic workflow_code for golden comparison", () => {
|
|
110
|
+
const first = stableStringifyWorkflowCode(irToWorkflowCodeV2(createBookingWorkflowIR).value!);
|
|
111
|
+
const second = stableStringifyWorkflowCode(irToWorkflowCodeV2(createBookingWorkflowIR).value!);
|
|
112
|
+
expect(first).toBe(second);
|
|
113
|
+
expect(first).toContain('"schema_version": "2.0"');
|
|
114
|
+
expect(first).not.toContain("operation\": \"mutation\"");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("round-trips legacy YAML -> IR -> workflow_code for create-booking", () => {
|
|
118
|
+
const yamlText = readFileSync(join(fixturesDir, "legacy-create-booking.yaml"), "utf-8");
|
|
119
|
+
const imported = legacyYamlToIr(yamlText, { file: "dypai/endpoints/create-booking.yaml" });
|
|
120
|
+
expect(imported.ok).toBe(true);
|
|
121
|
+
|
|
122
|
+
const validated = validateWorkflowIR(imported.value);
|
|
123
|
+
expect(validated.ok).toBe(true);
|
|
124
|
+
|
|
125
|
+
const adapted = irToWorkflowCodeV2(imported.value!);
|
|
126
|
+
expect(adapted.ok).toBe(true);
|
|
127
|
+
expect(adapted.value?.nodes.some((node) => node.node_type === "dypai_database")).toBe(true);
|
|
128
|
+
expect(adapted.value?.nodes.some((node) => node.node_type === "set_fields" && node.is_return)).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("legacy compatibility helpers", () => {
|
|
133
|
+
test("parses indexed vars placeholders into step refs", () => {
|
|
134
|
+
expect(placeholderToRef("${vars.count[0].total}")).toEqual({
|
|
135
|
+
kind: "step",
|
|
136
|
+
stepId: "count",
|
|
137
|
+
path: ["0", "total"],
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("imports unsupported legacy nodes as valid legacy.workflow steps", () => {
|
|
142
|
+
const yamlText = `
|
|
143
|
+
name: proxy
|
|
144
|
+
method: POST
|
|
145
|
+
trigger:
|
|
146
|
+
http_api:
|
|
147
|
+
auth_mode: public
|
|
148
|
+
input:
|
|
149
|
+
type: object
|
|
150
|
+
properties: {}
|
|
151
|
+
output:
|
|
152
|
+
type: object
|
|
153
|
+
properties:
|
|
154
|
+
ok:
|
|
155
|
+
type: boolean
|
|
156
|
+
workflow:
|
|
157
|
+
nodes:
|
|
158
|
+
- id: call_api
|
|
159
|
+
type: http_request
|
|
160
|
+
method: GET
|
|
161
|
+
url: https://example.com
|
|
162
|
+
`;
|
|
163
|
+
const imported = legacyYamlToIr(yamlText, { file: "dypai/endpoints/proxy.yaml" });
|
|
164
|
+
expect(imported.ok).toBe(true);
|
|
165
|
+
expect(imported.value?.steps.some((step) => step.node === "legacy.workflow")).toBe(true);
|
|
166
|
+
expect(validateWorkflowIR(imported.value).ok).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { EngineBindingJson } from "../capabilities/types";
|
|
2
|
+
import { serializeConfig } from "./refToLegacyPlaceholder";
|
|
3
|
+
|
|
4
|
+
export function applyParameterMap(
|
|
5
|
+
config: Record<string, unknown>,
|
|
6
|
+
binding: EngineBindingJson,
|
|
7
|
+
): Record<string, unknown> {
|
|
8
|
+
if (!binding.parameter_map) {
|
|
9
|
+
return config;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const mapped: Record<string, unknown> = {};
|
|
13
|
+
for (const [key, value] of Object.entries(config)) {
|
|
14
|
+
mapped[binding.parameter_map[key] ?? key] = value;
|
|
15
|
+
}
|
|
16
|
+
return mapped;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function buildEngineParameters(
|
|
20
|
+
config: Record<string, unknown>,
|
|
21
|
+
binding: EngineBindingJson,
|
|
22
|
+
): Record<string, unknown> {
|
|
23
|
+
const mapped = applyParameterMap(config, binding);
|
|
24
|
+
const parameters = serializeConfig(mapped);
|
|
25
|
+
|
|
26
|
+
if (binding.operation) {
|
|
27
|
+
parameters.operation = binding.operation;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (binding.static_parameters) {
|
|
31
|
+
Object.assign(parameters, binding.static_parameters);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return parameters;
|
|
35
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { CapabilityCatalog } from "../capabilities/types";
|
|
2
|
+
import { CapabilityRegistry, resolveCapabilityCall as resolveCatalogCall } from "../capabilities/capabilityRegistry";
|
|
3
|
+
import type {
|
|
4
|
+
AuthIR,
|
|
5
|
+
JsonSchema,
|
|
6
|
+
RefIR,
|
|
7
|
+
ResponseIR,
|
|
8
|
+
SourceLocation,
|
|
9
|
+
TriggerIR,
|
|
10
|
+
WorkflowIR,
|
|
11
|
+
WorkflowStepIR,
|
|
12
|
+
} from "../ir/types";
|
|
13
|
+
import { WORKFLOW_IR_VERSION } from "../ir/types";
|
|
14
|
+
|
|
15
|
+
export type FlowCompileContext = {
|
|
16
|
+
capabilityCatalog?: CapabilityCatalog;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type FlowStepDefinition = {
|
|
20
|
+
id: string;
|
|
21
|
+
call: string;
|
|
22
|
+
config: Record<string, unknown>;
|
|
23
|
+
source?: SourceLocation;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type FlowDefinition = {
|
|
27
|
+
name: string;
|
|
28
|
+
source?: {
|
|
29
|
+
kind: "flow-ts";
|
|
30
|
+
file: string;
|
|
31
|
+
};
|
|
32
|
+
endpoint?: {
|
|
33
|
+
description?: string;
|
|
34
|
+
isTool?: boolean;
|
|
35
|
+
toolDescription?: string;
|
|
36
|
+
};
|
|
37
|
+
trigger: TriggerIR;
|
|
38
|
+
auth: AuthIR;
|
|
39
|
+
input: JsonSchema;
|
|
40
|
+
output: JsonSchema;
|
|
41
|
+
response?: ResponseIR;
|
|
42
|
+
steps: FlowStepDefinition[];
|
|
43
|
+
returns: Record<string, RefIR>;
|
|
44
|
+
edges?: Array<{ from: string; to: string }>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const LEGACY_FLOW_ALIASES: Record<string, { node: string; operation: string; version: number }> = {
|
|
48
|
+
"db.query": { node: "dypai.db", operation: "query", version: 1 },
|
|
49
|
+
"db.insert": { node: "dypai.db", operation: "insert", version: 1 },
|
|
50
|
+
"db.update": { node: "dypai.db", operation: "update", version: 1 },
|
|
51
|
+
"db.list": { node: "dypai.db", operation: "list", version: 1 },
|
|
52
|
+
"email.send": { node: "dypai.email", operation: "send", version: 1 },
|
|
53
|
+
"flow.return": { node: "dypai.flow", operation: "return", version: 1 },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const BUILTIN_AUTHORING_GROUPS = new Set(["dypai.db", "dypai.email"]);
|
|
57
|
+
|
|
58
|
+
export function resolveFlowAlias(
|
|
59
|
+
call: string,
|
|
60
|
+
context: FlowCompileContext = {},
|
|
61
|
+
): { node: string; operation: string; version: number } | null {
|
|
62
|
+
const trimmed = call.trim();
|
|
63
|
+
if (LEGACY_FLOW_ALIASES[trimmed]) return LEGACY_FLOW_ALIASES[trimmed];
|
|
64
|
+
|
|
65
|
+
const registry = context.capabilityCatalog
|
|
66
|
+
? CapabilityRegistry.fromCatalog(context.capabilityCatalog)
|
|
67
|
+
: new CapabilityRegistry();
|
|
68
|
+
|
|
69
|
+
const resolved = resolveCatalogCall(trimmed, registry);
|
|
70
|
+
if (resolved) return resolved;
|
|
71
|
+
|
|
72
|
+
const parts = trimmed.split(".");
|
|
73
|
+
for (let splitAt = parts.length - 1; splitAt >= 2; splitAt -= 1) {
|
|
74
|
+
const groupId = parts.slice(0, splitAt).join(".");
|
|
75
|
+
if (!BUILTIN_AUTHORING_GROUPS.has(groupId)) continue;
|
|
76
|
+
const operation = parts.slice(splitAt).join(".");
|
|
77
|
+
return { node: groupId, operation, version: 1 };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function flowDefinitionToIr(
|
|
84
|
+
definition: FlowDefinition,
|
|
85
|
+
context: FlowCompileContext = {},
|
|
86
|
+
): WorkflowIR {
|
|
87
|
+
const steps: WorkflowStepIR[] = definition.steps.map((step) => {
|
|
88
|
+
const resolved = resolveFlowAlias(step.call, context);
|
|
89
|
+
if (!resolved) {
|
|
90
|
+
return {
|
|
91
|
+
id: step.id,
|
|
92
|
+
node: "legacy.workflow",
|
|
93
|
+
operation: "node",
|
|
94
|
+
version: 1,
|
|
95
|
+
config: {
|
|
96
|
+
legacyNode: {
|
|
97
|
+
id: step.id,
|
|
98
|
+
type: step.call,
|
|
99
|
+
...step.config,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
source: step.source,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
id: step.id,
|
|
108
|
+
node: resolved.node,
|
|
109
|
+
operation: resolved.operation,
|
|
110
|
+
version: resolved.version,
|
|
111
|
+
config: step.config,
|
|
112
|
+
source: step.source,
|
|
113
|
+
...(resolved.node === "dypai.flow" && resolved.operation === "return" ? { return: true } : {}),
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (!steps.some((step) => step.node === "dypai.flow" && step.operation === "return")) {
|
|
118
|
+
steps.push({
|
|
119
|
+
id: "return",
|
|
120
|
+
node: "dypai.flow",
|
|
121
|
+
operation: "return",
|
|
122
|
+
version: 1,
|
|
123
|
+
config: {},
|
|
124
|
+
return: true,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
version: WORKFLOW_IR_VERSION,
|
|
130
|
+
name: definition.name,
|
|
131
|
+
source: definition.source,
|
|
132
|
+
trigger: definition.trigger,
|
|
133
|
+
auth: definition.auth,
|
|
134
|
+
inputSchema: definition.input,
|
|
135
|
+
outputSchema: definition.output,
|
|
136
|
+
response: definition.response || { cardinality: "single" },
|
|
137
|
+
steps,
|
|
138
|
+
edges: definition.edges,
|
|
139
|
+
responseMap: definition.returns,
|
|
140
|
+
};
|
|
141
|
+
}
|