@darkhorseprojects/circuitry 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/node.js CHANGED
@@ -5,35 +5,11 @@ import path from "node:path";
5
5
  import os from "node:os";
6
6
 
7
7
  // src/yaml.ts
8
- import YAML from "yaml";
8
+ import YAML2 from "yaml";
9
9
 
10
10
  // src/graph.ts
11
- var CIRCUITRY_SPEC_VERSION = "0.3.0";
12
- var runtimeInputToText = (value) => {
13
- if (typeof value === "string") return value;
14
- if (value === void 0) return "";
15
- return JSON.stringify(value, null, 2);
16
- };
17
- var applyCircuitryRuntimeInputs = (graph, inputs = {}) => {
18
- const entries = Object.entries(inputs);
19
- if (entries.length === 0) return graph;
20
- const next = {
21
- ...graph,
22
- resources: graph.resources ? { ...graph.resources } : graph.resources
23
- };
24
- for (const [id, value] of entries) {
25
- const text = runtimeInputToText(value);
26
- const resource = next.resources?.[id];
27
- if (resource) {
28
- if (resource.type !== "text")
29
- throw new Error(`Runtime input ${id} targets non-text resource: ${resource.type}`);
30
- next.resources[id] = { ...resource, value: text };
31
- continue;
32
- }
33
- throw new Error(`Runtime input does not match a graph text resource: ${id}`);
34
- }
35
- return next;
36
- };
11
+ import YAML from "yaml";
12
+ var CIRCUITRY_SPEC_VERSION = "0.3.2";
37
13
  var DEFAULT_CIRCUITRY_VALIDATION_RULES = [
38
14
  "no-self-loops",
39
15
  "no-unknown-edge-endpoints",
@@ -46,25 +22,30 @@ var DEFAULT_CIRCUITRY_VALIDATION_STANDARD = {
46
22
  rules: DEFAULT_CIRCUITRY_VALIDATION_RULES,
47
23
  executableKinds: ["agent", "tool", "output"]
48
24
  };
49
- var executableKindsFromRules = (rules = []) => {
50
- const executableRule = rules.find(
51
- (entry) => typeof entry !== "string" && entry.rule === "require-executable-inputs"
52
- );
53
- return executableRule?.executableKinds;
25
+ var runtimeInputToText = (value) => {
26
+ if (typeof value === "string") return value;
27
+ if (value === void 0) return "";
28
+ return YAML.stringify(value).trimEnd();
29
+ };
30
+ var applyCircuitryRuntimeInputs = (graph, inputs = {}) => {
31
+ const next = { ...graph, resources: graph.resources ? { ...graph.resources } : graph.resources };
32
+ for (const [id, value] of Object.entries(inputs)) {
33
+ const resource = next.resources?.[id];
34
+ if (!resource) throw new Error(`unknown arg: ${id}`);
35
+ if (resource.type !== "text") throw new Error(`arg is not text: ${id}`);
36
+ next.resources[id] = { ...resource, value: runtimeInputToText(value) };
37
+ }
38
+ return next;
54
39
  };
40
+ var ruleName = (rule) => typeof rule === "string" ? rule : rule.rule;
41
+ var executableKindsFromRules = (rules) => rules.find((rule) => typeof rule !== "string" && rule.rule === "require-executable-inputs")?.executableKinds;
55
42
  var createCircuitryValidationStandard = (standard = {}, graph) => {
56
- const rules = [
57
- ...DEFAULT_CIRCUITRY_VALIDATION_RULES,
58
- ...graph?.validation?.rules || [],
59
- ...standard.rules || []
60
- ];
43
+ const rules = [...DEFAULT_CIRCUITRY_VALIDATION_RULES, ...graph?.validation?.rules || [], ...standard.rules || []];
61
44
  return {
62
45
  version: standard.version || DEFAULT_CIRCUITRY_VALIDATION_STANDARD.version,
63
46
  requireSpecVersion: standard.requireSpecVersion ?? DEFAULT_CIRCUITRY_VALIDATION_STANDARD.requireSpecVersion,
64
47
  rules,
65
- executableKinds: standard.executableKinds || executableKindsFromRules(rules) || [
66
- ...DEFAULT_CIRCUITRY_VALIDATION_STANDARD.executableKinds
67
- ]
48
+ executableKinds: standard.executableKinds || executableKindsFromRules(rules) || [...DEFAULT_CIRCUITRY_VALIDATION_STANDARD.executableKinds]
68
49
  };
69
50
  };
70
51
  var expandResources = (resources) => {
@@ -72,294 +53,151 @@ var expandResources = (resources) => {
72
53
  const edges = [];
73
54
  for (const [id, resource] of Object.entries(resources)) {
74
55
  if (resource.type === "text") {
75
- nodes.push({
76
- id,
77
- kind: "input",
78
- label: resource.label || id,
79
- input: { type: "text", value: resource.value }
80
- });
56
+ nodes.push({ id, kind: "input", label: resource.label || id, input: { type: "text", value: resource.value } });
81
57
  } else if (resource.type === "agent") {
58
+ const agent = resource;
82
59
  nodes.push({
83
60
  id,
84
61
  kind: "agent",
85
- label: resource.label || resource.identity || id,
62
+ label: agent.label || agent.identity || id,
86
63
  agent: {
87
- identity: resource.identity || resource.label || id,
88
- model: resource.model,
89
- thinkingLevel: resource.thinkingLevel,
90
- tools: resource.tools,
91
- instructions: resource.instructions,
92
- personality: resource.personality,
93
- context: resource.context
64
+ identity: agent.identity || agent.label || id,
65
+ model: agent.model,
66
+ tools: agent.tools,
67
+ instructions: agent.instructions,
68
+ personality: agent.personality,
69
+ context: agent.context
94
70
  },
95
- skills: resource.skills,
96
- expect: resource.expect
71
+ skills: agent.skills,
72
+ expect: agent.expect
97
73
  });
98
- for (const inputRef of resource.inputs || []) {
99
- edges.push({ from: inputRef, to: id, kind: "dependency" });
100
- }
74
+ for (const input of agent.inputs || []) edges.push({ from: input, to: id, kind: "dependency" });
101
75
  } else if (resource.type === "tool") {
102
- nodes.push({
103
- id,
104
- kind: "tool",
105
- label: resource.label || id,
106
- agent: { instructions: resource.instructions }
107
- });
108
- for (const inputRef of resource.inputs || []) {
109
- edges.push({ from: inputRef, to: id, kind: "dependency" });
110
- }
76
+ const tool = resource;
77
+ nodes.push({ id, kind: "tool", label: tool.label || id, agent: { instructions: tool.instructions } });
78
+ for (const input of tool.inputs || []) edges.push({ from: input, to: id, kind: "dependency" });
79
+ } else {
80
+ const input = resource;
81
+ nodes.push({ id, kind: "input", label: input.label || id, input: { type: input.type, value: input.value, uri: input.uri || input.path, mimeType: input.mimeType, data: input.data } });
111
82
  }
112
83
  }
113
84
  return { nodes, edges };
114
85
  };
115
86
  var normalizeCircuitryGraph = (graph) => {
116
- if (!graph.resources || Object.keys(graph.resources).length === 0) return { ...graph };
117
- const { nodes, edges } = expandResources(graph.resources);
118
- return { ...graph, nodes, edges };
87
+ if (!graph.resources) return { ...graph };
88
+ return { ...graph, ...expandResources(graph.resources) };
119
89
  };
120
- var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set(["0.3"]);
121
- var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
122
- const resolvedStandard = createCircuitryValidationStandard(
123
- standard,
124
- graph && typeof graph === "object" && !Array.isArray(graph) ? graph : void 0
125
- );
90
+ var validateCircuitryGraphInternal = (graph, standard = {}, source) => {
91
+ const resolved = createCircuitryValidationStandard(standard, graph && typeof graph === "object" && !Array.isArray(graph) ? graph : void 0);
126
92
  const errors = [];
127
- const addError = (code, message, path3) => errors.push({ code, message, ...path3 ? { path: path3 } : {} });
128
- if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
129
- addError("invalid_graph", "Circuitry graph must be an object");
130
- return { ok: false, errors, standard: resolvedStandard };
131
- }
132
- const graphObject = graph;
133
- const rules = resolvedStandard.rules;
134
- const hasRule = (name) => rules.some((entry) => (typeof entry === "string" ? entry : entry.rule) === name);
135
- if (resolvedStandard.requireSpecVersion && !VALID_SPEC_VERSIONS.has(String(graphObject.circuitry))) {
136
- addError(
137
- "invalid_spec_version",
138
- `Expected circuitry: "${CIRCUITRY_SPEC_VERSION}"`,
139
- ["circuitry"]
140
- );
141
- }
142
- if (options.sourceFormat) {
143
- if (!graphObject.resources || Object.keys(graphObject.resources).length === 0) {
144
- addError("missing_resources", "Circuitry v0.3 graphs must use a resources: section", ["resources"]);
145
- }
146
- if (graphObject.agents && Object.keys(graphObject.agents).length > 0) {
147
- addError("forbidden_agents", "Circuitry v0.3 graph files must not use top-level agents:; use resources:", ["agents"]);
148
- }
149
- if (graphObject.inputs && Object.keys(graphObject.inputs).length > 0) {
150
- addError("forbidden_inputs", "Circuitry v0.3 graph files must not use top-level inputs:; use resources:", ["inputs"]);
151
- }
152
- if (graphObject.nodes && graphObject.nodes.length > 0) {
153
- addError("forbidden_nodes", "Circuitry v0.3 graph files must not use top-level nodes:; use resources:", ["nodes"]);
154
- }
155
- if (graphObject.edges && graphObject.edges.length > 0) {
156
- addError("forbidden_edges", "Circuitry v0.3 graph files must not use top-level edges:; use resource inputs:", ["edges"]);
157
- }
158
- if (graphObject.links && graphObject.links.length > 0) {
159
- addError("forbidden_links", "Circuitry v0.3 graph files must use imports: instead of links:", ["links"]);
160
- }
161
- }
162
- const normalized = normalizeCircuitryGraph(graphObject);
93
+ const issue = (code, message = code, path2) => errors.push({ code, message, ...path2 ? { path: path2 } : {} });
94
+ if (!graph || typeof graph !== "object" || Array.isArray(graph)) return { ok: false, errors: [{ code: "invalid_graph", message: "invalid graph" }], standard: resolved };
95
+ const g = graph;
96
+ if (resolved.requireSpecVersion && g.circuitry !== resolved.version) issue("invalid_version", "invalid version", ["circuitry"]);
97
+ if (source) {
98
+ const raw = g;
99
+ if (!g.resources || Object.keys(g.resources).length === 0) issue("missing_resources", "missing resources", ["resources"]);
100
+ if (raw.agents) issue("forbidden_agents", "forbidden agents", ["agents"]);
101
+ if (raw.inputs) issue("forbidden_inputs", "forbidden inputs", ["inputs"]);
102
+ if (g.nodes?.length) issue("forbidden_nodes", "forbidden nodes", ["nodes"]);
103
+ if (g.edges?.length) issue("forbidden_edges", "forbidden edges", ["edges"]);
104
+ if (raw.links) issue("forbidden_links", "forbidden links", ["links"]);
105
+ }
106
+ const rules = new Set(resolved.rules.map(ruleName));
107
+ const normalized = normalizeCircuitryGraph(g);
163
108
  const ids = /* @__PURE__ */ new Set();
164
109
  for (const [index, node] of (normalized.nodes || []).entries()) {
165
110
  if (!node.id) {
166
- addError("missing_node_id", "Node is missing id", ["nodes", index, "id"]);
111
+ issue("missing_node_id", "missing node id", ["nodes", index, "id"]);
167
112
  continue;
168
113
  }
169
- if (ids.has(node.id)) {
170
- addError("duplicate_node_id", `Duplicate node id: ${node.id}`, ["nodes", index, "id"]);
171
- }
114
+ if (ids.has(node.id)) issue("duplicate_node_id", "duplicate node id", ["nodes", index, "id"]);
172
115
  ids.add(node.id);
173
- if (!node.kind) {
174
- addError("missing_node_kind", `Node ${node.id} is missing kind`, ["nodes", index, "kind"]);
175
- }
116
+ if (!node.kind) issue("missing_node_kind", "missing node kind", ["nodes", index, "kind"]);
176
117
  }
177
118
  const adjacency = /* @__PURE__ */ new Map();
178
- const incomingCount = /* @__PURE__ */ new Map();
119
+ const incoming = /* @__PURE__ */ new Map();
179
120
  for (const id of ids) {
180
121
  adjacency.set(id, []);
181
- incomingCount.set(id, 0);
122
+ incoming.set(id, 0);
182
123
  }
183
124
  for (const [index, edge] of (normalized.edges || []).entries()) {
184
125
  if (!edge.from || !edge.to) {
185
- addError("missing_edge_endpoint", "Edge is missing from/to", ["edges", index]);
126
+ issue("missing_edge_endpoint", "missing edge endpoint", ["edges", index]);
186
127
  continue;
187
128
  }
188
- if (hasRule("no-self-loops") && edge.from === edge.to) {
189
- addError("self_loop", `Edge creates self loop: ${edge.from} -> ${edge.to}`, ["edges", index]);
129
+ if (rules.has("no-self-loops") && edge.from === edge.to) {
130
+ issue("self_loop", "self loop", ["edges", index]);
190
131
  continue;
191
132
  }
192
- if (hasRule("no-unknown-edge-endpoints") && !ids.has(edge.from)) {
193
- addError("unknown_edge_source", `Edge references unknown source: ${edge.from}`, ["edges", index, "from"]);
133
+ const ok = ids.has(edge.from) && ids.has(edge.to);
134
+ if (rules.has("no-unknown-edge-endpoints")) {
135
+ if (!ids.has(edge.from)) issue("unknown_edge_source", "unknown edge source", ["edges", index, "from"]);
136
+ if (!ids.has(edge.to)) issue("unknown_edge_target", "unknown edge target", ["edges", index, "to"]);
194
137
  }
195
- if (hasRule("no-unknown-edge-endpoints") && !ids.has(edge.to)) {
196
- addError("unknown_edge_target", `Edge references unknown target: ${edge.to}`, ["edges", index, "to"]);
197
- }
198
- if (ids.has(edge.from) && ids.has(edge.to)) {
138
+ if (ok) {
199
139
  adjacency.get(edge.from).push(edge.to);
200
- incomingCount.set(edge.to, (incomingCount.get(edge.to) || 0) + 1);
140
+ incoming.set(edge.to, (incoming.get(edge.to) || 0) + 1);
201
141
  }
202
142
  }
203
- const executableKinds = new Set(resolvedStandard.executableKinds);
204
- if (hasRule("require-executable-inputs")) {
143
+ if (rules.has("require-executable-inputs")) {
144
+ const executable = new Set(resolved.executableKinds);
205
145
  for (const [index, node] of (normalized.nodes || []).entries()) {
206
- if (executableKinds.has(node.kind) && (incomingCount.get(node.id) || 0) === 0) {
207
- addError("executable_without_inputs", `Executable node has no inputs: ${node.id}`, ["nodes", index]);
208
- }
209
- }
210
- }
211
- const visiting = /* @__PURE__ */ new Set();
212
- const visited = /* @__PURE__ */ new Set();
213
- const path2 = [];
214
- let cycleMessage = "";
215
- const visit = (id) => {
216
- if (cycleMessage) return;
217
- if (visiting.has(id)) {
218
- cycleMessage = [...path2.slice(path2.indexOf(id)), id].join(" -> ");
219
- return;
220
- }
221
- if (visited.has(id)) return;
222
- visiting.add(id);
223
- path2.push(id);
224
- for (const next of adjacency.get(id) || []) visit(next);
225
- path2.pop();
226
- visiting.delete(id);
227
- visited.add(id);
228
- };
229
- if (hasRule("no-cycles")) {
230
- for (const id of ids) {
231
- visit(id);
232
- if (cycleMessage) {
233
- addError("cycle", `Graph contains cycle: ${cycleMessage}`);
234
- break;
235
- }
236
- }
237
- }
238
- const additionalRules = rules.filter((rule) => {
239
- if (typeof rule === "string") return false;
240
- return rule.rule === "require-graph-field" || rule.rule === "require-node-field" || rule.rule === "require-edge-field" || rule.rule === "require-string" || rule.rule === "reject-string";
241
- });
242
- const fieldPath = (field) => field.split(".").filter(Boolean);
243
- const getField = (value, field) => fieldPath(field).reduce((current, key) => {
244
- if (!current || typeof current !== "object") return void 0;
245
- return current[key];
246
- }, value);
247
- const isPresent = (value) => value !== void 0 && value !== null && value !== "";
248
- for (const rule of additionalRules) {
249
- if (rule.rule === "require-graph-field") {
250
- if (!isPresent(getField(normalized, rule.field))) {
251
- addError("missing_required_graph_field", `Graph is missing required field: ${rule.field}`, fieldPath(rule.field));
252
- }
253
- continue;
254
- }
255
- if (rule.rule === "require-node-field") {
256
- const nodeKinds = new Set(rule.nodeKinds || []);
257
- for (const [index, node] of (normalized.nodes || []).entries()) {
258
- if (nodeKinds.size > 0 && !nodeKinds.has(node.kind)) continue;
259
- if (!isPresent(getField(node, rule.field))) {
260
- addError("missing_required_node_field", `Node ${node.id || "<missing id>"} is missing required field: ${rule.field}`, ["nodes", index, ...fieldPath(rule.field)]);
261
- }
262
- }
263
- continue;
264
- }
265
- if (rule.rule === "require-edge-field") {
266
- const edgeKinds = new Set(rule.edgeKinds || []);
267
- for (const [index, edge] of (normalized.edges || []).entries()) {
268
- if (edgeKinds.size > 0 && (!edge.kind || !edgeKinds.has(edge.kind))) continue;
269
- if (!isPresent(getField(edge, rule.field))) {
270
- addError("missing_required_edge_field", `Edge ${edge.id || index} is missing required field: ${rule.field}`, ["edges", index, ...fieldPath(rule.field)]);
271
- }
272
- }
273
- continue;
274
- }
275
- const checkString = (target, subject, id, match, path3) => {
276
- const value = getField(subject, match.field);
277
- const text = typeof value === "string" ? value : "";
278
- const includes = text.includes(match.value);
279
- if (rule.rule === "require-string" && !includes) {
280
- addError("missing_required_string", `${target} ${id} field ${match.field} must include string: ${match.value}`, path3);
281
- }
282
- if (rule.rule === "reject-string" && includes) {
283
- addError("rejected_string", `${target} ${id} field ${match.field} must not include string: ${match.value}`, path3);
284
- }
146
+ if (executable.has(node.kind) && (incoming.get(node.id) || 0) === 0) issue("missing_executable_input", "missing executable input", ["nodes", index]);
147
+ }
148
+ }
149
+ if (rules.has("no-cycles")) {
150
+ const visiting = /* @__PURE__ */ new Set();
151
+ const visited = /* @__PURE__ */ new Set();
152
+ const visit = (id) => {
153
+ if (visiting.has(id)) return true;
154
+ if (visited.has(id)) return false;
155
+ visiting.add(id);
156
+ for (const next of adjacency.get(id) || []) if (visit(next)) return true;
157
+ visiting.delete(id);
158
+ visited.add(id);
159
+ return false;
285
160
  };
286
- for (const match of rule.graph || []) checkString("graph", normalized, "<root>", match, fieldPath(match.field));
287
- for (const match of rule.nodes || []) {
288
- const nodeKinds = new Set(match.nodeKinds || []);
289
- for (const [index, node] of (normalized.nodes || []).entries()) {
290
- if (nodeKinds.size > 0 && !nodeKinds.has(node.kind)) continue;
291
- checkString("node", node, node.id || "<missing id>", match, ["nodes", index, ...fieldPath(match.field)]);
292
- }
293
- }
294
- for (const match of rule.edges || []) {
295
- const edgeKinds = new Set(match.edgeKinds || []);
296
- for (const [index, edge] of (normalized.edges || []).entries()) {
297
- if (edgeKinds.size > 0 && (!edge.kind || !edgeKinds.has(edge.kind))) continue;
298
- checkString("edge", edge, edge.id || String(index), match, ["edges", index, ...fieldPath(match.field)]);
299
- }
161
+ for (const id of ids) if (visit(id)) {
162
+ issue("cycle", "cycle");
163
+ break;
300
164
  }
301
165
  }
302
- return { ok: errors.length === 0, errors, standard: resolvedStandard };
303
- };
304
- var validateCircuitryGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, { sourceFormat: true });
305
- var validateCircuitryExecutionGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, { sourceFormat: false });
306
- var validateCircuitryGraph = (graph, standard = {}) => validateCircuitryGraphWithStandard(graph, standard).errors.map(
307
- (error) => error.message
308
- );
309
- var parseCircuitryJson = (text, standard = {}, options = {}) => {
310
- let graph;
311
- try {
312
- graph = JSON.parse(text);
313
- } catch (error) {
314
- const message = error instanceof Error ? error.message : String(error);
315
- throw new Error(`Could not parse Circuitry JSON. Check commas, quotes, and braces.
316
- ${message}`);
317
- }
318
- if (options.validate === false) return graph;
319
- const errors = validateCircuitryGraph(graph, standard);
320
- if (errors.length) {
321
- throw new Error(`Invalid Circuitry graph:
322
- ${errors.join("\n")}`);
323
- }
324
- return normalizeCircuitryGraph(graph);
325
- };
326
- var stringifyCircuitryJson = (graph) => {
327
- return `${JSON.stringify(normalizeCircuitryGraph(graph), null, 2)}
328
- `;
166
+ return { ok: errors.length === 0, errors, standard: resolved };
329
167
  };
168
+ var validateCircuitryGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, true);
169
+ var validateCircuitryExecutionGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, false);
170
+ var validateCircuitryGraph = (graph, standard = {}) => validateCircuitryGraphWithStandard(graph, standard).errors.map((error) => error.message);
330
171
 
331
172
  // src/yaml.ts
332
- var parseCircuitryYaml = (text, standard = {}, options = {}) => {
333
- let graph;
173
+ var parseYamlData = (text) => {
334
174
  try {
335
- graph = YAML.parse(text);
175
+ return YAML2.parse(text);
336
176
  } catch (error) {
337
177
  const message = error instanceof Error ? error.message : String(error);
338
- throw new Error(`Could not parse Circuitry YAML. Check indentation and quotes.
178
+ throw new Error(`Could not parse YAML.
339
179
  ${message}`);
340
180
  }
181
+ };
182
+ var stringifyYamlData = (value) => YAML2.stringify(value);
183
+ var parseCircuitryYaml = (text, standard = {}, options = {}) => {
184
+ const graph = parseYamlData(text);
341
185
  if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
342
- throw new Error("Could not parse Circuitry YAML. Expected a graph object.");
186
+ throw new Error("Circuitry YAML must be a graph object.");
343
187
  }
344
- if (options.validate === false) return graph;
345
- const errors = validateCircuitryGraph(graph, standard);
346
- if (errors.length) {
347
- throw new Error(`Invalid Circuitry graph:
188
+ if (options.validate !== false) {
189
+ const errors = validateCircuitryGraph(graph, standard);
190
+ if (errors.length) throw new Error(`Invalid Circuitry graph:
348
191
  ${errors.join("\n")}`);
349
192
  }
350
- return normalizeCircuitryGraph(graph);
351
- };
352
- var stringifyCircuitryYaml = (graph) => {
353
- return YAML.stringify(normalizeCircuitryGraph(graph));
354
- };
355
- var parseCircuitryText = (text, filename = "graph.circuitry.yaml", standard = {}, options = {}) => {
356
- return filename.endsWith(".json") ? parseCircuitryJson(text, standard, options) : parseCircuitryYaml(text, standard, options);
357
- };
358
- var stringifyCircuitryText = (graph, filename = "graph.circuitry.yaml") => {
359
- return filename.endsWith(".json") ? stringifyCircuitryJson(graph) : stringifyCircuitryYaml(graph);
193
+ return graph;
360
194
  };
195
+ var stringifyCircuitryYaml = (graph) => YAML2.stringify(graph);
196
+ var parseCircuitryText = (text, standard = {}, options = {}) => parseCircuitryYaml(text, standard, options);
197
+ var stringifyCircuitryText = (graph) => stringifyCircuitryYaml(graph);
361
198
 
362
199
  // src/scheduler.ts
200
+ import YAML3 from "yaml";
363
201
  var buildCircuitryExecutionGraph = (graph) => {
364
202
  const normalized = normalizeCircuitryGraph(graph);
365
203
  const nodes = /* @__PURE__ */ new Map();
@@ -399,7 +237,7 @@ var formatEdgeData = (data) => {
399
237
  if (!data || Object.keys(data).length === 0) {
400
238
  return "";
401
239
  }
402
- return ` data=${JSON.stringify(data)}`;
240
+ return ` data=${YAML3.stringify(data).trimEnd()}`;
403
241
  };
404
242
  var describeEdge = (edge, direction) => {
405
243
  const otherId = direction === "incoming" ? edge.from : edge.to;
@@ -444,17 +282,17 @@ ${contextSection}`
444
282
  ].filter(Boolean).join("\n\n");
445
283
  };
446
284
  var collectImages = (contextInputs) => contextInputs.filter((item) => item.kind === "image" && item.image).map((item) => item.image);
447
- var stripJsonFence = (output) => {
285
+ var stripYamlFence = (output) => {
448
286
  const trimmed = output.trim();
449
- const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
287
+ const fenced = trimmed.match(/^```(?:ya?ml)?\s*([\s\S]*?)\s*```$/i);
450
288
  return fenced ? fenced[1].trim() : trimmed;
451
289
  };
452
290
  var parseExpectedOutput = (nodeId, output) => {
453
291
  try {
454
- return JSON.parse(stripJsonFence(output));
292
+ return YAML3.parse(stripYamlFence(output));
455
293
  } catch (error) {
456
294
  const detail = error instanceof Error ? error.message : String(error);
457
- throw new Error(`Node ${nodeId} output does not match expect: output is not valid JSON (${detail})`);
295
+ throw new Error(`Node ${nodeId} output does not match expect: output is not valid YAML (${detail})`);
458
296
  }
459
297
  };
460
298
  var isFieldObject = (schema) => !!schema && typeof schema === "object" && !Array.isArray(schema) && typeof schema.type === "string";
@@ -537,7 +375,7 @@ ${errors.join("\n")}`);
537
375
  var executeCircuitryNode = async ({
538
376
  graph,
539
377
  item,
540
- fallbackCycle,
378
+ cycle,
541
379
  inputPayload,
542
380
  contextInputs,
543
381
  defaultModel,
@@ -545,13 +383,13 @@ var executeCircuitryNode = async ({
545
383
  onNodeStart,
546
384
  onNodeComplete
547
385
  }) => {
548
- onNodeStart?.(item.id, fallbackCycle);
386
+ onNodeStart?.(item.id, cycle);
549
387
  if (item.node.kind === "output") {
550
388
  const output = inputPayload.map((input) => input.output).join("\n\n");
551
389
  onNodeComplete?.(item.id, { output });
552
390
  return {
553
391
  nodeId: item.id,
554
- fallbackCycle,
392
+ cycle,
555
393
  inputNodeIds: inputPayload.map((input) => input.nodeId),
556
394
  output
557
395
  };
@@ -563,7 +401,6 @@ var executeCircuitryNode = async ({
563
401
  nodeId: item.id,
564
402
  model: (agent.model === "inherit" ? void 0 : agent.model) || (defaultModel === "inherit" ? void 0 : defaultModel) || "inherit",
565
403
  tools: agent.tools || [],
566
- thinkingLevel: agent.thinkingLevel || "off",
567
404
  personality: agent.personality || "",
568
405
  instructions: agent.instructions || "",
569
406
  context: agent.context || "",
@@ -577,7 +414,7 @@ var executeCircuitryNode = async ({
577
414
  onNodeComplete?.(item.id, { error: message });
578
415
  return {
579
416
  nodeId: item.id,
580
- fallbackCycle,
417
+ cycle,
581
418
  inputNodeIds: inputPayload.map((input) => input.nodeId),
582
419
  output: "",
583
420
  error: message
@@ -587,7 +424,7 @@ var executeCircuitryNode = async ({
587
424
  onNodeComplete?.(item.id, { output: result.output });
588
425
  return {
589
426
  nodeId: item.id,
590
- fallbackCycle,
427
+ cycle,
591
428
  inputNodeIds: inputPayload.map((input) => input.nodeId),
592
429
  output: result.output
593
430
  };
@@ -606,7 +443,7 @@ var runCircuitryGraphExecution = async ({
606
443
  const parallelism = Math.max(1, Math.floor(maxParallelRuns));
607
444
  const defaultModel = graph.runtime?.model;
608
445
  const runOne = async (nodeId, outputs2) => {
609
- const fallbackCycle = false;
446
+ const cycle = false;
610
447
  const item = executionGraph.nodes.get(nodeId);
611
448
  const inputPayload = item.inputNodeIds.filter((sourceId) => outputs2.has(sourceId)).map((sourceId) => ({
612
449
  nodeId: sourceId,
@@ -620,7 +457,7 @@ var runCircuitryGraphExecution = async ({
620
457
  return executeCircuitryNode({
621
458
  graph,
622
459
  item,
623
- fallbackCycle,
460
+ cycle,
624
461
  inputPayload,
625
462
  contextInputs,
626
463
  defaultModel,
@@ -706,7 +543,8 @@ var loadPiSDK = async () => {
706
543
  return await import(modulePath);
707
544
  }
708
545
  }
709
- return await import("@earendil-works/pi-coding-agent");
546
+ const packageName = "@earendil-works/pi-coding-agent";
547
+ return await import(packageName);
710
548
  };
711
549
  var extractAssistantText = (message) => {
712
550
  if (!message || message.role !== "assistant") return "";
@@ -745,8 +583,7 @@ var runNodeWithPiSDK = async ({
745
583
  model,
746
584
  prompt,
747
585
  images = [],
748
- tools = [],
749
- thinkingLevel = "off"
586
+ tools = []
750
587
  }) => {
751
588
  let sdk;
752
589
  try {
@@ -770,7 +607,6 @@ var runNodeWithPiSDK = async ({
770
607
  const selectedModel = requestedModel && requestedModel !== "inherit" ? findRequestedModel(modelRegistry, await modelRegistry.getAvailable(), requestedModel) : void 0;
771
608
  const { session } = await createAgentSession({
772
609
  ...selectedModel ? { model: selectedModel } : {},
773
- thinkingLevel,
774
610
  sessionManager: SessionManager.inMemory(),
775
611
  authStorage,
776
612
  modelRegistry,
@@ -804,6 +640,63 @@ var runNodeWithPiSDK = async ({
804
640
  }
805
641
  };
806
642
  var defaultFilename = "graph.circuitry.yaml";
643
+ var resourceInputs = (resource) => "inputs" in resource && Array.isArray(resource.inputs) ? resource.inputs : [];
644
+ var withResourceInputs = (resource, inputs) => "inputs" in resource ? { ...resource, inputs } : resource;
645
+ var selectedImportResources = (imp, resources) => {
646
+ if (imp.resources === "*") return Object.fromEntries(Object.keys(resources).map((id) => [id, id]));
647
+ if (Array.isArray(imp.resources)) return Object.fromEntries(imp.resources.map((id) => [id, id]));
648
+ return imp.resources;
649
+ };
650
+ var importedId = (localId, prefix) => `${prefix || ""}${localId}`;
651
+ var rewriteResourceInputs = (resource, aliasMap) => {
652
+ const inputs = resourceInputs(resource);
653
+ if (inputs.length === 0) return resource;
654
+ return withResourceInputs(resource, inputs.map((input) => aliasMap[input] || input));
655
+ };
656
+ var resolveCircuitryGraph = async (parsed, graphFile, text, standard, stack = []) => {
657
+ if (stack.includes(graphFile)) {
658
+ throw new Error(`Circuitry graph import cycle: ${[...stack, graphFile].join(" -> ")}`);
659
+ }
660
+ const nextStack = [...stack, graphFile];
661
+ const resources = {};
662
+ const origins = {};
663
+ for (const imp of parsed.imports || []) {
664
+ const linkedFile = path.resolve(path.dirname(graphFile), imp.path);
665
+ const loaded = await loadCircuitryGraphFile(linkedFile, standard, nextStack);
666
+ const available = loaded.graph.resources || {};
667
+ const aliases = selectedImportResources(imp, available);
668
+ const aliasMap = {};
669
+ for (const [sourceId, localId] of Object.entries(aliases)) aliasMap[sourceId] = importedId(localId, imp.prefix);
670
+ for (const [sourceId, localId] of Object.entries(aliases)) {
671
+ const resource = available[sourceId];
672
+ if (!resource) throw new Error(`Imported resource not found: ${sourceId} in ${imp.path}`);
673
+ const nextId = importedId(localId, imp.prefix);
674
+ if (resources[nextId]) throw new Error(`Imported Circuitry resource id collision: ${nextId} from ${imp.path}`);
675
+ resources[nextId] = rewriteResourceInputs(resource, aliasMap);
676
+ origins[nextId] = loaded.origins[sourceId] || linkedFile;
677
+ }
678
+ }
679
+ for (const [id, resource] of Object.entries(parsed.resources || {})) {
680
+ if (resources[id]) throw new Error(`Circuitry resource id collision: ${id} in ${graphFile}`);
681
+ resources[id] = resource;
682
+ origins[id] = graphFile;
683
+ }
684
+ const graph = normalizeCircuitryGraph({ ...parsed, imports: [], resources });
685
+ if (stack.length === 0) {
686
+ const validation = validateCircuitryExecutionGraphWithStandard(graph, standard);
687
+ if (validation.errors.length) {
688
+ throw new Error(`Invalid Circuitry graph:
689
+ ${validation.errors.map((e) => e.message).join("\n")}`);
690
+ }
691
+ }
692
+ return { filename: graphFile, text, graph, origins };
693
+ };
694
+ var loadCircuitryGraphFile = async (filename = defaultFilename, standard, stack = []) => {
695
+ const graphFile = path.resolve(process.cwd(), filename);
696
+ const text = await readFile(graphFile, "utf8");
697
+ const parsed = parseCircuitryText(text, standard, { validate: false });
698
+ return resolveCircuitryGraph(parsed, graphFile, text, standard, stack);
699
+ };
807
700
  var NodeCircuitryHost = class {
808
701
  resolveGraphFile(filename) {
809
702
  return path.resolve(
@@ -812,7 +705,7 @@ var NodeCircuitryHost = class {
812
705
  );
813
706
  }
814
707
  runFileFor(filename) {
815
- return `${filename}.run.json`;
708
+ return `${filename}.run.yaml`;
816
709
  }
817
710
  async exists(filename) {
818
711
  try {
@@ -829,98 +722,42 @@ var NodeCircuitryHost = class {
829
722
  path: [filename]
830
723
  };
831
724
  }
832
- parseGraphForValidation(text, filename, standard) {
725
+ parseSourceGraph(text, filename, standard) {
833
726
  try {
834
- return {
835
- graph: parseCircuitryText(text || "", filename, standard, {
836
- validate: false
837
- })
838
- };
727
+ return { graph: parseCircuitryText(text || "", standard, { validate: false }) };
839
728
  } catch (error) {
840
729
  return { error: this.parseIssue(error, filename) };
841
730
  }
842
731
  }
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
732
  async parseAndResolveGraph(text, filename, standard) {
886
- const parsed = this.parseGraphForValidation(text, filename, standard);
887
- if (!parsed.graph) return parsed;
888
733
  try {
889
- return { graph: await this.resolveGraphImports(parsed.graph, filename, standard) };
734
+ const graphFile = path.resolve(process.cwd(), filename);
735
+ const source = text || "";
736
+ const parsed = parseCircuitryText(source, standard, { validate: false });
737
+ return { graph: (await resolveCircuitryGraph(parsed, graphFile, source, standard)).graph };
890
738
  } catch (error) {
891
739
  return { error: this.parseIssue(error, filename) };
892
740
  }
893
741
  }
894
742
  async readGraphFile(filename) {
895
- const graphFile = this.resolveGraphFile(filename);
896
- const text = await readFile(graphFile, "utf8");
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) };
743
+ const loaded = await loadCircuitryGraphFile(this.resolveGraphFile(filename));
744
+ return { graphFile: loaded.filename, text: loaded.text, graph: loaded.graph, origins: loaded.origins };
908
745
  }
909
746
  async readGraph(input = {}) {
910
747
  const { graphFile, text, graph } = await this.readGraphFile(input.filename);
911
748
  const lastRunFile = this.runFileFor(graphFile);
912
- const lastRun = await this.exists(lastRunFile) ? JSON.parse(await readFile(lastRunFile, "utf8")) : void 0;
749
+ const lastRun = await this.exists(lastRunFile) ? parseYamlData(await readFile(lastRunFile, "utf8")) : void 0;
913
750
  return { filename: graphFile, graph, text, lastRun };
914
751
  }
915
752
  async validateGraph(input) {
916
753
  if (input.graph)
917
754
  return validateCircuitryGraphWithStandard(input.graph, input.standard);
918
755
  const filename = input.filename || defaultFilename;
919
- const parsed = await this.parseAndResolveGraph(input.text, filename, input.standard);
920
- if ("error" in parsed) {
756
+ const parsed = this.parseSourceGraph(input.text, filename, input.standard);
757
+ if (!("graph" in parsed)) {
921
758
  return {
922
759
  ok: false,
923
- errors: [parsed.error],
760
+ errors: parsed.error ? [parsed.error] : [],
924
761
  standard: validateCircuitryGraphWithStandard({}, input.standard).standard
925
762
  };
926
763
  }
@@ -928,16 +765,13 @@ ${validation.errors.map((e) => e.message).join("\n")}`
928
765
  }
929
766
  async writeGraph(input) {
930
767
  const graphFile = this.resolveGraphFile(input.filename);
931
- const parsed = input.graph ? { graph: input.graph } : await this.parseAndResolveGraph(
932
- input.text,
933
- input.filename || graphFile,
934
- input.standard
935
- );
936
- if (!parsed.graph)
768
+ const parsed = input.graph ? { graph: input.graph } : this.parseSourceGraph(input.text, input.filename || graphFile, input.standard);
769
+ if (!("graph" in parsed))
937
770
  throw new Error(`Invalid Circuitry graph:
938
771
  ${parsed.error?.message}`);
772
+ const graph = parsed.graph;
939
773
  const validation = await this.validateGraph({
940
- graph: parsed.graph,
774
+ graph,
941
775
  standard: input.standard
942
776
  });
943
777
  if (validation.errors.length)
@@ -947,12 +781,12 @@ ${validation.errors.map((e) => e.message).join("\n")}`
947
781
  );
948
782
  await writeFile(
949
783
  graphFile,
950
- stringifyCircuitryText(parsed.graph, graphFile),
784
+ stringifyCircuitryText(graph),
951
785
  "utf8"
952
786
  );
953
787
  return {
954
788
  filename: graphFile,
955
- graph: parsed.graph,
789
+ graph,
956
790
  mode: "replace"
957
791
  };
958
792
  }
@@ -986,7 +820,7 @@ ${validation.errors.map((e) => e.message).join("\n")}`
986
820
  if (source !== "text")
987
821
  await writeFile(
988
822
  this.runFileFor(graphFile),
989
- JSON.stringify(result, null, 2),
823
+ stringifyYamlData(result),
990
824
  "utf8"
991
825
  );
992
826
  return result;
@@ -994,5 +828,6 @@ ${validation.errors.map((e) => e.message).join("\n")}`
994
828
  };
995
829
  export {
996
830
  NodeCircuitryHost,
831
+ loadCircuitryGraphFile,
997
832
  runNodeWithPiSDK
998
833
  };