@darkhorseprojects/circuitry 0.2.21 → 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 +9 -0
- package/dist/host.d.ts +2 -1
- package/dist/index.js +44 -0
- package/dist/node.d.ts +2 -1
- package/dist/node.js +899 -0
- package/dist/tools.d.ts +6 -0
- package/package.json +11 -1
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.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",
|
|
@@ -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
|
}),
|
|
@@ -1097,6 +1140,7 @@ export {
|
|
|
1097
1140
|
DEFAULT_CIRCUITRY_VALIDATION_STANDARD,
|
|
1098
1141
|
DEFAULT_MAX_PARALLEL_RUNS,
|
|
1099
1142
|
applyAgentPresets,
|
|
1143
|
+
applyCircuitryRuntimeInputs,
|
|
1100
1144
|
buildCircuitryExecutionGraph,
|
|
1101
1145
|
buildSimulationGraph,
|
|
1102
1146
|
createBundleFromSelection,
|
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.
|
|
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",
|