@darkhorseprojects/circuitry 0.2.20 → 0.2.22

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/graph.d.ts CHANGED
@@ -162,6 +162,15 @@ export type CircuitryValidationRuleEntry = CircuitryBuiltInValidationRule | {
162
162
  export type CircuitryGraphValidation = {
163
163
  rules?: CircuitryValidationRuleEntry[];
164
164
  };
165
+ export type CircuitryRuntimeInputs = Record<string, unknown>;
166
+ /**
167
+ * Return a graph copy with runtime input values applied to existing text inputs.
168
+ *
169
+ * Runtime inputs are execution-time args for graph programs: small overlays on
170
+ * declared text inputs. They must target declared input resources; this keeps
171
+ * graph source stable and catches misspelled input names before execution.
172
+ */
173
+ export declare const applyCircuitryRuntimeInputs: (graph: CircuitryGraph, inputs?: CircuitryRuntimeInputs) => CircuitryGraph;
165
174
  export type CircuitryGraph = {
166
175
  /** Spec version. Use "0.2" for new graphs. */
167
176
  circuitry: CircuitrySpecVersion | string;
package/dist/host.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CircuitryGraph, CircuitryValidationResult, CircuitryValidationStandard } from "./graph";
1
+ import type { CircuitryGraph, CircuitryRuntimeInputs, CircuitryValidationResult, CircuitryValidationStandard } from "./graph";
2
2
  import type { SimulationRunItem } from "./simulation";
3
3
  export interface CircuitryHost {
4
4
  readGraph(input: {
@@ -31,6 +31,7 @@ export interface CircuitryHost {
31
31
  text?: string;
32
32
  filename?: string;
33
33
  selectedNodeId?: string;
34
+ inputs?: CircuitryRuntimeInputs;
34
35
  standard?: CircuitryValidationStandard;
35
36
  hostModel?: string;
36
37
  }): Promise<{
package/dist/index.d.ts CHANGED
@@ -8,4 +8,3 @@ export * from "./bundle";
8
8
  export * from "./runtime";
9
9
  export * from "./host";
10
10
  export * from "./tools";
11
- export * from "./node";
package/dist/index.js CHANGED
@@ -29,6 +29,46 @@ var isNodeElement = (element) => {
29
29
  // src/graph.ts
30
30
  var CIRCUITRY_SPEC_VERSION = "0.2";
31
31
  var CIRCUITRY_SPEC_VERSION_LEGACY = "0.1";
32
+ var runtimeInputToText = (value) => {
33
+ if (typeof value === "string") return value;
34
+ if (value === void 0) return "";
35
+ return JSON.stringify(value, null, 2);
36
+ };
37
+ var applyCircuitryRuntimeInputs = (graph, inputs = {}) => {
38
+ const entries = Object.entries(inputs);
39
+ if (entries.length === 0) return graph;
40
+ const next = {
41
+ ...graph,
42
+ resources: graph.resources ? { ...graph.resources } : graph.resources,
43
+ inputs: graph.inputs ? { ...graph.inputs } : graph.inputs,
44
+ nodes: graph.nodes ? graph.nodes.map((node) => ({
45
+ ...node,
46
+ input: node.input ? { ...node.input } : node.input
47
+ })) : graph.nodes
48
+ };
49
+ for (const [id, value] of entries) {
50
+ const text = runtimeInputToText(value);
51
+ const resource = next.resources?.[id];
52
+ if (resource) {
53
+ if (resource.type !== "text")
54
+ throw new Error(`Runtime input ${id} targets non-text resource: ${resource.type}`);
55
+ next.resources[id] = { ...resource, value: text };
56
+ continue;
57
+ }
58
+ const legacyInput = next.inputs?.[id];
59
+ if (legacyInput) {
60
+ next.inputs[id] = { ...legacyInput, value: text };
61
+ continue;
62
+ }
63
+ const node = next.nodes?.find((candidate) => candidate.id === id);
64
+ if (node?.kind === "input" && node.input) {
65
+ node.input.value = text;
66
+ continue;
67
+ }
68
+ throw new Error(`Runtime input does not match a graph text input: ${id}`);
69
+ }
70
+ return next;
71
+ };
32
72
  var DEFAULT_CIRCUITRY_VALIDATION_RULES = [
33
73
  "no-self-loops",
34
74
  "no-unknown-edge-endpoints",
@@ -176,7 +216,7 @@ var validateCircuitryGraphWithStandard = (graph, standard = {}) => {
176
216
  graph && typeof graph === "object" && !Array.isArray(graph) ? graph : void 0
177
217
  );
178
218
  const errors = [];
179
- const addError = (code, message, path3) => errors.push({ code, message, ...path3 ? { path: path3 } : {} });
219
+ const addError = (code, message, path2) => errors.push({ code, message, ...path2 ? { path: path2 } : {} });
180
220
  if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
181
221
  addError("invalid_graph", "Circuitry graph must be an object");
182
222
  return { ok: false, errors, standard: resolvedStandard };
@@ -253,19 +293,19 @@ var validateCircuitryGraphWithStandard = (graph, standard = {}) => {
253
293
  }
254
294
  const visiting = /* @__PURE__ */ new Set();
255
295
  const visited = /* @__PURE__ */ new Set();
256
- const path2 = [];
296
+ const path = [];
257
297
  let cycleMessage = "";
258
298
  const visit = (id) => {
259
299
  if (cycleMessage) return;
260
300
  if (visiting.has(id)) {
261
- cycleMessage = [...path2.slice(path2.indexOf(id)), id].join(" -> ");
301
+ cycleMessage = [...path.slice(path.indexOf(id)), id].join(" -> ");
262
302
  return;
263
303
  }
264
304
  if (visited.has(id)) return;
265
305
  visiting.add(id);
266
- path2.push(id);
306
+ path.push(id);
267
307
  for (const next of adjacency.get(id) || []) visit(next);
268
- path2.pop();
308
+ path.pop();
269
309
  visiting.delete(id);
270
310
  visited.add(id);
271
311
  };
@@ -315,15 +355,15 @@ var validateCircuitryGraphWithStandard = (graph, standard = {}) => {
315
355
  }
316
356
  continue;
317
357
  }
318
- const checkString = (target, subject, id, match, path3) => {
358
+ const checkString = (target, subject, id, match, path2) => {
319
359
  const value = getField(subject, match.field);
320
360
  const text = typeof value === "string" ? value : "";
321
361
  const includes = text.includes(match.value);
322
362
  if (rule.rule === "require-string" && !includes) {
323
- addError("missing_required_string", `${target} ${id} field ${match.field} must include string: ${match.value}`, path3);
363
+ addError("missing_required_string", `${target} ${id} field ${match.field} must include string: ${match.value}`, path2);
324
364
  }
325
365
  if (rule.rule === "reject-string" && includes) {
326
- addError("rejected_string", `${target} ${id} field ${match.field} must not include string: ${match.value}`, path3);
366
+ addError("rejected_string", `${target} ${id} field ${match.field} must not include string: ${match.value}`, path2);
327
367
  }
328
368
  };
329
369
  for (const match of rule.graph || []) checkString("graph", normalized, "<root>", match, fieldPath(match.field));
@@ -1065,6 +1105,9 @@ var createCircuitryRunGraphTool = (host) => ({
1065
1105
  text: z.string().optional().describe("YAML graph text if source is 'text'."),
1066
1106
  filename: z.string().optional().describe("Optional filename."),
1067
1107
  selectedNodeId: z.string().optional().describe("Optional specific node to run."),
1108
+ inputs: z.record(z.unknown()).optional().describe(
1109
+ "Runtime input values keyed by existing text input/resource id. Applied only for this run; graph source is not mutated."
1110
+ ),
1068
1111
  standard: standardSchema,
1069
1112
  hostModel: z.string().optional().describe("Optional model to use when a node is set to 'inherit'.")
1070
1113
  }),
@@ -1086,237 +1129,6 @@ var createCircuitryTools = (host) => [
1086
1129
  createCircuitryRunGraphTool(host),
1087
1130
  createCircuitryValidateGraphTool(host)
1088
1131
  ];
1089
-
1090
- // src/node.ts
1091
- import { access, readFile, writeFile } from "node:fs/promises";
1092
- import fs from "node:fs";
1093
- import path from "node:path";
1094
- import os from "node:os";
1095
- var loadPiSDK = async () => {
1096
- const possiblePaths = [
1097
- path.join(process.cwd(), "node_modules/@earendil-works/pi-coding-agent/dist/index.js"),
1098
- path.join(os.homedir(), ".npm-global/lib/node_modules/@earendil-works/pi-coding-agent/dist/index.js")
1099
- ];
1100
- for (const modulePath of possiblePaths) {
1101
- if (fs.existsSync(modulePath)) {
1102
- return await import(modulePath);
1103
- }
1104
- }
1105
- return await import("@earendil-works/pi-coding-agent");
1106
- };
1107
- var extractAssistantText = (message) => {
1108
- if (!message || message.role !== "assistant") return "";
1109
- if (typeof message.content === "string") return message.content;
1110
- if (!Array.isArray(message.content)) return "";
1111
- return message.content.filter((part) => part?.type === "text" && typeof part.text === "string").map((part) => part.text).join("");
1112
- };
1113
- var withTimeout = async (promise, timeoutMs) => {
1114
- let timeout;
1115
- try {
1116
- return await Promise.race([
1117
- promise,
1118
- new Promise((_, reject) => {
1119
- timeout = setTimeout(
1120
- () => reject(new Error(`Pi SDK request timed out after ${timeoutMs}ms`)),
1121
- timeoutMs
1122
- );
1123
- })
1124
- ]);
1125
- } finally {
1126
- clearTimeout(timeout);
1127
- }
1128
- };
1129
- var runNodeWithPiSDK = async ({
1130
- model,
1131
- prompt,
1132
- images = [],
1133
- tools = [],
1134
- thinkingLevel = "off"
1135
- }) => {
1136
- let sdk;
1137
- try {
1138
- sdk = await loadPiSDK();
1139
- } catch (error) {
1140
- const message = error instanceof Error ? error.message : String(error);
1141
- throw new Error(
1142
- [
1143
- "Circuitry could not load the Pi SDK package for execution.",
1144
- "Ensure @earendil-works/pi-coding-agent is installed.",
1145
- message
1146
- ].join("\n")
1147
- );
1148
- }
1149
- const { AuthStorage, ModelRegistry, SessionManager, createAgentSession } = sdk;
1150
- const authStorage = AuthStorage.create();
1151
- const modelRegistry = ModelRegistry.create(authStorage);
1152
- const [provider, ...modelParts] = String(model || "").split("/");
1153
- const modelId = modelParts.join("/");
1154
- const available = await modelRegistry.getAvailable();
1155
- const selectedModel = (provider && modelId ? modelRegistry.find(provider, modelId) : void 0) || available.find((entry) => {
1156
- if (!modelId) return false;
1157
- return `${entry.provider}/${entry.id}` === `${provider}/${modelId}` || entry.id === modelId;
1158
- }) || available[0];
1159
- if (!selectedModel) {
1160
- throw new Error("No Pi SDK models are available. Configure ~/.pi/agent/auth.json");
1161
- }
1162
- const { session } = await createAgentSession({
1163
- model: selectedModel,
1164
- thinkingLevel,
1165
- sessionManager: SessionManager.inMemory(),
1166
- authStorage,
1167
- modelRegistry
1168
- });
1169
- session.setActiveToolsByName(Array.isArray(tools) ? tools.map((tool) => String(tool)) : []);
1170
- let output = "";
1171
- const unsubscribe = session.subscribe((event) => {
1172
- if ((event.type === "message_update" || event.type === "message_end") && event.message?.role === "assistant") {
1173
- output = extractAssistantText(event.message);
1174
- }
1175
- if (event.type === "agent_end" && Array.isArray(event.messages)) {
1176
- const assistantMessage = [...event.messages].reverse().find((message) => message.role === "assistant");
1177
- output = extractAssistantText(assistantMessage);
1178
- }
1179
- });
1180
- try {
1181
- const promptImages = images.filter((image) => image?.mediaType && image?.data).map((image) => ({
1182
- type: "image",
1183
- source: { type: "base64", mediaType: image.mediaType, data: image.data }
1184
- }));
1185
- await withTimeout(session.prompt(prompt, { images: promptImages }), 18e4);
1186
- return { output: output.trim() };
1187
- } finally {
1188
- unsubscribe();
1189
- session.dispose();
1190
- }
1191
- };
1192
- var defaultFilename = "graph.circuitry.yaml";
1193
- var NodeCircuitryHost = class {
1194
- resolveGraphFile(filename) {
1195
- return path.resolve(
1196
- process.cwd(),
1197
- filename || process.env.CIRCUITRY_GRAPH || defaultFilename
1198
- );
1199
- }
1200
- runFileFor(filename) {
1201
- return `${filename}.run.json`;
1202
- }
1203
- async exists(filename) {
1204
- try {
1205
- await access(filename);
1206
- return true;
1207
- } catch {
1208
- return false;
1209
- }
1210
- }
1211
- parseIssue(error, filename) {
1212
- return {
1213
- code: "parse_error",
1214
- message: error instanceof Error ? error.message : String(error),
1215
- path: [filename]
1216
- };
1217
- }
1218
- parseGraphForValidation(text, filename, standard) {
1219
- try {
1220
- return {
1221
- graph: parseCircuitryText(text || "", filename, standard, {
1222
- validate: false
1223
- })
1224
- };
1225
- } catch (error) {
1226
- return { error: this.parseIssue(error, filename) };
1227
- }
1228
- }
1229
- async readGraphFile(filename) {
1230
- const graphFile = this.resolveGraphFile(filename);
1231
- const text = await readFile(graphFile, "utf8");
1232
- const graph = parseCircuitryText(text, graphFile);
1233
- return { graphFile, text, graph };
1234
- }
1235
- async readGraph(input = {}) {
1236
- const { graphFile, text, graph } = await this.readGraphFile(input.filename);
1237
- const lastRunFile = this.runFileFor(graphFile);
1238
- const lastRun = await this.exists(lastRunFile) ? JSON.parse(await readFile(lastRunFile, "utf8")) : void 0;
1239
- return { filename: graphFile, graph, text, lastRun };
1240
- }
1241
- async validateGraph(input) {
1242
- if (input.graph)
1243
- return validateCircuitryGraphWithStandard(input.graph, input.standard);
1244
- const filename = input.filename || defaultFilename;
1245
- const parsed = this.parseGraphForValidation(input.text, filename, input.standard);
1246
- if (parsed.error) {
1247
- return {
1248
- ok: false,
1249
- errors: [parsed.error],
1250
- standard: validateCircuitryGraphWithStandard({}, input.standard).standard
1251
- };
1252
- }
1253
- return validateCircuitryGraphWithStandard(parsed.graph, input.standard);
1254
- }
1255
- async writeGraph(input) {
1256
- const graphFile = this.resolveGraphFile(input.filename);
1257
- const parsed = input.graph ? { graph: input.graph } : this.parseGraphForValidation(
1258
- input.text,
1259
- input.filename || graphFile,
1260
- input.standard
1261
- );
1262
- if (!parsed.graph)
1263
- throw new Error(`Invalid Circuitry graph:
1264
- ${parsed.error?.message}`);
1265
- const validation = await this.validateGraph({
1266
- graph: parsed.graph,
1267
- standard: input.standard
1268
- });
1269
- if (validation.errors.length)
1270
- throw new Error(
1271
- `Invalid Circuitry graph:
1272
- ${validation.errors.map((e) => e.message).join("\n")}`
1273
- );
1274
- await writeFile(
1275
- graphFile,
1276
- stringifyCircuitryText(parsed.graph, graphFile),
1277
- "utf8"
1278
- );
1279
- return {
1280
- filename: graphFile,
1281
- graph: parsed.graph,
1282
- mode: "replace"
1283
- };
1284
- }
1285
- async runGraph(input = {}) {
1286
- const source = input.source || (input.text ? "text" : "current");
1287
- const graphFile = this.resolveGraphFile(input.filename);
1288
- const parsed = source === "text" ? this.parseGraphForValidation(
1289
- input.text,
1290
- input.filename || graphFile,
1291
- input.standard
1292
- ) : { graph: (await this.readGraphFile(input.filename)).graph };
1293
- if (!parsed.graph)
1294
- throw new Error(`Invalid Circuitry graph:
1295
- ${parsed.error?.message}`);
1296
- const validation = await this.validateGraph({
1297
- graph: parsed.graph,
1298
- standard: input.standard
1299
- });
1300
- if (validation.errors.length)
1301
- throw new Error(
1302
- `Invalid Circuitry graph:
1303
- ${validation.errors.map((e) => e.message).join("\n")}`
1304
- );
1305
- const runItems = await runCircuitryGraphExecution({
1306
- graph: parsed.graph,
1307
- selectedNodeId: input.selectedNodeId,
1308
- executeNode: (req) => runNodeWithPiSDK(req)
1309
- });
1310
- const result = { completedAt: (/* @__PURE__ */ new Date()).toISOString(), runItems };
1311
- if (source !== "text")
1312
- await writeFile(
1313
- this.runFileFor(graphFile),
1314
- JSON.stringify(result, null, 2),
1315
- "utf8"
1316
- );
1317
- return result;
1318
- }
1319
- };
1320
1132
  export {
1321
1133
  BUNDLE_MIME,
1322
1134
  CIRCUITRY_AGENT_PRESET_DIRS,
@@ -1327,8 +1139,8 @@ export {
1327
1139
  DEFAULT_CIRCUITRY_VALIDATION_RULES,
1328
1140
  DEFAULT_CIRCUITRY_VALIDATION_STANDARD,
1329
1141
  DEFAULT_MAX_PARALLEL_RUNS,
1330
- NodeCircuitryHost,
1331
1142
  applyAgentPresets,
1143
+ applyCircuitryRuntimeInputs,
1332
1144
  buildCircuitryExecutionGraph,
1333
1145
  buildSimulationGraph,
1334
1146
  createBundleFromSelection,
@@ -1353,7 +1165,6 @@ export {
1353
1165
  runCircuitryGraphExecution,
1354
1166
  runDependencySimulation,
1355
1167
  runGraphExecution,
1356
- runNodeWithPiSDK,
1357
1168
  standardSchema,
1358
1169
  stringifyCircuitryJson,
1359
1170
  stringifyCircuitryText,
package/dist/node.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { CircuitryHost } from "./host";
2
- import type { CircuitryGraph, CircuitryValidationStandard } from "./graph";
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
5
  export declare class NodeCircuitryHost implements CircuitryHost {
@@ -39,6 +39,7 @@ export declare class NodeCircuitryHost implements CircuitryHost {
39
39
  text?: string;
40
40
  filename?: string;
41
41
  selectedNodeId?: string;
42
+ inputs?: CircuitryRuntimeInputs;
42
43
  standard?: CircuitryValidationStandard;
43
44
  hostModel?: string;
44
45
  }): Promise<{
package/dist/node.js ADDED
@@ -0,0 +1,899 @@
1
+ // src/node.ts
2
+ import { access, readFile, writeFile } from "node:fs/promises";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+
7
+ // src/yaml.ts
8
+ import YAML from "yaml";
9
+
10
+ // src/graph.ts
11
+ var CIRCUITRY_SPEC_VERSION = "0.2";
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
+ inputs: graph.inputs ? { ...graph.inputs } : graph.inputs,
24
+ nodes: graph.nodes ? graph.nodes.map((node) => ({
25
+ ...node,
26
+ input: node.input ? { ...node.input } : node.input
27
+ })) : graph.nodes
28
+ };
29
+ for (const [id, value] of entries) {
30
+ const text = runtimeInputToText(value);
31
+ const resource = next.resources?.[id];
32
+ if (resource) {
33
+ if (resource.type !== "text")
34
+ throw new Error(`Runtime input ${id} targets non-text resource: ${resource.type}`);
35
+ next.resources[id] = { ...resource, value: text };
36
+ continue;
37
+ }
38
+ const legacyInput = next.inputs?.[id];
39
+ if (legacyInput) {
40
+ next.inputs[id] = { ...legacyInput, value: text };
41
+ continue;
42
+ }
43
+ const node = next.nodes?.find((candidate) => candidate.id === id);
44
+ if (node?.kind === "input" && node.input) {
45
+ node.input.value = text;
46
+ continue;
47
+ }
48
+ throw new Error(`Runtime input does not match a graph text input: ${id}`);
49
+ }
50
+ return next;
51
+ };
52
+ var DEFAULT_CIRCUITRY_VALIDATION_RULES = [
53
+ "no-self-loops",
54
+ "no-unknown-edge-endpoints",
55
+ "require-executable-inputs",
56
+ "no-cycles"
57
+ ];
58
+ var DEFAULT_CIRCUITRY_VALIDATION_STANDARD = {
59
+ version: CIRCUITRY_SPEC_VERSION,
60
+ requireSpecVersion: true,
61
+ rules: DEFAULT_CIRCUITRY_VALIDATION_RULES,
62
+ executableKinds: ["agent", "tool", "output"]
63
+ };
64
+ var executableKindsFromRules = (rules = []) => {
65
+ const executableRule = rules.find(
66
+ (entry) => typeof entry !== "string" && entry.rule === "require-executable-inputs"
67
+ );
68
+ return executableRule?.executableKinds;
69
+ };
70
+ var createCircuitryValidationStandard = (standard = {}, graph) => {
71
+ const rules = [
72
+ ...DEFAULT_CIRCUITRY_VALIDATION_RULES,
73
+ ...graph?.validation?.rules || [],
74
+ ...standard.rules || []
75
+ ];
76
+ return {
77
+ version: standard.version || DEFAULT_CIRCUITRY_VALIDATION_STANDARD.version,
78
+ requireSpecVersion: standard.requireSpecVersion ?? DEFAULT_CIRCUITRY_VALIDATION_STANDARD.requireSpecVersion,
79
+ rules,
80
+ executableKinds: standard.executableKinds || executableKindsFromRules(rules) || [
81
+ ...DEFAULT_CIRCUITRY_VALIDATION_STANDARD.executableKinds
82
+ ]
83
+ };
84
+ };
85
+ var expandResources = (resources) => {
86
+ const nodes = [];
87
+ const edges = [];
88
+ for (const [id, resource] of Object.entries(resources)) {
89
+ if (resource.type === "text") {
90
+ nodes.push({
91
+ id,
92
+ kind: "input",
93
+ label: resource.label || id,
94
+ input: { type: "text", value: resource.value }
95
+ });
96
+ } else if (resource.type === "agent") {
97
+ nodes.push({
98
+ id,
99
+ kind: "agent",
100
+ label: resource.label || resource.identity || id,
101
+ agent: {
102
+ identity: resource.identity || resource.label || id,
103
+ model: resource.model,
104
+ thinkingLevel: resource.thinkingLevel,
105
+ tools: resource.tools,
106
+ instructions: resource.instructions,
107
+ personality: resource.personality,
108
+ context: resource.context
109
+ },
110
+ skills: resource.skills,
111
+ expect: resource.expect
112
+ });
113
+ for (const inputRef of resource.inputs || []) {
114
+ edges.push({ from: inputRef, to: id, kind: "dependency" });
115
+ }
116
+ } else if (resource.type === "tool") {
117
+ nodes.push({
118
+ id,
119
+ kind: "tool",
120
+ label: resource.label || id,
121
+ agent: { instructions: resource.instructions }
122
+ });
123
+ for (const inputRef of resource.inputs || []) {
124
+ edges.push({ from: inputRef, to: id, kind: "dependency" });
125
+ }
126
+ }
127
+ }
128
+ return { nodes, edges };
129
+ };
130
+ var normalizeCircuitryGraph = (graph) => {
131
+ if (graph.resources && Object.keys(graph.resources).length > 0) {
132
+ const { nodes: nodes2, edges } = expandResources(graph.resources);
133
+ return { ...graph, nodes: nodes2, edges };
134
+ }
135
+ if (graph.nodes && graph.nodes.length > 0) {
136
+ const nodeIds = new Set(graph.nodes.map((n) => n.id));
137
+ const agents = graph.agents || {};
138
+ const inputs = graph.inputs || {};
139
+ const nodes2 = [...graph.nodes];
140
+ for (const [id, agent] of Object.entries(agents)) {
141
+ if (!nodeIds.has(id)) {
142
+ nodes2.push({
143
+ ...agent,
144
+ id,
145
+ kind: "agent",
146
+ label: agent.label || agent.agent?.identity || id,
147
+ agent: {
148
+ identity: agent.agent?.identity || agent.label || id,
149
+ ...agent.agent
150
+ }
151
+ });
152
+ }
153
+ }
154
+ for (const [id, input] of Object.entries(inputs)) {
155
+ if (!nodeIds.has(id)) {
156
+ nodes2.push({
157
+ id,
158
+ kind: "input",
159
+ label: input.label || id,
160
+ input
161
+ });
162
+ }
163
+ }
164
+ return { ...graph, nodes: nodes2 };
165
+ }
166
+ const nodes = [];
167
+ for (const [id, agent] of Object.entries(graph.agents || {})) {
168
+ nodes.push({
169
+ ...agent,
170
+ id,
171
+ kind: "agent",
172
+ label: agent.label || agent.agent?.identity || id,
173
+ agent: {
174
+ identity: agent.agent?.identity || agent.label || id,
175
+ ...agent.agent
176
+ }
177
+ });
178
+ }
179
+ for (const [id, input] of Object.entries(graph.inputs || {})) {
180
+ nodes.push({
181
+ id,
182
+ kind: "input",
183
+ label: input.label || id,
184
+ input
185
+ });
186
+ }
187
+ return {
188
+ ...graph,
189
+ nodes
190
+ };
191
+ };
192
+ var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set([CIRCUITRY_SPEC_VERSION]);
193
+ var validateCircuitryGraphWithStandard = (graph, standard = {}) => {
194
+ const resolvedStandard = createCircuitryValidationStandard(
195
+ standard,
196
+ graph && typeof graph === "object" && !Array.isArray(graph) ? graph : void 0
197
+ );
198
+ const errors = [];
199
+ const addError = (code, message, path3) => errors.push({ code, message, ...path3 ? { path: path3 } : {} });
200
+ if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
201
+ addError("invalid_graph", "Circuitry graph must be an object");
202
+ return { ok: false, errors, standard: resolvedStandard };
203
+ }
204
+ const graphObject = graph;
205
+ const rules = resolvedStandard.rules;
206
+ const hasRule = (name) => rules.some((entry) => (typeof entry === "string" ? entry : entry.rule) === name);
207
+ if (resolvedStandard.requireSpecVersion && !VALID_SPEC_VERSIONS.has(String(graphObject.circuitry))) {
208
+ addError(
209
+ "invalid_spec_version",
210
+ `Expected circuitry: "${CIRCUITRY_SPEC_VERSION}"`,
211
+ ["circuitry"]
212
+ );
213
+ }
214
+ if (graphObject.circuitry === CIRCUITRY_SPEC_VERSION && graphObject.resources && Object.keys(graphObject.resources).length > 0) {
215
+ if (graphObject.agents && Object.keys(graphObject.agents).length > 0) {
216
+ addError("v02_mixed_format", "v0.2 graphs with resources: must not use legacy agents: section", ["agents"]);
217
+ }
218
+ if (graphObject.inputs && Object.keys(graphObject.inputs).length > 0) {
219
+ addError("v02_mixed_format", "v0.2 graphs with resources: must not use legacy inputs: section", ["inputs"]);
220
+ }
221
+ if (graphObject.edges && graphObject.edges.length > 0) {
222
+ addError("v02_mixed_format", "v0.2 graphs with resources: must not use legacy edges: section", ["edges"]);
223
+ }
224
+ }
225
+ const normalized = normalizeCircuitryGraph(graphObject);
226
+ const ids = /* @__PURE__ */ new Set();
227
+ for (const [index, node] of (normalized.nodes || []).entries()) {
228
+ if (!node.id) {
229
+ addError("missing_node_id", "Node is missing id", ["nodes", index, "id"]);
230
+ continue;
231
+ }
232
+ if (ids.has(node.id)) {
233
+ addError("duplicate_node_id", `Duplicate node id: ${node.id}`, ["nodes", index, "id"]);
234
+ }
235
+ ids.add(node.id);
236
+ if (!node.kind) {
237
+ addError("missing_node_kind", `Node ${node.id} is missing kind`, ["nodes", index, "kind"]);
238
+ }
239
+ }
240
+ const adjacency = /* @__PURE__ */ new Map();
241
+ const incomingCount = /* @__PURE__ */ new Map();
242
+ for (const id of ids) {
243
+ adjacency.set(id, []);
244
+ incomingCount.set(id, 0);
245
+ }
246
+ for (const [index, edge] of (normalized.edges || []).entries()) {
247
+ if (!edge.from || !edge.to) {
248
+ addError("missing_edge_endpoint", "Edge is missing from/to", ["edges", index]);
249
+ continue;
250
+ }
251
+ if (hasRule("no-self-loops") && edge.from === edge.to) {
252
+ addError("self_loop", `Edge creates self loop: ${edge.from} -> ${edge.to}`, ["edges", index]);
253
+ continue;
254
+ }
255
+ if (hasRule("no-unknown-edge-endpoints") && !ids.has(edge.from)) {
256
+ addError("unknown_edge_source", `Edge references unknown source: ${edge.from}`, ["edges", index, "from"]);
257
+ }
258
+ if (hasRule("no-unknown-edge-endpoints") && !ids.has(edge.to)) {
259
+ addError("unknown_edge_target", `Edge references unknown target: ${edge.to}`, ["edges", index, "to"]);
260
+ }
261
+ if (ids.has(edge.from) && ids.has(edge.to)) {
262
+ adjacency.get(edge.from).push(edge.to);
263
+ incomingCount.set(edge.to, (incomingCount.get(edge.to) || 0) + 1);
264
+ }
265
+ }
266
+ const executableKinds = new Set(resolvedStandard.executableKinds);
267
+ if (hasRule("require-executable-inputs")) {
268
+ for (const [index, node] of (normalized.nodes || []).entries()) {
269
+ if (executableKinds.has(node.kind) && (incomingCount.get(node.id) || 0) === 0) {
270
+ addError("executable_without_inputs", `Executable node has no inputs: ${node.id}`, ["nodes", index]);
271
+ }
272
+ }
273
+ }
274
+ const visiting = /* @__PURE__ */ new Set();
275
+ const visited = /* @__PURE__ */ new Set();
276
+ const path2 = [];
277
+ let cycleMessage = "";
278
+ const visit = (id) => {
279
+ if (cycleMessage) return;
280
+ if (visiting.has(id)) {
281
+ cycleMessage = [...path2.slice(path2.indexOf(id)), id].join(" -> ");
282
+ return;
283
+ }
284
+ if (visited.has(id)) return;
285
+ visiting.add(id);
286
+ path2.push(id);
287
+ for (const next of adjacency.get(id) || []) visit(next);
288
+ path2.pop();
289
+ visiting.delete(id);
290
+ visited.add(id);
291
+ };
292
+ if (hasRule("no-cycles")) {
293
+ for (const id of ids) {
294
+ visit(id);
295
+ if (cycleMessage) {
296
+ addError("cycle", `Graph contains cycle: ${cycleMessage}`);
297
+ break;
298
+ }
299
+ }
300
+ }
301
+ const additionalRules = rules.filter((rule) => {
302
+ if (typeof rule === "string") return false;
303
+ return rule.rule === "require-graph-field" || rule.rule === "require-node-field" || rule.rule === "require-edge-field" || rule.rule === "require-string" || rule.rule === "reject-string";
304
+ });
305
+ const fieldPath = (field) => field.split(".").filter(Boolean);
306
+ const getField = (value, field) => fieldPath(field).reduce((current, key) => {
307
+ if (!current || typeof current !== "object") return void 0;
308
+ return current[key];
309
+ }, value);
310
+ const isPresent = (value) => value !== void 0 && value !== null && value !== "";
311
+ for (const rule of additionalRules) {
312
+ if (rule.rule === "require-graph-field") {
313
+ if (!isPresent(getField(normalized, rule.field))) {
314
+ addError("missing_required_graph_field", `Graph is missing required field: ${rule.field}`, fieldPath(rule.field));
315
+ }
316
+ continue;
317
+ }
318
+ if (rule.rule === "require-node-field") {
319
+ const nodeKinds = new Set(rule.nodeKinds || []);
320
+ for (const [index, node] of (normalized.nodes || []).entries()) {
321
+ if (nodeKinds.size > 0 && !nodeKinds.has(node.kind)) continue;
322
+ if (!isPresent(getField(node, rule.field))) {
323
+ addError("missing_required_node_field", `Node ${node.id || "<missing id>"} is missing required field: ${rule.field}`, ["nodes", index, ...fieldPath(rule.field)]);
324
+ }
325
+ }
326
+ continue;
327
+ }
328
+ if (rule.rule === "require-edge-field") {
329
+ const edgeKinds = new Set(rule.edgeKinds || []);
330
+ for (const [index, edge] of (normalized.edges || []).entries()) {
331
+ if (edgeKinds.size > 0 && (!edge.kind || !edgeKinds.has(edge.kind))) continue;
332
+ if (!isPresent(getField(edge, rule.field))) {
333
+ addError("missing_required_edge_field", `Edge ${edge.id || index} is missing required field: ${rule.field}`, ["edges", index, ...fieldPath(rule.field)]);
334
+ }
335
+ }
336
+ continue;
337
+ }
338
+ const checkString = (target, subject, id, match, path3) => {
339
+ const value = getField(subject, match.field);
340
+ const text = typeof value === "string" ? value : "";
341
+ const includes = text.includes(match.value);
342
+ if (rule.rule === "require-string" && !includes) {
343
+ addError("missing_required_string", `${target} ${id} field ${match.field} must include string: ${match.value}`, path3);
344
+ }
345
+ if (rule.rule === "reject-string" && includes) {
346
+ addError("rejected_string", `${target} ${id} field ${match.field} must not include string: ${match.value}`, path3);
347
+ }
348
+ };
349
+ for (const match of rule.graph || []) checkString("graph", normalized, "<root>", match, fieldPath(match.field));
350
+ for (const match of rule.nodes || []) {
351
+ const nodeKinds = new Set(match.nodeKinds || []);
352
+ for (const [index, node] of (normalized.nodes || []).entries()) {
353
+ if (nodeKinds.size > 0 && !nodeKinds.has(node.kind)) continue;
354
+ checkString("node", node, node.id || "<missing id>", match, ["nodes", index, ...fieldPath(match.field)]);
355
+ }
356
+ }
357
+ for (const match of rule.edges || []) {
358
+ const edgeKinds = new Set(match.edgeKinds || []);
359
+ for (const [index, edge] of (normalized.edges || []).entries()) {
360
+ if (edgeKinds.size > 0 && (!edge.kind || !edgeKinds.has(edge.kind))) continue;
361
+ checkString("edge", edge, edge.id || String(index), match, ["edges", index, ...fieldPath(match.field)]);
362
+ }
363
+ }
364
+ }
365
+ return { ok: errors.length === 0, errors, standard: resolvedStandard };
366
+ };
367
+ var validateCircuitryGraph = (graph, standard = {}) => validateCircuitryGraphWithStandard(graph, standard).errors.map(
368
+ (error) => error.message
369
+ );
370
+ var parseCircuitryJson = (text, standard = {}, options = {}) => {
371
+ let graph;
372
+ try {
373
+ graph = JSON.parse(text);
374
+ } catch (error) {
375
+ const message = error instanceof Error ? error.message : String(error);
376
+ throw new Error(`Could not parse Circuitry JSON. Check commas, quotes, and braces.
377
+ ${message}`);
378
+ }
379
+ if (options.validate !== false) {
380
+ const errors = validateCircuitryGraph(graph, standard);
381
+ if (errors.length) {
382
+ throw new Error(`Invalid Circuitry graph:
383
+ ${errors.join("\n")}`);
384
+ }
385
+ }
386
+ return normalizeCircuitryGraph(graph);
387
+ };
388
+ var stringifyCircuitryJson = (graph) => {
389
+ return `${JSON.stringify(normalizeCircuitryGraph(graph), null, 2)}
390
+ `;
391
+ };
392
+
393
+ // src/yaml.ts
394
+ var parseCircuitryYaml = (text, standard = {}, options = {}) => {
395
+ let graph;
396
+ try {
397
+ graph = YAML.parse(text);
398
+ } catch (error) {
399
+ const message = error instanceof Error ? error.message : String(error);
400
+ throw new Error(`Could not parse Circuitry YAML. Check indentation and quotes.
401
+ ${message}`);
402
+ }
403
+ if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
404
+ throw new Error("Could not parse Circuitry YAML. Expected a graph object.");
405
+ }
406
+ if (options.validate !== false) {
407
+ const errors = validateCircuitryGraph(graph, standard);
408
+ if (errors.length) {
409
+ throw new Error(`Invalid Circuitry graph:
410
+ ${errors.join("\n")}`);
411
+ }
412
+ }
413
+ return normalizeCircuitryGraph(graph);
414
+ };
415
+ var stringifyCircuitryYaml = (graph) => {
416
+ return YAML.stringify(normalizeCircuitryGraph(graph));
417
+ };
418
+ var parseCircuitryText = (text, filename = "graph.circuitry.yaml", standard = {}, options = {}) => {
419
+ return filename.endsWith(".json") ? parseCircuitryJson(text, standard, options) : parseCircuitryYaml(text, standard, options);
420
+ };
421
+ var stringifyCircuitryText = (graph, filename = "graph.circuitry.yaml") => {
422
+ return filename.endsWith(".json") ? stringifyCircuitryJson(graph) : stringifyCircuitryYaml(graph);
423
+ };
424
+
425
+ // src/scheduler.ts
426
+ var buildCircuitryExecutionGraph = (graph) => {
427
+ const normalized = normalizeCircuitryGraph(graph);
428
+ const nodes = /* @__PURE__ */ new Map();
429
+ const inputSets = /* @__PURE__ */ new Map();
430
+ const outputSets = /* @__PURE__ */ new Map();
431
+ for (const node of normalized.nodes || []) {
432
+ if (node.kind !== "agent" && node.kind !== "tool" && node.kind !== "output" && node.kind !== "input") {
433
+ continue;
434
+ }
435
+ nodes.set(node.id, {
436
+ id: node.id,
437
+ node,
438
+ inputNodeIds: [],
439
+ outputNodeIds: [],
440
+ // Input nodes are already "done" with their value as output
441
+ ...node.kind === "input" ? { output: node.input?.value || "", completed: true } : {}
442
+ });
443
+ inputSets.set(node.id, /* @__PURE__ */ new Set());
444
+ outputSets.set(node.id, /* @__PURE__ */ new Set());
445
+ }
446
+ for (const edge of normalized.edges || []) {
447
+ if (!nodes.has(edge.to)) {
448
+ continue;
449
+ }
450
+ inputSets.get(edge.to).add(edge.from);
451
+ if (nodes.has(edge.from)) {
452
+ outputSets.get(edge.from).add(edge.to);
453
+ }
454
+ }
455
+ for (const [id, node] of nodes) {
456
+ node.inputNodeIds = [...inputSets.get(id)];
457
+ node.outputNodeIds = [...outputSets.get(id)];
458
+ }
459
+ return { nodes };
460
+ };
461
+ var formatEdgeData = (data) => {
462
+ if (!data || Object.keys(data).length === 0) {
463
+ return "";
464
+ }
465
+ return ` data=${JSON.stringify(data)}`;
466
+ };
467
+ var describeEdge = (edge, direction) => {
468
+ const otherId = direction === "incoming" ? edge.from : edge.to;
469
+ const kind = edge.kind ? ` [${edge.kind}]` : "";
470
+ const label = edge.label ? `: ${edge.label}` : "";
471
+ return `- ${direction === "incoming" ? "from" : "to"} ${otherId}${kind}${label}${formatEdgeData(edge.data)}`;
472
+ };
473
+ var composeCircuitryPrompt = (graph, node, inputs, contextInputs) => {
474
+ const agent = node.agent || {};
475
+ const upstreamSection = inputs.length ? inputs.map(
476
+ (input, index) => `Upstream ${index + 1} from ${input.nodeId}:
477
+ ${input.output}`
478
+ ).join("\n\n") : "No upstream node outputs.";
479
+ const contextSection = contextInputs.length ? contextInputs.map(
480
+ (input, index) => input.kind === "text" ? `Context ${index + 1} [text] from ${input.sourceId}:
481
+ ${input.text || ""}` : `Context ${index + 1} [${input.kind}] from ${input.sourceId}${input.text ? `:
482
+ ${input.text}` : ""}`
483
+ ).join("\n\n") : "No connected external context.";
484
+ const incomingEdges = (graph.edges || []).filter((edge) => edge.to === node.id);
485
+ const outgoingEdges = (graph.edges || []).filter((edge) => edge.from === node.id);
486
+ const wiringSection = [
487
+ "Canvas Wiring:",
488
+ "Incoming edges:",
489
+ incomingEdges.length ? incomingEdges.map((edge) => describeEdge(edge, "incoming")).join("\n") : "- none",
490
+ "Outgoing edges:",
491
+ outgoingEdges.length ? outgoingEdges.map((edge) => describeEdge(edge, "outgoing")).join("\n") : "- none"
492
+ ].join("\n");
493
+ return [
494
+ `Node label: ${node.label}`,
495
+ agent.identity ? `Identity: ${agent.identity}` : "",
496
+ agent.context ? `Context:
497
+ ${agent.context}` : "",
498
+ agent.personality ? `Personality:
499
+ ${agent.personality}` : "",
500
+ agent.instructions ? `Instructions:
501
+ ${agent.instructions}` : "",
502
+ wiringSection,
503
+ `Upstream Node Outputs:
504
+ ${upstreamSection}`,
505
+ `Connected Context Inputs:
506
+ ${contextSection}`
507
+ ].filter(Boolean).join("\n\n");
508
+ };
509
+ var collectImages = (contextInputs) => contextInputs.filter((item) => item.kind === "image" && item.image).map((item) => item.image);
510
+ var executeCircuitryNode = async ({
511
+ graph,
512
+ item,
513
+ fallbackCycle,
514
+ inputPayload,
515
+ contextInputs,
516
+ defaultModel,
517
+ executeNode,
518
+ onNodeStart,
519
+ onNodeComplete
520
+ }) => {
521
+ onNodeStart?.(item.id, fallbackCycle);
522
+ if (item.node.kind === "output") {
523
+ const output = inputPayload.map((input) => input.output).join("\n\n");
524
+ onNodeComplete?.(item.id, { output });
525
+ return {
526
+ nodeId: item.id,
527
+ fallbackCycle,
528
+ inputNodeIds: inputPayload.map((input) => input.nodeId),
529
+ output
530
+ };
531
+ }
532
+ const agent = item.node.agent || {};
533
+ try {
534
+ const result = await executeNode({
535
+ nodeId: item.id,
536
+ model: (agent.model === "inherit" ? void 0 : agent.model) || (defaultModel === "inherit" ? void 0 : defaultModel) || "inherit",
537
+ tools: agent.tools || [],
538
+ thinkingLevel: agent.thinkingLevel || "off",
539
+ personality: agent.personality || "",
540
+ instructions: agent.instructions || "",
541
+ context: agent.context || "",
542
+ inputs: inputPayload,
543
+ contextInputs,
544
+ images: collectImages(contextInputs),
545
+ prompt: composeCircuitryPrompt(graph, item.node, inputPayload, contextInputs)
546
+ });
547
+ onNodeComplete?.(item.id, { output: result.output });
548
+ return {
549
+ nodeId: item.id,
550
+ fallbackCycle,
551
+ inputNodeIds: inputPayload.map((input) => input.nodeId),
552
+ output: result.output
553
+ };
554
+ } catch (error) {
555
+ const message = error instanceof Error ? error.message : String(error);
556
+ onNodeComplete?.(item.id, { error: message });
557
+ return {
558
+ nodeId: item.id,
559
+ fallbackCycle,
560
+ inputNodeIds: inputPayload.map((input) => input.nodeId),
561
+ output: "",
562
+ error: message
563
+ };
564
+ }
565
+ };
566
+ var runCircuitryGraphExecution = async ({
567
+ graph,
568
+ selectedNodeId,
569
+ executeNode,
570
+ resolveContextInputs,
571
+ onNodeStart,
572
+ onNodeComplete,
573
+ maxParallelRuns = 4
574
+ }) => {
575
+ const executionGraph = buildCircuitryExecutionGraph(graph);
576
+ const runs = [];
577
+ const parallelism = Math.max(1, Math.floor(maxParallelRuns));
578
+ const defaultModel = graph.runtime?.model;
579
+ const runOne = async (nodeId, outputs2) => {
580
+ const fallbackCycle = false;
581
+ const item = executionGraph.nodes.get(nodeId);
582
+ const inputPayload = item.inputNodeIds.filter((sourceId) => outputs2.has(sourceId)).map((sourceId) => ({
583
+ nodeId: sourceId,
584
+ output: outputs2.get(sourceId)
585
+ }));
586
+ const contextInputs = await resolveContextInputs?.({
587
+ nodeId,
588
+ inputNodeIds: new Set(item.inputNodeIds),
589
+ graph
590
+ }) || [];
591
+ return executeCircuitryNode({
592
+ graph,
593
+ item,
594
+ fallbackCycle,
595
+ inputPayload,
596
+ contextInputs,
597
+ defaultModel,
598
+ executeNode,
599
+ onNodeStart,
600
+ onNodeComplete
601
+ });
602
+ };
603
+ if (selectedNodeId) {
604
+ if (!executionGraph.nodes.has(selectedNodeId)) {
605
+ return runs;
606
+ }
607
+ runs.push(await runOne(selectedNodeId, /* @__PURE__ */ new Map()));
608
+ return runs;
609
+ }
610
+ const completed = /* @__PURE__ */ new Set();
611
+ const queued = /* @__PURE__ */ new Set();
612
+ const outputs = /* @__PURE__ */ new Map();
613
+ const nodeIds = [...executionGraph.nodes.keys()];
614
+ const remainingDependencies = /* @__PURE__ */ new Map();
615
+ const ready = [];
616
+ for (const nodeId of nodeIds) {
617
+ const node = executionGraph.nodes.get(nodeId);
618
+ if (node.completed) {
619
+ completed.add(nodeId);
620
+ if (node.output) {
621
+ outputs.set(nodeId, node.output);
622
+ }
623
+ continue;
624
+ }
625
+ const count = node.inputNodeIds.filter(
626
+ (sourceId) => executionGraph.nodes.has(sourceId)
627
+ ).length;
628
+ remainingDependencies.set(nodeId, count);
629
+ if (count === 0) {
630
+ ready.push(nodeId);
631
+ queued.add(nodeId);
632
+ }
633
+ }
634
+ while (completed.size < nodeIds.length) {
635
+ if (ready.length === 0) {
636
+ const blocked = nodeIds.filter((id) => !completed.has(id));
637
+ throw new Error(`Circuitry execution stalled; unresolved dependencies: ${blocked.join(", ")}`);
638
+ }
639
+ const chunk = ready.splice(0, parallelism);
640
+ const chunkResults = await Promise.all(
641
+ chunk.map(async (nodeId) => ({
642
+ nodeId,
643
+ result: await runOne(nodeId, outputs)
644
+ }))
645
+ );
646
+ for (const { nodeId, result } of chunkResults) {
647
+ outputs.set(nodeId, result.output);
648
+ completed.add(nodeId);
649
+ runs.push(result);
650
+ for (const outputNodeId of executionGraph.nodes.get(nodeId).outputNodeIds) {
651
+ if (completed.has(outputNodeId) || queued.has(outputNodeId)) {
652
+ continue;
653
+ }
654
+ const nextCount = Math.max(
655
+ 0,
656
+ (remainingDependencies.get(outputNodeId) || 0) - 1
657
+ );
658
+ remainingDependencies.set(outputNodeId, nextCount);
659
+ if (nextCount === 0) {
660
+ ready.push(outputNodeId);
661
+ queued.add(outputNodeId);
662
+ }
663
+ }
664
+ }
665
+ }
666
+ return runs;
667
+ };
668
+
669
+ // src/node.ts
670
+ var loadPiSDK = async () => {
671
+ const possiblePaths = [
672
+ path.join(process.cwd(), "node_modules/@earendil-works/pi-coding-agent/dist/index.js"),
673
+ path.join(os.homedir(), ".npm-global/lib/node_modules/@earendil-works/pi-coding-agent/dist/index.js")
674
+ ];
675
+ for (const modulePath of possiblePaths) {
676
+ if (fs.existsSync(modulePath)) {
677
+ return await import(modulePath);
678
+ }
679
+ }
680
+ return await import("@earendil-works/pi-coding-agent");
681
+ };
682
+ var extractAssistantText = (message) => {
683
+ if (!message || message.role !== "assistant") return "";
684
+ if (typeof message.content === "string") return message.content;
685
+ if (!Array.isArray(message.content)) return "";
686
+ return message.content.filter((part) => part?.type === "text" && typeof part.text === "string").map((part) => part.text).join("");
687
+ };
688
+ var withTimeout = async (promise, timeoutMs) => {
689
+ let timeout;
690
+ try {
691
+ return await Promise.race([
692
+ promise,
693
+ new Promise((_, reject) => {
694
+ timeout = setTimeout(
695
+ () => reject(new Error(`Pi SDK request timed out after ${timeoutMs}ms`)),
696
+ timeoutMs
697
+ );
698
+ })
699
+ ]);
700
+ } finally {
701
+ clearTimeout(timeout);
702
+ }
703
+ };
704
+ var runNodeWithPiSDK = async ({
705
+ model,
706
+ prompt,
707
+ images = [],
708
+ tools = [],
709
+ thinkingLevel = "off"
710
+ }) => {
711
+ let sdk;
712
+ try {
713
+ sdk = await loadPiSDK();
714
+ } catch (error) {
715
+ const message = error instanceof Error ? error.message : String(error);
716
+ throw new Error(
717
+ [
718
+ "Circuitry could not load the Pi SDK package for execution.",
719
+ "Ensure @earendil-works/pi-coding-agent is installed.",
720
+ message
721
+ ].join("\n")
722
+ );
723
+ }
724
+ const { AuthStorage, ModelRegistry, SessionManager, createAgentSession } = sdk;
725
+ const authStorage = AuthStorage.create();
726
+ const modelRegistry = ModelRegistry.create(authStorage);
727
+ const [provider, ...modelParts] = String(model || "").split("/");
728
+ const modelId = modelParts.join("/");
729
+ const available = await modelRegistry.getAvailable();
730
+ const selectedModel = (provider && modelId ? modelRegistry.find(provider, modelId) : void 0) || available.find((entry) => {
731
+ if (!modelId) return false;
732
+ return `${entry.provider}/${entry.id}` === `${provider}/${modelId}` || entry.id === modelId;
733
+ }) || available[0];
734
+ if (!selectedModel) {
735
+ throw new Error("No Pi SDK models are available. Configure ~/.pi/agent/auth.json");
736
+ }
737
+ const { session } = await createAgentSession({
738
+ model: selectedModel,
739
+ thinkingLevel,
740
+ sessionManager: SessionManager.inMemory(),
741
+ authStorage,
742
+ modelRegistry
743
+ });
744
+ session.setActiveToolsByName(Array.isArray(tools) ? tools.map((tool) => String(tool)) : []);
745
+ let output = "";
746
+ const unsubscribe = session.subscribe((event) => {
747
+ if ((event.type === "message_update" || event.type === "message_end") && event.message?.role === "assistant") {
748
+ output = extractAssistantText(event.message);
749
+ }
750
+ if (event.type === "agent_end" && Array.isArray(event.messages)) {
751
+ const assistantMessage = [...event.messages].reverse().find((message) => message.role === "assistant");
752
+ output = extractAssistantText(assistantMessage);
753
+ }
754
+ });
755
+ try {
756
+ const promptImages = images.filter((image) => image?.mediaType && image?.data).map((image) => ({
757
+ type: "image",
758
+ source: { type: "base64", mediaType: image.mediaType, data: image.data }
759
+ }));
760
+ await withTimeout(session.prompt(prompt, { images: promptImages }), 18e4);
761
+ return { output: output.trim() };
762
+ } finally {
763
+ unsubscribe();
764
+ session.dispose();
765
+ }
766
+ };
767
+ var defaultFilename = "graph.circuitry.yaml";
768
+ var NodeCircuitryHost = class {
769
+ resolveGraphFile(filename) {
770
+ return path.resolve(
771
+ process.cwd(),
772
+ filename || process.env.CIRCUITRY_GRAPH || defaultFilename
773
+ );
774
+ }
775
+ runFileFor(filename) {
776
+ return `${filename}.run.json`;
777
+ }
778
+ async exists(filename) {
779
+ try {
780
+ await access(filename);
781
+ return true;
782
+ } catch {
783
+ return false;
784
+ }
785
+ }
786
+ parseIssue(error, filename) {
787
+ return {
788
+ code: "parse_error",
789
+ message: error instanceof Error ? error.message : String(error),
790
+ path: [filename]
791
+ };
792
+ }
793
+ parseGraphForValidation(text, filename, standard) {
794
+ try {
795
+ return {
796
+ graph: parseCircuitryText(text || "", filename, standard, {
797
+ validate: false
798
+ })
799
+ };
800
+ } catch (error) {
801
+ return { error: this.parseIssue(error, filename) };
802
+ }
803
+ }
804
+ async readGraphFile(filename) {
805
+ const graphFile = this.resolveGraphFile(filename);
806
+ const text = await readFile(graphFile, "utf8");
807
+ const graph = parseCircuitryText(text, graphFile);
808
+ return { graphFile, text, graph };
809
+ }
810
+ async readGraph(input = {}) {
811
+ const { graphFile, text, graph } = await this.readGraphFile(input.filename);
812
+ const lastRunFile = this.runFileFor(graphFile);
813
+ const lastRun = await this.exists(lastRunFile) ? JSON.parse(await readFile(lastRunFile, "utf8")) : void 0;
814
+ return { filename: graphFile, graph, text, lastRun };
815
+ }
816
+ async validateGraph(input) {
817
+ if (input.graph)
818
+ return validateCircuitryGraphWithStandard(input.graph, input.standard);
819
+ const filename = input.filename || defaultFilename;
820
+ const parsed = this.parseGraphForValidation(input.text, filename, input.standard);
821
+ if (parsed.error) {
822
+ return {
823
+ ok: false,
824
+ errors: [parsed.error],
825
+ standard: validateCircuitryGraphWithStandard({}, input.standard).standard
826
+ };
827
+ }
828
+ return validateCircuitryGraphWithStandard(parsed.graph, input.standard);
829
+ }
830
+ async writeGraph(input) {
831
+ const graphFile = this.resolveGraphFile(input.filename);
832
+ const parsed = input.graph ? { graph: input.graph } : this.parseGraphForValidation(
833
+ input.text,
834
+ input.filename || graphFile,
835
+ input.standard
836
+ );
837
+ if (!parsed.graph)
838
+ throw new Error(`Invalid Circuitry graph:
839
+ ${parsed.error?.message}`);
840
+ const validation = await this.validateGraph({
841
+ graph: parsed.graph,
842
+ standard: input.standard
843
+ });
844
+ if (validation.errors.length)
845
+ throw new Error(
846
+ `Invalid Circuitry graph:
847
+ ${validation.errors.map((e) => e.message).join("\n")}`
848
+ );
849
+ await writeFile(
850
+ graphFile,
851
+ stringifyCircuitryText(parsed.graph, graphFile),
852
+ "utf8"
853
+ );
854
+ return {
855
+ filename: graphFile,
856
+ graph: parsed.graph,
857
+ mode: "replace"
858
+ };
859
+ }
860
+ async runGraph(input = {}) {
861
+ const source = input.source || (input.text ? "text" : "current");
862
+ const graphFile = this.resolveGraphFile(input.filename);
863
+ const parsed = source === "text" ? this.parseGraphForValidation(
864
+ input.text,
865
+ input.filename || graphFile,
866
+ input.standard
867
+ ) : { graph: (await this.readGraphFile(input.filename)).graph };
868
+ if (!parsed.graph)
869
+ throw new Error(`Invalid Circuitry graph:
870
+ ${parsed.error?.message}`);
871
+ const runnableGraph = applyCircuitryRuntimeInputs(parsed.graph, input.inputs);
872
+ const validation = await this.validateGraph({
873
+ graph: runnableGraph,
874
+ standard: input.standard
875
+ });
876
+ if (validation.errors.length)
877
+ throw new Error(
878
+ `Invalid Circuitry graph:
879
+ ${validation.errors.map((e) => e.message).join("\n")}`
880
+ );
881
+ const runItems = await runCircuitryGraphExecution({
882
+ graph: runnableGraph,
883
+ selectedNodeId: input.selectedNodeId,
884
+ executeNode: (req) => runNodeWithPiSDK(req)
885
+ });
886
+ const result = { completedAt: (/* @__PURE__ */ new Date()).toISOString(), runItems };
887
+ if (source !== "text")
888
+ await writeFile(
889
+ this.runFileFor(graphFile),
890
+ JSON.stringify(result, null, 2),
891
+ "utf8"
892
+ );
893
+ return result;
894
+ }
895
+ };
896
+ export {
897
+ NodeCircuitryHost,
898
+ runNodeWithPiSDK
899
+ };
package/dist/tools.d.ts CHANGED
@@ -141,6 +141,7 @@ export declare const createCircuitryRunGraphTool: (host: CircuitryHost) => {
141
141
  text: z.ZodOptional<z.ZodString>;
142
142
  filename: z.ZodOptional<z.ZodString>;
143
143
  selectedNodeId: z.ZodOptional<z.ZodString>;
144
+ inputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
144
145
  standard: z.ZodOptional<z.ZodObject<{
145
146
  version: z.ZodOptional<z.ZodString>;
146
147
  requireSpecVersion: z.ZodOptional<z.ZodBoolean>;
@@ -178,6 +179,7 @@ export declare const createCircuitryRunGraphTool: (host: CircuitryHost) => {
178
179
  hostModel: z.ZodOptional<z.ZodString>;
179
180
  }, "strip", z.ZodTypeAny, {
180
181
  text?: string | undefined;
182
+ inputs?: Record<string, unknown> | undefined;
181
183
  selectedNodeId?: string | undefined;
182
184
  filename?: string | undefined;
183
185
  standard?: z.objectOutputType<{
@@ -196,6 +198,7 @@ export declare const createCircuitryRunGraphTool: (host: CircuitryHost) => {
196
198
  hostModel?: string | undefined;
197
199
  }, {
198
200
  text?: string | undefined;
201
+ inputs?: Record<string, unknown> | undefined;
199
202
  selectedNodeId?: string | undefined;
200
203
  filename?: string | undefined;
201
204
  standard?: z.objectInputType<{
@@ -396,6 +399,7 @@ export declare const createCircuitryTools: (host: CircuitryHost) => ({
396
399
  text: z.ZodOptional<z.ZodString>;
397
400
  filename: z.ZodOptional<z.ZodString>;
398
401
  selectedNodeId: z.ZodOptional<z.ZodString>;
402
+ inputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
399
403
  standard: z.ZodOptional<z.ZodObject<{
400
404
  version: z.ZodOptional<z.ZodString>;
401
405
  requireSpecVersion: z.ZodOptional<z.ZodBoolean>;
@@ -433,6 +437,7 @@ export declare const createCircuitryTools: (host: CircuitryHost) => ({
433
437
  hostModel: z.ZodOptional<z.ZodString>;
434
438
  }, "strip", z.ZodTypeAny, {
435
439
  text?: string | undefined;
440
+ inputs?: Record<string, unknown> | undefined;
436
441
  selectedNodeId?: string | undefined;
437
442
  filename?: string | undefined;
438
443
  standard?: z.objectOutputType<{
@@ -451,6 +456,7 @@ export declare const createCircuitryTools: (host: CircuitryHost) => ({
451
456
  hostModel?: string | undefined;
452
457
  }, {
453
458
  text?: string | undefined;
459
+ inputs?: Record<string, unknown> | undefined;
454
460
  selectedNodeId?: string | undefined;
455
461
  filename?: string | undefined;
456
462
  standard?: z.objectInputType<{
package/package.json CHANGED
@@ -1,9 +1,19 @@
1
1
  {
2
2
  "name": "@darkhorseprojects/circuitry",
3
- "version": "0.2.20",
3
+ "version": "0.2.22",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ },
12
+ "./node": {
13
+ "types": "./dist/node.d.ts",
14
+ "import": "./dist/node.js"
15
+ }
16
+ },
7
17
  "license": "LGPL-3.0-only",
8
18
  "files": [
9
19
  "dist",