@darkhorseprojects/circuitry 0.2.33 → 0.3.0

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
@@ -1,5 +1,5 @@
1
- export declare const CIRCUITRY_SPEC_VERSION: "0.2";
2
- export type CircuitrySpecVersion = typeof CIRCUITRY_SPEC_VERSION;
1
+ export declare const CIRCUITRY_SPEC_VERSION: "0.3.0";
2
+ export type CircuitrySpecVersion = typeof CIRCUITRY_SPEC_VERSION | "0.2";
3
3
  export type CircuitryNodeKind = "agent" | "input" | "tool" | "output" | string;
4
4
  export type CircuitryEdgeKind = "context" | "dependency" | "message" | "control" | string;
5
5
  export type CircuitryInputKind = "text" | "file" | "url" | "image" | "uri" | "canvas" | "mcp" | string;
@@ -170,12 +170,23 @@ export type CircuitryRuntimeInputs = Record<string, unknown>;
170
170
  * graph source stable and catches misspelled input names before execution.
171
171
  */
172
172
  export declare const applyCircuitryRuntimeInputs: (graph: CircuitryGraph, inputs?: CircuitryRuntimeInputs) => CircuitryGraph;
173
+ /** Import declaration for bringing resources from another graph file. */
174
+ export type CircuitryImport = {
175
+ /** Path to another .circuitry.yaml/.json file, resolved relative to this file. */
176
+ path: string;
177
+ /** Resource id to import from the source file. */
178
+ resource: string;
179
+ /** Optional local name (alias) for the imported resource. */
180
+ as?: string;
181
+ };
173
182
  export type CircuitryGraph = {
174
- /** Spec version. Use "0.2" for new graphs. */
183
+ /** Spec version. Use "0.3.0" for import-based graph files. */
175
184
  circuitry: CircuitrySpecVersion | string;
176
185
  id?: string;
177
186
  title?: string;
178
187
  description?: string;
188
+ /** Explicit imports of specific resources from other graph files. */
189
+ imports?: CircuitryImport[];
179
190
  resources?: Record<string, CircuitryResourceEntry>;
180
191
  /** Internal normalized execution nodes. Authored graph files must not set this. */
181
192
  nodes?: CircuitryNode[];
@@ -215,7 +226,7 @@ export type CircuitryValidationResult = {
215
226
  };
216
227
  export declare const DEFAULT_CIRCUITRY_VALIDATION_RULES: CircuitryValidationRuleEntry[];
217
228
  export declare const DEFAULT_CIRCUITRY_VALIDATION_STANDARD: {
218
- readonly version: "0.2";
229
+ readonly version: "0.3.0";
219
230
  readonly requireSpecVersion: true;
220
231
  readonly rules: CircuitryValidationRuleEntry[];
221
232
  readonly executableKinds: ["agent", "tool", "output"];
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ var isNodeElement = (element) => {
27
27
  };
28
28
 
29
29
  // src/graph.ts
30
- var CIRCUITRY_SPEC_VERSION = "0.2";
30
+ var CIRCUITRY_SPEC_VERSION = "0.3.0";
31
31
  var runtimeInputToText = (value) => {
32
32
  if (typeof value === "string") return value;
33
33
  if (value === void 0) return "";
@@ -136,7 +136,7 @@ var normalizeCircuitryGraph = (graph) => {
136
136
  const { nodes, edges } = expandResources(graph.resources);
137
137
  return { ...graph, nodes, edges };
138
138
  };
139
- var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set([CIRCUITRY_SPEC_VERSION]);
139
+ var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set(["0.3"]);
140
140
  var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
141
141
  const resolvedStandard = createCircuitryValidationStandard(
142
142
  standard,
@@ -160,19 +160,22 @@ var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
160
160
  }
161
161
  if (options.sourceFormat) {
162
162
  if (!graphObject.resources || Object.keys(graphObject.resources).length === 0) {
163
- addError("missing_resources", "Circuitry v0.2 graphs must use a resources: section", ["resources"]);
163
+ addError("missing_resources", "Circuitry v0.3 graphs must use a resources: section", ["resources"]);
164
164
  }
165
165
  if (graphObject.agents && Object.keys(graphObject.agents).length > 0) {
166
- addError("forbidden_agents", "Circuitry v0.2 graph files must not use top-level agents:; use resources:", ["agents"]);
166
+ addError("forbidden_agents", "Circuitry v0.3 graph files must not use top-level agents:; use resources:", ["agents"]);
167
167
  }
168
168
  if (graphObject.inputs && Object.keys(graphObject.inputs).length > 0) {
169
- addError("forbidden_inputs", "Circuitry v0.2 graph files must not use top-level inputs:; use resources:", ["inputs"]);
169
+ addError("forbidden_inputs", "Circuitry v0.3 graph files must not use top-level inputs:; use resources:", ["inputs"]);
170
170
  }
171
171
  if (graphObject.nodes && graphObject.nodes.length > 0) {
172
- addError("forbidden_nodes", "Circuitry v0.2 graph files must not use top-level nodes:; use resources:", ["nodes"]);
172
+ addError("forbidden_nodes", "Circuitry v0.3 graph files must not use top-level nodes:; use resources:", ["nodes"]);
173
173
  }
174
174
  if (graphObject.edges && graphObject.edges.length > 0) {
175
- addError("forbidden_edges", "Circuitry v0.2 graph files must not use top-level edges:; use resource inputs:", ["edges"]);
175
+ addError("forbidden_edges", "Circuitry v0.3 graph files must not use top-level edges:; use resource inputs:", ["edges"]);
176
+ }
177
+ if (graphObject.links && graphObject.links.length > 0) {
178
+ addError("forbidden_links", "Circuitry v0.3 graph files must use imports: instead of links:", ["links"]);
176
179
  }
177
180
  }
178
181
  const normalized = normalizeCircuitryGraph(graphObject);
package/dist/node.d.ts CHANGED
@@ -8,6 +8,10 @@ export declare class NodeCircuitryHost implements CircuitryHost {
8
8
  private exists;
9
9
  private parseIssue;
10
10
  private parseGraphForValidation;
11
+ private resolveGraphImports;
12
+ /** Rewrite inputs in imported resource to use aliased resource ids. */
13
+ private rewriteImportInputs;
14
+ private parseAndResolveGraph;
11
15
  private readGraphFile;
12
16
  readGraph(input?: {
13
17
  filename?: string;
package/dist/node.js CHANGED
@@ -8,7 +8,7 @@ import os from "node:os";
8
8
  import YAML from "yaml";
9
9
 
10
10
  // src/graph.ts
11
- var CIRCUITRY_SPEC_VERSION = "0.2";
11
+ var CIRCUITRY_SPEC_VERSION = "0.3.0";
12
12
  var runtimeInputToText = (value) => {
13
13
  if (typeof value === "string") return value;
14
14
  if (value === void 0) return "";
@@ -117,7 +117,7 @@ var normalizeCircuitryGraph = (graph) => {
117
117
  const { nodes, edges } = expandResources(graph.resources);
118
118
  return { ...graph, nodes, edges };
119
119
  };
120
- var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set([CIRCUITRY_SPEC_VERSION]);
120
+ var VALID_SPEC_VERSIONS = /* @__PURE__ */ new Set(["0.3"]);
121
121
  var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
122
122
  const resolvedStandard = createCircuitryValidationStandard(
123
123
  standard,
@@ -141,19 +141,22 @@ var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
141
141
  }
142
142
  if (options.sourceFormat) {
143
143
  if (!graphObject.resources || Object.keys(graphObject.resources).length === 0) {
144
- addError("missing_resources", "Circuitry v0.2 graphs must use a resources: section", ["resources"]);
144
+ addError("missing_resources", "Circuitry v0.3 graphs must use a resources: section", ["resources"]);
145
145
  }
146
146
  if (graphObject.agents && Object.keys(graphObject.agents).length > 0) {
147
- addError("forbidden_agents", "Circuitry v0.2 graph files must not use top-level agents:; use resources:", ["agents"]);
147
+ addError("forbidden_agents", "Circuitry v0.3 graph files must not use top-level agents:; use resources:", ["agents"]);
148
148
  }
149
149
  if (graphObject.inputs && Object.keys(graphObject.inputs).length > 0) {
150
- addError("forbidden_inputs", "Circuitry v0.2 graph files must not use top-level inputs:; use resources:", ["inputs"]);
150
+ addError("forbidden_inputs", "Circuitry v0.3 graph files must not use top-level inputs:; use resources:", ["inputs"]);
151
151
  }
152
152
  if (graphObject.nodes && graphObject.nodes.length > 0) {
153
- addError("forbidden_nodes", "Circuitry v0.2 graph files must not use top-level nodes:; use resources:", ["nodes"]);
153
+ addError("forbidden_nodes", "Circuitry v0.3 graph files must not use top-level nodes:; use resources:", ["nodes"]);
154
154
  }
155
155
  if (graphObject.edges && graphObject.edges.length > 0) {
156
- addError("forbidden_edges", "Circuitry v0.2 graph files must not use top-level edges:; use resource inputs:", ["edges"]);
156
+ addError("forbidden_edges", "Circuitry v0.3 graph files must not use top-level edges:; use resource inputs:", ["edges"]);
157
+ }
158
+ if (graphObject.links && graphObject.links.length > 0) {
159
+ addError("forbidden_links", "Circuitry v0.3 graph files must use imports: instead of links:", ["links"]);
157
160
  }
158
161
  }
159
162
  const normalized = normalizeCircuitryGraph(graphObject);
@@ -837,11 +840,71 @@ var NodeCircuitryHost = class {
837
840
  return { error: this.parseIssue(error, filename) };
838
841
  }
839
842
  }
843
+ async resolveGraphImports(graph, filename, standard, stack = []) {
844
+ const graphFile = path.resolve(filename);
845
+ if (stack.includes(graphFile)) {
846
+ throw new Error(`Circuitry graph import cycle: ${[...stack, graphFile].join(" -> ")}`);
847
+ }
848
+ const importedResources = {};
849
+ const aliasMap = {};
850
+ const nextStack = [...stack, graphFile];
851
+ for (const imp of graph.imports || []) {
852
+ const linkedFile = path.resolve(path.dirname(graphFile), imp.path);
853
+ const linkedText = await readFile(linkedFile, "utf8");
854
+ const parsed = parseCircuitryText(linkedText, linkedFile, standard, { validate: false });
855
+ const resolved = await this.resolveGraphImports(parsed, linkedFile, standard, nextStack);
856
+ const resourceId = imp.resource;
857
+ const resource = resolved.resources?.[resourceId];
858
+ if (!resource) {
859
+ throw new Error(`Imported resource not found: ${resourceId} in ${imp.path}`);
860
+ }
861
+ const localId = imp.as || resourceId;
862
+ if (importedResources[localId]) {
863
+ throw new Error(`Imported Circuitry resource id collision: ${localId} from ${imp.path}`);
864
+ }
865
+ if (imp.as) aliasMap[resourceId] = localId;
866
+ const resourceWithRewrittenInputs = this.rewriteImportInputs(resource, aliasMap);
867
+ importedResources[localId] = resourceWithRewrittenInputs;
868
+ }
869
+ return {
870
+ ...graph,
871
+ resources: {
872
+ ...importedResources,
873
+ ...graph.resources || {}
874
+ }
875
+ };
876
+ }
877
+ /** Rewrite inputs in imported resource to use aliased resource ids. */
878
+ rewriteImportInputs(resource, aliasMap) {
879
+ if (resource.type === "agent" || resource.type === "tool") {
880
+ const newInputs = (resource.inputs || []).map((input) => aliasMap[input] || input);
881
+ return { ...resource, inputs: newInputs };
882
+ }
883
+ return resource;
884
+ }
885
+ async parseAndResolveGraph(text, filename, standard) {
886
+ const parsed = this.parseGraphForValidation(text, filename, standard);
887
+ if (!parsed.graph) return parsed;
888
+ try {
889
+ return { graph: await this.resolveGraphImports(parsed.graph, filename, standard) };
890
+ } catch (error) {
891
+ return { error: this.parseIssue(error, filename) };
892
+ }
893
+ }
840
894
  async readGraphFile(filename) {
841
895
  const graphFile = this.resolveGraphFile(filename);
842
896
  const text = await readFile(graphFile, "utf8");
843
- const graph = parseCircuitryText(text, graphFile);
844
- return { graphFile, text, graph };
897
+ const parsed = await this.parseAndResolveGraph(text, graphFile);
898
+ if (!parsed.graph) throw new Error(`Invalid Circuitry graph:
899
+ ${parsed.error?.message}`);
900
+ const validation = validateCircuitryGraphWithStandard(parsed.graph);
901
+ if (validation.errors.length) {
902
+ throw new Error(
903
+ `Invalid Circuitry graph:
904
+ ${validation.errors.map((e) => e.message).join("\n")}`
905
+ );
906
+ }
907
+ return { graphFile, text, graph: normalizeCircuitryGraph(parsed.graph) };
845
908
  }
846
909
  async readGraph(input = {}) {
847
910
  const { graphFile, text, graph } = await this.readGraphFile(input.filename);
@@ -853,8 +916,8 @@ var NodeCircuitryHost = class {
853
916
  if (input.graph)
854
917
  return validateCircuitryGraphWithStandard(input.graph, input.standard);
855
918
  const filename = input.filename || defaultFilename;
856
- const parsed = this.parseGraphForValidation(input.text, filename, input.standard);
857
- if (parsed.error) {
919
+ const parsed = await this.parseAndResolveGraph(input.text, filename, input.standard);
920
+ if ("error" in parsed) {
858
921
  return {
859
922
  ok: false,
860
923
  errors: [parsed.error],
@@ -865,7 +928,7 @@ var NodeCircuitryHost = class {
865
928
  }
866
929
  async writeGraph(input) {
867
930
  const graphFile = this.resolveGraphFile(input.filename);
868
- const parsed = input.graph ? { graph: input.graph } : this.parseGraphForValidation(
931
+ const parsed = input.graph ? { graph: input.graph } : await this.parseAndResolveGraph(
869
932
  input.text,
870
933
  input.filename || graphFile,
871
934
  input.standard
@@ -896,7 +959,7 @@ ${validation.errors.map((e) => e.message).join("\n")}`
896
959
  async runGraph(input = {}) {
897
960
  const source = input.source || (input.text ? "text" : "current");
898
961
  const graphFile = this.resolveGraphFile(input.filename);
899
- const parsed = source === "text" ? this.parseGraphForValidation(
962
+ const parsed = source === "text" ? await this.parseAndResolveGraph(
900
963
  input.text,
901
964
  input.filename || graphFile,
902
965
  input.standard
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darkhorseprojects/circuitry",
3
- "version": "0.2.33",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",