@darkhorseprojects/circuitry 0.2.32 → 0.2.99
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 +12 -4
- package/dist/index.js +102 -10
- package/dist/node.d.ts +6 -0
- package/dist/node.js +181 -16
- package/package.json +1 -1
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.2.99";
|
|
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,20 @@ 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
|
+
export type CircuitryGraphLink = string | {
|
|
174
|
+
/** Path to another .circuitry.yaml/.json file, resolved relative to this file. */
|
|
175
|
+
path: string;
|
|
176
|
+
/** Optional id prefix applied to imported resources. */
|
|
177
|
+
prefix?: string;
|
|
178
|
+
};
|
|
173
179
|
export type CircuitryGraph = {
|
|
174
|
-
/** Spec version. Use "0.2" for
|
|
180
|
+
/** Spec version. Use "0.2.99" for linked graph files. */
|
|
175
181
|
circuitry: CircuitrySpecVersion | string;
|
|
176
182
|
id?: string;
|
|
177
183
|
title?: string;
|
|
178
184
|
description?: string;
|
|
185
|
+
/** Files whose resources are merged into this graph before validation/execution. */
|
|
186
|
+
links?: CircuitryGraphLink[];
|
|
179
187
|
resources?: Record<string, CircuitryResourceEntry>;
|
|
180
188
|
/** Internal normalized execution nodes. Authored graph files must not set this. */
|
|
181
189
|
nodes?: CircuitryNode[];
|
|
@@ -215,7 +223,7 @@ export type CircuitryValidationResult = {
|
|
|
215
223
|
};
|
|
216
224
|
export declare const DEFAULT_CIRCUITRY_VALIDATION_RULES: CircuitryValidationRuleEntry[];
|
|
217
225
|
export declare const DEFAULT_CIRCUITRY_VALIDATION_STANDARD: {
|
|
218
|
-
readonly version: "0.2";
|
|
226
|
+
readonly version: "0.2.99";
|
|
219
227
|
readonly requireSpecVersion: true;
|
|
220
228
|
readonly rules: CircuitryValidationRuleEntry[];
|
|
221
229
|
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.2.99";
|
|
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.2", CIRCUITRY_SPEC_VERSION]);
|
|
140
140
|
var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
|
|
141
141
|
const resolvedStandard = createCircuitryValidationStandard(
|
|
142
142
|
standard,
|
|
@@ -519,6 +519,96 @@ ${contextSection}`
|
|
|
519
519
|
].filter(Boolean).join("\n\n");
|
|
520
520
|
};
|
|
521
521
|
var collectImages = (contextInputs) => contextInputs.filter((item) => item.kind === "image" && item.image).map((item) => item.image);
|
|
522
|
+
var stripJsonFence = (output) => {
|
|
523
|
+
const trimmed = output.trim();
|
|
524
|
+
const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
|
|
525
|
+
return fenced ? fenced[1].trim() : trimmed;
|
|
526
|
+
};
|
|
527
|
+
var parseExpectedOutput = (nodeId, output) => {
|
|
528
|
+
try {
|
|
529
|
+
return JSON.parse(stripJsonFence(output));
|
|
530
|
+
} catch (error) {
|
|
531
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
532
|
+
throw new Error(`Node ${nodeId} output does not match expect: output is not valid JSON (${detail})`);
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
var isFieldObject = (schema) => !!schema && typeof schema === "object" && !Array.isArray(schema) && typeof schema.type === "string";
|
|
536
|
+
var describeExpectedType = (schema) => Array.isArray(schema) ? "list" : typeof schema === "string" ? schema : isFieldObject(schema) ? schema.type : "dict";
|
|
537
|
+
var matchesPrimitiveType = (value, type) => {
|
|
538
|
+
switch (type) {
|
|
539
|
+
case "str":
|
|
540
|
+
return typeof value === "string";
|
|
541
|
+
case "int":
|
|
542
|
+
return Number.isInteger(value);
|
|
543
|
+
case "float":
|
|
544
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
545
|
+
case "bool":
|
|
546
|
+
return typeof value === "boolean";
|
|
547
|
+
case "list":
|
|
548
|
+
return Array.isArray(value);
|
|
549
|
+
case "dict":
|
|
550
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
551
|
+
default:
|
|
552
|
+
return true;
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
var validateExpectValue = (value, schema, path, errors) => {
|
|
556
|
+
if (typeof schema === "string") {
|
|
557
|
+
if (!matchesPrimitiveType(value, schema)) {
|
|
558
|
+
errors.push(`${path} expected ${schema}, got ${Array.isArray(value) ? "list" : typeof value}`);
|
|
559
|
+
}
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (Array.isArray(schema)) {
|
|
563
|
+
if (!Array.isArray(value)) {
|
|
564
|
+
errors.push(`${path} expected list, got ${typeof value}`);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
if (schema.length > 0) {
|
|
568
|
+
value.forEach((item, index) => validateExpectValue(item, schema[0], `${path}[${index}]`, errors));
|
|
569
|
+
}
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (isFieldObject(schema)) {
|
|
573
|
+
if (schema.optional && value === void 0) return;
|
|
574
|
+
if (!matchesPrimitiveType(value, schema.type)) {
|
|
575
|
+
errors.push(`${path} expected ${schema.type}, got ${Array.isArray(value) ? "list" : typeof value}`);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (schema.contains && typeof value === "string" && !value.includes(schema.contains)) {
|
|
579
|
+
errors.push(`${path} must include string: ${schema.contains}`);
|
|
580
|
+
}
|
|
581
|
+
if (schema.type === "list" && schema.items && Array.isArray(value)) {
|
|
582
|
+
value.forEach((item, index) => validateExpectValue(item, schema.items, `${path}[${index}]`, errors));
|
|
583
|
+
}
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if (schema && typeof schema === "object") {
|
|
587
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
588
|
+
errors.push(`${path} expected dict, got ${Array.isArray(value) ? "list" : typeof value}`);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
for (const [key, childSchema] of Object.entries(schema)) {
|
|
592
|
+
const childOptional = isFieldObject(childSchema) && childSchema.optional;
|
|
593
|
+
const childValue = value[key];
|
|
594
|
+
if (childValue === void 0 && !childOptional) {
|
|
595
|
+
errors.push(`${path}.${key} is missing required field of type ${describeExpectedType(childSchema)}`);
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
validateExpectValue(childValue, childSchema, `${path}.${key}`, errors);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
var assertExpectedOutput = (node, output) => {
|
|
603
|
+
if (!node.expect) return;
|
|
604
|
+
const parsed = parseExpectedOutput(node.id, output);
|
|
605
|
+
const errors = [];
|
|
606
|
+
validateExpectValue(parsed, node.expect, node.id, errors);
|
|
607
|
+
if (errors.length) {
|
|
608
|
+
throw new Error(`Node ${node.id} output does not match expect:
|
|
609
|
+
${errors.join("\n")}`);
|
|
610
|
+
}
|
|
611
|
+
};
|
|
522
612
|
var executeCircuitryNode = async ({
|
|
523
613
|
graph,
|
|
524
614
|
item,
|
|
@@ -542,8 +632,9 @@ var executeCircuitryNode = async ({
|
|
|
542
632
|
};
|
|
543
633
|
}
|
|
544
634
|
const agent = item.node.agent || {};
|
|
635
|
+
let result;
|
|
545
636
|
try {
|
|
546
|
-
|
|
637
|
+
result = await executeNode({
|
|
547
638
|
nodeId: item.id,
|
|
548
639
|
model: (agent.model === "inherit" ? void 0 : agent.model) || (defaultModel === "inherit" ? void 0 : defaultModel) || "inherit",
|
|
549
640
|
tools: agent.tools || [],
|
|
@@ -556,13 +647,6 @@ var executeCircuitryNode = async ({
|
|
|
556
647
|
images: collectImages(contextInputs),
|
|
557
648
|
prompt: composeCircuitryPrompt(graph, item.node, inputPayload, contextInputs)
|
|
558
649
|
});
|
|
559
|
-
onNodeComplete?.(item.id, { output: result.output });
|
|
560
|
-
return {
|
|
561
|
-
nodeId: item.id,
|
|
562
|
-
fallbackCycle,
|
|
563
|
-
inputNodeIds: inputPayload.map((input) => input.nodeId),
|
|
564
|
-
output: result.output
|
|
565
|
-
};
|
|
566
650
|
} catch (error) {
|
|
567
651
|
const message = error instanceof Error ? error.message : String(error);
|
|
568
652
|
onNodeComplete?.(item.id, { error: message });
|
|
@@ -574,6 +658,14 @@ var executeCircuitryNode = async ({
|
|
|
574
658
|
error: message
|
|
575
659
|
};
|
|
576
660
|
}
|
|
661
|
+
assertExpectedOutput(item.node, result.output);
|
|
662
|
+
onNodeComplete?.(item.id, { output: result.output });
|
|
663
|
+
return {
|
|
664
|
+
nodeId: item.id,
|
|
665
|
+
fallbackCycle,
|
|
666
|
+
inputNodeIds: inputPayload.map((input) => input.nodeId),
|
|
667
|
+
output: result.output
|
|
668
|
+
};
|
|
577
669
|
};
|
|
578
670
|
var runCircuitryGraphExecution = async ({
|
|
579
671
|
graph,
|
package/dist/node.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ export declare class NodeCircuitryHost implements CircuitryHost {
|
|
|
8
8
|
private exists;
|
|
9
9
|
private parseIssue;
|
|
10
10
|
private parseGraphForValidation;
|
|
11
|
+
private linkPath;
|
|
12
|
+
private linkPrefix;
|
|
13
|
+
private prefixResourceIds;
|
|
14
|
+
private mergeResources;
|
|
15
|
+
private resolveGraphLinks;
|
|
16
|
+
private parseAndResolveGraph;
|
|
11
17
|
private readGraphFile;
|
|
12
18
|
readGraph(input?: {
|
|
13
19
|
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.2.99";
|
|
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.2", CIRCUITRY_SPEC_VERSION]);
|
|
121
121
|
var validateCircuitryGraphInternal = (graph, standard = {}, options) => {
|
|
122
122
|
const resolvedStandard = createCircuitryValidationStandard(
|
|
123
123
|
standard,
|
|
@@ -441,6 +441,96 @@ ${contextSection}`
|
|
|
441
441
|
].filter(Boolean).join("\n\n");
|
|
442
442
|
};
|
|
443
443
|
var collectImages = (contextInputs) => contextInputs.filter((item) => item.kind === "image" && item.image).map((item) => item.image);
|
|
444
|
+
var stripJsonFence = (output) => {
|
|
445
|
+
const trimmed = output.trim();
|
|
446
|
+
const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
|
|
447
|
+
return fenced ? fenced[1].trim() : trimmed;
|
|
448
|
+
};
|
|
449
|
+
var parseExpectedOutput = (nodeId, output) => {
|
|
450
|
+
try {
|
|
451
|
+
return JSON.parse(stripJsonFence(output));
|
|
452
|
+
} catch (error) {
|
|
453
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
454
|
+
throw new Error(`Node ${nodeId} output does not match expect: output is not valid JSON (${detail})`);
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
var isFieldObject = (schema) => !!schema && typeof schema === "object" && !Array.isArray(schema) && typeof schema.type === "string";
|
|
458
|
+
var describeExpectedType = (schema) => Array.isArray(schema) ? "list" : typeof schema === "string" ? schema : isFieldObject(schema) ? schema.type : "dict";
|
|
459
|
+
var matchesPrimitiveType = (value, type) => {
|
|
460
|
+
switch (type) {
|
|
461
|
+
case "str":
|
|
462
|
+
return typeof value === "string";
|
|
463
|
+
case "int":
|
|
464
|
+
return Number.isInteger(value);
|
|
465
|
+
case "float":
|
|
466
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
467
|
+
case "bool":
|
|
468
|
+
return typeof value === "boolean";
|
|
469
|
+
case "list":
|
|
470
|
+
return Array.isArray(value);
|
|
471
|
+
case "dict":
|
|
472
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
473
|
+
default:
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
var validateExpectValue = (value, schema, path2, errors) => {
|
|
478
|
+
if (typeof schema === "string") {
|
|
479
|
+
if (!matchesPrimitiveType(value, schema)) {
|
|
480
|
+
errors.push(`${path2} expected ${schema}, got ${Array.isArray(value) ? "list" : typeof value}`);
|
|
481
|
+
}
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (Array.isArray(schema)) {
|
|
485
|
+
if (!Array.isArray(value)) {
|
|
486
|
+
errors.push(`${path2} expected list, got ${typeof value}`);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (schema.length > 0) {
|
|
490
|
+
value.forEach((item, index) => validateExpectValue(item, schema[0], `${path2}[${index}]`, errors));
|
|
491
|
+
}
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (isFieldObject(schema)) {
|
|
495
|
+
if (schema.optional && value === void 0) return;
|
|
496
|
+
if (!matchesPrimitiveType(value, schema.type)) {
|
|
497
|
+
errors.push(`${path2} expected ${schema.type}, got ${Array.isArray(value) ? "list" : typeof value}`);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (schema.contains && typeof value === "string" && !value.includes(schema.contains)) {
|
|
501
|
+
errors.push(`${path2} must include string: ${schema.contains}`);
|
|
502
|
+
}
|
|
503
|
+
if (schema.type === "list" && schema.items && Array.isArray(value)) {
|
|
504
|
+
value.forEach((item, index) => validateExpectValue(item, schema.items, `${path2}[${index}]`, errors));
|
|
505
|
+
}
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (schema && typeof schema === "object") {
|
|
509
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
510
|
+
errors.push(`${path2} expected dict, got ${Array.isArray(value) ? "list" : typeof value}`);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
for (const [key, childSchema] of Object.entries(schema)) {
|
|
514
|
+
const childOptional = isFieldObject(childSchema) && childSchema.optional;
|
|
515
|
+
const childValue = value[key];
|
|
516
|
+
if (childValue === void 0 && !childOptional) {
|
|
517
|
+
errors.push(`${path2}.${key} is missing required field of type ${describeExpectedType(childSchema)}`);
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
validateExpectValue(childValue, childSchema, `${path2}.${key}`, errors);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
var assertExpectedOutput = (node, output) => {
|
|
525
|
+
if (!node.expect) return;
|
|
526
|
+
const parsed = parseExpectedOutput(node.id, output);
|
|
527
|
+
const errors = [];
|
|
528
|
+
validateExpectValue(parsed, node.expect, node.id, errors);
|
|
529
|
+
if (errors.length) {
|
|
530
|
+
throw new Error(`Node ${node.id} output does not match expect:
|
|
531
|
+
${errors.join("\n")}`);
|
|
532
|
+
}
|
|
533
|
+
};
|
|
444
534
|
var executeCircuitryNode = async ({
|
|
445
535
|
graph,
|
|
446
536
|
item,
|
|
@@ -464,8 +554,9 @@ var executeCircuitryNode = async ({
|
|
|
464
554
|
};
|
|
465
555
|
}
|
|
466
556
|
const agent = item.node.agent || {};
|
|
557
|
+
let result;
|
|
467
558
|
try {
|
|
468
|
-
|
|
559
|
+
result = await executeNode({
|
|
469
560
|
nodeId: item.id,
|
|
470
561
|
model: (agent.model === "inherit" ? void 0 : agent.model) || (defaultModel === "inherit" ? void 0 : defaultModel) || "inherit",
|
|
471
562
|
tools: agent.tools || [],
|
|
@@ -478,13 +569,6 @@ var executeCircuitryNode = async ({
|
|
|
478
569
|
images: collectImages(contextInputs),
|
|
479
570
|
prompt: composeCircuitryPrompt(graph, item.node, inputPayload, contextInputs)
|
|
480
571
|
});
|
|
481
|
-
onNodeComplete?.(item.id, { output: result.output });
|
|
482
|
-
return {
|
|
483
|
-
nodeId: item.id,
|
|
484
|
-
fallbackCycle,
|
|
485
|
-
inputNodeIds: inputPayload.map((input) => input.nodeId),
|
|
486
|
-
output: result.output
|
|
487
|
-
};
|
|
488
572
|
} catch (error) {
|
|
489
573
|
const message = error instanceof Error ? error.message : String(error);
|
|
490
574
|
onNodeComplete?.(item.id, { error: message });
|
|
@@ -496,6 +580,14 @@ var executeCircuitryNode = async ({
|
|
|
496
580
|
error: message
|
|
497
581
|
};
|
|
498
582
|
}
|
|
583
|
+
assertExpectedOutput(item.node, result.output);
|
|
584
|
+
onNodeComplete?.(item.id, { output: result.output });
|
|
585
|
+
return {
|
|
586
|
+
nodeId: item.id,
|
|
587
|
+
fallbackCycle,
|
|
588
|
+
inputNodeIds: inputPayload.map((input) => input.nodeId),
|
|
589
|
+
output: result.output
|
|
590
|
+
};
|
|
499
591
|
};
|
|
500
592
|
var runCircuitryGraphExecution = async ({
|
|
501
593
|
graph,
|
|
@@ -745,11 +837,84 @@ var NodeCircuitryHost = class {
|
|
|
745
837
|
return { error: this.parseIssue(error, filename) };
|
|
746
838
|
}
|
|
747
839
|
}
|
|
840
|
+
linkPath(link) {
|
|
841
|
+
return typeof link === "string" ? link : link.path;
|
|
842
|
+
}
|
|
843
|
+
linkPrefix(link) {
|
|
844
|
+
return typeof link === "string" ? "" : link.prefix || "";
|
|
845
|
+
}
|
|
846
|
+
prefixResourceIds(resources, prefix) {
|
|
847
|
+
if (!prefix) return resources;
|
|
848
|
+
const ids = new Set(Object.keys(resources));
|
|
849
|
+
const prefixed = {};
|
|
850
|
+
for (const [id, resource] of Object.entries(resources)) {
|
|
851
|
+
const next = { ...resource };
|
|
852
|
+
if ((next.type === "agent" || next.type === "tool") && next.inputs) {
|
|
853
|
+
next.inputs = next.inputs.map((input) => ids.has(input) ? `${prefix}${input}` : input);
|
|
854
|
+
}
|
|
855
|
+
prefixed[`${prefix}${id}`] = next;
|
|
856
|
+
}
|
|
857
|
+
return prefixed;
|
|
858
|
+
}
|
|
859
|
+
mergeResources(target, source, fromFile) {
|
|
860
|
+
for (const [id, resource] of Object.entries(source)) {
|
|
861
|
+
if (target[id]) {
|
|
862
|
+
throw new Error(`Linked Circuitry resource id collision: ${id} from ${fromFile}`);
|
|
863
|
+
}
|
|
864
|
+
target[id] = resource;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
async resolveGraphLinks(graph, filename, standard, stack = []) {
|
|
868
|
+
const graphFile = path.resolve(filename);
|
|
869
|
+
if (stack.includes(graphFile)) {
|
|
870
|
+
throw new Error(`Circuitry graph link cycle: ${[...stack, graphFile].join(" -> ")}`);
|
|
871
|
+
}
|
|
872
|
+
const linkedResources = {};
|
|
873
|
+
const nextStack = [...stack, graphFile];
|
|
874
|
+
for (const link of graph.links || []) {
|
|
875
|
+
const rawPath = this.linkPath(link);
|
|
876
|
+
if (!rawPath) throw new Error(`Circuitry graph link is missing path in ${graphFile}`);
|
|
877
|
+
const linkedFile = path.resolve(path.dirname(graphFile), rawPath);
|
|
878
|
+
const linkedText = await readFile(linkedFile, "utf8");
|
|
879
|
+
const parsed = parseCircuitryText(linkedText, linkedFile, standard, { validate: false });
|
|
880
|
+
const resolved = await this.resolveGraphLinks(parsed, linkedFile, standard, nextStack);
|
|
881
|
+
this.mergeResources(
|
|
882
|
+
linkedResources,
|
|
883
|
+
this.prefixResourceIds(resolved.resources || {}, this.linkPrefix(link)),
|
|
884
|
+
linkedFile
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
return {
|
|
888
|
+
...graph,
|
|
889
|
+
resources: {
|
|
890
|
+
...linkedResources,
|
|
891
|
+
...graph.resources || {}
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
async parseAndResolveGraph(text, filename, standard) {
|
|
896
|
+
const parsed = this.parseGraphForValidation(text, filename, standard);
|
|
897
|
+
if (!parsed.graph) return parsed;
|
|
898
|
+
try {
|
|
899
|
+
return { graph: await this.resolveGraphLinks(parsed.graph, filename, standard) };
|
|
900
|
+
} catch (error) {
|
|
901
|
+
return { error: this.parseIssue(error, filename) };
|
|
902
|
+
}
|
|
903
|
+
}
|
|
748
904
|
async readGraphFile(filename) {
|
|
749
905
|
const graphFile = this.resolveGraphFile(filename);
|
|
750
906
|
const text = await readFile(graphFile, "utf8");
|
|
751
|
-
const
|
|
752
|
-
|
|
907
|
+
const parsed = await this.parseAndResolveGraph(text, graphFile);
|
|
908
|
+
if (!parsed.graph) throw new Error(`Invalid Circuitry graph:
|
|
909
|
+
${parsed.error?.message}`);
|
|
910
|
+
const validation = validateCircuitryGraphWithStandard(parsed.graph);
|
|
911
|
+
if (validation.errors.length) {
|
|
912
|
+
throw new Error(
|
|
913
|
+
`Invalid Circuitry graph:
|
|
914
|
+
${validation.errors.map((e) => e.message).join("\n")}`
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
return { graphFile, text, graph: normalizeCircuitryGraph(parsed.graph) };
|
|
753
918
|
}
|
|
754
919
|
async readGraph(input = {}) {
|
|
755
920
|
const { graphFile, text, graph } = await this.readGraphFile(input.filename);
|
|
@@ -761,8 +926,8 @@ var NodeCircuitryHost = class {
|
|
|
761
926
|
if (input.graph)
|
|
762
927
|
return validateCircuitryGraphWithStandard(input.graph, input.standard);
|
|
763
928
|
const filename = input.filename || defaultFilename;
|
|
764
|
-
const parsed = this.
|
|
765
|
-
if (parsed
|
|
929
|
+
const parsed = await this.parseAndResolveGraph(input.text, filename, input.standard);
|
|
930
|
+
if ("error" in parsed) {
|
|
766
931
|
return {
|
|
767
932
|
ok: false,
|
|
768
933
|
errors: [parsed.error],
|
|
@@ -773,7 +938,7 @@ var NodeCircuitryHost = class {
|
|
|
773
938
|
}
|
|
774
939
|
async writeGraph(input) {
|
|
775
940
|
const graphFile = this.resolveGraphFile(input.filename);
|
|
776
|
-
const parsed = input.graph ? { graph: input.graph } : this.
|
|
941
|
+
const parsed = input.graph ? { graph: input.graph } : await this.parseAndResolveGraph(
|
|
777
942
|
input.text,
|
|
778
943
|
input.filename || graphFile,
|
|
779
944
|
input.standard
|
|
@@ -804,7 +969,7 @@ ${validation.errors.map((e) => e.message).join("\n")}`
|
|
|
804
969
|
async runGraph(input = {}) {
|
|
805
970
|
const source = input.source || (input.text ? "text" : "current");
|
|
806
971
|
const graphFile = this.resolveGraphFile(input.filename);
|
|
807
|
-
const parsed = source === "text" ? this.
|
|
972
|
+
const parsed = source === "text" ? await this.parseAndResolveGraph(
|
|
808
973
|
input.text,
|
|
809
974
|
input.filename || graphFile,
|
|
810
975
|
input.standard
|