@darkhorseprojects/circuitry 0.2.31 → 0.2.33

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.
Files changed (3) hide show
  1. package/dist/index.js +100 -8
  2. package/dist/node.js +100 -8
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -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
- const result = await executeNode({
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.js CHANGED
@@ -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
- const result = await executeNode({
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darkhorseprojects/circuitry",
3
- "version": "0.2.31",
3
+ "version": "0.2.33",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",