@darkhorseprojects/circuitry 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle.d.ts +2 -1
- package/dist/cli.js +102 -252
- package/dist/graph.d.ts +11 -63
- package/dist/index.js +129 -309
- package/dist/node.d.ts +3 -3
- package/dist/node.js +123 -311
- package/dist/presets.d.ts +1 -1
- package/dist/scheduler.d.ts +1 -1
- package/dist/simulation.d.ts +3 -4
- package/dist/types.d.ts +0 -1
- package/dist/yaml.d.ts +4 -2
- package/package.json +1 -1
package/dist/bundle.d.ts
CHANGED
|
@@ -16,13 +16,14 @@ type SceneElement = {
|
|
|
16
16
|
elementId?: string;
|
|
17
17
|
} | null;
|
|
18
18
|
};
|
|
19
|
-
export declare const BUNDLE_MIME = "application/vnd.circuitry.bundle+
|
|
19
|
+
export declare const BUNDLE_MIME = "application/vnd.circuitry.bundle+yaml";
|
|
20
20
|
export type CircuitryBundle = {
|
|
21
21
|
version: 1;
|
|
22
22
|
createdAt: string;
|
|
23
23
|
elements: SceneElement[];
|
|
24
24
|
};
|
|
25
25
|
export declare const createBundleFromSelection: (elements: readonly SceneElement[], selectedIds: Readonly<Record<string, true>>) => CircuitryBundle | null;
|
|
26
|
+
export declare const stringifyBundle: (bundle: CircuitryBundle) => string;
|
|
26
27
|
export declare const parseBundleText: (text: string) => CircuitryBundle;
|
|
27
28
|
export declare const importBundleElements: (bundle: CircuitryBundle, opts?: {
|
|
28
29
|
offsetX?: number;
|
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
5
|
+
|
|
3
6
|
// src/node.ts
|
|
4
7
|
import { access, readFile, writeFile } from "node:fs/promises";
|
|
5
8
|
import path from "node:path";
|
|
6
9
|
|
|
7
10
|
// src/yaml.ts
|
|
8
|
-
import
|
|
11
|
+
import YAML2 from "yaml";
|
|
9
12
|
|
|
10
13
|
// src/graph.ts
|
|
11
|
-
|
|
14
|
+
import YAML from "yaml";
|
|
15
|
+
var CIRCUITRY_SPEC_VERSION = "0.3.2";
|
|
12
16
|
var DEFAULT_CIRCUITRY_VALIDATION_RULES = [
|
|
13
17
|
"no-self-loops",
|
|
14
18
|
"no-unknown-edge-endpoints",
|
|
@@ -21,25 +25,15 @@ var DEFAULT_CIRCUITRY_VALIDATION_STANDARD = {
|
|
|
21
25
|
rules: DEFAULT_CIRCUITRY_VALIDATION_RULES,
|
|
22
26
|
executableKinds: ["agent", "tool", "output"]
|
|
23
27
|
};
|
|
24
|
-
var
|
|
25
|
-
|
|
26
|
-
(entry) => typeof entry !== "string" && entry.rule === "require-executable-inputs"
|
|
27
|
-
);
|
|
28
|
-
return executableRule?.executableKinds;
|
|
29
|
-
};
|
|
28
|
+
var ruleName = (rule) => typeof rule === "string" ? rule : rule.rule;
|
|
29
|
+
var executableKindsFromRules = (rules) => rules.find((rule) => typeof rule !== "string" && rule.rule === "require-executable-inputs")?.executableKinds;
|
|
30
30
|
var createCircuitryValidationStandard = (standard = {}, graph) => {
|
|
31
|
-
const rules = [
|
|
32
|
-
...DEFAULT_CIRCUITRY_VALIDATION_RULES,
|
|
33
|
-
...graph?.validation?.rules || [],
|
|
34
|
-
...standard.rules || []
|
|
35
|
-
];
|
|
31
|
+
const rules = [...DEFAULT_CIRCUITRY_VALIDATION_RULES, ...graph?.validation?.rules || [], ...standard.rules || []];
|
|
36
32
|
return {
|
|
37
33
|
version: standard.version || DEFAULT_CIRCUITRY_VALIDATION_STANDARD.version,
|
|
38
34
|
requireSpecVersion: standard.requireSpecVersion ?? DEFAULT_CIRCUITRY_VALIDATION_STANDARD.requireSpecVersion,
|
|
39
35
|
rules,
|
|
40
|
-
executableKinds: standard.executableKinds || executableKindsFromRules(rules) || [
|
|
41
|
-
...DEFAULT_CIRCUITRY_VALIDATION_STANDARD.executableKinds
|
|
42
|
-
]
|
|
36
|
+
executableKinds: standard.executableKinds || executableKindsFromRules(rules) || [...DEFAULT_CIRCUITRY_VALIDATION_STANDARD.executableKinds]
|
|
43
37
|
};
|
|
44
38
|
};
|
|
45
39
|
var expandResources = (resources) => {
|
|
@@ -47,12 +41,7 @@ var expandResources = (resources) => {
|
|
|
47
41
|
const edges = [];
|
|
48
42
|
for (const [id, resource] of Object.entries(resources)) {
|
|
49
43
|
if (resource.type === "text") {
|
|
50
|
-
nodes.push({
|
|
51
|
-
id,
|
|
52
|
-
kind: "input",
|
|
53
|
-
label: resource.label || id,
|
|
54
|
-
input: { type: "text", value: resource.value }
|
|
55
|
-
});
|
|
44
|
+
nodes.push({ id, kind: "input", label: resource.label || id, input: { type: "text", value: resource.value } });
|
|
56
45
|
} else if (resource.type === "agent") {
|
|
57
46
|
const agent = resource;
|
|
58
47
|
nodes.push({
|
|
@@ -62,7 +51,6 @@ var expandResources = (resources) => {
|
|
|
62
51
|
agent: {
|
|
63
52
|
identity: agent.identity || agent.label || id,
|
|
64
53
|
model: agent.model,
|
|
65
|
-
thinkingLevel: agent.thinkingLevel,
|
|
66
54
|
tools: agent.tools,
|
|
67
55
|
instructions: agent.instructions,
|
|
68
56
|
personality: agent.personality,
|
|
@@ -71,274 +59,131 @@ var expandResources = (resources) => {
|
|
|
71
59
|
skills: agent.skills,
|
|
72
60
|
expect: agent.expect
|
|
73
61
|
});
|
|
74
|
-
for (const
|
|
75
|
-
edges.push({ from: inputRef, to: id, kind: "dependency" });
|
|
76
|
-
}
|
|
62
|
+
for (const input of agent.inputs || []) edges.push({ from: input, to: id, kind: "dependency" });
|
|
77
63
|
} else if (resource.type === "tool") {
|
|
78
64
|
const tool = resource;
|
|
79
|
-
nodes.push({
|
|
80
|
-
|
|
81
|
-
kind: "tool",
|
|
82
|
-
label: tool.label || id,
|
|
83
|
-
agent: { instructions: tool.instructions }
|
|
84
|
-
});
|
|
85
|
-
for (const inputRef of tool.inputs || []) {
|
|
86
|
-
edges.push({ from: inputRef, to: id, kind: "dependency" });
|
|
87
|
-
}
|
|
65
|
+
nodes.push({ id, kind: "tool", label: tool.label || id, agent: { instructions: tool.instructions } });
|
|
66
|
+
for (const input of tool.inputs || []) edges.push({ from: input, to: id, kind: "dependency" });
|
|
88
67
|
} else {
|
|
89
68
|
const input = resource;
|
|
90
|
-
nodes.push({
|
|
91
|
-
id,
|
|
92
|
-
kind: "input",
|
|
93
|
-
label: input.label || id,
|
|
94
|
-
input: {
|
|
95
|
-
type: input.type,
|
|
96
|
-
value: input.value,
|
|
97
|
-
uri: input.uri || input.path,
|
|
98
|
-
mimeType: input.mimeType,
|
|
99
|
-
data: input.data
|
|
100
|
-
}
|
|
101
|
-
});
|
|
69
|
+
nodes.push({ id, kind: "input", label: input.label || id, input: { type: input.type, value: input.value, uri: input.uri || input.path, mimeType: input.mimeType, data: input.data } });
|
|
102
70
|
}
|
|
103
71
|
}
|
|
104
72
|
return { nodes, edges };
|
|
105
73
|
};
|
|
106
74
|
var normalizeCircuitryGraph = (graph) => {
|
|
107
|
-
if (!graph.resources
|
|
108
|
-
|
|
109
|
-
return { ...graph, nodes, edges };
|
|
75
|
+
if (!graph.resources) return { ...graph };
|
|
76
|
+
return { ...graph, ...expandResources(graph.resources) };
|
|
110
77
|
};
|
|
111
|
-
var
|
|
112
|
-
|
|
113
|
-
const resolvedStandard = createCircuitryValidationStandard(
|
|
114
|
-
standard,
|
|
115
|
-
graph && typeof graph === "object" && !Array.isArray(graph) ? graph : void 0
|
|
116
|
-
);
|
|
78
|
+
var validateCircuitryGraphInternal = (graph, standard = {}, source) => {
|
|
79
|
+
const resolved = createCircuitryValidationStandard(standard, graph && typeof graph === "object" && !Array.isArray(graph) ? graph : void 0);
|
|
117
80
|
const errors = [];
|
|
118
|
-
const
|
|
119
|
-
if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
|
|
120
|
-
|
|
121
|
-
|
|
81
|
+
const issue = (code, message = code, path2) => errors.push({ code, message, ...path2 ? { path: path2 } : {} });
|
|
82
|
+
if (!graph || typeof graph !== "object" || Array.isArray(graph)) return { ok: false, errors: [{ code: "invalid_graph", message: "invalid graph" }], standard: resolved };
|
|
83
|
+
const g = graph;
|
|
84
|
+
if (resolved.requireSpecVersion && g.circuitry !== resolved.version) issue("invalid_version", "invalid version", ["circuitry"]);
|
|
85
|
+
if (source) {
|
|
86
|
+
const raw = g;
|
|
87
|
+
if (!g.resources || Object.keys(g.resources).length === 0) issue("missing_resources", "missing resources", ["resources"]);
|
|
88
|
+
if (raw.agents) issue("forbidden_agents", "forbidden agents", ["agents"]);
|
|
89
|
+
if (raw.inputs) issue("forbidden_inputs", "forbidden inputs", ["inputs"]);
|
|
90
|
+
if (g.nodes?.length) issue("forbidden_nodes", "forbidden nodes", ["nodes"]);
|
|
91
|
+
if (g.edges?.length) issue("forbidden_edges", "forbidden edges", ["edges"]);
|
|
92
|
+
if (raw.links) issue("forbidden_links", "forbidden links", ["links"]);
|
|
122
93
|
}
|
|
123
|
-
const
|
|
124
|
-
const
|
|
125
|
-
const hasRule = (name) => rules.some((entry) => (typeof entry === "string" ? entry : entry.rule) === name);
|
|
126
|
-
if (resolvedStandard.requireSpecVersion && !VALID_SPEC_VERSIONS.has(String(graphObject.circuitry))) {
|
|
127
|
-
addError(
|
|
128
|
-
"invalid_spec_version",
|
|
129
|
-
`Expected circuitry: "${CIRCUITRY_SPEC_VERSION}"`,
|
|
130
|
-
["circuitry"]
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
if (options.sourceFormat) {
|
|
134
|
-
if (!graphObject.resources || Object.keys(graphObject.resources).length === 0) {
|
|
135
|
-
addError("missing_resources", "Circuitry graphs must use a resources: section", ["resources"]);
|
|
136
|
-
}
|
|
137
|
-
if (graphObject.agents && Object.keys(graphObject.agents).length > 0) {
|
|
138
|
-
addError("forbidden_agents", "Circuitry graph files must not use top-level agents:; use resources:", ["agents"]);
|
|
139
|
-
}
|
|
140
|
-
if (graphObject.inputs && Object.keys(graphObject.inputs).length > 0) {
|
|
141
|
-
addError("forbidden_inputs", "Circuitry graph files use args: for runtime arguments and resource inputs for edges", ["inputs"]);
|
|
142
|
-
}
|
|
143
|
-
if (graphObject.nodes && graphObject.nodes.length > 0) {
|
|
144
|
-
addError("forbidden_nodes", "Circuitry graph files must not use top-level nodes:; use resources:", ["nodes"]);
|
|
145
|
-
}
|
|
146
|
-
if (graphObject.edges && graphObject.edges.length > 0) {
|
|
147
|
-
addError("forbidden_edges", "Circuitry graph files must not use top-level edges:; use resource inputs:", ["edges"]);
|
|
148
|
-
}
|
|
149
|
-
if (graphObject.links && graphObject.links.length > 0) {
|
|
150
|
-
addError("forbidden_links", "Circuitry graph files use imports: for multi-file graphs", ["links"]);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
const normalized = normalizeCircuitryGraph(graphObject);
|
|
94
|
+
const rules = new Set(resolved.rules.map(ruleName));
|
|
95
|
+
const normalized = normalizeCircuitryGraph(g);
|
|
154
96
|
const ids = /* @__PURE__ */ new Set();
|
|
155
97
|
for (const [index, node] of (normalized.nodes || []).entries()) {
|
|
156
98
|
if (!node.id) {
|
|
157
|
-
|
|
99
|
+
issue("missing_node_id", "missing node id", ["nodes", index, "id"]);
|
|
158
100
|
continue;
|
|
159
101
|
}
|
|
160
|
-
if (ids.has(node.id))
|
|
161
|
-
addError("duplicate_node_id", `Duplicate node id: ${node.id}`, ["nodes", index, "id"]);
|
|
162
|
-
}
|
|
102
|
+
if (ids.has(node.id)) issue("duplicate_node_id", "duplicate node id", ["nodes", index, "id"]);
|
|
163
103
|
ids.add(node.id);
|
|
164
|
-
if (!node.kind)
|
|
165
|
-
addError("missing_node_kind", `Node ${node.id} is missing kind`, ["nodes", index, "kind"]);
|
|
166
|
-
}
|
|
104
|
+
if (!node.kind) issue("missing_node_kind", "missing node kind", ["nodes", index, "kind"]);
|
|
167
105
|
}
|
|
168
106
|
const adjacency = /* @__PURE__ */ new Map();
|
|
169
|
-
const
|
|
107
|
+
const incoming = /* @__PURE__ */ new Map();
|
|
170
108
|
for (const id of ids) {
|
|
171
109
|
adjacency.set(id, []);
|
|
172
|
-
|
|
110
|
+
incoming.set(id, 0);
|
|
173
111
|
}
|
|
174
112
|
for (const [index, edge] of (normalized.edges || []).entries()) {
|
|
175
113
|
if (!edge.from || !edge.to) {
|
|
176
|
-
|
|
114
|
+
issue("missing_edge_endpoint", "missing edge endpoint", ["edges", index]);
|
|
177
115
|
continue;
|
|
178
116
|
}
|
|
179
|
-
if (
|
|
180
|
-
|
|
117
|
+
if (rules.has("no-self-loops") && edge.from === edge.to) {
|
|
118
|
+
issue("self_loop", "self loop", ["edges", index]);
|
|
181
119
|
continue;
|
|
182
120
|
}
|
|
183
|
-
|
|
184
|
-
|
|
121
|
+
const ok = ids.has(edge.from) && ids.has(edge.to);
|
|
122
|
+
if (rules.has("no-unknown-edge-endpoints")) {
|
|
123
|
+
if (!ids.has(edge.from)) issue("unknown_edge_source", "unknown edge source", ["edges", index, "from"]);
|
|
124
|
+
if (!ids.has(edge.to)) issue("unknown_edge_target", "unknown edge target", ["edges", index, "to"]);
|
|
185
125
|
}
|
|
186
|
-
if (
|
|
187
|
-
addError("unknown_edge_target", `Edge references unknown target: ${edge.to}`, ["edges", index, "to"]);
|
|
188
|
-
}
|
|
189
|
-
if (ids.has(edge.from) && ids.has(edge.to)) {
|
|
126
|
+
if (ok) {
|
|
190
127
|
adjacency.get(edge.from).push(edge.to);
|
|
191
|
-
|
|
128
|
+
incoming.set(edge.to, (incoming.get(edge.to) || 0) + 1);
|
|
192
129
|
}
|
|
193
130
|
}
|
|
194
|
-
|
|
195
|
-
|
|
131
|
+
if (rules.has("require-executable-inputs")) {
|
|
132
|
+
const executable = new Set(resolved.executableKinds);
|
|
196
133
|
for (const [index, node] of (normalized.nodes || []).entries()) {
|
|
197
|
-
if (
|
|
198
|
-
addError("executable_without_inputs", `Executable node has no inputs: ${node.id}`, ["nodes", index]);
|
|
199
|
-
}
|
|
134
|
+
if (executable.has(node.kind) && (incoming.get(node.id) || 0) === 0) issue("missing_executable_input", "missing executable input", ["nodes", index]);
|
|
200
135
|
}
|
|
201
136
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
visiting.add(id);
|
|
214
|
-
path2.push(id);
|
|
215
|
-
for (const next of adjacency.get(id) || []) visit(next);
|
|
216
|
-
path2.pop();
|
|
217
|
-
visiting.delete(id);
|
|
218
|
-
visited.add(id);
|
|
219
|
-
};
|
|
220
|
-
if (hasRule("no-cycles")) {
|
|
221
|
-
for (const id of ids) {
|
|
222
|
-
visit(id);
|
|
223
|
-
if (cycleMessage) {
|
|
224
|
-
addError("cycle", `Graph contains cycle: ${cycleMessage}`);
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
const additionalRules = rules.filter((rule) => {
|
|
230
|
-
if (typeof rule === "string") return false;
|
|
231
|
-
return rule.rule === "require-graph-field" || rule.rule === "require-node-field" || rule.rule === "require-edge-field" || rule.rule === "require-string" || rule.rule === "reject-string";
|
|
232
|
-
});
|
|
233
|
-
const fieldPath = (field) => field.split(".").filter(Boolean);
|
|
234
|
-
const getField = (value, field) => fieldPath(field).reduce((current, key) => {
|
|
235
|
-
if (!current || typeof current !== "object") return void 0;
|
|
236
|
-
return current[key];
|
|
237
|
-
}, value);
|
|
238
|
-
const isPresent = (value) => value !== void 0 && value !== null && value !== "";
|
|
239
|
-
for (const rule of additionalRules) {
|
|
240
|
-
if (rule.rule === "require-graph-field") {
|
|
241
|
-
if (!isPresent(getField(normalized, rule.field))) {
|
|
242
|
-
addError("missing_required_graph_field", `Graph is missing required field: ${rule.field}`, fieldPath(rule.field));
|
|
243
|
-
}
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
if (rule.rule === "require-node-field") {
|
|
247
|
-
const nodeKinds = new Set(rule.nodeKinds || []);
|
|
248
|
-
for (const [index, node] of (normalized.nodes || []).entries()) {
|
|
249
|
-
if (nodeKinds.size > 0 && !nodeKinds.has(node.kind)) continue;
|
|
250
|
-
if (!isPresent(getField(node, rule.field))) {
|
|
251
|
-
addError("missing_required_node_field", `Node ${node.id || "<missing id>"} is missing required field: ${rule.field}`, ["nodes", index, ...fieldPath(rule.field)]);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
if (rule.rule === "require-edge-field") {
|
|
257
|
-
const edgeKinds = new Set(rule.edgeKinds || []);
|
|
258
|
-
for (const [index, edge] of (normalized.edges || []).entries()) {
|
|
259
|
-
if (edgeKinds.size > 0 && (!edge.kind || !edgeKinds.has(edge.kind))) continue;
|
|
260
|
-
if (!isPresent(getField(edge, rule.field))) {
|
|
261
|
-
addError("missing_required_edge_field", `Edge ${edge.id || index} is missing required field: ${rule.field}`, ["edges", index, ...fieldPath(rule.field)]);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
continue;
|
|
265
|
-
}
|
|
266
|
-
const checkString = (target, subject, id, match, path3) => {
|
|
267
|
-
const value = getField(subject, match.field);
|
|
268
|
-
const text = typeof value === "string" ? value : "";
|
|
269
|
-
const includes = text.includes(match.value);
|
|
270
|
-
if (rule.rule === "require-string" && !includes) {
|
|
271
|
-
addError("missing_required_string", `${target} ${id} field ${match.field} must include string: ${match.value}`, path3);
|
|
272
|
-
}
|
|
273
|
-
if (rule.rule === "reject-string" && includes) {
|
|
274
|
-
addError("rejected_string", `${target} ${id} field ${match.field} must not include string: ${match.value}`, path3);
|
|
275
|
-
}
|
|
137
|
+
if (rules.has("no-cycles")) {
|
|
138
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
139
|
+
const visited = /* @__PURE__ */ new Set();
|
|
140
|
+
const visit = (id) => {
|
|
141
|
+
if (visiting.has(id)) return true;
|
|
142
|
+
if (visited.has(id)) return false;
|
|
143
|
+
visiting.add(id);
|
|
144
|
+
for (const next of adjacency.get(id) || []) if (visit(next)) return true;
|
|
145
|
+
visiting.delete(id);
|
|
146
|
+
visited.add(id);
|
|
147
|
+
return false;
|
|
276
148
|
};
|
|
277
|
-
for (const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
for (const [index, node] of (normalized.nodes || []).entries()) {
|
|
281
|
-
if (nodeKinds.size > 0 && !nodeKinds.has(node.kind)) continue;
|
|
282
|
-
checkString("node", node, node.id || "<missing id>", match, ["nodes", index, ...fieldPath(match.field)]);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
for (const match of rule.edges || []) {
|
|
286
|
-
const edgeKinds = new Set(match.edgeKinds || []);
|
|
287
|
-
for (const [index, edge] of (normalized.edges || []).entries()) {
|
|
288
|
-
if (edgeKinds.size > 0 && (!edge.kind || !edgeKinds.has(edge.kind))) continue;
|
|
289
|
-
checkString("edge", edge, edge.id || String(index), match, ["edges", index, ...fieldPath(match.field)]);
|
|
290
|
-
}
|
|
149
|
+
for (const id of ids) if (visit(id)) {
|
|
150
|
+
issue("cycle", "cycle");
|
|
151
|
+
break;
|
|
291
152
|
}
|
|
292
153
|
}
|
|
293
|
-
return { ok: errors.length === 0, errors, standard:
|
|
294
|
-
};
|
|
295
|
-
var validateCircuitryGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, { sourceFormat: true });
|
|
296
|
-
var validateCircuitryExecutionGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, { sourceFormat: false });
|
|
297
|
-
var validateCircuitryGraph = (graph, standard = {}) => validateCircuitryGraphWithStandard(graph, standard).errors.map(
|
|
298
|
-
(error) => error.message
|
|
299
|
-
);
|
|
300
|
-
var parseCircuitryJson = (text, standard = {}, options = {}) => {
|
|
301
|
-
let graph;
|
|
302
|
-
try {
|
|
303
|
-
graph = JSON.parse(text);
|
|
304
|
-
} catch (error) {
|
|
305
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
306
|
-
throw new Error(`Could not parse Circuitry JSON. Check commas, quotes, and braces.
|
|
307
|
-
${message}`);
|
|
308
|
-
}
|
|
309
|
-
if (options.validate === false) return graph;
|
|
310
|
-
const errors = validateCircuitryGraph(graph, standard);
|
|
311
|
-
if (errors.length) {
|
|
312
|
-
throw new Error(`Invalid Circuitry graph:
|
|
313
|
-
${errors.join("\n")}`);
|
|
314
|
-
}
|
|
315
|
-
return normalizeCircuitryGraph(graph);
|
|
154
|
+
return { ok: errors.length === 0, errors, standard: resolved };
|
|
316
155
|
};
|
|
156
|
+
var validateCircuitryGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, true);
|
|
157
|
+
var validateCircuitryExecutionGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, false);
|
|
158
|
+
var validateCircuitryGraph = (graph, standard = {}) => validateCircuitryGraphWithStandard(graph, standard).errors.map((error) => error.message);
|
|
317
159
|
|
|
318
160
|
// src/yaml.ts
|
|
319
|
-
var
|
|
320
|
-
let graph;
|
|
161
|
+
var parseYamlData = (text) => {
|
|
321
162
|
try {
|
|
322
|
-
|
|
163
|
+
return YAML2.parse(text);
|
|
323
164
|
} catch (error) {
|
|
324
165
|
const message = error instanceof Error ? error.message : String(error);
|
|
325
|
-
throw new Error(`Could not parse
|
|
166
|
+
throw new Error(`Could not parse YAML.
|
|
326
167
|
${message}`);
|
|
327
168
|
}
|
|
169
|
+
};
|
|
170
|
+
var stringifyYamlData = (value) => YAML2.stringify(value);
|
|
171
|
+
var parseCircuitryYaml = (text, standard = {}, options = {}) => {
|
|
172
|
+
const graph = parseYamlData(text);
|
|
328
173
|
if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
|
|
329
|
-
throw new Error("
|
|
174
|
+
throw new Error("Circuitry YAML must be a graph object.");
|
|
330
175
|
}
|
|
331
|
-
if (options.validate
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
throw new Error(`Invalid Circuitry graph:
|
|
176
|
+
if (options.validate !== false) {
|
|
177
|
+
const errors = validateCircuitryGraph(graph, standard);
|
|
178
|
+
if (errors.length) throw new Error(`Invalid Circuitry graph:
|
|
335
179
|
${errors.join("\n")}`);
|
|
336
180
|
}
|
|
337
|
-
return
|
|
338
|
-
};
|
|
339
|
-
var parseCircuitryText = (text, filename2 = "graph.circuitry.yaml", standard = {}, options = {}) => {
|
|
340
|
-
return filename2.endsWith(".json") ? parseCircuitryJson(text, standard, options) : parseCircuitryYaml(text, standard, options);
|
|
181
|
+
return graph;
|
|
341
182
|
};
|
|
183
|
+
var parseCircuitryText = (text, standard = {}, options = {}) => parseCircuitryYaml(text, standard, options);
|
|
184
|
+
|
|
185
|
+
// src/scheduler.ts
|
|
186
|
+
import YAML3 from "yaml";
|
|
342
187
|
|
|
343
188
|
// src/node.ts
|
|
344
189
|
var defaultFilename = "graph.circuitry.yaml";
|
|
@@ -396,30 +241,35 @@ ${validation.errors.map((e) => e.message).join("\n")}`);
|
|
|
396
241
|
var loadCircuitryGraphFile = async (filename2 = defaultFilename, standard, stack = []) => {
|
|
397
242
|
const graphFile = path.resolve(process.cwd(), filename2);
|
|
398
243
|
const text = await readFile(graphFile, "utf8");
|
|
399
|
-
const parsed = parseCircuitryText(text,
|
|
244
|
+
const parsed = parseCircuitryText(text, standard, { validate: false });
|
|
400
245
|
return resolveCircuitryGraph(parsed, graphFile, text, standard, stack);
|
|
401
246
|
};
|
|
402
247
|
|
|
403
248
|
// src/cli.ts
|
|
404
249
|
var [, , command, filename] = process.argv;
|
|
405
250
|
var usage = () => {
|
|
406
|
-
console.error("usage: circuitry <check|normalize> <
|
|
251
|
+
console.error("usage: circuitry <check|normalize|parse> <file.yaml>");
|
|
407
252
|
process.exit(2);
|
|
408
253
|
};
|
|
409
254
|
if (!command || !filename) usage();
|
|
410
255
|
try {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (!result.ok) {
|
|
415
|
-
console.error(result.errors.map((error) => error.message).join("\n"));
|
|
416
|
-
process.exit(1);
|
|
417
|
-
}
|
|
418
|
-
console.log(`ok: ${loaded.filename}`);
|
|
419
|
-
} else if (command === "normalize") {
|
|
420
|
-
console.log(JSON.stringify({ graph: loaded.graph, origins: loaded.origins }, null, 2));
|
|
256
|
+
if (command === "parse") {
|
|
257
|
+
const text = await readFile2(filename, "utf8");
|
|
258
|
+
process.stdout.write(stringifyYamlData(parseYamlData(text)));
|
|
421
259
|
} else {
|
|
422
|
-
|
|
260
|
+
const loaded = await loadCircuitryGraphFile(filename);
|
|
261
|
+
if (command === "check") {
|
|
262
|
+
const result = validateCircuitryExecutionGraphWithStandard(loaded.graph);
|
|
263
|
+
if (!result.ok) {
|
|
264
|
+
console.error(result.errors.map((error) => error.message).join("\n"));
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
console.log(`ok: ${loaded.filename}`);
|
|
268
|
+
} else if (command === "normalize") {
|
|
269
|
+
process.stdout.write(stringifyYamlData({ circuitryBundle: CIRCUITRY_SPEC_VERSION, graph: loaded.graph, origins: loaded.origins }));
|
|
270
|
+
} else {
|
|
271
|
+
usage();
|
|
272
|
+
}
|
|
423
273
|
}
|
|
424
274
|
} catch (error) {
|
|
425
275
|
console.error(error instanceof Error ? error.message : String(error));
|
package/dist/graph.d.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
export declare const CIRCUITRY_SPEC_VERSION: "0.3.
|
|
1
|
+
export declare const CIRCUITRY_SPEC_VERSION: "0.3.2";
|
|
2
2
|
export type CircuitrySpecVersion = typeof CIRCUITRY_SPEC_VERSION;
|
|
3
3
|
export type CircuitryNodeKind = "agent" | "input" | "tool" | "output" | string;
|
|
4
|
-
export type CircuitryEdgeKind = "
|
|
4
|
+
export type CircuitryEdgeKind = "dependency" | "context" | "message" | "control" | string;
|
|
5
5
|
export type CircuitryInputKind = "text" | "file" | "url" | "image" | "uri" | "canvas" | "mcp" | string;
|
|
6
6
|
export type CircuitryAgent = {
|
|
7
7
|
uses?: string;
|
|
8
8
|
model?: string;
|
|
9
9
|
tools?: string[];
|
|
10
|
-
thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
11
10
|
identity?: string;
|
|
12
11
|
personality?: string;
|
|
13
12
|
instructions?: string;
|
|
@@ -51,9 +50,7 @@ export type CircuitryEdge = {
|
|
|
51
50
|
export type CircuitryExpectFieldType = "str" | "int" | "float" | "bool" | "list" | "dict" | (string & {});
|
|
52
51
|
export type CircuitryExpectField = {
|
|
53
52
|
type: CircuitryExpectFieldType;
|
|
54
|
-
/** Optional substring that must appear in the string value. */
|
|
55
53
|
contains?: string;
|
|
56
|
-
/** For list fields: shape of each item. */
|
|
57
54
|
items?: CircuitryExpectSchema | CircuitryExpectFieldType;
|
|
58
55
|
optional?: boolean;
|
|
59
56
|
};
|
|
@@ -70,14 +67,9 @@ export type CircuitryResourceAgent = {
|
|
|
70
67
|
identity?: string;
|
|
71
68
|
label?: string;
|
|
72
69
|
model?: string;
|
|
73
|
-
thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
74
|
-
/** References to other resource ids that feed this agent. */
|
|
75
70
|
inputs?: string[];
|
|
76
|
-
/** Per-resource tool whitelist. */
|
|
77
71
|
tools?: string[];
|
|
78
|
-
/** Per-resource skill whitelist. */
|
|
79
72
|
skills?: string[];
|
|
80
|
-
/** Expected output schema — stored as metadata, used for optional validation. */
|
|
81
73
|
expect?: CircuitryExpectSchema;
|
|
82
74
|
instructions?: string;
|
|
83
75
|
personality?: string;
|
|
@@ -135,38 +127,10 @@ export type CircuitryRuntime = {
|
|
|
135
127
|
permissions?: Record<string, unknown>;
|
|
136
128
|
};
|
|
137
129
|
export type CircuitryBuiltInValidationRule = "no-self-loops" | "no-unknown-edge-endpoints" | "require-executable-inputs" | "no-cycles";
|
|
138
|
-
export type CircuitryStringValidationMatch = {
|
|
139
|
-
field: string;
|
|
140
|
-
value: string;
|
|
141
|
-
nodeKinds?: string[];
|
|
142
|
-
edgeKinds?: string[];
|
|
143
|
-
};
|
|
144
|
-
export type CircuitryAdditionalValidationRule = {
|
|
145
|
-
rule: "require-graph-field";
|
|
146
|
-
field: string;
|
|
147
|
-
} | {
|
|
148
|
-
rule: "require-node-field";
|
|
149
|
-
field: string;
|
|
150
|
-
nodeKinds?: string[];
|
|
151
|
-
} | {
|
|
152
|
-
rule: "require-edge-field";
|
|
153
|
-
field: string;
|
|
154
|
-
edgeKinds?: string[];
|
|
155
|
-
} | {
|
|
156
|
-
rule: "require-string";
|
|
157
|
-
graph?: CircuitryStringValidationMatch[];
|
|
158
|
-
nodes?: CircuitryStringValidationMatch[];
|
|
159
|
-
edges?: CircuitryStringValidationMatch[];
|
|
160
|
-
} | {
|
|
161
|
-
rule: "reject-string";
|
|
162
|
-
graph?: CircuitryStringValidationMatch[];
|
|
163
|
-
nodes?: CircuitryStringValidationMatch[];
|
|
164
|
-
edges?: CircuitryStringValidationMatch[];
|
|
165
|
-
};
|
|
166
130
|
export type CircuitryValidationRuleEntry = CircuitryBuiltInValidationRule | {
|
|
167
|
-
rule:
|
|
131
|
+
rule: "require-executable-inputs";
|
|
168
132
|
executableKinds?: string[];
|
|
169
|
-
}
|
|
133
|
+
};
|
|
170
134
|
export type CircuitryGraphValidation = {
|
|
171
135
|
rules?: CircuitryValidationRuleEntry[];
|
|
172
136
|
};
|
|
@@ -179,44 +143,31 @@ export type CircuitryGraphArg = {
|
|
|
179
143
|
mimeType?: string;
|
|
180
144
|
};
|
|
181
145
|
export type CircuitryRuntimeInputs = Record<string, unknown>;
|
|
182
|
-
/**
|
|
183
|
-
* Return a graph copy with runtime input values applied to existing text inputs.
|
|
184
|
-
*
|
|
185
|
-
* Runtime inputs are execution-time args for graph programs: small overlays on
|
|
186
|
-
* declared text inputs. They must target declared input resources; this keeps
|
|
187
|
-
* graph source stable and catches misspelled input names before execution.
|
|
188
|
-
*/
|
|
189
|
-
export declare const applyCircuitryRuntimeInputs: (graph: CircuitryGraph, inputs?: CircuitryRuntimeInputs) => CircuitryGraph;
|
|
190
|
-
/** Import declaration for bringing resources from another graph file. */
|
|
191
146
|
export type CircuitryImport = {
|
|
192
|
-
/** Path to another .circuitry.yaml/.json file, resolved relative to this file. */
|
|
193
147
|
path: string;
|
|
194
|
-
/** Import all resources, a resource list, or an old->new resource id map. */
|
|
195
148
|
resources: "*" | string[] | Record<string, string>;
|
|
196
|
-
/** Optional namespace applied to imported resource ids and their internal refs. */
|
|
197
149
|
prefix?: string;
|
|
198
150
|
};
|
|
199
151
|
export type CircuitryGraph = {
|
|
200
|
-
/** Spec version. */
|
|
201
152
|
circuitry: CircuitrySpecVersion | string;
|
|
202
153
|
id?: string;
|
|
203
154
|
title?: string;
|
|
204
155
|
description?: string;
|
|
205
|
-
/** Explicit imports of resources from other graph files. */
|
|
206
156
|
imports?: CircuitryImport[];
|
|
207
|
-
/** Runtime call contract. Args bind to resources with matching ids. */
|
|
208
157
|
args?: Record<string, CircuitryGraphArg>;
|
|
209
158
|
resources?: Record<string, CircuitryResourceEntry>;
|
|
210
|
-
/** Internal normalized execution nodes. Authored graph files must not set this. */
|
|
211
159
|
nodes?: CircuitryNode[];
|
|
212
|
-
/** Internal normalized execution edges. Authored graph files must not set this. */
|
|
213
160
|
edges?: CircuitryEdge[];
|
|
214
|
-
/** Forbidden in authored graph files. */
|
|
215
161
|
agents?: never;
|
|
216
162
|
runtime?: CircuitryRuntime;
|
|
217
163
|
validation?: CircuitryGraphValidation;
|
|
218
164
|
metadata?: Record<string, unknown>;
|
|
219
165
|
};
|
|
166
|
+
export type LoadedCircuitryBundle = {
|
|
167
|
+
circuitryBundle: CircuitrySpecVersion;
|
|
168
|
+
graph: CircuitryGraph;
|
|
169
|
+
origins: Record<string, string>;
|
|
170
|
+
};
|
|
220
171
|
export type CircuitryAgentPreset = {
|
|
221
172
|
name: string;
|
|
222
173
|
description?: string;
|
|
@@ -243,18 +194,15 @@ export type CircuitryValidationResult = {
|
|
|
243
194
|
};
|
|
244
195
|
export declare const DEFAULT_CIRCUITRY_VALIDATION_RULES: CircuitryValidationRuleEntry[];
|
|
245
196
|
export declare const DEFAULT_CIRCUITRY_VALIDATION_STANDARD: {
|
|
246
|
-
readonly version: "0.3.
|
|
197
|
+
readonly version: "0.3.2";
|
|
247
198
|
readonly requireSpecVersion: true;
|
|
248
199
|
readonly rules: CircuitryValidationRuleEntry[];
|
|
249
200
|
readonly executableKinds: ["agent", "tool", "output"];
|
|
250
201
|
};
|
|
202
|
+
export declare const applyCircuitryRuntimeInputs: (graph: CircuitryGraph, inputs?: CircuitryRuntimeInputs) => CircuitryGraph;
|
|
251
203
|
export declare const createCircuitryValidationStandard: (standard?: CircuitryValidationStandard, graph?: CircuitryGraph) => Required<CircuitryValidationStandard>;
|
|
252
204
|
export declare const normalizeCircuitryGraph: (graph: CircuitryGraph) => CircuitryGraph;
|
|
253
205
|
export declare const validateCircuitryGraphWithStandard: (graph: unknown, standard?: CircuitryValidationStandard) => CircuitryValidationResult;
|
|
254
206
|
export declare const validateCircuitryExecutionGraphWithStandard: (graph: unknown, standard?: CircuitryValidationStandard) => CircuitryValidationResult;
|
|
255
207
|
export declare const validateCircuitryGraph: (graph: unknown, standard?: CircuitryValidationStandard) => string[];
|
|
256
|
-
export declare const parseCircuitryJson: (text: string, standard?: CircuitryValidationStandard, options?: {
|
|
257
|
-
validate?: boolean;
|
|
258
|
-
}) => CircuitryGraph;
|
|
259
|
-
export declare const stringifyCircuitryJson: (graph: CircuitryGraph) => string;
|
|
260
208
|
export declare const isUriInput: (input: CircuitryInput | Omit<CircuitryInput, "id">) => boolean;
|