@darkhorseprojects/circuitry 0.2.99 → 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 +32 -12
- package/dist/index.js +40 -21
- package/dist/node.d.ts +8 -5
- package/dist/node.js +111 -98
- 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.
|
|
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.
|
|
@@ -170,29 +187,32 @@ export type CircuitryRuntimeInputs = Record<string, unknown>;
|
|
|
170
187
|
* graph source stable and catches misspelled input names before execution.
|
|
171
188
|
*/
|
|
172
189
|
export declare const applyCircuitryRuntimeInputs: (graph: CircuitryGraph, inputs?: CircuitryRuntimeInputs) => CircuitryGraph;
|
|
173
|
-
|
|
190
|
+
/** Import declaration for bringing resources from another graph file. */
|
|
191
|
+
export type CircuitryImport = {
|
|
174
192
|
/** Path to another .circuitry.yaml/.json file, resolved relative to this file. */
|
|
175
193
|
path: string;
|
|
176
|
-
/**
|
|
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. */
|
|
177
197
|
prefix?: string;
|
|
178
198
|
};
|
|
179
199
|
export type CircuitryGraph = {
|
|
180
|
-
/** Spec version.
|
|
200
|
+
/** Spec version. */
|
|
181
201
|
circuitry: CircuitrySpecVersion | string;
|
|
182
202
|
id?: string;
|
|
183
203
|
title?: string;
|
|
184
204
|
description?: string;
|
|
185
|
-
/**
|
|
186
|
-
|
|
205
|
+
/** Explicit imports of resources from other graph files. */
|
|
206
|
+
imports?: CircuitryImport[];
|
|
207
|
+
/** Runtime call contract. Args bind to resources with matching ids. */
|
|
208
|
+
args?: Record<string, CircuitryGraphArg>;
|
|
187
209
|
resources?: Record<string, CircuitryResourceEntry>;
|
|
188
210
|
/** Internal normalized execution nodes. Authored graph files must not set this. */
|
|
189
211
|
nodes?: CircuitryNode[];
|
|
190
212
|
/** Internal normalized execution edges. Authored graph files must not set this. */
|
|
191
213
|
edges?: CircuitryEdge[];
|
|
192
|
-
/** Forbidden in authored
|
|
214
|
+
/** Forbidden in authored graph files. */
|
|
193
215
|
agents?: never;
|
|
194
|
-
/** Forbidden in authored v0.2 graph files. */
|
|
195
|
-
inputs?: never;
|
|
196
216
|
runtime?: CircuitryRuntime;
|
|
197
217
|
validation?: CircuitryGraphValidation;
|
|
198
218
|
metadata?: Record<string, unknown>;
|
|
@@ -223,7 +243,7 @@ export type CircuitryValidationResult = {
|
|
|
223
243
|
};
|
|
224
244
|
export declare const DEFAULT_CIRCUITRY_VALIDATION_RULES: CircuitryValidationRuleEntry[];
|
|
225
245
|
export declare const DEFAULT_CIRCUITRY_VALIDATION_STANDARD: {
|
|
226
|
-
readonly version: "0.
|
|
246
|
+
readonly version: "0.3.1";
|
|
227
247
|
readonly requireSpecVersion: true;
|
|
228
248
|
readonly rules: CircuitryValidationRuleEntry[];
|
|
229
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.
|
|
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,19 +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"]);
|
|
192
|
+
}
|
|
193
|
+
if (graphObject.links && graphObject.links.length > 0) {
|
|
194
|
+
addError("forbidden_links", "Circuitry graph files use imports: for multi-file graphs", ["links"]);
|
|
176
195
|
}
|
|
177
196
|
}
|
|
178
197
|
const normalized = normalizeCircuitryGraph(graphObject);
|
package/dist/node.d.ts
CHANGED
|
@@ -2,17 +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 linkPath;
|
|
12
|
-
private linkPrefix;
|
|
13
|
-
private prefixResourceIds;
|
|
14
|
-
private mergeResources;
|
|
15
|
-
private resolveGraphLinks;
|
|
16
19
|
private parseAndResolveGraph;
|
|
17
20
|
private readGraphFile;
|
|
18
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.
|
|
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,19 +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"]);
|
|
173
|
+
}
|
|
174
|
+
if (graphObject.links && graphObject.links.length > 0) {
|
|
175
|
+
addError("forbidden_links", "Circuitry graph files use imports: for multi-file graphs", ["links"]);
|
|
157
176
|
}
|
|
158
177
|
}
|
|
159
178
|
const normalized = normalizeCircuitryGraph(graphObject);
|
|
@@ -801,6 +820,63 @@ var runNodeWithPiSDK = async ({
|
|
|
801
820
|
}
|
|
802
821
|
};
|
|
803
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
|
+
};
|
|
804
880
|
var NodeCircuitryHost = class {
|
|
805
881
|
resolveGraphFile(filename) {
|
|
806
882
|
return path.resolve(
|
|
@@ -837,84 +913,19 @@ var NodeCircuitryHost = class {
|
|
|
837
913
|
return { error: this.parseIssue(error, filename) };
|
|
838
914
|
}
|
|
839
915
|
}
|
|
840
|
-
linkPath(link) {
|
|
841
|
-
return typeof link === "string" ? link : link.path;
|
|
842
|
-
}
|
|
843
|
-
linkPrefix(link) {
|
|
844
|
-
return typeof link === "string" ? "" : link.prefix || "";
|
|
845
|
-
}
|
|
846
|
-
prefixResourceIds(resources, prefix) {
|
|
847
|
-
if (!prefix) return resources;
|
|
848
|
-
const ids = new Set(Object.keys(resources));
|
|
849
|
-
const prefixed = {};
|
|
850
|
-
for (const [id, resource] of Object.entries(resources)) {
|
|
851
|
-
const next = { ...resource };
|
|
852
|
-
if ((next.type === "agent" || next.type === "tool") && next.inputs) {
|
|
853
|
-
next.inputs = next.inputs.map((input) => ids.has(input) ? `${prefix}${input}` : input);
|
|
854
|
-
}
|
|
855
|
-
prefixed[`${prefix}${id}`] = next;
|
|
856
|
-
}
|
|
857
|
-
return prefixed;
|
|
858
|
-
}
|
|
859
|
-
mergeResources(target, source, fromFile) {
|
|
860
|
-
for (const [id, resource] of Object.entries(source)) {
|
|
861
|
-
if (target[id]) {
|
|
862
|
-
throw new Error(`Linked Circuitry resource id collision: ${id} from ${fromFile}`);
|
|
863
|
-
}
|
|
864
|
-
target[id] = resource;
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
async resolveGraphLinks(graph, filename, standard, stack = []) {
|
|
868
|
-
const graphFile = path.resolve(filename);
|
|
869
|
-
if (stack.includes(graphFile)) {
|
|
870
|
-
throw new Error(`Circuitry graph link cycle: ${[...stack, graphFile].join(" -> ")}`);
|
|
871
|
-
}
|
|
872
|
-
const linkedResources = {};
|
|
873
|
-
const nextStack = [...stack, graphFile];
|
|
874
|
-
for (const link of graph.links || []) {
|
|
875
|
-
const rawPath = this.linkPath(link);
|
|
876
|
-
if (!rawPath) throw new Error(`Circuitry graph link is missing path in ${graphFile}`);
|
|
877
|
-
const linkedFile = path.resolve(path.dirname(graphFile), rawPath);
|
|
878
|
-
const linkedText = await readFile(linkedFile, "utf8");
|
|
879
|
-
const parsed = parseCircuitryText(linkedText, linkedFile, standard, { validate: false });
|
|
880
|
-
const resolved = await this.resolveGraphLinks(parsed, linkedFile, standard, nextStack);
|
|
881
|
-
this.mergeResources(
|
|
882
|
-
linkedResources,
|
|
883
|
-
this.prefixResourceIds(resolved.resources || {}, this.linkPrefix(link)),
|
|
884
|
-
linkedFile
|
|
885
|
-
);
|
|
886
|
-
}
|
|
887
|
-
return {
|
|
888
|
-
...graph,
|
|
889
|
-
resources: {
|
|
890
|
-
...linkedResources,
|
|
891
|
-
...graph.resources || {}
|
|
892
|
-
}
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
916
|
async parseAndResolveGraph(text, filename, standard) {
|
|
896
|
-
const parsed = this.parseGraphForValidation(text, filename, standard);
|
|
897
|
-
if (!parsed.graph) return parsed;
|
|
898
917
|
try {
|
|
899
|
-
|
|
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 };
|
|
900
922
|
} catch (error) {
|
|
901
923
|
return { error: this.parseIssue(error, filename) };
|
|
902
924
|
}
|
|
903
925
|
}
|
|
904
926
|
async readGraphFile(filename) {
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
const parsed = await this.parseAndResolveGraph(text, graphFile);
|
|
908
|
-
if (!parsed.graph) throw new Error(`Invalid Circuitry graph:
|
|
909
|
-
${parsed.error?.message}`);
|
|
910
|
-
const validation = validateCircuitryGraphWithStandard(parsed.graph);
|
|
911
|
-
if (validation.errors.length) {
|
|
912
|
-
throw new Error(
|
|
913
|
-
`Invalid Circuitry graph:
|
|
914
|
-
${validation.errors.map((e) => e.message).join("\n")}`
|
|
915
|
-
);
|
|
916
|
-
}
|
|
917
|
-
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 };
|
|
918
929
|
}
|
|
919
930
|
async readGraph(input = {}) {
|
|
920
931
|
const { graphFile, text, graph } = await this.readGraphFile(input.filename);
|
|
@@ -927,10 +938,10 @@ ${validation.errors.map((e) => e.message).join("\n")}`
|
|
|
927
938
|
return validateCircuitryGraphWithStandard(input.graph, input.standard);
|
|
928
939
|
const filename = input.filename || defaultFilename;
|
|
929
940
|
const parsed = await this.parseAndResolveGraph(input.text, filename, input.standard);
|
|
930
|
-
if ("
|
|
941
|
+
if (!("graph" in parsed)) {
|
|
931
942
|
return {
|
|
932
943
|
ok: false,
|
|
933
|
-
errors: [parsed.error],
|
|
944
|
+
errors: parsed.error ? [parsed.error] : [],
|
|
934
945
|
standard: validateCircuitryGraphWithStandard({}, input.standard).standard
|
|
935
946
|
};
|
|
936
947
|
}
|
|
@@ -943,11 +954,12 @@ ${validation.errors.map((e) => e.message).join("\n")}`
|
|
|
943
954
|
input.filename || graphFile,
|
|
944
955
|
input.standard
|
|
945
956
|
);
|
|
946
|
-
if (!parsed
|
|
957
|
+
if (!("graph" in parsed))
|
|
947
958
|
throw new Error(`Invalid Circuitry graph:
|
|
948
959
|
${parsed.error?.message}`);
|
|
960
|
+
const graph = parsed.graph;
|
|
949
961
|
const validation = await this.validateGraph({
|
|
950
|
-
graph
|
|
962
|
+
graph,
|
|
951
963
|
standard: input.standard
|
|
952
964
|
});
|
|
953
965
|
if (validation.errors.length)
|
|
@@ -957,12 +969,12 @@ ${validation.errors.map((e) => e.message).join("\n")}`
|
|
|
957
969
|
);
|
|
958
970
|
await writeFile(
|
|
959
971
|
graphFile,
|
|
960
|
-
stringifyCircuitryText(
|
|
972
|
+
stringifyCircuitryText(graph, graphFile),
|
|
961
973
|
"utf8"
|
|
962
974
|
);
|
|
963
975
|
return {
|
|
964
976
|
filename: graphFile,
|
|
965
|
-
graph
|
|
977
|
+
graph,
|
|
966
978
|
mode: "replace"
|
|
967
979
|
};
|
|
968
980
|
}
|
|
@@ -1004,5 +1016,6 @@ ${validation.errors.map((e) => e.message).join("\n")}`
|
|
|
1004
1016
|
};
|
|
1005
1017
|
export {
|
|
1006
1018
|
NodeCircuitryHost,
|
|
1019
|
+
loadCircuitryGraphFile,
|
|
1007
1020
|
runNodeWithPiSDK
|
|
1008
1021
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@darkhorseprojects/circuitry",
|
|
3
|
-
"version": "0.
|
|
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",
|