@darkhorseprojects/circuitry 0.3.0 → 0.3.1
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/cli.d.ts +1 -0
- package/dist/cli.js +427 -0
- package/dist/graph.d.ts +30 -13
- package/dist/index.js +38 -22
- package/dist/node.d.ts +8 -3
- package/dist/node.js +109 -86
- package/package.json +4 -1
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/node.ts
|
|
4
|
+
import { access, readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
// src/yaml.ts
|
|
8
|
+
import YAML from "yaml";
|
|
9
|
+
|
|
10
|
+
// src/graph.ts
|
|
11
|
+
var CIRCUITRY_SPEC_VERSION = "0.3.1";
|
|
12
|
+
var DEFAULT_CIRCUITRY_VALIDATION_RULES = [
|
|
13
|
+
"no-self-loops",
|
|
14
|
+
"no-unknown-edge-endpoints",
|
|
15
|
+
"require-executable-inputs",
|
|
16
|
+
"no-cycles"
|
|
17
|
+
];
|
|
18
|
+
var DEFAULT_CIRCUITRY_VALIDATION_STANDARD = {
|
|
19
|
+
version: CIRCUITRY_SPEC_VERSION,
|
|
20
|
+
requireSpecVersion: true,
|
|
21
|
+
rules: DEFAULT_CIRCUITRY_VALIDATION_RULES,
|
|
22
|
+
executableKinds: ["agent", "tool", "output"]
|
|
23
|
+
};
|
|
24
|
+
var executableKindsFromRules = (rules = []) => {
|
|
25
|
+
const executableRule = rules.find(
|
|
26
|
+
(entry) => typeof entry !== "string" && entry.rule === "require-executable-inputs"
|
|
27
|
+
);
|
|
28
|
+
return executableRule?.executableKinds;
|
|
29
|
+
};
|
|
30
|
+
var createCircuitryValidationStandard = (standard = {}, graph) => {
|
|
31
|
+
const rules = [
|
|
32
|
+
...DEFAULT_CIRCUITRY_VALIDATION_RULES,
|
|
33
|
+
...graph?.validation?.rules || [],
|
|
34
|
+
...standard.rules || []
|
|
35
|
+
];
|
|
36
|
+
return {
|
|
37
|
+
version: standard.version || DEFAULT_CIRCUITRY_VALIDATION_STANDARD.version,
|
|
38
|
+
requireSpecVersion: standard.requireSpecVersion ?? DEFAULT_CIRCUITRY_VALIDATION_STANDARD.requireSpecVersion,
|
|
39
|
+
rules,
|
|
40
|
+
executableKinds: standard.executableKinds || executableKindsFromRules(rules) || [
|
|
41
|
+
...DEFAULT_CIRCUITRY_VALIDATION_STANDARD.executableKinds
|
|
42
|
+
]
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
var expandResources = (resources) => {
|
|
46
|
+
const nodes = [];
|
|
47
|
+
const edges = [];
|
|
48
|
+
for (const [id, resource] of Object.entries(resources)) {
|
|
49
|
+
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
|
+
});
|
|
56
|
+
} else if (resource.type === "agent") {
|
|
57
|
+
const agent = resource;
|
|
58
|
+
nodes.push({
|
|
59
|
+
id,
|
|
60
|
+
kind: "agent",
|
|
61
|
+
label: agent.label || agent.identity || id,
|
|
62
|
+
agent: {
|
|
63
|
+
identity: agent.identity || agent.label || id,
|
|
64
|
+
model: agent.model,
|
|
65
|
+
thinkingLevel: agent.thinkingLevel,
|
|
66
|
+
tools: agent.tools,
|
|
67
|
+
instructions: agent.instructions,
|
|
68
|
+
personality: agent.personality,
|
|
69
|
+
context: agent.context
|
|
70
|
+
},
|
|
71
|
+
skills: agent.skills,
|
|
72
|
+
expect: agent.expect
|
|
73
|
+
});
|
|
74
|
+
for (const inputRef of agent.inputs || []) {
|
|
75
|
+
edges.push({ from: inputRef, to: id, kind: "dependency" });
|
|
76
|
+
}
|
|
77
|
+
} else if (resource.type === "tool") {
|
|
78
|
+
const tool = resource;
|
|
79
|
+
nodes.push({
|
|
80
|
+
id,
|
|
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
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
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
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { nodes, edges };
|
|
105
|
+
};
|
|
106
|
+
var normalizeCircuitryGraph = (graph) => {
|
|
107
|
+
if (!graph.resources || Object.keys(graph.resources).length === 0) return { ...graph };
|
|
108
|
+
const { nodes, edges } = expandResources(graph.resources);
|
|
109
|
+
return { ...graph, nodes, edges };
|
|
110
|
+
};
|
|
111
|
+
var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set([CIRCUITRY_SPEC_VERSION]);
|
|
112
|
+
var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
|
|
113
|
+
const resolvedStandard = createCircuitryValidationStandard(
|
|
114
|
+
standard,
|
|
115
|
+
graph && typeof graph === "object" && !Array.isArray(graph) ? graph : void 0
|
|
116
|
+
);
|
|
117
|
+
const errors = [];
|
|
118
|
+
const addError = (code, message, path3) => errors.push({ code, message, ...path3 ? { path: path3 } : {} });
|
|
119
|
+
if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
|
|
120
|
+
addError("invalid_graph", "Circuitry graph must be an object");
|
|
121
|
+
return { ok: false, errors, standard: resolvedStandard };
|
|
122
|
+
}
|
|
123
|
+
const graphObject = graph;
|
|
124
|
+
const rules = resolvedStandard.rules;
|
|
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);
|
|
154
|
+
const ids = /* @__PURE__ */ new Set();
|
|
155
|
+
for (const [index, node] of (normalized.nodes || []).entries()) {
|
|
156
|
+
if (!node.id) {
|
|
157
|
+
addError("missing_node_id", "Node is missing id", ["nodes", index, "id"]);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (ids.has(node.id)) {
|
|
161
|
+
addError("duplicate_node_id", `Duplicate node id: ${node.id}`, ["nodes", index, "id"]);
|
|
162
|
+
}
|
|
163
|
+
ids.add(node.id);
|
|
164
|
+
if (!node.kind) {
|
|
165
|
+
addError("missing_node_kind", `Node ${node.id} is missing kind`, ["nodes", index, "kind"]);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
169
|
+
const incomingCount = /* @__PURE__ */ new Map();
|
|
170
|
+
for (const id of ids) {
|
|
171
|
+
adjacency.set(id, []);
|
|
172
|
+
incomingCount.set(id, 0);
|
|
173
|
+
}
|
|
174
|
+
for (const [index, edge] of (normalized.edges || []).entries()) {
|
|
175
|
+
if (!edge.from || !edge.to) {
|
|
176
|
+
addError("missing_edge_endpoint", "Edge is missing from/to", ["edges", index]);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (hasRule("no-self-loops") && edge.from === edge.to) {
|
|
180
|
+
addError("self_loop", `Edge creates self loop: ${edge.from} -> ${edge.to}`, ["edges", index]);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (hasRule("no-unknown-edge-endpoints") && !ids.has(edge.from)) {
|
|
184
|
+
addError("unknown_edge_source", `Edge references unknown source: ${edge.from}`, ["edges", index, "from"]);
|
|
185
|
+
}
|
|
186
|
+
if (hasRule("no-unknown-edge-endpoints") && !ids.has(edge.to)) {
|
|
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)) {
|
|
190
|
+
adjacency.get(edge.from).push(edge.to);
|
|
191
|
+
incomingCount.set(edge.to, (incomingCount.get(edge.to) || 0) + 1);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const executableKinds = new Set(resolvedStandard.executableKinds);
|
|
195
|
+
if (hasRule("require-executable-inputs")) {
|
|
196
|
+
for (const [index, node] of (normalized.nodes || []).entries()) {
|
|
197
|
+
if (executableKinds.has(node.kind) && (incomingCount.get(node.id) || 0) === 0) {
|
|
198
|
+
addError("executable_without_inputs", `Executable node has no inputs: ${node.id}`, ["nodes", index]);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
203
|
+
const visited = /* @__PURE__ */ new Set();
|
|
204
|
+
const path2 = [];
|
|
205
|
+
let cycleMessage = "";
|
|
206
|
+
const visit = (id) => {
|
|
207
|
+
if (cycleMessage) return;
|
|
208
|
+
if (visiting.has(id)) {
|
|
209
|
+
cycleMessage = [...path2.slice(path2.indexOf(id)), id].join(" -> ");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (visited.has(id)) return;
|
|
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
|
+
}
|
|
276
|
+
};
|
|
277
|
+
for (const match of rule.graph || []) checkString("graph", normalized, "<root>", match, fieldPath(match.field));
|
|
278
|
+
for (const match of rule.nodes || []) {
|
|
279
|
+
const nodeKinds = new Set(match.nodeKinds || []);
|
|
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
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return { ok: errors.length === 0, errors, standard: resolvedStandard };
|
|
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);
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// src/yaml.ts
|
|
319
|
+
var parseCircuitryYaml = (text, standard = {}, options = {}) => {
|
|
320
|
+
let graph;
|
|
321
|
+
try {
|
|
322
|
+
graph = YAML.parse(text);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
325
|
+
throw new Error(`Could not parse Circuitry YAML. Check indentation and quotes.
|
|
326
|
+
${message}`);
|
|
327
|
+
}
|
|
328
|
+
if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
|
|
329
|
+
throw new Error("Could not parse Circuitry YAML. Expected a graph object.");
|
|
330
|
+
}
|
|
331
|
+
if (options.validate === false) return graph;
|
|
332
|
+
const errors = validateCircuitryGraph(graph, standard);
|
|
333
|
+
if (errors.length) {
|
|
334
|
+
throw new Error(`Invalid Circuitry graph:
|
|
335
|
+
${errors.join("\n")}`);
|
|
336
|
+
}
|
|
337
|
+
return normalizeCircuitryGraph(graph);
|
|
338
|
+
};
|
|
339
|
+
var parseCircuitryText = (text, filename2 = "graph.circuitry.yaml", standard = {}, options = {}) => {
|
|
340
|
+
return filename2.endsWith(".json") ? parseCircuitryJson(text, standard, options) : parseCircuitryYaml(text, standard, options);
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// src/node.ts
|
|
344
|
+
var defaultFilename = "graph.circuitry.yaml";
|
|
345
|
+
var resourceInputs = (resource) => "inputs" in resource && Array.isArray(resource.inputs) ? resource.inputs : [];
|
|
346
|
+
var withResourceInputs = (resource, inputs) => "inputs" in resource ? { ...resource, inputs } : resource;
|
|
347
|
+
var selectedImportResources = (imp, resources) => {
|
|
348
|
+
if (imp.resources === "*") return Object.fromEntries(Object.keys(resources).map((id) => [id, id]));
|
|
349
|
+
if (Array.isArray(imp.resources)) return Object.fromEntries(imp.resources.map((id) => [id, id]));
|
|
350
|
+
return imp.resources;
|
|
351
|
+
};
|
|
352
|
+
var importedId = (localId, prefix) => `${prefix || ""}${localId}`;
|
|
353
|
+
var rewriteResourceInputs = (resource, aliasMap) => {
|
|
354
|
+
const inputs = resourceInputs(resource);
|
|
355
|
+
if (inputs.length === 0) return resource;
|
|
356
|
+
return withResourceInputs(resource, inputs.map((input) => aliasMap[input] || input));
|
|
357
|
+
};
|
|
358
|
+
var resolveCircuitryGraph = async (parsed, graphFile, text, standard, stack = []) => {
|
|
359
|
+
if (stack.includes(graphFile)) {
|
|
360
|
+
throw new Error(`Circuitry graph import cycle: ${[...stack, graphFile].join(" -> ")}`);
|
|
361
|
+
}
|
|
362
|
+
const nextStack = [...stack, graphFile];
|
|
363
|
+
const resources = {};
|
|
364
|
+
const origins = {};
|
|
365
|
+
for (const imp of parsed.imports || []) {
|
|
366
|
+
const linkedFile = path.resolve(path.dirname(graphFile), imp.path);
|
|
367
|
+
const loaded = await loadCircuitryGraphFile(linkedFile, standard, nextStack);
|
|
368
|
+
const available = loaded.graph.resources || {};
|
|
369
|
+
const aliases = selectedImportResources(imp, available);
|
|
370
|
+
const aliasMap = {};
|
|
371
|
+
for (const [sourceId, localId] of Object.entries(aliases)) aliasMap[sourceId] = importedId(localId, imp.prefix);
|
|
372
|
+
for (const [sourceId, localId] of Object.entries(aliases)) {
|
|
373
|
+
const resource = available[sourceId];
|
|
374
|
+
if (!resource) throw new Error(`Imported resource not found: ${sourceId} in ${imp.path}`);
|
|
375
|
+
const nextId = importedId(localId, imp.prefix);
|
|
376
|
+
if (resources[nextId]) throw new Error(`Imported Circuitry resource id collision: ${nextId} from ${imp.path}`);
|
|
377
|
+
resources[nextId] = rewriteResourceInputs(resource, aliasMap);
|
|
378
|
+
origins[nextId] = loaded.origins[sourceId] || linkedFile;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
for (const [id, resource] of Object.entries(parsed.resources || {})) {
|
|
382
|
+
if (resources[id]) throw new Error(`Circuitry resource id collision: ${id} in ${graphFile}`);
|
|
383
|
+
resources[id] = resource;
|
|
384
|
+
origins[id] = graphFile;
|
|
385
|
+
}
|
|
386
|
+
const graph = normalizeCircuitryGraph({ ...parsed, imports: [], resources });
|
|
387
|
+
if (stack.length === 0) {
|
|
388
|
+
const validation = validateCircuitryExecutionGraphWithStandard(graph, standard);
|
|
389
|
+
if (validation.errors.length) {
|
|
390
|
+
throw new Error(`Invalid Circuitry graph:
|
|
391
|
+
${validation.errors.map((e) => e.message).join("\n")}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return { filename: graphFile, text, graph, origins };
|
|
395
|
+
};
|
|
396
|
+
var loadCircuitryGraphFile = async (filename2 = defaultFilename, standard, stack = []) => {
|
|
397
|
+
const graphFile = path.resolve(process.cwd(), filename2);
|
|
398
|
+
const text = await readFile(graphFile, "utf8");
|
|
399
|
+
const parsed = parseCircuitryText(text, graphFile, standard, { validate: false });
|
|
400
|
+
return resolveCircuitryGraph(parsed, graphFile, text, standard, stack);
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
// src/cli.ts
|
|
404
|
+
var [, , command, filename] = process.argv;
|
|
405
|
+
var usage = () => {
|
|
406
|
+
console.error("usage: circuitry <check|normalize> <graph.circuitry.yaml>");
|
|
407
|
+
process.exit(2);
|
|
408
|
+
};
|
|
409
|
+
if (!command || !filename) usage();
|
|
410
|
+
try {
|
|
411
|
+
const loaded = await loadCircuitryGraphFile(filename);
|
|
412
|
+
if (command === "check") {
|
|
413
|
+
const result = validateCircuitryExecutionGraphWithStandard(loaded.graph);
|
|
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));
|
|
421
|
+
} else {
|
|
422
|
+
usage();
|
|
423
|
+
}
|
|
424
|
+
} catch (error) {
|
|
425
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
package/dist/graph.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export declare const CIRCUITRY_SPEC_VERSION: "0.3.
|
|
2
|
-
export type CircuitrySpecVersion = typeof CIRCUITRY_SPEC_VERSION
|
|
1
|
+
export declare const CIRCUITRY_SPEC_VERSION: "0.3.1";
|
|
2
|
+
export type CircuitrySpecVersion = typeof CIRCUITRY_SPEC_VERSION;
|
|
3
3
|
export type CircuitryNodeKind = "agent" | "input" | "tool" | "output" | string;
|
|
4
4
|
export type CircuitryEdgeKind = "context" | "dependency" | "message" | "control" | string;
|
|
5
5
|
export type CircuitryInputKind = "text" | "file" | "url" | "image" | "uri" | "canvas" | "mcp" | string;
|
|
@@ -89,7 +89,16 @@ export type CircuitryResourceTool = {
|
|
|
89
89
|
inputs?: string[];
|
|
90
90
|
instructions?: string;
|
|
91
91
|
};
|
|
92
|
-
export type
|
|
92
|
+
export type CircuitryResourceData = {
|
|
93
|
+
type: CircuitryInputKind;
|
|
94
|
+
value?: string;
|
|
95
|
+
uri?: string;
|
|
96
|
+
path?: string;
|
|
97
|
+
mimeType?: string;
|
|
98
|
+
label?: string;
|
|
99
|
+
data?: Record<string, unknown>;
|
|
100
|
+
};
|
|
101
|
+
export type CircuitryResourceEntry = CircuitryResourceText | CircuitryResourceAgent | CircuitryResourceTool | CircuitryResourceData;
|
|
93
102
|
export type CircuitrySandboxProvider = "local" | "docker" | "e2b" | "daytona" | "cloudflare" | "modal" | "openai" | (string & {});
|
|
94
103
|
export type CircuitrySandboxClient = {
|
|
95
104
|
provider: CircuitrySandboxProvider;
|
|
@@ -161,6 +170,14 @@ export type CircuitryValidationRuleEntry = CircuitryBuiltInValidationRule | {
|
|
|
161
170
|
export type CircuitryGraphValidation = {
|
|
162
171
|
rules?: CircuitryValidationRuleEntry[];
|
|
163
172
|
};
|
|
173
|
+
export type CircuitryGraphArg = {
|
|
174
|
+
type: CircuitryInputKind;
|
|
175
|
+
required?: boolean;
|
|
176
|
+
label?: string;
|
|
177
|
+
description?: string;
|
|
178
|
+
default?: unknown;
|
|
179
|
+
mimeType?: string;
|
|
180
|
+
};
|
|
164
181
|
export type CircuitryRuntimeInputs = Record<string, unknown>;
|
|
165
182
|
/**
|
|
166
183
|
* Return a graph copy with runtime input values applied to existing text inputs.
|
|
@@ -174,28 +191,28 @@ export declare const applyCircuitryRuntimeInputs: (graph: CircuitryGraph, inputs
|
|
|
174
191
|
export type CircuitryImport = {
|
|
175
192
|
/** Path to another .circuitry.yaml/.json file, resolved relative to this file. */
|
|
176
193
|
path: string;
|
|
177
|
-
/**
|
|
178
|
-
|
|
179
|
-
/** Optional
|
|
180
|
-
|
|
194
|
+
/** Import all resources, a resource list, or an old->new resource id map. */
|
|
195
|
+
resources: "*" | string[] | Record<string, string>;
|
|
196
|
+
/** Optional namespace applied to imported resource ids and their internal refs. */
|
|
197
|
+
prefix?: string;
|
|
181
198
|
};
|
|
182
199
|
export type CircuitryGraph = {
|
|
183
|
-
/** Spec version.
|
|
200
|
+
/** Spec version. */
|
|
184
201
|
circuitry: CircuitrySpecVersion | string;
|
|
185
202
|
id?: string;
|
|
186
203
|
title?: string;
|
|
187
204
|
description?: string;
|
|
188
|
-
/** Explicit imports of
|
|
205
|
+
/** Explicit imports of resources from other graph files. */
|
|
189
206
|
imports?: CircuitryImport[];
|
|
207
|
+
/** Runtime call contract. Args bind to resources with matching ids. */
|
|
208
|
+
args?: Record<string, CircuitryGraphArg>;
|
|
190
209
|
resources?: Record<string, CircuitryResourceEntry>;
|
|
191
210
|
/** Internal normalized execution nodes. Authored graph files must not set this. */
|
|
192
211
|
nodes?: CircuitryNode[];
|
|
193
212
|
/** Internal normalized execution edges. Authored graph files must not set this. */
|
|
194
213
|
edges?: CircuitryEdge[];
|
|
195
|
-
/** Forbidden in authored
|
|
214
|
+
/** Forbidden in authored graph files. */
|
|
196
215
|
agents?: never;
|
|
197
|
-
/** Forbidden in authored v0.2 graph files. */
|
|
198
|
-
inputs?: never;
|
|
199
216
|
runtime?: CircuitryRuntime;
|
|
200
217
|
validation?: CircuitryGraphValidation;
|
|
201
218
|
metadata?: Record<string, unknown>;
|
|
@@ -226,7 +243,7 @@ export type CircuitryValidationResult = {
|
|
|
226
243
|
};
|
|
227
244
|
export declare const DEFAULT_CIRCUITRY_VALIDATION_RULES: CircuitryValidationRuleEntry[];
|
|
228
245
|
export declare const DEFAULT_CIRCUITRY_VALIDATION_STANDARD: {
|
|
229
|
-
readonly version: "0.3.
|
|
246
|
+
readonly version: "0.3.1";
|
|
230
247
|
readonly requireSpecVersion: true;
|
|
231
248
|
readonly rules: CircuitryValidationRuleEntry[];
|
|
232
249
|
readonly executableKinds: ["agent", "tool", "output"];
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ var isNodeElement = (element) => {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
// src/graph.ts
|
|
30
|
-
var CIRCUITRY_SPEC_VERSION = "0.3.
|
|
30
|
+
var CIRCUITRY_SPEC_VERSION = "0.3.1";
|
|
31
31
|
var runtimeInputToText = (value) => {
|
|
32
32
|
if (typeof value === "string") return value;
|
|
33
33
|
if (value === void 0) return "";
|
|
@@ -98,35 +98,51 @@ var expandResources = (resources) => {
|
|
|
98
98
|
input: { type: "text", value: resource.value }
|
|
99
99
|
});
|
|
100
100
|
} else if (resource.type === "agent") {
|
|
101
|
+
const agent = resource;
|
|
101
102
|
nodes.push({
|
|
102
103
|
id,
|
|
103
104
|
kind: "agent",
|
|
104
|
-
label:
|
|
105
|
+
label: agent.label || agent.identity || id,
|
|
105
106
|
agent: {
|
|
106
|
-
identity:
|
|
107
|
-
model:
|
|
108
|
-
thinkingLevel:
|
|
109
|
-
tools:
|
|
110
|
-
instructions:
|
|
111
|
-
personality:
|
|
112
|
-
context:
|
|
107
|
+
identity: agent.identity || agent.label || id,
|
|
108
|
+
model: agent.model,
|
|
109
|
+
thinkingLevel: agent.thinkingLevel,
|
|
110
|
+
tools: agent.tools,
|
|
111
|
+
instructions: agent.instructions,
|
|
112
|
+
personality: agent.personality,
|
|
113
|
+
context: agent.context
|
|
113
114
|
},
|
|
114
|
-
skills:
|
|
115
|
-
expect:
|
|
115
|
+
skills: agent.skills,
|
|
116
|
+
expect: agent.expect
|
|
116
117
|
});
|
|
117
|
-
for (const inputRef of
|
|
118
|
+
for (const inputRef of agent.inputs || []) {
|
|
118
119
|
edges.push({ from: inputRef, to: id, kind: "dependency" });
|
|
119
120
|
}
|
|
120
121
|
} else if (resource.type === "tool") {
|
|
122
|
+
const tool = resource;
|
|
121
123
|
nodes.push({
|
|
122
124
|
id,
|
|
123
125
|
kind: "tool",
|
|
124
|
-
label:
|
|
125
|
-
agent: { instructions:
|
|
126
|
+
label: tool.label || id,
|
|
127
|
+
agent: { instructions: tool.instructions }
|
|
126
128
|
});
|
|
127
|
-
for (const inputRef of
|
|
129
|
+
for (const inputRef of tool.inputs || []) {
|
|
128
130
|
edges.push({ from: inputRef, to: id, kind: "dependency" });
|
|
129
131
|
}
|
|
132
|
+
} else {
|
|
133
|
+
const input = resource;
|
|
134
|
+
nodes.push({
|
|
135
|
+
id,
|
|
136
|
+
kind: "input",
|
|
137
|
+
label: input.label || id,
|
|
138
|
+
input: {
|
|
139
|
+
type: input.type,
|
|
140
|
+
value: input.value,
|
|
141
|
+
uri: input.uri || input.path,
|
|
142
|
+
mimeType: input.mimeType,
|
|
143
|
+
data: input.data
|
|
144
|
+
}
|
|
145
|
+
});
|
|
130
146
|
}
|
|
131
147
|
}
|
|
132
148
|
return { nodes, edges };
|
|
@@ -136,7 +152,7 @@ var normalizeCircuitryGraph = (graph) => {
|
|
|
136
152
|
const { nodes, edges } = expandResources(graph.resources);
|
|
137
153
|
return { ...graph, nodes, edges };
|
|
138
154
|
};
|
|
139
|
-
var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set([
|
|
155
|
+
var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set([CIRCUITRY_SPEC_VERSION]);
|
|
140
156
|
var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
|
|
141
157
|
const resolvedStandard = createCircuitryValidationStandard(
|
|
142
158
|
standard,
|
|
@@ -160,22 +176,22 @@ var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
|
|
|
160
176
|
}
|
|
161
177
|
if (options.sourceFormat) {
|
|
162
178
|
if (!graphObject.resources || Object.keys(graphObject.resources).length === 0) {
|
|
163
|
-
addError("missing_resources", "Circuitry
|
|
179
|
+
addError("missing_resources", "Circuitry graphs must use a resources: section", ["resources"]);
|
|
164
180
|
}
|
|
165
181
|
if (graphObject.agents && Object.keys(graphObject.agents).length > 0) {
|
|
166
|
-
addError("forbidden_agents", "Circuitry
|
|
182
|
+
addError("forbidden_agents", "Circuitry graph files must not use top-level agents:; use resources:", ["agents"]);
|
|
167
183
|
}
|
|
168
184
|
if (graphObject.inputs && Object.keys(graphObject.inputs).length > 0) {
|
|
169
|
-
addError("forbidden_inputs", "Circuitry
|
|
185
|
+
addError("forbidden_inputs", "Circuitry graph files use args: for runtime arguments and resource inputs for edges", ["inputs"]);
|
|
170
186
|
}
|
|
171
187
|
if (graphObject.nodes && graphObject.nodes.length > 0) {
|
|
172
|
-
addError("forbidden_nodes", "Circuitry
|
|
188
|
+
addError("forbidden_nodes", "Circuitry graph files must not use top-level nodes:; use resources:", ["nodes"]);
|
|
173
189
|
}
|
|
174
190
|
if (graphObject.edges && graphObject.edges.length > 0) {
|
|
175
|
-
addError("forbidden_edges", "Circuitry
|
|
191
|
+
addError("forbidden_edges", "Circuitry graph files must not use top-level edges:; use resource inputs:", ["edges"]);
|
|
176
192
|
}
|
|
177
193
|
if (graphObject.links && graphObject.links.length > 0) {
|
|
178
|
-
addError("forbidden_links", "Circuitry
|
|
194
|
+
addError("forbidden_links", "Circuitry graph files use imports: for multi-file graphs", ["links"]);
|
|
179
195
|
}
|
|
180
196
|
}
|
|
181
197
|
const normalized = normalizeCircuitryGraph(graphObject);
|
package/dist/node.d.ts
CHANGED
|
@@ -2,15 +2,20 @@ import type { CircuitryHost } from "./host";
|
|
|
2
2
|
import type { CircuitryGraph, CircuitryRuntimeInputs, CircuitryValidationStandard } from "./graph";
|
|
3
3
|
import type { NodeExecutionRequest, NodeExecutionResult, SimulationRunItem } from "./simulation";
|
|
4
4
|
export declare const runNodeWithPiSDK: ({ model, prompt, images, tools, thinkingLevel, }: NodeExecutionRequest) => Promise<NodeExecutionResult>;
|
|
5
|
+
export type CircuitryResourceOrigins = Record<string, string>;
|
|
6
|
+
export type LoadedCircuitryGraph = {
|
|
7
|
+
filename: string;
|
|
8
|
+
text: string;
|
|
9
|
+
graph: CircuitryGraph;
|
|
10
|
+
origins: CircuitryResourceOrigins;
|
|
11
|
+
};
|
|
12
|
+
export declare const loadCircuitryGraphFile: (filename?: string, standard?: CircuitryValidationStandard, stack?: string[]) => Promise<LoadedCircuitryGraph>;
|
|
5
13
|
export declare class NodeCircuitryHost implements CircuitryHost {
|
|
6
14
|
private resolveGraphFile;
|
|
7
15
|
private runFileFor;
|
|
8
16
|
private exists;
|
|
9
17
|
private parseIssue;
|
|
10
18
|
private parseGraphForValidation;
|
|
11
|
-
private resolveGraphImports;
|
|
12
|
-
/** Rewrite inputs in imported resource to use aliased resource ids. */
|
|
13
|
-
private rewriteImportInputs;
|
|
14
19
|
private parseAndResolveGraph;
|
|
15
20
|
private readGraphFile;
|
|
16
21
|
readGraph(input?: {
|
package/dist/node.js
CHANGED
|
@@ -8,7 +8,7 @@ import os from "node:os";
|
|
|
8
8
|
import YAML from "yaml";
|
|
9
9
|
|
|
10
10
|
// src/graph.ts
|
|
11
|
-
var CIRCUITRY_SPEC_VERSION = "0.3.
|
|
11
|
+
var CIRCUITRY_SPEC_VERSION = "0.3.1";
|
|
12
12
|
var runtimeInputToText = (value) => {
|
|
13
13
|
if (typeof value === "string") return value;
|
|
14
14
|
if (value === void 0) return "";
|
|
@@ -79,35 +79,51 @@ var expandResources = (resources) => {
|
|
|
79
79
|
input: { type: "text", value: resource.value }
|
|
80
80
|
});
|
|
81
81
|
} else if (resource.type === "agent") {
|
|
82
|
+
const agent = resource;
|
|
82
83
|
nodes.push({
|
|
83
84
|
id,
|
|
84
85
|
kind: "agent",
|
|
85
|
-
label:
|
|
86
|
+
label: agent.label || agent.identity || id,
|
|
86
87
|
agent: {
|
|
87
|
-
identity:
|
|
88
|
-
model:
|
|
89
|
-
thinkingLevel:
|
|
90
|
-
tools:
|
|
91
|
-
instructions:
|
|
92
|
-
personality:
|
|
93
|
-
context:
|
|
88
|
+
identity: agent.identity || agent.label || id,
|
|
89
|
+
model: agent.model,
|
|
90
|
+
thinkingLevel: agent.thinkingLevel,
|
|
91
|
+
tools: agent.tools,
|
|
92
|
+
instructions: agent.instructions,
|
|
93
|
+
personality: agent.personality,
|
|
94
|
+
context: agent.context
|
|
94
95
|
},
|
|
95
|
-
skills:
|
|
96
|
-
expect:
|
|
96
|
+
skills: agent.skills,
|
|
97
|
+
expect: agent.expect
|
|
97
98
|
});
|
|
98
|
-
for (const inputRef of
|
|
99
|
+
for (const inputRef of agent.inputs || []) {
|
|
99
100
|
edges.push({ from: inputRef, to: id, kind: "dependency" });
|
|
100
101
|
}
|
|
101
102
|
} else if (resource.type === "tool") {
|
|
103
|
+
const tool = resource;
|
|
102
104
|
nodes.push({
|
|
103
105
|
id,
|
|
104
106
|
kind: "tool",
|
|
105
|
-
label:
|
|
106
|
-
agent: { instructions:
|
|
107
|
+
label: tool.label || id,
|
|
108
|
+
agent: { instructions: tool.instructions }
|
|
107
109
|
});
|
|
108
|
-
for (const inputRef of
|
|
110
|
+
for (const inputRef of tool.inputs || []) {
|
|
109
111
|
edges.push({ from: inputRef, to: id, kind: "dependency" });
|
|
110
112
|
}
|
|
113
|
+
} else {
|
|
114
|
+
const input = resource;
|
|
115
|
+
nodes.push({
|
|
116
|
+
id,
|
|
117
|
+
kind: "input",
|
|
118
|
+
label: input.label || id,
|
|
119
|
+
input: {
|
|
120
|
+
type: input.type,
|
|
121
|
+
value: input.value,
|
|
122
|
+
uri: input.uri || input.path,
|
|
123
|
+
mimeType: input.mimeType,
|
|
124
|
+
data: input.data
|
|
125
|
+
}
|
|
126
|
+
});
|
|
111
127
|
}
|
|
112
128
|
}
|
|
113
129
|
return { nodes, edges };
|
|
@@ -117,7 +133,7 @@ var normalizeCircuitryGraph = (graph) => {
|
|
|
117
133
|
const { nodes, edges } = expandResources(graph.resources);
|
|
118
134
|
return { ...graph, nodes, edges };
|
|
119
135
|
};
|
|
120
|
-
var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set([
|
|
136
|
+
var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set([CIRCUITRY_SPEC_VERSION]);
|
|
121
137
|
var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
|
|
122
138
|
const resolvedStandard = createCircuitryValidationStandard(
|
|
123
139
|
standard,
|
|
@@ -141,22 +157,22 @@ var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
|
|
|
141
157
|
}
|
|
142
158
|
if (options.sourceFormat) {
|
|
143
159
|
if (!graphObject.resources || Object.keys(graphObject.resources).length === 0) {
|
|
144
|
-
addError("missing_resources", "Circuitry
|
|
160
|
+
addError("missing_resources", "Circuitry graphs must use a resources: section", ["resources"]);
|
|
145
161
|
}
|
|
146
162
|
if (graphObject.agents && Object.keys(graphObject.agents).length > 0) {
|
|
147
|
-
addError("forbidden_agents", "Circuitry
|
|
163
|
+
addError("forbidden_agents", "Circuitry graph files must not use top-level agents:; use resources:", ["agents"]);
|
|
148
164
|
}
|
|
149
165
|
if (graphObject.inputs && Object.keys(graphObject.inputs).length > 0) {
|
|
150
|
-
addError("forbidden_inputs", "Circuitry
|
|
166
|
+
addError("forbidden_inputs", "Circuitry graph files use args: for runtime arguments and resource inputs for edges", ["inputs"]);
|
|
151
167
|
}
|
|
152
168
|
if (graphObject.nodes && graphObject.nodes.length > 0) {
|
|
153
|
-
addError("forbidden_nodes", "Circuitry
|
|
169
|
+
addError("forbidden_nodes", "Circuitry graph files must not use top-level nodes:; use resources:", ["nodes"]);
|
|
154
170
|
}
|
|
155
171
|
if (graphObject.edges && graphObject.edges.length > 0) {
|
|
156
|
-
addError("forbidden_edges", "Circuitry
|
|
172
|
+
addError("forbidden_edges", "Circuitry graph files must not use top-level edges:; use resource inputs:", ["edges"]);
|
|
157
173
|
}
|
|
158
174
|
if (graphObject.links && graphObject.links.length > 0) {
|
|
159
|
-
addError("forbidden_links", "Circuitry
|
|
175
|
+
addError("forbidden_links", "Circuitry graph files use imports: for multi-file graphs", ["links"]);
|
|
160
176
|
}
|
|
161
177
|
}
|
|
162
178
|
const normalized = normalizeCircuitryGraph(graphObject);
|
|
@@ -804,6 +820,63 @@ var runNodeWithPiSDK = async ({
|
|
|
804
820
|
}
|
|
805
821
|
};
|
|
806
822
|
var defaultFilename = "graph.circuitry.yaml";
|
|
823
|
+
var resourceInputs = (resource) => "inputs" in resource && Array.isArray(resource.inputs) ? resource.inputs : [];
|
|
824
|
+
var withResourceInputs = (resource, inputs) => "inputs" in resource ? { ...resource, inputs } : resource;
|
|
825
|
+
var selectedImportResources = (imp, resources) => {
|
|
826
|
+
if (imp.resources === "*") return Object.fromEntries(Object.keys(resources).map((id) => [id, id]));
|
|
827
|
+
if (Array.isArray(imp.resources)) return Object.fromEntries(imp.resources.map((id) => [id, id]));
|
|
828
|
+
return imp.resources;
|
|
829
|
+
};
|
|
830
|
+
var importedId = (localId, prefix) => `${prefix || ""}${localId}`;
|
|
831
|
+
var rewriteResourceInputs = (resource, aliasMap) => {
|
|
832
|
+
const inputs = resourceInputs(resource);
|
|
833
|
+
if (inputs.length === 0) return resource;
|
|
834
|
+
return withResourceInputs(resource, inputs.map((input) => aliasMap[input] || input));
|
|
835
|
+
};
|
|
836
|
+
var resolveCircuitryGraph = async (parsed, graphFile, text, standard, stack = []) => {
|
|
837
|
+
if (stack.includes(graphFile)) {
|
|
838
|
+
throw new Error(`Circuitry graph import cycle: ${[...stack, graphFile].join(" -> ")}`);
|
|
839
|
+
}
|
|
840
|
+
const nextStack = [...stack, graphFile];
|
|
841
|
+
const resources = {};
|
|
842
|
+
const origins = {};
|
|
843
|
+
for (const imp of parsed.imports || []) {
|
|
844
|
+
const linkedFile = path.resolve(path.dirname(graphFile), imp.path);
|
|
845
|
+
const loaded = await loadCircuitryGraphFile(linkedFile, standard, nextStack);
|
|
846
|
+
const available = loaded.graph.resources || {};
|
|
847
|
+
const aliases = selectedImportResources(imp, available);
|
|
848
|
+
const aliasMap = {};
|
|
849
|
+
for (const [sourceId, localId] of Object.entries(aliases)) aliasMap[sourceId] = importedId(localId, imp.prefix);
|
|
850
|
+
for (const [sourceId, localId] of Object.entries(aliases)) {
|
|
851
|
+
const resource = available[sourceId];
|
|
852
|
+
if (!resource) throw new Error(`Imported resource not found: ${sourceId} in ${imp.path}`);
|
|
853
|
+
const nextId = importedId(localId, imp.prefix);
|
|
854
|
+
if (resources[nextId]) throw new Error(`Imported Circuitry resource id collision: ${nextId} from ${imp.path}`);
|
|
855
|
+
resources[nextId] = rewriteResourceInputs(resource, aliasMap);
|
|
856
|
+
origins[nextId] = loaded.origins[sourceId] || linkedFile;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
for (const [id, resource] of Object.entries(parsed.resources || {})) {
|
|
860
|
+
if (resources[id]) throw new Error(`Circuitry resource id collision: ${id} in ${graphFile}`);
|
|
861
|
+
resources[id] = resource;
|
|
862
|
+
origins[id] = graphFile;
|
|
863
|
+
}
|
|
864
|
+
const graph = normalizeCircuitryGraph({ ...parsed, imports: [], resources });
|
|
865
|
+
if (stack.length === 0) {
|
|
866
|
+
const validation = validateCircuitryExecutionGraphWithStandard(graph, standard);
|
|
867
|
+
if (validation.errors.length) {
|
|
868
|
+
throw new Error(`Invalid Circuitry graph:
|
|
869
|
+
${validation.errors.map((e) => e.message).join("\n")}`);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
return { filename: graphFile, text, graph, origins };
|
|
873
|
+
};
|
|
874
|
+
var loadCircuitryGraphFile = async (filename = defaultFilename, standard, stack = []) => {
|
|
875
|
+
const graphFile = path.resolve(process.cwd(), filename);
|
|
876
|
+
const text = await readFile(graphFile, "utf8");
|
|
877
|
+
const parsed = parseCircuitryText(text, graphFile, standard, { validate: false });
|
|
878
|
+
return resolveCircuitryGraph(parsed, graphFile, text, standard, stack);
|
|
879
|
+
};
|
|
807
880
|
var NodeCircuitryHost = class {
|
|
808
881
|
resolveGraphFile(filename) {
|
|
809
882
|
return path.resolve(
|
|
@@ -840,71 +913,19 @@ var NodeCircuitryHost = class {
|
|
|
840
913
|
return { error: this.parseIssue(error, filename) };
|
|
841
914
|
}
|
|
842
915
|
}
|
|
843
|
-
async resolveGraphImports(graph, filename, standard, stack = []) {
|
|
844
|
-
const graphFile = path.resolve(filename);
|
|
845
|
-
if (stack.includes(graphFile)) {
|
|
846
|
-
throw new Error(`Circuitry graph import cycle: ${[...stack, graphFile].join(" -> ")}`);
|
|
847
|
-
}
|
|
848
|
-
const importedResources = {};
|
|
849
|
-
const aliasMap = {};
|
|
850
|
-
const nextStack = [...stack, graphFile];
|
|
851
|
-
for (const imp of graph.imports || []) {
|
|
852
|
-
const linkedFile = path.resolve(path.dirname(graphFile), imp.path);
|
|
853
|
-
const linkedText = await readFile(linkedFile, "utf8");
|
|
854
|
-
const parsed = parseCircuitryText(linkedText, linkedFile, standard, { validate: false });
|
|
855
|
-
const resolved = await this.resolveGraphImports(parsed, linkedFile, standard, nextStack);
|
|
856
|
-
const resourceId = imp.resource;
|
|
857
|
-
const resource = resolved.resources?.[resourceId];
|
|
858
|
-
if (!resource) {
|
|
859
|
-
throw new Error(`Imported resource not found: ${resourceId} in ${imp.path}`);
|
|
860
|
-
}
|
|
861
|
-
const localId = imp.as || resourceId;
|
|
862
|
-
if (importedResources[localId]) {
|
|
863
|
-
throw new Error(`Imported Circuitry resource id collision: ${localId} from ${imp.path}`);
|
|
864
|
-
}
|
|
865
|
-
if (imp.as) aliasMap[resourceId] = localId;
|
|
866
|
-
const resourceWithRewrittenInputs = this.rewriteImportInputs(resource, aliasMap);
|
|
867
|
-
importedResources[localId] = resourceWithRewrittenInputs;
|
|
868
|
-
}
|
|
869
|
-
return {
|
|
870
|
-
...graph,
|
|
871
|
-
resources: {
|
|
872
|
-
...importedResources,
|
|
873
|
-
...graph.resources || {}
|
|
874
|
-
}
|
|
875
|
-
};
|
|
876
|
-
}
|
|
877
|
-
/** Rewrite inputs in imported resource to use aliased resource ids. */
|
|
878
|
-
rewriteImportInputs(resource, aliasMap) {
|
|
879
|
-
if (resource.type === "agent" || resource.type === "tool") {
|
|
880
|
-
const newInputs = (resource.inputs || []).map((input) => aliasMap[input] || input);
|
|
881
|
-
return { ...resource, inputs: newInputs };
|
|
882
|
-
}
|
|
883
|
-
return resource;
|
|
884
|
-
}
|
|
885
916
|
async parseAndResolveGraph(text, filename, standard) {
|
|
886
|
-
const parsed = this.parseGraphForValidation(text, filename, standard);
|
|
887
|
-
if (!parsed.graph) return parsed;
|
|
888
917
|
try {
|
|
889
|
-
|
|
918
|
+
const graphFile = path.resolve(process.cwd(), filename);
|
|
919
|
+
const source = text || "";
|
|
920
|
+
const parsed = parseCircuitryText(source, graphFile, standard, { validate: false });
|
|
921
|
+
return { graph: (await resolveCircuitryGraph(parsed, graphFile, source, standard)).graph };
|
|
890
922
|
} catch (error) {
|
|
891
923
|
return { error: this.parseIssue(error, filename) };
|
|
892
924
|
}
|
|
893
925
|
}
|
|
894
926
|
async readGraphFile(filename) {
|
|
895
|
-
const
|
|
896
|
-
|
|
897
|
-
const parsed = await this.parseAndResolveGraph(text, graphFile);
|
|
898
|
-
if (!parsed.graph) throw new Error(`Invalid Circuitry graph:
|
|
899
|
-
${parsed.error?.message}`);
|
|
900
|
-
const validation = validateCircuitryGraphWithStandard(parsed.graph);
|
|
901
|
-
if (validation.errors.length) {
|
|
902
|
-
throw new Error(
|
|
903
|
-
`Invalid Circuitry graph:
|
|
904
|
-
${validation.errors.map((e) => e.message).join("\n")}`
|
|
905
|
-
);
|
|
906
|
-
}
|
|
907
|
-
return { graphFile, text, graph: normalizeCircuitryGraph(parsed.graph) };
|
|
927
|
+
const loaded = await loadCircuitryGraphFile(this.resolveGraphFile(filename));
|
|
928
|
+
return { graphFile: loaded.filename, text: loaded.text, graph: loaded.graph, origins: loaded.origins };
|
|
908
929
|
}
|
|
909
930
|
async readGraph(input = {}) {
|
|
910
931
|
const { graphFile, text, graph } = await this.readGraphFile(input.filename);
|
|
@@ -917,10 +938,10 @@ ${validation.errors.map((e) => e.message).join("\n")}`
|
|
|
917
938
|
return validateCircuitryGraphWithStandard(input.graph, input.standard);
|
|
918
939
|
const filename = input.filename || defaultFilename;
|
|
919
940
|
const parsed = await this.parseAndResolveGraph(input.text, filename, input.standard);
|
|
920
|
-
if ("
|
|
941
|
+
if (!("graph" in parsed)) {
|
|
921
942
|
return {
|
|
922
943
|
ok: false,
|
|
923
|
-
errors: [parsed.error],
|
|
944
|
+
errors: parsed.error ? [parsed.error] : [],
|
|
924
945
|
standard: validateCircuitryGraphWithStandard({}, input.standard).standard
|
|
925
946
|
};
|
|
926
947
|
}
|
|
@@ -933,11 +954,12 @@ ${validation.errors.map((e) => e.message).join("\n")}`
|
|
|
933
954
|
input.filename || graphFile,
|
|
934
955
|
input.standard
|
|
935
956
|
);
|
|
936
|
-
if (!parsed
|
|
957
|
+
if (!("graph" in parsed))
|
|
937
958
|
throw new Error(`Invalid Circuitry graph:
|
|
938
959
|
${parsed.error?.message}`);
|
|
960
|
+
const graph = parsed.graph;
|
|
939
961
|
const validation = await this.validateGraph({
|
|
940
|
-
graph
|
|
962
|
+
graph,
|
|
941
963
|
standard: input.standard
|
|
942
964
|
});
|
|
943
965
|
if (validation.errors.length)
|
|
@@ -947,12 +969,12 @@ ${validation.errors.map((e) => e.message).join("\n")}`
|
|
|
947
969
|
);
|
|
948
970
|
await writeFile(
|
|
949
971
|
graphFile,
|
|
950
|
-
stringifyCircuitryText(
|
|
972
|
+
stringifyCircuitryText(graph, graphFile),
|
|
951
973
|
"utf8"
|
|
952
974
|
);
|
|
953
975
|
return {
|
|
954
976
|
filename: graphFile,
|
|
955
|
-
graph
|
|
977
|
+
graph,
|
|
956
978
|
mode: "replace"
|
|
957
979
|
};
|
|
958
980
|
}
|
|
@@ -994,5 +1016,6 @@ ${validation.errors.map((e) => e.message).join("\n")}`
|
|
|
994
1016
|
};
|
|
995
1017
|
export {
|
|
996
1018
|
NodeCircuitryHost,
|
|
1019
|
+
loadCircuitryGraphFile,
|
|
997
1020
|
runNodeWithPiSDK
|
|
998
1021
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@darkhorseprojects/circuitry",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"import": "./dist/node.js"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"circuitry": "./dist/cli.js"
|
|
19
|
+
},
|
|
17
20
|
"license": "LGPL-3.0-only",
|
|
18
21
|
"files": [
|
|
19
22
|
"dist",
|