@desplega.ai/agent-swarm 2.4.3 → 2.4.4

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 (150) hide show
  1. package/dist/{acp-adapter-1885dpr7.js → acp-adapter-thq04adh.js} +1 -2
  2. package/dist/{actions-pn76n32q.js → actions-j6ywh6x5.js} +17 -17
  3. package/dist/{amp-adapter-af84rx1d.js → amp-adapter-k8b5xmj8.js} +2 -2
  4. package/dist/{app-6sv8mhzm.js → app-rb7vc02s.js} +3 -3
  5. package/dist/{assistant-sc2tf825.js → assistant-ftta1zjf.js} +18 -18
  6. package/dist/{boot-reembed-y3m0ydzj.js → boot-reembed-srp6v6af.js} +1 -1
  7. package/dist/{boot-reembed-gvxjs2py.js → boot-reembed-xns7h5h9.js} +1 -1
  8. package/dist/{boot-scrub-sweep-1j9mk05f.js → boot-scrub-sweep-3qqrh2mz.js} +2 -2
  9. package/dist/{boot-seeds-37sp626b.js → boot-seeds-9ks8hce7.js} +4 -4
  10. package/dist/{claude-adapter-rp0jtnwr.js → claude-adapter-k5ksyres.js} +2 -2
  11. package/dist/{claude-managed-setup-rdt248k7.js → claude-managed-setup-n5a5eqks.js} +1 -1
  12. package/dist/{claude-sdk-session-53rcw5we.js → claude-sdk-session-2j3j4kwn.js} +2 -2
  13. package/dist/{cli-v87dr6e3.js → cli-1d70crsy.js} +1 -1
  14. package/dist/{cli-1ma335fz.js → cli-1ephgwnq.js} +2 -2
  15. package/dist/{cli-4yef8ghv.js → cli-2v045xyf.js} +3 -3
  16. package/dist/{cli-73ezfhha.js → cli-2y91wnbh.js} +10 -5
  17. package/dist/{cli-1eszr343.js → cli-2zqpkvtt.js} +1 -1
  18. package/dist/{cli-e4d78xsv.js → cli-37yb7307.js} +1 -1
  19. package/dist/{cli-8y99kfey.js → cli-3cgngyd8.js} +9 -9
  20. package/dist/{cli-wk28hzqn.js → cli-44159k28.js} +1 -1
  21. package/dist/{cli-qp2m4btv.js → cli-4dg55wsk.js} +2 -2
  22. package/dist/{cli-0dxxwfkt.js → cli-51y09c6p.js} +17 -14
  23. package/dist/{cli-aa3tb29r.js → cli-55qmbp6z.js} +1 -1
  24. package/dist/{cli-mzkwkbdy.js → cli-82d3nkc3.js} +1 -1
  25. package/dist/{cli-sds5zzs8.js → cli-9fhkkpj5.js} +78 -12
  26. package/dist/{cli-vq9gqarq.js → cli-amyhm3gj.js} +1 -1
  27. package/dist/{cli-bxrqvpg5.js → cli-b8k7epj1.js} +5 -5
  28. package/dist/{cli-8vverzf7.js → cli-ba8ne1es.js} +5 -0
  29. package/dist/{cli-58vwn100.js → cli-bnz0r3kc.js} +1 -1
  30. package/dist/{cli-6eyncg0y.js → cli-cynvc9fj.js} +34 -6
  31. package/dist/{cli-f0p0f6ja.js → cli-dqb4n629.js} +7 -7
  32. package/dist/{cli-pygdsgh8.js → cli-esxerzg7.js} +1 -1
  33. package/dist/{cli-dm8p2dpf.js → cli-gqvh8d8a.js} +6 -6
  34. package/dist/{cli-p0qerd2r.js → cli-hj3debgw.js} +1 -1
  35. package/dist/{cli-836ehxmt.js → cli-nc35pxb7.js} +2 -2
  36. package/dist/{cli-vcgscaan.js → cli-p20r8w42.js} +8 -6
  37. package/dist/{cli-1xsraagq.js → cli-pccfmtb5.js} +4 -4
  38. package/dist/{cli-s9rr4gr2.js → cli-q6r7t299.js} +1036 -298
  39. package/dist/{cli-tkf4x6ha.js → cli-qkhc5aqy.js} +11 -11
  40. package/dist/{cli-1n9gdg56.js → cli-t7cyef5g.js} +4 -4
  41. package/dist/{cli-038fga5k.js → cli-te6b5wvg.js} +148 -183
  42. package/dist/{cli-kgnkyabk.js → cli-v3yr2gbg.js} +109 -35
  43. package/dist/{cli-h0mgxann.js → cli-vdm20wej.js} +2 -2
  44. package/dist/{cli-zvbb4n0e.js → cli-vq97g1jq.js} +2 -2
  45. package/dist/{cli-gx0j2khs.js → cli-w6nadkh4.js} +3 -3
  46. package/dist/{cli-99jez72r.js → cli-w9etnnxk.js} +6 -1
  47. package/dist/{cli-npfe9t1s.js → cli-xc27fwzg.js} +2 -2
  48. package/dist/{cli-6aen3ezb.js → cli-xj0re5tm.js} +1 -1
  49. package/dist/{cli-aypbg9r2.js → cli-zrk6jxv8.js} +1 -1
  50. package/dist/{cli-1012xb7j.js → cli-zrwpctp4.js} +20 -20
  51. package/dist/cli.js +41 -16
  52. package/dist/{codex-adapter-2zrmab9b.js → codex-adapter-968xwfy7.js} +1 -1
  53. package/dist/{codex-session-runner-kbkz62vc.js → codex-session-runner-ht4z99fa.js} +1 -1
  54. package/dist/{commands-hd574th6.js → commands-pcymaeg9.js} +2 -2
  55. package/dist/{controller-pz5mcfg4.js → controller-fpjyk46r.js} +2 -2
  56. package/dist/{controllers-connect-5rgq2hfk.js → controllers-connect-mwzx1fh6.js} +18 -18
  57. package/dist/{cursor-adapter-mgaprq0j.js → cursor-adapter-cp4s2e2f.js} +1 -1
  58. package/dist/{db-h06dxp05.js → db-sgd12bt9.js} +3 -3
  59. package/dist/{db-6nf1207q.js → db-xsjg9jqq.js} +1 -1
  60. package/dist/{dispatcher-zthpy4m1.js → dispatcher-x0jfw2c9.js} +16 -16
  61. package/dist/{dsh-adapter-64dhw0j7.js → dsh-adapter-jrq7ma7h.js} +1 -1
  62. package/dist/{external-channel-x01t7ke6.js → external-channel-8njj4h3b.js} +2 -2
  63. package/dist/{grok-adapter-h21jv594.js → grok-adapter-kkr97v2f.js} +2 -3
  64. package/dist/handlers-0pvee9nw.js +39 -0
  65. package/dist/{heartbeat-zzfksckz.js → heartbeat-wd03zhn0.js} +19 -19
  66. package/dist/{hook-z4zhzj1f.js → hook-f0vghj42.js} +1 -1
  67. package/dist/{hook-3xfqhh2s.js → hook-xzj2ts2p.js} +1 -1
  68. package/dist/{http-q813tmya.js → http-4hwxv62p.js} +255 -117
  69. package/dist/{index-va3y63es.js → index-6srwvd5b.js} +1 -2
  70. package/dist/{index-220tk9bw.js → index-97q4xn03.js} +19 -10
  71. package/dist/{index-s98037v0.js → index-cbxkmfc5.js} +1 -2
  72. package/dist/{index-m0a4ydqj.js → index-gdh8jrdm.js} +16 -16
  73. package/dist/{index-wbbs14c5.js → index-hg0x2cdv.js} +16 -16
  74. package/dist/{index-d0g18bax.js → index-thm0y6a8.js} +19 -19
  75. package/dist/{index-x8xbyn1t.js → index-ww2vznkp.js} +1 -1
  76. package/dist/{keepalive-y5t1my7m.js → keepalive-hrh7xnr9.js} +3 -3
  77. package/dist/{lead-2xwtq66b.js → lead-ee8vazz2.js} +26 -27
  78. package/dist/{maintenance-ybfsaedv.js → maintenance-e8wnwnqx.js} +1 -1
  79. package/dist/{oauth-refresh-sweep-909jmn12.js → oauth-refresh-sweep-zm0yj4sj.js} +2 -2
  80. package/dist/{onboard-xn6fkkys.js → onboard-dtwcq603.js} +2 -2
  81. package/dist/{otel-impl-e6mn0z13.js → otel-impl-w3fbhpn0.js} +1 -1
  82. package/dist/{pricing-refresh-ygd362ep.js → pricing-refresh-j38dehfs.js} +3 -3
  83. package/dist/{rbac-roles-c9axvfnz.js → rbac-roles-ae8yttq4.js} +2 -2
  84. package/dist/{rbac-roles-mb470p4e.js → rbac-roles-ejxx9b35.js} +2 -2
  85. package/dist/{render-v2-g0306nh1.js → render-v2-n16n0phe.js} +3 -3
  86. package/dist/{resume-v315hnq6.js → resume-z0fgjc38.js} +5 -5
  87. package/dist/{session-access-2zg285a1.js → session-access-cv3mq6mc.js} +6 -4
  88. package/dist/{setup-a4mtf02k.js → setup-mafhj3yt.js} +1 -1
  89. package/dist/{work-objects-9fr0fgtn.js → work-objects-8b86mgjp.js} +19 -19
  90. package/dist/{worker-qae22jeb.js → worker-16s75hm5.js} +26 -27
  91. package/dist/worker-token-xf11yyds.js +136 -0
  92. package/openapi.json +295 -12
  93. package/package.json +4 -4
  94. package/src/apps/action-runner.ts +4 -0
  95. package/src/apps/catalog.generated.json +3 -0
  96. package/src/apps/page-validator.ts +8 -2
  97. package/src/be/migrations/232_agent_session_tokens.sql +41 -0
  98. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +10 -1
  99. package/src/be/swarm-config-guard.ts +16 -0
  100. package/src/be/users.ts +169 -12
  101. package/src/cli.tsx +24 -0
  102. package/src/commands/worker-token.ts +164 -0
  103. package/src/http/apps.ts +43 -5
  104. package/src/http/codex-oauth-keep-warm.ts +27 -9
  105. package/src/http/config.ts +5 -3
  106. package/src/http/core.ts +2 -0
  107. package/src/http/session-access.ts +51 -2
  108. package/src/http/sessions.ts +249 -10
  109. package/src/http/tasks.ts +4 -32
  110. package/src/providers/acp-adapter.ts +33 -13
  111. package/src/providers/acp-swarm-events.ts +1 -1
  112. package/src/providers/amp-adapter.ts +1 -1
  113. package/src/providers/codex-oauth/storage.ts +1 -1
  114. package/src/rbac/legacy-policy.ts +3 -1
  115. package/src/rbac/permissions.ts +4 -0
  116. package/src/tests/acp-adapter.test.ts +10 -23
  117. package/src/tests/acp-fake-agent.ts +36 -0
  118. package/src/tests/acp-swarm-events.test.ts +1 -1
  119. package/src/tests/agent-session-tokens.test.ts +452 -0
  120. package/src/tests/apps-action-parent-task.test.ts +295 -0
  121. package/src/tests/codex-oauth-keep-warm.test.ts +85 -1
  122. package/src/tests/config-session-auth.test.ts +14 -0
  123. package/src/tests/docs-index-footprint.test.js +2 -2
  124. package/src/tests/grok-adapter.test.ts +3 -3
  125. package/src/tests/heartbeat-jev-gate.test.ts +23 -0
  126. package/src/tests/migration-232-agent-session-tokens.test.ts +67 -0
  127. package/src/tests/rbac-engine.test.ts +1 -0
  128. package/src/tests/runtime-child-token-cleanup.test.ts +3 -3
  129. package/src/tests/script-connections.test.ts +2 -0
  130. package/src/tests/session-route-posture.test.ts +2 -0
  131. package/src/tests/worker-token-cli.test.ts +76 -0
  132. package/src/tests/workflow-executors.test.ts +4 -2
  133. package/src/tests/workflow-system-one-extraction.test.ts +805 -0
  134. package/src/types.ts +2 -1
  135. package/src/utils/request-auth-context.ts +2 -1
  136. package/src/workflows/definition.ts +7 -1
  137. package/src/workflows/engine.ts +18 -12
  138. package/src/workflows/executors/index.ts +1 -0
  139. package/src/workflows/executors/registry.ts +2 -0
  140. package/src/workflows/executors/system-one-decision.ts +56 -255
  141. package/src/workflows/executors/system-one-extract-wire.ts +57 -0
  142. package/src/workflows/executors/system-one-extraction.ts +830 -0
  143. package/src/workflows/executors/system-one-providers.ts +277 -110
  144. package/src/workflows/executors/system-one-transport.ts +264 -0
  145. package/templates/official/lead/HEARTBEAT.md +1 -1
  146. package/templates/skills/apps/SKILL.md +3 -1
  147. package/templates/skills/apps/content.md +3 -1
  148. package/templates/skills/heartbeat-runbook/content.md +3 -3
  149. package/dist/cli-v1sjhn52.js +0 -7
  150. package/dist/handlers-2qjw434w.js +0 -39
@@ -5,10 +5,10 @@ import {
5
5
  completeApprovalCancellationNotificationClaim2,
6
6
  releaseApprovalCancellationNotificationClaim2,
7
7
  init_db3
8
- } from "./cli-vcgscaan.js";
8
+ } from "./cli-p20r8w42.js";
9
9
  import {
10
10
  getSlackApp2
11
- } from "./cli-tkf4x6ha.js";
11
+ } from "./cli-qkhc5aqy.js";
12
12
  import {
13
13
  DEFAULT_MODEL,
14
14
  resolveCredential
@@ -16,7 +16,7 @@ import {
16
16
  import {
17
17
  TERMINAL_TASK_STATUSES,
18
18
  init_types
19
- } from "./cli-836ehxmt.js";
19
+ } from "./cli-nc35pxb7.js";
20
20
  import {
21
21
  selectCredential,
22
22
  init_credentials
@@ -45,6 +45,7 @@ import {
45
45
  strictObject2,
46
46
  union2,
47
47
  discriminatedUnion,
48
+ tuple,
48
49
  record2,
49
50
  _enum2,
50
51
  literal2
@@ -66,7 +67,6 @@ import {
66
67
 
67
68
  // src/workflows/executors/system-one-decision.ts
68
69
  init_zod();
69
- init_openrouter_base_url();
70
70
  init_secret_scrubber();
71
71
 
72
72
  // src/workflows/executors/base.ts
@@ -454,7 +454,7 @@ class HumanInTheLoopExecutor extends BaseExecutor {
454
454
  }
455
455
  if (notification.channel === "slack") {
456
456
  try {
457
- const { getSlackApp } = await import("./app-6sv8mhzm.js");
457
+ const { getSlackApp } = await import("./app-rb7vc02s.js");
458
458
  const slackApp = getSlackApp();
459
459
  if (!slackApp) {
460
460
  console.warn("[HITL] Slack not initialized — cannot send notification");
@@ -541,6 +541,9 @@ function formatTimeout(seconds) {
541
541
  return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
542
542
  }
543
543
 
544
+ // src/workflows/executors/system-one-extraction.ts
545
+ init_zod();
546
+
544
547
  // src/workflows/executors/system-one-providers.ts
545
548
  init_openrouter_base_url();
546
549
 
@@ -585,6 +588,35 @@ function cloudflareErrorCode(body) {
585
588
  return typeof code === "string" && ERROR_CODE_RE.test(code) ? code : undefined;
586
589
  }
587
590
 
591
+ // src/workflows/executors/system-one-extract-wire.ts
592
+ function extractionStateText(state) {
593
+ return typeof state === "string" ? state : JSON.stringify(state);
594
+ }
595
+ var CLAIRIC_WIRE = {
596
+ buildRequest: (config, model) => ({
597
+ ...model === undefined ? {} : { model },
598
+ state: extractionStateText(config.state),
599
+ fields: config.fields,
600
+ ...config.images?.length ? { images: config.images } : {},
601
+ ...config.referenceDate ? { reference_date: config.referenceDate } : {}
602
+ }),
603
+ toExtraction: (body) => body
604
+ };
605
+ var CLOUDFLARE_EXTRACT_WIRE = {
606
+ buildRequest: CLAIRIC_WIRE.buildRequest,
607
+ toExtraction(body) {
608
+ if (!isRecord(body))
609
+ wireContractError("response body must be a JSON object");
610
+ if (body.success !== true) {
611
+ const code = cloudflareErrorCode(body);
612
+ throw new SystemOneWireError(`Cloudflare Workers AI reported a failed run${code ? ` (${code})` : ""}. Nothing was extracted.`);
613
+ }
614
+ if (!isRecord(body.result))
615
+ wireContractError("Cloudflare response is missing its result");
616
+ return body.result;
617
+ }
618
+ };
619
+
588
620
  // src/workflows/executors/system-one-openai-wire.ts
589
621
  function entryText(entry) {
590
622
  if (entry === undefined || entry === null)
@@ -764,27 +796,43 @@ var LAYA_KEY = "LAYA_API_KEY";
764
796
  var LAYA_URL_KEY = "LAYA_URL";
765
797
  var OPENAI_DECISIONS_KEY = "OPENAI_DECISIONS_API_KEY";
766
798
  var CLOUDFLARE_TOKEN_KEY = "CLOUDFLARE_API_TOKEN";
799
+ var CLOUDFLARE_KEY_FALLBACK = "CLOUDFLARE_API_KEY";
767
800
  var CLOUDFLARE_ACCOUNT_KEY = "CLOUDFLARE_ACCOUNT_ID";
768
801
  var CLOUDFLARE_ACCOUNT_RE = /^[0-9a-f]{32}$/;
769
802
  async function readGlobalConfig(db, key) {
770
803
  const rows = await db.getSwarmConfigs({ scope: "global", key });
771
804
  return rows.find((row) => row.scope === "global" && row.key === key)?.value;
772
805
  }
773
- var SYSTEM_ONE_PROVIDERS = {
806
+ async function readFirstGlobalConfig(db, keys) {
807
+ for (const keyName of keys) {
808
+ const value = await readGlobalConfig(db, keyName);
809
+ if (value && value.trim() !== "")
810
+ return { value, keyName };
811
+ }
812
+ return;
813
+ }
814
+ var CLOUDFLARE_ENDPOINT = {
815
+ configKey: CLOUDFLARE_ACCOUNT_KEY,
816
+ describe: "the 32-character id of a Cloudflare account",
817
+ build: (accountId, model) => {
818
+ const id = accountId.trim();
819
+ return CLOUDFLARE_ACCOUNT_RE.test(id) ? `https://api.cloudflare.com/client/v4/accounts/${id}/ai/run/@cf/cloudflare/${model}` : null;
820
+ }
821
+ };
822
+ var SYSTEM_ONE_HOSTS = {
774
823
  typesafe: {
775
824
  label: "TypeSafe",
776
825
  keyName: TYPESAFE_KEY,
777
- endpoint: "https://api.typesafe.ai/v1/systemone",
778
- defaultModel: "jev-latest",
779
- confidenceField: "confidence",
780
- readKey: ({ db }) => readGlobalConfig(db, TYPESAFE_KEY)
826
+ readKey: ({ db }) => readGlobalConfig(db, TYPESAFE_KEY),
827
+ decide: {
828
+ endpoint: "https://api.typesafe.ai/v1/systemone",
829
+ defaultModel: "jev-latest",
830
+ confidenceField: "confidence"
831
+ }
781
832
  },
782
833
  openrouter: {
783
834
  label: "OpenRouter",
784
835
  keyName: OPENROUTER_KEY,
785
- endpoint: "https://openrouter.ai/api/alpha/decisions",
786
- defaultModel: "~typesafe/jev-latest",
787
- confidenceField: "confidence",
788
836
  deploymentProblem(env) {
789
837
  return getOpenRouterBaseUrl(env) === DEFAULT_OPENROUTER_BASE_URL ? null : 'OPENROUTER_BASE_URL points at a gateway. SystemOne provider "openrouter" calls openrouter.ai directly and is not supported through a gateway; unset OPENROUTER_BASE_URL or use provider "typesafe".';
790
838
  },
@@ -794,97 +842,140 @@ var SYSTEM_ONE_PROVIDERS = {
794
842
  } catch {
795
843
  return null;
796
844
  }
845
+ },
846
+ decide: {
847
+ endpoint: "https://openrouter.ai/api/alpha/decisions",
848
+ defaultModel: "~typesafe/jev-latest",
849
+ confidenceField: "confidence"
797
850
  }
798
851
  },
799
852
  laya: {
800
853
  label: "laya",
801
854
  keyName: LAYA_KEY,
802
- endpoint: { configKey: LAYA_URL_KEY, path: "/v1/systemone" },
803
- confidenceField: "answer_confidence",
804
- readKey: ({ db }) => readGlobalConfig(db, LAYA_KEY)
855
+ readKey: ({ db }) => readGlobalConfig(db, LAYA_KEY),
856
+ decide: {
857
+ endpoint: { configKey: LAYA_URL_KEY, path: "/v1/systemone" },
858
+ confidenceField: "answer_confidence"
859
+ }
805
860
  },
806
861
  openai: {
807
862
  label: "OpenAI",
808
863
  keyName: OPENAI_DECISIONS_KEY,
809
- endpoint: "https://api.openai.com/v1/decisions",
810
- defaultModel: "gpt-6-luna",
811
- confidenceField: "confidence",
812
- wire: OPENAI_DECISIONS_WIRE,
813
- readKey: ({ db }) => readGlobalConfig(db, OPENAI_DECISIONS_KEY)
864
+ readKey: ({ db }) => readGlobalConfig(db, OPENAI_DECISIONS_KEY),
865
+ decide: {
866
+ endpoint: "https://api.openai.com/v1/decisions",
867
+ defaultModel: "gpt-6-luna",
868
+ confidenceField: "confidence",
869
+ wire: OPENAI_DECISIONS_WIRE
870
+ }
814
871
  },
815
872
  cloudflare: {
816
873
  label: "Cloudflare Workers AI",
817
874
  keyName: CLOUDFLARE_TOKEN_KEY,
818
- endpoint: {
819
- configKey: CLOUDFLARE_ACCOUNT_KEY,
820
- describe: "the 32-character id of a Cloudflare account",
821
- build: (accountId, model) => {
822
- const id = accountId.trim();
823
- return CLOUDFLARE_ACCOUNT_RE.test(id) ? `https://api.cloudflare.com/client/v4/accounts/${id}/ai/run/@cf/cloudflare/${model}` : null;
824
- }
875
+ fallbackKeyNames: [CLOUDFLARE_KEY_FALLBACK],
876
+ readKey: ({ db }) => readFirstGlobalConfig(db, [CLOUDFLARE_TOKEN_KEY, CLOUDFLARE_KEY_FALLBACK]),
877
+ decide: {
878
+ endpoint: CLOUDFLARE_ENDPOINT,
879
+ defaultModel: "clef",
880
+ allowedModels: ["clef", "clef-flash"],
881
+ limits: { maxQuestions: 64 },
882
+ confidenceField: "confidence",
883
+ wire: CLOUDFLARE_WIRE
825
884
  },
826
- defaultModel: "clef",
827
- allowedModels: ["clef", "clef-flash"],
828
- limits: { maxQuestions: 64 },
829
- confidenceField: "confidence",
830
- wire: CLOUDFLARE_WIRE,
831
- readKey: ({ db }) => readGlobalConfig(db, CLOUDFLARE_TOKEN_KEY)
885
+ extract: {
886
+ endpoint: CLOUDFLARE_ENDPOINT,
887
+ defaultModel: "clairic",
888
+ allowedModels: ["clairic"],
889
+ limits: { maxFields: 64, maxImages: 4 },
890
+ wire: CLOUDFLARE_EXTRACT_WIRE
891
+ }
832
892
  }
833
893
  };
834
- var SYSTEM_ONE_PROVIDER_IDS = Object.keys(SYSTEM_ONE_PROVIDERS);
894
+ function hostsServing(capability) {
895
+ const ids = Object.keys(SYSTEM_ONE_HOSTS).filter((id) => SYSTEM_ONE_HOSTS[id][capability] !== undefined);
896
+ return ids;
897
+ }
898
+ var SYSTEM_ONE_PROVIDERS = Object.fromEntries(hostsServing("decide").map((id) => {
899
+ const { decide, extract: _extract, ...host } = SYSTEM_ONE_HOSTS[id];
900
+ return [id, { ...host, ...decide }];
901
+ }));
902
+ var SYSTEM_ONE_PROVIDER_IDS = hostsServing("decide");
903
+ var SYSTEM_ONE_EXTRACT_PROVIDER_IDS = hostsServing("extract");
835
904
  var SYSTEM_ONE_DEFAULT_PROVIDER = "typesafe";
905
+ var SYSTEM_ONE_EXTRACT_DEFAULT_PROVIDER = "cloudflare";
836
906
  function isSystemOneProviderId(value) {
837
- return typeof value === "string" && Object.hasOwn(SYSTEM_ONE_PROVIDERS, value);
907
+ return typeof value === "string" && SYSTEM_ONE_PROVIDER_IDS.includes(value);
908
+ }
909
+ function isSystemOneExtractProviderId(value) {
910
+ return typeof value === "string" && SYSTEM_ONE_EXTRACT_PROVIDER_IDS.includes(value);
838
911
  }
839
912
  function systemOneProviderOf(config) {
840
913
  if (config.provider === undefined)
841
914
  return SYSTEM_ONE_DEFAULT_PROVIDER;
842
915
  return isSystemOneProviderId(config.provider) ? config.provider : undefined;
843
916
  }
917
+ function systemOneExtractProviderOf(config) {
918
+ if (config.provider === undefined)
919
+ return SYSTEM_ONE_EXTRACT_DEFAULT_PROVIDER;
920
+ return isSystemOneExtractProviderId(config.provider) ? config.provider : undefined;
921
+ }
922
+ function serviceOf(id, capability) {
923
+ const service = SYSTEM_ONE_HOSTS[id][capability];
924
+ if (!service)
925
+ throw new Error(`SystemOne provider "${id}" does not serve ${capability}`);
926
+ return service;
927
+ }
928
+ var NOTHING_PRODUCED = {
929
+ decide: "No decision was made.",
930
+ extract: "Nothing was extracted."
931
+ };
844
932
  var SET_IT = (keyName) => `Set ${keyName} as a global secret on the Secrets page (Settings > Secrets).`;
845
933
  var SET_URL = (configKey) => `Set ${configKey} as a global config value (set-config with scope global).`;
846
934
  function systemOneKeyMissingMessage(id) {
847
- const { label, keyName } = SYSTEM_ONE_PROVIDERS[id];
848
- return `${keyName} is not configured. SystemOne provider "${id}" needs a working ${label} API key. ${SET_IT(keyName)}`;
935
+ const { label, keyName, fallbackKeyNames } = SYSTEM_ONE_HOSTS[id];
936
+ const also = fallbackKeyNames?.length ? ` (${fallbackKeyNames.join(", ")} is read when it is unset, and is not configured either)` : "";
937
+ return `${keyName} is not configured${also}. SystemOne provider "${id}" needs a working ${label} API key. ${SET_IT(keyName)}`;
849
938
  }
850
939
  function systemOneKeyUnreadableMessage(id) {
851
- return `Could not read ${SYSTEM_ONE_PROVIDERS[id].keyName} from swarm config`;
940
+ return `Could not read ${SYSTEM_ONE_HOSTS[id].keyName} from swarm config`;
852
941
  }
853
- function systemOneKeyMalformedMessage(id) {
854
- return `${SYSTEM_ONE_PROVIDERS[id].keyName} is not a valid bearer token value. ${SET_IT(SYSTEM_ONE_PROVIDERS[id].keyName)}`;
942
+ function systemOneKeyMalformedMessage(id, keyName) {
943
+ const name = keyName ?? SYSTEM_ONE_HOSTS[id].keyName;
944
+ return `${name} is not a valid bearer token value. ${SET_IT(name)}`;
855
945
  }
856
- function systemOneKeyRejectedMessage(id, status, code) {
857
- const { label, keyName } = SYSTEM_ONE_PROVIDERS[id];
946
+ function systemOneKeyRejectedMessage(id, status, code, options = {}) {
947
+ const { label } = SYSTEM_ONE_HOSTS[id];
948
+ const keyName = options.keyName ?? SYSTEM_ONE_HOSTS[id].keyName;
858
949
  const detail = code ? ` (${code})` : "";
859
- return `${keyName} was rejected by ${label} (HTTP ${status}${detail}). ` + `The key is invalid, revoked, or lacks access to the model. Replace it on the Secrets page (Settings > Secrets). No decision was made.`;
950
+ return `${keyName} was rejected by ${label} (HTTP ${status}${detail}). ` + `The key is invalid, revoked, or lacks access to the model. Replace it on the Secrets page (Settings > Secrets). ${NOTHING_PRODUCED[options.capability ?? "decide"]}`;
860
951
  }
861
- function urlKeyOf(provider) {
862
- return typeof provider.endpoint === "string" ? null : provider.endpoint.configKey;
952
+ function urlKeyOf(endpoint) {
953
+ return typeof endpoint === "string" ? null : endpoint.configKey;
863
954
  }
864
955
  function isBuiltEndpoint(endpoint) {
865
956
  return typeof endpoint !== "string" && "build" in endpoint;
866
957
  }
867
- function urlDescriptionOf(provider) {
868
- return isBuiltEndpoint(provider.endpoint) ? provider.endpoint.describe : `the URL of a ${provider.label} server`;
958
+ function urlDescriptionOf(id, endpoint) {
959
+ return isBuiltEndpoint(endpoint) ? endpoint.describe : `the URL of a ${SYSTEM_ONE_HOSTS[id].label} server`;
869
960
  }
870
- function systemOneUrlMissingMessage(id) {
871
- const provider = SYSTEM_ONE_PROVIDERS[id];
872
- const configKey = urlKeyOf(provider) ?? "";
873
- return `${configKey} is not configured. SystemOne provider "${id}" needs ${urlDescriptionOf(provider)}. ${SET_URL(configKey)}`;
961
+ function systemOneUrlMissingMessage(id, capability = "decide") {
962
+ const { endpoint } = serviceOf(id, capability);
963
+ const configKey = urlKeyOf(endpoint) ?? "";
964
+ return `${configKey} is not configured. SystemOne provider "${id}" needs ${urlDescriptionOf(id, endpoint)}. ${SET_URL(configKey)}`;
874
965
  }
875
- function systemOneUrlUnreadableMessage(id) {
876
- return `Could not read ${urlKeyOf(SYSTEM_ONE_PROVIDERS[id]) ?? ""} from swarm config`;
966
+ function systemOneUrlUnreadableMessage(id, capability = "decide") {
967
+ return `Could not read ${urlKeyOf(serviceOf(id, capability).endpoint) ?? ""} from swarm config`;
877
968
  }
878
- function systemOneUrlInvalidMessage(id) {
879
- const provider = SYSTEM_ONE_PROVIDERS[id];
880
- const configKey = urlKeyOf(provider) ?? "";
881
- if (isBuiltEndpoint(provider.endpoint)) {
882
- return `${configKey} is not ${provider.endpoint.describe}. ${SET_URL(configKey)}`;
969
+ function systemOneUrlInvalidMessage(id, capability = "decide") {
970
+ const { endpoint } = serviceOf(id, capability);
971
+ const configKey = urlKeyOf(endpoint) ?? "";
972
+ if (isBuiltEndpoint(endpoint)) {
973
+ return `${configKey} is not ${endpoint.describe}. ${SET_URL(configKey)}`;
883
974
  }
884
- return `${configKey} is not a usable ${provider.label} server URL. It must be an https URL (http only for localhost) with no credentials, query, or fragment. ${SET_URL(configKey)}`;
975
+ return `${configKey} is not a usable ${SYSTEM_ONE_HOSTS[id].label} server URL. It must be an https URL (http only for localhost) with no credentials, query, or fragment. ${SET_URL(configKey)}`;
885
976
  }
886
- function systemOneModelProblem(id, model) {
887
- const { allowedModels } = SYSTEM_ONE_PROVIDERS[id];
977
+ function systemOneModelProblem(id, model, capability = "decide") {
978
+ const { allowedModels } = serviceOf(id, capability);
888
979
  if (!allowedModels || model !== undefined && allowedModels.includes(model))
889
980
  return null;
890
981
  return `SystemOne provider "${id}" serves only the models ${allowedModels.join(", ")}; set model to one of them or leave it unset.`;
@@ -918,75 +1009,879 @@ function systemOneEndpointFromBase(base, path) {
918
1009
  return `${url.origin}${url.pathname.replace(/\/+$/, "")}${path}`;
919
1010
  }
920
1011
  async function resolveSystemOneTarget(id, ctx, overrides = {}) {
921
- const provider = SYSTEM_ONE_PROVIDERS[id];
922
- const deploymentProblem = provider.deploymentProblem?.(ctx.env);
1012
+ const host = SYSTEM_ONE_HOSTS[id];
1013
+ const capability = overrides.capability ?? "decide";
1014
+ const service = serviceOf(id, capability);
1015
+ const deploymentProblem = host.deploymentProblem?.(ctx.env);
923
1016
  if (deploymentProblem)
924
1017
  return { ok: false, error: deploymentProblem };
925
1018
  const errors = [];
926
1019
  let endpoint;
927
- if (typeof provider.endpoint === "string") {
928
- endpoint = provider.endpoint;
1020
+ if (typeof service.endpoint === "string") {
1021
+ endpoint = service.endpoint;
929
1022
  } else {
930
- const { configKey } = provider.endpoint;
1023
+ const { configKey } = service.endpoint;
931
1024
  let base;
932
1025
  let readable = true;
933
1026
  try {
934
1027
  base = await (overrides.serverUrl ? overrides.serverUrl(id) : readGlobalConfig(ctx.db, configKey));
935
1028
  } catch {
936
1029
  readable = false;
937
- errors.push(systemOneUrlUnreadableMessage(id));
1030
+ errors.push(systemOneUrlUnreadableMessage(id, capability));
938
1031
  }
939
1032
  if (readable) {
940
1033
  if (!base || base.trim() === "") {
941
- errors.push(systemOneUrlMissingMessage(id));
1034
+ errors.push(systemOneUrlMissingMessage(id, capability));
942
1035
  } else {
943
- const resolved = isBuiltEndpoint(provider.endpoint) ? provider.endpoint.build(base, overrides.model ?? provider.defaultModel ?? "") : systemOneEndpointFromBase(base, provider.endpoint.path);
1036
+ const resolved = isBuiltEndpoint(service.endpoint) ? service.endpoint.build(base, overrides.model ?? service.defaultModel ?? "") : systemOneEndpointFromBase(base, service.endpoint.path);
944
1037
  if (resolved)
945
1038
  endpoint = resolved;
946
1039
  else
947
- errors.push(systemOneUrlInvalidMessage(id));
1040
+ errors.push(systemOneUrlInvalidMessage(id, capability));
948
1041
  }
949
1042
  }
950
1043
  }
951
- let apiKey;
1044
+ let read;
952
1045
  let keyProblem;
953
1046
  try {
954
- apiKey = await (overrides.apiKey ? overrides.apiKey(id) : provider.readKey(ctx));
1047
+ read = await (overrides.apiKey ? overrides.apiKey(id) : host.readKey(ctx));
955
1048
  } catch {
956
1049
  keyProblem = systemOneKeyUnreadableMessage(id);
957
1050
  }
1051
+ const apiKey = typeof read === "string" ? read : read?.value;
1052
+ const keyName = typeof read === "object" && read?.keyName || host.keyName;
958
1053
  if (!keyProblem) {
959
1054
  if (!apiKey || apiKey.trim() === "")
960
1055
  keyProblem = systemOneKeyMissingMessage(id);
961
1056
  else if (hasWhitespaceOrControl(apiKey))
962
- keyProblem = systemOneKeyMalformedMessage(id);
1057
+ keyProblem = systemOneKeyMalformedMessage(id, keyName);
963
1058
  }
964
1059
  if (keyProblem)
965
1060
  errors.push(keyProblem);
966
1061
  if (errors.length > 0 || !endpoint || !apiKey)
967
1062
  return { ok: false, error: errors.join(" ") };
968
- return { ok: true, apiKey, endpoint };
1063
+ return { ok: true, apiKey, endpoint, keyName };
969
1064
  }
970
1065
 
971
- // src/workflows/executors/system-one-decision.ts
972
- var SYSTEM_ONE_DECISION_NODE_TYPE = "system-one-decision";
973
- var SYSTEM_ONE_ENDPOINT = SYSTEM_ONE_PROVIDERS.typesafe.endpoint;
974
- var SYSTEM_ONE_API_KEY_CONFIG_KEY = SYSTEM_ONE_PROVIDERS.typesafe.keyName;
1066
+ // src/workflows/executors/system-one-transport.ts
1067
+ init_openrouter_base_url();
975
1068
  var SYSTEM_ONE_DEFAULT_TIMEOUT_MS = 30000;
976
1069
  var SYSTEM_ONE_MIN_TIMEOUT_MS = 1000;
977
1070
  var SYSTEM_ONE_MAX_TIMEOUT_MS = 300000;
978
1071
  var SYSTEM_ONE_DEFAULT_MAX_RETRIES = 2;
979
1072
  var SYSTEM_ONE_MAX_RETRIES_LIMIT = 3;
980
- var CHOICE_MIN_OPTIONS = 2;
981
- var CHOICE_MAX_OPTIONS = 255;
982
- var SCORE_MIN_LEVELS = 2;
983
- var SCORE_MAX_LEVELS = 10;
984
1073
  var DEADLINE_MARGIN_MS = 250;
985
1074
  var BACKOFF_BASE_MS = 500;
986
1075
  var BACKOFF_CAP_MS = 8000;
987
- var DISTRIBUTION_TOLERANCE = 0.02;
988
1076
  var MAX_RESPONSE_CHARS = 1048576;
989
1077
  var MAX_ERROR_BODY_CHARS = 65536;
1078
+ var REQUEST_ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;
1079
+ var ERROR_CODE_RE2 = /^[A-Za-z0-9._:-]{1,64}$/;
1080
+ function readRequestId(headers) {
1081
+ const value = headers.get("x-request-id") ?? headers.get("request-id") ?? headers.get("cf-ray");
1082
+ return value && REQUEST_ID_RE.test(value) ? value : undefined;
1083
+ }
1084
+ function parseRetryAfter(value, now) {
1085
+ if (!value)
1086
+ return;
1087
+ const trimmed = value.trim();
1088
+ if (/^\d+(\.\d+)?$/.test(trimmed))
1089
+ return Math.round(Number(trimmed) * 1000);
1090
+ const at = Date.parse(trimmed);
1091
+ return Number.isNaN(at) ? undefined : Math.max(0, at - now);
1092
+ }
1093
+ function readErrorCode(text) {
1094
+ if (text.length === 0 || text.length > MAX_ERROR_BODY_CHARS)
1095
+ return;
1096
+ let parsed;
1097
+ try {
1098
+ parsed = JSON.parse(text);
1099
+ } catch {
1100
+ return;
1101
+ }
1102
+ if (!isRecord(parsed))
1103
+ return;
1104
+ const nested = isRecord(parsed.error) ? parsed.error : undefined;
1105
+ const detail = isRecord(parsed.detail) ? parsed.detail : undefined;
1106
+ for (const candidate of [
1107
+ nested?.code,
1108
+ nested?.type,
1109
+ detail?.error_type,
1110
+ parsed.code,
1111
+ parsed.type
1112
+ ]) {
1113
+ if (typeof candidate === "string" && ERROR_CODE_RE2.test(candidate))
1114
+ return candidate;
1115
+ }
1116
+ return cloudflareErrorCode(parsed);
1117
+ }
1118
+ function defaultSleep(ms, signal) {
1119
+ return new Promise((resolve) => {
1120
+ if (signal.aborted)
1121
+ return resolve();
1122
+ const timer = setTimeout(done, ms);
1123
+ function done() {
1124
+ clearTimeout(timer);
1125
+ signal.removeEventListener("abort", done);
1126
+ resolve();
1127
+ }
1128
+ signal.addEventListener("abort", done, { once: true });
1129
+ });
1130
+ }
1131
+ async function sendSystemOneRequest(request, options = {}) {
1132
+ const { timeoutMs, maxRetries } = request;
1133
+ const doFetch = options.fetch ?? fetch;
1134
+ const sleep = options.sleep ?? defaultSleep;
1135
+ const now = options.now ?? Date.now;
1136
+ const random = options.random ?? Math.random;
1137
+ const deadlineAt = now() + timeoutMs - DEADLINE_MARGIN_MS;
1138
+ const controller = new AbortController;
1139
+ const timer = setTimeout(() => controller.abort(), Math.max(0, deadlineAt - now()));
1140
+ const timedOut = () => ({
1141
+ ok: false,
1142
+ error: `SystemOne request exceeded its ${timeoutMs}ms time budget`
1143
+ });
1144
+ try {
1145
+ for (let attempt = 0;; attempt++) {
1146
+ const result = await attemptOnce(doFetch, request, controller.signal, now);
1147
+ if (result.kind === "ok") {
1148
+ return { ok: true, text: result.text, requestId: result.requestId };
1149
+ }
1150
+ if (result.kind === "timeout" || controller.signal.aborted)
1151
+ return timedOut();
1152
+ const attempts = attempt + 1;
1153
+ const suffix = attempts > 1 ? ` after ${attempts} attempts` : "";
1154
+ if (!result.retryable || attempt >= maxRetries) {
1155
+ return { ok: false, error: `${result.message}${suffix}` };
1156
+ }
1157
+ const backoff = Math.min(BACKOFF_CAP_MS, BACKOFF_BASE_MS * 2 ** attempt);
1158
+ const delay = result.retryAfterMs ?? Math.round(backoff * (0.5 + random() * 0.5));
1159
+ if (delay >= deadlineAt - now()) {
1160
+ return {
1161
+ ok: false,
1162
+ error: `${result.message}${suffix}; the ${delay}ms retry delay does not fit the ${timeoutMs}ms time budget`
1163
+ };
1164
+ }
1165
+ await sleep(delay, controller.signal);
1166
+ if (controller.signal.aborted)
1167
+ return timedOut();
1168
+ }
1169
+ } finally {
1170
+ clearTimeout(timer);
1171
+ }
1172
+ }
1173
+ async function attemptOnce(doFetch, request, signal, now) {
1174
+ const { endpoint, apiKey, body } = request;
1175
+ let res;
1176
+ try {
1177
+ res = await doFetch(endpoint, {
1178
+ method: "POST",
1179
+ headers: {
1180
+ ...getOpenRouterAttributionHeaders(endpoint, request.env),
1181
+ "content-type": "application/json",
1182
+ accept: "application/json",
1183
+ authorization: `Bearer ${apiKey}`
1184
+ },
1185
+ body,
1186
+ redirect: "manual",
1187
+ signal
1188
+ });
1189
+ } catch (err) {
1190
+ if (signal.aborted)
1191
+ return { kind: "timeout" };
1192
+ const code = err?.code;
1193
+ const detail = typeof code === "string" && /^[A-Z0-9_]{3,40}$/.test(code) ? ` (${code})` : "";
1194
+ return {
1195
+ kind: "fail",
1196
+ message: `SystemOne request failed: network error${detail}`,
1197
+ retryable: true
1198
+ };
1199
+ }
1200
+ const requestId = readRequestId(res.headers);
1201
+ const withId = (message) => requestId ? `${message} [request ${requestId}]` : message;
1202
+ if (res.status >= 200 && res.status < 300) {
1203
+ let text;
1204
+ try {
1205
+ text = await res.text();
1206
+ } catch {
1207
+ if (signal.aborted)
1208
+ return { kind: "timeout" };
1209
+ return {
1210
+ kind: "fail",
1211
+ message: withId("SystemOne response body could not be read"),
1212
+ retryable: true
1213
+ };
1214
+ }
1215
+ if (text.length > MAX_RESPONSE_CHARS) {
1216
+ return {
1217
+ kind: "fail",
1218
+ message: withId("SystemOne response exceeded the size limit"),
1219
+ retryable: false
1220
+ };
1221
+ }
1222
+ return { kind: "ok", text, requestId };
1223
+ }
1224
+ if (res.status >= 300 && res.status < 400) {
1225
+ return {
1226
+ kind: "fail",
1227
+ message: withId(`SystemOne request was redirected (HTTP ${res.status}); redirects are refused`),
1228
+ retryable: false
1229
+ };
1230
+ }
1231
+ let errorText = "";
1232
+ try {
1233
+ errorText = await res.text();
1234
+ } catch {
1235
+ if (signal.aborted)
1236
+ return { kind: "timeout" };
1237
+ }
1238
+ const code = readErrorCode(errorText);
1239
+ if (res.status === 401 || res.status === 403) {
1240
+ return {
1241
+ kind: "fail",
1242
+ message: withId(systemOneKeyRejectedMessage(request.providerId, res.status, code, {
1243
+ keyName: request.keyName,
1244
+ capability: request.capability
1245
+ })),
1246
+ retryable: false
1247
+ };
1248
+ }
1249
+ const detail = code ? ` (${code})` : "";
1250
+ const retryable = res.status === 408 || res.status === 429 || res.status >= 500;
1251
+ return {
1252
+ kind: "fail",
1253
+ message: withId(`SystemOne API returned HTTP ${res.status}${detail}`),
1254
+ retryable,
1255
+ retryAfterMs: retryable ? parseRetryAfter(res.headers.get("retry-after"), now()) : undefined
1256
+ };
1257
+ }
1258
+
1259
+ // src/workflows/executors/system-one-extraction.ts
1260
+ var SYSTEM_ONE_EXTRACTION_NODE_TYPE = "system-one-extraction";
1261
+ var EXTRACTION_FIELD_TYPES = [
1262
+ "text",
1263
+ "number",
1264
+ "date",
1265
+ "time",
1266
+ "datetime",
1267
+ "duration",
1268
+ "list",
1269
+ "estimate",
1270
+ "forecast"
1271
+ ];
1272
+ var DURATION_UNITS = ["seconds", "minutes", "hours", "days", "weeks", "months", "years"];
1273
+ var LIST_ITEMS = ["text", "number", "date"];
1274
+ var FORECAST_INTERVALS = [
1275
+ "step",
1276
+ "minute",
1277
+ "hour",
1278
+ "day",
1279
+ "week",
1280
+ "month",
1281
+ "quarter",
1282
+ "year"
1283
+ ];
1284
+ var FIELD_ID_RE = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
1285
+ var FIELD_ID_MESSAGE = "field ids must start with a letter or underscore, use only letters, digits, _ or -, and be at most 64 characters";
1286
+ var RESERVED_FIELD_IDS = new Set([...Object.getOwnPropertyNames(Object.prototype), "prototype"]);
1287
+ var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
1288
+ var MAX_IMAGES = 4;
1289
+ var MAX_IMAGE_BYTES = 4 * 1024 * 1024;
1290
+ var MAX_IMAGES_TOTAL_BYTES = 8 * 1024 * 1024;
1291
+ var IMAGE_TYPES = ["image/png", "image/jpeg", "image/webp"];
1292
+ var DATA_URL_RE = /^data:(image\/(?:png|jpeg|webp));base64,([A-Za-z0-9+/=\s]+)$/i;
1293
+ var Instructions = string2().trim().min(1, "instructions must not be blank");
1294
+ var NullThreshold = number2().gt(0, "null_threshold must be above 0").max(1, "null_threshold must be at most 1");
1295
+ var Decimals = number2().int().min(0).max(6);
1296
+ var Unit = string2().trim().min(1, "unit must not be blank");
1297
+ var Scale = _enum2(["linear", "log"]);
1298
+ var Bound = union2([
1299
+ number2(),
1300
+ string2().regex(ISO_DATE_RE, "must be a number or an ISO date (YYYY-MM-DD)")
1301
+ ]);
1302
+ var plain = { instructions: Instructions.optional(), null_threshold: NullThreshold.optional() };
1303
+ var TextField = strictObject2({ type: literal2("text"), ...plain });
1304
+ var NumberField = strictObject2({
1305
+ type: literal2("number"),
1306
+ ...plain,
1307
+ unit: Unit.optional(),
1308
+ integer: boolean().optional(),
1309
+ decimals: Decimals.optional()
1310
+ });
1311
+ var DateField = strictObject2({ type: literal2("date"), ...plain });
1312
+ var TimeField = strictObject2({ type: literal2("time"), ...plain });
1313
+ var DatetimeField = strictObject2({ type: literal2("datetime"), ...plain });
1314
+ var DurationField = strictObject2({
1315
+ type: literal2("duration"),
1316
+ ...plain,
1317
+ unit: _enum2(DURATION_UNITS).optional(),
1318
+ integer: boolean().optional(),
1319
+ decimals: Decimals.optional()
1320
+ });
1321
+ var ListField = strictObject2({
1322
+ type: literal2("list"),
1323
+ instructions: Instructions.optional(),
1324
+ items: _enum2(LIST_ITEMS),
1325
+ decimals: Decimals.optional()
1326
+ });
1327
+ var EstimateField = strictObject2({
1328
+ type: literal2("estimate"),
1329
+ instructions: Instructions.optional(),
1330
+ unit: Unit.optional(),
1331
+ min: Bound,
1332
+ max: Bound,
1333
+ step: union2([number2().positive(), _enum2(["month", "year"])]).optional(),
1334
+ scale: Scale.optional()
1335
+ });
1336
+ var ForecastField = strictObject2({
1337
+ type: literal2("forecast"),
1338
+ instructions: Instructions.optional(),
1339
+ unit: Unit.optional(),
1340
+ min: number2(),
1341
+ max: number2(),
1342
+ horizon: number2().int().min(1).max(64).optional(),
1343
+ interval: _enum2(FORECAST_INTERVALS).optional(),
1344
+ scale: Scale.optional()
1345
+ });
1346
+ var SystemOneExtractionFieldSchema = discriminatedUnion("type", [
1347
+ TextField,
1348
+ NumberField,
1349
+ DateField,
1350
+ TimeField,
1351
+ DatetimeField,
1352
+ DurationField,
1353
+ ListField,
1354
+ EstimateField,
1355
+ ForecastField
1356
+ ]).superRefine((field, ctx) => {
1357
+ if ("integer" in field && field.integer && field.decimals !== undefined && field.decimals > 0) {
1358
+ ctx.addIssue({
1359
+ code: "custom",
1360
+ path: ["decimals"],
1361
+ message: "an integer field cannot also set decimals above 0"
1362
+ });
1363
+ }
1364
+ if (field.type === "list" && field.decimals !== undefined && field.items !== "number") {
1365
+ ctx.addIssue({
1366
+ code: "custom",
1367
+ path: ["decimals"],
1368
+ message: 'decimals applies only to a list of items "number"'
1369
+ });
1370
+ }
1371
+ if (field.type === "estimate" || field.type === "forecast") {
1372
+ const { min, max } = field;
1373
+ if (typeof min !== typeof max) {
1374
+ ctx.addIssue({
1375
+ code: "custom",
1376
+ path: ["max"],
1377
+ message: "min and max must both be numbers or both be ISO dates"
1378
+ });
1379
+ } else if (min >= max) {
1380
+ ctx.addIssue({ code: "custom", path: ["max"], message: "max must be greater than min" });
1381
+ }
1382
+ if (field.scale === "log" && typeof min === "number" && min <= 0) {
1383
+ ctx.addIssue({
1384
+ code: "custom",
1385
+ path: ["min"],
1386
+ message: 'a "log" scale needs min above 0'
1387
+ });
1388
+ }
1389
+ if (field.type === "estimate" && typeof field.step === "string" && typeof field.min === "number") {
1390
+ ctx.addIssue({
1391
+ code: "custom",
1392
+ path: ["step"],
1393
+ message: `step "${field.step}" applies only to a date estimate (ISO date min and max)`
1394
+ });
1395
+ }
1396
+ }
1397
+ });
1398
+ var ImageSchema = union2([
1399
+ string2().regex(DATA_URL_RE, "an image must be a base64 data: URL of a PNG, JPEG or WebP"),
1400
+ strictObject2({ content_type: _enum2(IMAGE_TYPES), base64: string2().min(1) })
1401
+ ]);
1402
+ function imageBytes(image) {
1403
+ const base64 = (typeof image === "string" ? DATA_URL_RE.exec(image)?.[2] ?? "" : image.base64).replace(/\s+/g, "");
1404
+ const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
1405
+ return Math.floor(base64.length * 3 / 4) - padding;
1406
+ }
1407
+ var SystemOneExtractionConfigSchema = strictObject2({
1408
+ provider: _enum2(SYSTEM_ONE_EXTRACT_PROVIDER_IDS).default(SYSTEM_ONE_EXTRACT_DEFAULT_PROVIDER),
1409
+ model: string2().min(1).optional(),
1410
+ state: union2([string2(), array2(unknown()), record2(string2(), unknown())]).default(""),
1411
+ images: array2(ImageSchema).max(MAX_IMAGES).optional(),
1412
+ referenceDate: string2().regex(ISO_DATE_RE, "referenceDate must be an ISO date (YYYY-MM-DD)").optional(),
1413
+ timeoutMs: number2().int().min(SYSTEM_ONE_MIN_TIMEOUT_MS).max(SYSTEM_ONE_MAX_TIMEOUT_MS).default(SYSTEM_ONE_DEFAULT_TIMEOUT_MS),
1414
+ maxRetries: number2().int().min(0).max(SYSTEM_ONE_MAX_RETRIES_LIMIT).default(SYSTEM_ONE_DEFAULT_MAX_RETRIES),
1415
+ fields: record2(string2().regex(FIELD_ID_RE, FIELD_ID_MESSAGE), SystemOneExtractionFieldSchema)
1416
+ }).superRefine((config, ctx) => {
1417
+ const fieldIds = Object.keys(config.fields);
1418
+ if (fieldIds.length === 0) {
1419
+ ctx.addIssue({
1420
+ code: "custom",
1421
+ path: ["fields"],
1422
+ message: "system-one-extraction needs at least one field"
1423
+ });
1424
+ }
1425
+ for (const id of fieldIds) {
1426
+ if (RESERVED_FIELD_IDS.has(id)) {
1427
+ ctx.addIssue({
1428
+ code: "custom",
1429
+ path: ["fields", id],
1430
+ message: `"${id}" is reserved and cannot be a field id`
1431
+ });
1432
+ }
1433
+ }
1434
+ const stateText = typeof config.state === "string" ? config.state : "nonempty";
1435
+ if (stateText.trim() === "" && !config.images?.length) {
1436
+ ctx.addIssue({
1437
+ code: "custom",
1438
+ path: ["state"],
1439
+ message: "state must not be empty unless images are sent"
1440
+ });
1441
+ }
1442
+ const images = config.images ?? [];
1443
+ let total = 0;
1444
+ images.forEach((image, index) => {
1445
+ const bytes = imageBytes(image);
1446
+ total += bytes;
1447
+ if (bytes > MAX_IMAGE_BYTES) {
1448
+ ctx.addIssue({
1449
+ code: "custom",
1450
+ path: ["images", index],
1451
+ message: "an image must be at most 4 MiB decoded"
1452
+ });
1453
+ }
1454
+ });
1455
+ if (total > MAX_IMAGES_TOTAL_BYTES) {
1456
+ ctx.addIssue({
1457
+ code: "custom",
1458
+ path: ["images"],
1459
+ message: "images must total at most 8 MiB decoded"
1460
+ });
1461
+ }
1462
+ const host = SYSTEM_ONE_HOSTS[config.provider];
1463
+ const service = host.extract;
1464
+ const modelProblem = systemOneModelProblem(config.provider, config.model ?? service?.defaultModel, "extract");
1465
+ if (modelProblem)
1466
+ ctx.addIssue({ code: "custom", path: ["model"], message: modelProblem });
1467
+ const maxFields = service?.limits?.maxFields;
1468
+ if (maxFields !== undefined && fieldIds.length > maxFields) {
1469
+ ctx.addIssue({
1470
+ code: "custom",
1471
+ path: ["fields"],
1472
+ message: `SystemOne provider "${config.provider}" takes at most ${maxFields} fields per node; this node has ${fieldIds.length}.`
1473
+ });
1474
+ }
1475
+ const maxImages = service?.limits?.maxImages;
1476
+ if (maxImages !== undefined && images.length > maxImages) {
1477
+ ctx.addIssue({
1478
+ code: "custom",
1479
+ path: ["images"],
1480
+ message: `SystemOne provider "${config.provider}" takes at most ${maxImages} images per node.`
1481
+ });
1482
+ }
1483
+ });
1484
+ var UnitInterval = number2().min(0).max(1);
1485
+ var Span = tuple([number2().int().min(0), number2().int().min(0)]);
1486
+ var Point = union2([number2(), string2()]);
1487
+ var SystemOneExtractionAnswerSchema = strictObject2({
1488
+ type: _enum2(EXTRACTION_FIELD_TYPES),
1489
+ value: unknown(),
1490
+ guess: unknown().optional(),
1491
+ p_null: UnitInterval.optional(),
1492
+ confidence: UnitInterval.optional(),
1493
+ span: Span.optional(),
1494
+ spans: array2(Span).optional(),
1495
+ unit: string2().optional(),
1496
+ mean: union2([Point, array2(Point)]).optional(),
1497
+ median: union2([Point, array2(Point)]).optional(),
1498
+ p10: union2([Point, array2(Point)]).optional(),
1499
+ p90: union2([Point, array2(Point)]).optional(),
1500
+ p_below: UnitInterval.optional(),
1501
+ p_above: UnitInterval.optional()
1502
+ });
1503
+ var SystemOneExtractionOutputSchema = object2({
1504
+ model: string2().min(1),
1505
+ answers: record2(string2(), SystemOneExtractionAnswerSchema),
1506
+ usage: object2({
1507
+ input_tokens: number2().int().min(0),
1508
+ output_tokens: number2().int().min(0)
1509
+ }),
1510
+ truncated: boolean().optional(),
1511
+ requestId: string2().optional()
1512
+ });
1513
+ function hasToken(value) {
1514
+ if (typeof value === "string")
1515
+ return value.includes("{{") && value.includes("}}");
1516
+ if (Array.isArray(value))
1517
+ return value.some(hasToken);
1518
+ if (isRecord(value))
1519
+ return Object.values(value).some(hasToken);
1520
+ return false;
1521
+ }
1522
+ function templatedFieldViolations(nodeId, fields) {
1523
+ const violations = [];
1524
+ for (const [id, spec] of Object.entries(fields)) {
1525
+ if (!hasToken(spec))
1526
+ continue;
1527
+ const at = `Node "${nodeId}" (system-one-extraction) config.fields.${id}`;
1528
+ if (!FIELD_ID_RE.test(id)) {
1529
+ violations.push(`${at}: ${FIELD_ID_MESSAGE}`);
1530
+ } else if (RESERVED_FIELD_IDS.has(id)) {
1531
+ violations.push(`${at}: "${id}" is reserved and cannot be a field id`);
1532
+ }
1533
+ if (!isRecord(spec)) {
1534
+ violations.push(`${at} must be an object: field types and options are static, only instructions may use {{tokens}}`);
1535
+ continue;
1536
+ }
1537
+ const tokened = Object.keys(spec).filter((key) => key !== "instructions" && hasToken(spec[key]));
1538
+ for (const key of tokened) {
1539
+ violations.push(`${at}.${key} must be a literal: only instructions may use {{tokens}}`);
1540
+ }
1541
+ if (tokened.length > 0)
1542
+ continue;
1543
+ if (typeof spec.instructions !== "string") {
1544
+ violations.push(`${at}.instructions must be a string to use {{tokens}}`);
1545
+ continue;
1546
+ }
1547
+ const parsed = SystemOneExtractionFieldSchema.safeParse({ ...spec, instructions: "x" });
1548
+ if (parsed.success)
1549
+ continue;
1550
+ for (const issue of parsed.error.issues) {
1551
+ const path = issue.path.length > 0 ? `${at}.${issue.path.map(String).join(".")}` : at;
1552
+ violations.push(`${path}: ${issue.message}`);
1553
+ }
1554
+ }
1555
+ return violations;
1556
+ }
1557
+ var STAND_IN_IMAGE = "data:image/png;base64,AAAA";
1558
+ function standInConfig(config) {
1559
+ const known = new Set(Object.keys(SystemOneExtractionConfigSchema.shape));
1560
+ const out = {};
1561
+ for (const [key, value] of Object.entries(config)) {
1562
+ if (!hasToken(value) || key === "fields" || !known.has(key)) {
1563
+ out[key] = value;
1564
+ } else if (key === "state") {
1565
+ out.state = typeof value === "string" ? "x" : value;
1566
+ } else if (key === "images" && Array.isArray(value)) {
1567
+ out.images = value.map((image) => hasToken(image) ? STAND_IN_IMAGE : image);
1568
+ }
1569
+ }
1570
+ if (isRecord(config.fields)) {
1571
+ out.fields = Object.fromEntries(Object.entries(config.fields).map(([id, spec]) => [
1572
+ id,
1573
+ isRecord(spec) && hasToken(spec) ? { ...spec, instructions: "x" } : spec
1574
+ ]));
1575
+ }
1576
+ return out;
1577
+ }
1578
+ function templatedConfigViolations(nodeId, config) {
1579
+ if (!hasToken(config))
1580
+ return [];
1581
+ const parsed = SystemOneExtractionConfigSchema.safeParse(standInConfig(config));
1582
+ if (parsed.success)
1583
+ return [];
1584
+ const violations = [];
1585
+ for (const issue of parsed.error.issues) {
1586
+ let value = config;
1587
+ for (const segment of issue.path) {
1588
+ value = isRecord(value) || Array.isArray(value) ? value[segment] : undefined;
1589
+ }
1590
+ if (!hasToken(value) || typeof value === "string")
1591
+ continue;
1592
+ const path = ["config", ...issue.path.map(String)].join(".");
1593
+ violations.push(`Node "${nodeId}" (system-one-extraction) ${path}: ${issue.message}`);
1594
+ }
1595
+ return violations;
1596
+ }
1597
+ function systemOneExtractionStaticShapeViolations(node) {
1598
+ if (node.type !== SYSTEM_ONE_EXTRACTION_NODE_TYPE)
1599
+ return [];
1600
+ const violations = [];
1601
+ const fields = node.config.fields;
1602
+ if (fields !== undefined && (typeof fields !== "object" || fields === null || Array.isArray(fields))) {
1603
+ violations.push(`Node "${node.id}" (system-one-extraction) config.fields must be an object: field ids and types are static, only state, images, and instructions may use {{tokens}}`);
1604
+ } else if (fields !== undefined) {
1605
+ violations.push(...templatedFieldViolations(node.id, fields));
1606
+ }
1607
+ const provider = node.config.provider;
1608
+ if (typeof provider === "string" && provider.includes("{{")) {
1609
+ violations.push(`Node "${node.id}" (system-one-extraction) config.provider must be a literal (${SYSTEM_ONE_EXTRACT_PROVIDER_IDS.join(", ")}), not a {{token}}: the provider decides which credential is checked before the run starts`);
1610
+ }
1611
+ if (violations.length === 0) {
1612
+ violations.push(...templatedConfigViolations(node.id, node.config));
1613
+ }
1614
+ return violations;
1615
+ }
1616
+
1617
+ class ExtractionContractError extends Error {
1618
+ }
1619
+ function fail(message) {
1620
+ throw new ExtractionContractError(message);
1621
+ }
1622
+ function unitOrUndefined(value, what) {
1623
+ if (value === undefined)
1624
+ return;
1625
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
1626
+ fail(`${what} must be a finite number between 0 and 1`);
1627
+ }
1628
+ return value;
1629
+ }
1630
+ function isFiniteNumber(value) {
1631
+ return typeof value === "number" && Number.isFinite(value);
1632
+ }
1633
+ function isSpan(value) {
1634
+ return Array.isArray(value) && value.length === 2 && value.every((n) => Number.isInteger(n) && n >= 0) && value[0] <= value[1];
1635
+ }
1636
+ function isPoint(value, dates) {
1637
+ return dates ? typeof value === "string" : isFiniteNumber(value);
1638
+ }
1639
+ function checkScalar(at, field, value) {
1640
+ if (value === null)
1641
+ return;
1642
+ switch (field.type) {
1643
+ case "text":
1644
+ case "date":
1645
+ case "time":
1646
+ case "datetime":
1647
+ if (typeof value !== "string")
1648
+ fail(`${at}.value must be a string or null`);
1649
+ return;
1650
+ case "number":
1651
+ case "duration":
1652
+ if (!isFiniteNumber(value))
1653
+ fail(`${at}.value must be a finite number or null`);
1654
+ if (field.integer && !Number.isInteger(value))
1655
+ fail(`${at}.value must be an integer`);
1656
+ return;
1657
+ default:
1658
+ return;
1659
+ }
1660
+ }
1661
+ function validateAnswer(id, field, raw) {
1662
+ const at = `answer "${id}"`;
1663
+ if (!isRecord(raw))
1664
+ fail(`${at} must be an object`);
1665
+ if (raw.type !== field.type) {
1666
+ fail(`${at} has type ${JSON.stringify(raw.type)} but the field declares "${field.type}"`);
1667
+ }
1668
+ if (!Object.hasOwn(raw, "value"))
1669
+ fail(`${at} is missing its value`);
1670
+ const answer = { type: field.type, value: raw.value };
1671
+ switch (field.type) {
1672
+ case "list": {
1673
+ if (!Array.isArray(raw.value))
1674
+ fail(`${at}.value must be a list`);
1675
+ const ok = raw.value.every((item) => field.items === "number" ? isFiniteNumber(item) : typeof item === "string");
1676
+ if (!ok)
1677
+ fail(`${at}.value items must all be ${field.items === "number" ? "numbers" : "strings"}`);
1678
+ if (raw.spans !== undefined) {
1679
+ if (!Array.isArray(raw.spans) || !raw.spans.every(isSpan)) {
1680
+ fail(`${at}.spans must be a list of [start, end] offsets`);
1681
+ }
1682
+ answer.spans = raw.spans;
1683
+ }
1684
+ break;
1685
+ }
1686
+ case "estimate": {
1687
+ const dates = typeof field.min === "string";
1688
+ if (raw.value !== null && !isPoint(raw.value, dates)) {
1689
+ fail(`${at}.value must be ${dates ? "an ISO date" : "a finite number"} or null`);
1690
+ }
1691
+ for (const key of ["median", "p10", "p90"]) {
1692
+ if (!isPoint(raw[key], dates))
1693
+ fail(`${at}.${key} must be ${dates ? "an ISO date" : "a finite number"}`);
1694
+ answer[key] = raw[key];
1695
+ }
1696
+ if (raw.mean !== undefined && isPoint(raw.mean, dates))
1697
+ answer.mean = raw.mean;
1698
+ break;
1699
+ }
1700
+ case "forecast": {
1701
+ const steps = (value) => Array.isArray(value) && value.every(isFiniteNumber);
1702
+ if (!steps(raw.value))
1703
+ fail(`${at}.value must be a list of numbers, one per step`);
1704
+ const length = raw.value.length;
1705
+ if (field.horizon !== undefined && length !== field.horizon) {
1706
+ fail(`${at}.value must have ${field.horizon} step(s), got ${length}`);
1707
+ }
1708
+ for (const key of ["median", "p10", "p90"]) {
1709
+ if (!steps(raw[key]) || raw[key].length !== length) {
1710
+ fail(`${at}.${key} must be a list of ${length} number(s)`);
1711
+ }
1712
+ answer[key] = raw[key];
1713
+ }
1714
+ break;
1715
+ }
1716
+ default:
1717
+ checkScalar(at, field, raw.value);
1718
+ if (raw.span !== undefined) {
1719
+ if (!isSpan(raw.span))
1720
+ fail(`${at}.span must be [start, end] offsets`);
1721
+ answer.span = raw.span;
1722
+ }
1723
+ if (raw.guess !== undefined && raw.guess !== null) {
1724
+ checkScalar(`${at}.guess`, field, raw.guess);
1725
+ answer.guess = raw.guess;
1726
+ }
1727
+ }
1728
+ const pNull = unitOrUndefined(raw.p_null, `${at}.p_null`);
1729
+ if (pNull !== undefined)
1730
+ answer.p_null = pNull;
1731
+ const confidence = unitOrUndefined(raw.confidence, `${at}.confidence`);
1732
+ if (confidence !== undefined)
1733
+ answer.confidence = confidence;
1734
+ if (field.type === "estimate") {
1735
+ const below = unitOrUndefined(raw.p_below, `${at}.p_below`);
1736
+ const above = unitOrUndefined(raw.p_above, `${at}.p_above`);
1737
+ if (below !== undefined)
1738
+ answer.p_below = below;
1739
+ if (above !== undefined)
1740
+ answer.p_above = above;
1741
+ }
1742
+ if (typeof raw.unit === "string" && raw.unit !== "")
1743
+ answer.unit = raw.unit;
1744
+ return answer;
1745
+ }
1746
+ function validateSystemOneExtraction(fields, body, requestId) {
1747
+ if (!isRecord(body))
1748
+ fail("response body must be a JSON object");
1749
+ if (typeof body.model !== "string" || body.model === "") {
1750
+ fail("response is missing the model that produced the answers");
1751
+ }
1752
+ if (!isRecord(body.answers))
1753
+ fail("response is missing the answers object");
1754
+ const usage = object2({ input_tokens: number2().int().min(0), output_tokens: number2().int().min(0) }).safeParse(body.usage);
1755
+ if (!usage.success) {
1756
+ fail("response usage must report non-negative integer input_tokens and output_tokens");
1757
+ }
1758
+ const declared = Object.keys(fields);
1759
+ if (declared.length === 0)
1760
+ fail("a system-one-extraction call needs at least one field");
1761
+ const unexpected = Object.keys(body.answers).filter((id) => !Object.hasOwn(fields, id));
1762
+ if (unexpected.length > 0) {
1763
+ fail(`response contains ${unexpected.length} answer(s) for undeclared fields`);
1764
+ }
1765
+ const answers = {};
1766
+ for (const id of declared) {
1767
+ if (!Object.hasOwn(body.answers, id))
1768
+ fail(`response is missing the answer for field "${id}"`);
1769
+ const field = fields[id];
1770
+ if (!field)
1771
+ fail(`field "${id}" is not defined`);
1772
+ answers[id] = validateAnswer(id, field, body.answers[id]);
1773
+ }
1774
+ return {
1775
+ model: body.model,
1776
+ answers,
1777
+ usage: usage.data,
1778
+ ...typeof body.truncated === "boolean" ? { truncated: body.truncated } : {},
1779
+ ...requestId ? { requestId } : {}
1780
+ };
1781
+ }
1782
+
1783
+ class SystemOneExtractionExecutor extends BaseExecutor {
1784
+ options;
1785
+ type = SYSTEM_ONE_EXTRACTION_NODE_TYPE;
1786
+ mode = "instant";
1787
+ configSchema = SystemOneExtractionConfigSchema;
1788
+ outputSchema = SystemOneExtractionOutputSchema;
1789
+ constructor(deps, options = {}) {
1790
+ super(deps);
1791
+ this.options = options;
1792
+ }
1793
+ providerContext() {
1794
+ return { db: this.deps.db, env: this.options.env ?? process.env };
1795
+ }
1796
+ resolveTarget(id, model) {
1797
+ const { getApiKey, getServerUrl } = this.options;
1798
+ return resolveSystemOneTarget(id, this.providerContext(), {
1799
+ apiKey: getApiKey ? () => getApiKey(id) : undefined,
1800
+ serverUrl: getServerUrl ? () => getServerUrl(id) : undefined,
1801
+ model,
1802
+ capability: "extract"
1803
+ });
1804
+ }
1805
+ async checkReadiness(nodes) {
1806
+ const nodeIdsByProvider = new Map;
1807
+ for (const node of nodes) {
1808
+ const id = systemOneExtractProviderOf(node.config);
1809
+ if (id)
1810
+ nodeIdsByProvider.set(id, [...nodeIdsByProvider.get(id) ?? [], node.id]);
1811
+ }
1812
+ const problems = [];
1813
+ for (const [id, nodeIds] of nodeIdsByProvider) {
1814
+ const target = await this.resolveTarget(id);
1815
+ if (!target.ok)
1816
+ problems.push({ nodeIds, message: target.error });
1817
+ }
1818
+ return problems;
1819
+ }
1820
+ async execute(config, _context, _meta) {
1821
+ const extracted = await this.extract(config);
1822
+ return extracted.ok ? { status: "success", output: extracted.output } : { status: "failed", error: extracted.error };
1823
+ }
1824
+ async extract(config) {
1825
+ const providerId = config.provider;
1826
+ const service = SYSTEM_ONE_HOSTS[providerId].extract;
1827
+ if (!service) {
1828
+ return { ok: false, error: `SystemOne provider "${providerId}" does not serve extraction` };
1829
+ }
1830
+ const model = config.model ?? service.defaultModel;
1831
+ const modelProblem = systemOneModelProblem(providerId, model, "extract");
1832
+ if (modelProblem)
1833
+ return { ok: false, error: modelProblem };
1834
+ const target = await this.resolveTarget(providerId, model);
1835
+ if (!target.ok)
1836
+ return { ok: false, error: target.error };
1837
+ const { apiKey, endpoint, keyName } = target;
1838
+ const body = JSON.stringify(service.wire.buildRequest(config, model));
1839
+ const scrub = (message) => message.split(apiKey).join("[REDACTED]");
1840
+ const sent = await sendSystemOneRequest({
1841
+ providerId,
1842
+ capability: "extract",
1843
+ endpoint,
1844
+ apiKey,
1845
+ keyName,
1846
+ body,
1847
+ timeoutMs: config.timeoutMs,
1848
+ maxRetries: config.maxRetries,
1849
+ env: this.providerContext().env
1850
+ }, this.options);
1851
+ if (!sent.ok)
1852
+ return { ok: false, error: scrub(sent.error) };
1853
+ let parsed;
1854
+ try {
1855
+ parsed = JSON.parse(sent.text);
1856
+ } catch {
1857
+ return { ok: false, error: "SystemOne response was not valid JSON" };
1858
+ }
1859
+ try {
1860
+ return {
1861
+ ok: true,
1862
+ output: validateSystemOneExtraction(config.fields, service.wire.toExtraction(parsed), sent.requestId)
1863
+ };
1864
+ } catch (err) {
1865
+ const withId = (message) => sent.requestId ? `${message} [request ${sent.requestId}]` : message;
1866
+ if (err instanceof SystemOneWireError)
1867
+ return { ok: false, error: scrub(withId(err.message)) };
1868
+ if (err instanceof ExtractionContractError) {
1869
+ return { ok: false, error: scrub(withId(`${VALIDATION_PREFIX}${err.message}`)) };
1870
+ }
1871
+ throw err;
1872
+ }
1873
+ }
1874
+ }
1875
+
1876
+ // src/workflows/executors/system-one-decision.ts
1877
+ var SYSTEM_ONE_DECISION_NODE_TYPE = "system-one-decision";
1878
+ var SYSTEM_ONE_ENDPOINT = SYSTEM_ONE_PROVIDERS.typesafe.endpoint;
1879
+ var SYSTEM_ONE_API_KEY_CONFIG_KEY = SYSTEM_ONE_PROVIDERS.typesafe.keyName;
1880
+ var CHOICE_MIN_OPTIONS = 2;
1881
+ var CHOICE_MAX_OPTIONS = 255;
1882
+ var SCORE_MIN_LEVELS = 2;
1883
+ var SCORE_MAX_LEVELS = 10;
1884
+ var DISTRIBUTION_TOLERANCE = 0.02;
990
1885
  var REVIEW_PORTS = ["approved", "rejected", "timeout"];
991
1886
  var ENGINE_RETRY_DEFAULT = 3;
992
1887
  var SystemOneEntrySchema = union2([
@@ -1028,7 +1923,7 @@ var SystemOneQuestionSchema = discriminatedUnion("type", [
1028
1923
  ChoiceQuestionSchema,
1029
1924
  ScoreQuestionSchema
1030
1925
  ]);
1031
- var Unit = number2().min(0).max(1);
1926
+ var Unit2 = number2().min(0).max(1);
1032
1927
  var QUESTION_ID_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
1033
1928
  var RESERVED_QUESTION_IDS = new Set([
1034
1929
  ...Object.getOwnPropertyNames(Object.prototype),
@@ -1036,7 +1931,7 @@ var RESERVED_QUESTION_IDS = new Set([
1036
1931
  ]);
1037
1932
  var SystemOnePrimitiveTypeSchema = _enum2(["noul", "choice", "score"]);
1038
1933
  var HumanReviewSchema = strictObject2({
1039
- band: strictObject2({ min: Unit, max: Unit }).refine((band) => band.min <= band.max, { message: "band.min must not exceed band.max" }),
1934
+ band: strictObject2({ min: Unit2, max: Unit2 }).refine((band) => band.min <= band.max, { message: "band.min must not exceed band.max" }),
1040
1935
  approvers: ApproverConfigSchema,
1041
1936
  title: string2().min(1).optional(),
1042
1937
  timeout: HITLTimeoutSchema.optional(),
@@ -1099,19 +1994,19 @@ var SystemOneDecisionConfigSchema = strictObject2({
1099
1994
  if (limitProblem)
1100
1995
  ctx.addIssue({ code: "custom", path: ["questions"], message: limitProblem });
1101
1996
  });
1102
- var NoulAnswerSchema = strictObject2({ type: literal2("noul"), noul: Unit });
1997
+ var NoulAnswerSchema = strictObject2({ type: literal2("noul"), noul: Unit2 });
1103
1998
  var ChoiceAnswerSchema = strictObject2({
1104
1999
  type: literal2("choice"),
1105
2000
  choice: string2(),
1106
- probabilities: record2(string2(), Unit),
1107
- confidence: Unit
2001
+ probabilities: record2(string2(), Unit2),
2002
+ confidence: Unit2
1108
2003
  });
1109
2004
  var ScoreAnswerSchema = strictObject2({
1110
2005
  type: literal2("score"),
1111
2006
  score: number2(),
1112
2007
  legend: record2(string2(), SystemOneEntrySchema),
1113
- probabilities: record2(string2(), Unit),
1114
- confidence: Unit
2008
+ probabilities: record2(string2(), Unit2),
2009
+ confidence: Unit2
1115
2010
  });
1116
2011
  var SystemOneAnswerSchema = discriminatedUnion("type", [
1117
2012
  NoulAnswerSchema,
@@ -1119,7 +2014,7 @@ var SystemOneAnswerSchema = discriminatedUnion("type", [
1119
2014
  ScoreAnswerSchema
1120
2015
  ]);
1121
2016
  var ReviewQuestionSchema = strictObject2({
1122
- confidence: Unit,
2017
+ confidence: Unit2,
1123
2018
  inBand: boolean(),
1124
2019
  decidedBy: _enum2(["model", "human"]),
1125
2020
  modelAnswer: union2([string2(), number2()]).optional()
@@ -1141,16 +2036,23 @@ var SystemOneDecisionOutputSchema = object2({
1141
2036
  routing: strictObject2({ model: string2().min(1) }).optional(),
1142
2037
  review: ReviewSchema.optional()
1143
2038
  });
2039
+ var SYSTEM_ONE_NODE_LABELS = {
2040
+ [SYSTEM_ONE_DECISION_NODE_TYPE]: "SystemOne Decision",
2041
+ [SYSTEM_ONE_EXTRACTION_NODE_TYPE]: "SystemOne Extraction"
2042
+ };
2043
+ function isSystemOneNodeType(type) {
2044
+ return Object.hasOwn(SYSTEM_ONE_NODE_LABELS, type);
2045
+ }
1144
2046
  function systemOneRetryViolations(node) {
1145
- if (node.type !== SYSTEM_ONE_DECISION_NODE_TYPE)
2047
+ if (!isSystemOneNodeType(node.type))
1146
2048
  return [];
1147
2049
  const violations = [];
1148
2050
  const attempts = (retry) => retry ? retry.maxRetries ?? ENGINE_RETRY_DEFAULT : 0;
1149
2051
  if (attempts(node.retry) > 0) {
1150
- violations.push(`Node "${node.id}" (system-one-decision) must not set retry.maxRetries > 0: the system-one-decision executor retries transient transport errors itself (config.maxRetries), and engine retries would re-send rejected requests`);
2052
+ violations.push(`Node "${node.id}" (${node.type}) must not set retry.maxRetries > 0: the ${node.type} executor retries transient transport errors itself (config.maxRetries), and engine retries would re-send rejected requests`);
1151
2053
  }
1152
2054
  if (attempts(node.validation?.retry) > 0) {
1153
- violations.push(`Node "${node.id}" (system-one-decision) must not set validation.retry.maxRetries > 0: a validation-driven retry would send another paid request`);
2055
+ violations.push(`Node "${node.id}" (${node.type}) must not set validation.retry.maxRetries > 0: a validation-driven retry would send another paid request`);
1154
2056
  }
1155
2057
  return violations;
1156
2058
  }
@@ -1184,14 +2086,15 @@ function systemOneStaticShapeViolations(node) {
1184
2086
  }
1185
2087
  return violations;
1186
2088
  }
1187
- function systemOneUnresolvedError(nodeId, tokens) {
2089
+ function systemOneUnresolvedError(nodeId, tokens, nodeType = SYSTEM_ONE_DECISION_NODE_TYPE) {
1188
2090
  const rendered = [...new Set(tokens)].map((token) => `{{${token}}}`).join(", ");
1189
- return `SystemOne Decision node "${nodeId}" has unresolved interpolation token(s): ${rendered}. ` + "No request was sent. Declare the value in the node's inputs mapping or fix the path.";
2091
+ const label = SYSTEM_ONE_NODE_LABELS[nodeType] ?? SYSTEM_ONE_NODE_LABELS[SYSTEM_ONE_DECISION_NODE_TYPE];
2092
+ return `${label} node "${nodeId}" has unresolved interpolation token(s): ${rendered}. ` + "No request was sent. Declare the value in the node's inputs mapping or fix the path.";
1190
2093
  }
1191
2094
 
1192
2095
  class SystemOneContractError extends Error {
1193
2096
  }
1194
- function fail(message) {
2097
+ function fail2(message) {
1195
2098
  throw new SystemOneContractError(message);
1196
2099
  }
1197
2100
  function isRecord2(value) {
@@ -1199,18 +2102,18 @@ function isRecord2(value) {
1199
2102
  }
1200
2103
  function unit(value, what) {
1201
2104
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
1202
- fail(`${what} must be a finite number between 0 and 1`);
2105
+ fail2(`${what} must be a finite number between 0 and 1`);
1203
2106
  }
1204
2107
  return value;
1205
2108
  }
1206
2109
  function distribution(value, keys, what) {
1207
2110
  if (!isRecord2(value))
1208
- fail(`${what} must be an object`);
2111
+ fail2(`${what} must be an object`);
1209
2112
  const actual = Object.keys(value);
1210
2113
  const missing = keys.filter((key) => !Object.hasOwn(value, key));
1211
2114
  const extra = actual.filter((key) => !keys.includes(key));
1212
2115
  if (missing.length > 0 || extra.length > 0) {
1213
- fail(`${what} keys must be exactly the declared ${keys.length} option(s)` + (missing.length > 0 ? `; missing ${missing.length}` : "") + (extra.length > 0 ? `; ${extra.length} unexpected` : ""));
2116
+ fail2(`${what} keys must be exactly the declared ${keys.length} option(s)` + (missing.length > 0 ? `; missing ${missing.length}` : "") + (extra.length > 0 ? `; ${extra.length} unexpected` : ""));
1214
2117
  }
1215
2118
  const out = {};
1216
2119
  let sum = 0;
@@ -1220,16 +2123,16 @@ function distribution(value, keys, what) {
1220
2123
  sum += p;
1221
2124
  }
1222
2125
  if (Math.abs(sum - 1) > DISTRIBUTION_TOLERANCE) {
1223
- fail(`${what} must sum to 1 (within ${DISTRIBUTION_TOLERANCE})`);
2126
+ fail2(`${what} must sum to 1 (within ${DISTRIBUTION_TOLERANCE})`);
1224
2127
  }
1225
2128
  return out;
1226
2129
  }
1227
- function validateAnswer(id, question, raw, confidenceField) {
2130
+ function validateAnswer2(id, question, raw, confidenceField) {
1228
2131
  const at = `answer "${id}"`;
1229
2132
  if (!isRecord2(raw))
1230
- fail(`${at} must be an object`);
2133
+ fail2(`${at} must be an object`);
1231
2134
  if (raw.type !== question.type) {
1232
- fail(`${at} has type ${JSON.stringify(raw.type)} but the question declares "${question.type}"`);
2135
+ fail2(`${at} has type ${JSON.stringify(raw.type)} but the question declares "${question.type}"`);
1233
2136
  }
1234
2137
  switch (question.type) {
1235
2138
  case "noul":
@@ -1237,7 +2140,7 @@ function validateAnswer(id, question, raw, confidenceField) {
1237
2140
  case "choice": {
1238
2141
  const options = Object.keys(question.criteria);
1239
2142
  if (typeof raw.choice !== "string" || !Object.hasOwn(question.criteria, raw.choice)) {
1240
- fail(`${at}.choice is not one of the declared options`);
2143
+ fail2(`${at}.choice is not one of the declared options`);
1241
2144
  }
1242
2145
  return {
1243
2146
  type: "choice",
@@ -1250,17 +2153,17 @@ function validateAnswer(id, question, raw, confidenceField) {
1250
2153
  const indices = question.criteria.map((_, index) => String(index));
1251
2154
  const top = indices.length - 1;
1252
2155
  if (typeof raw.score !== "number" || !Number.isFinite(raw.score) || raw.score < 0 || raw.score > top) {
1253
- fail(`${at}.score must be a finite number between 0 and ${top}`);
2156
+ fail2(`${at}.score must be a finite number between 0 and ${top}`);
1254
2157
  }
1255
2158
  if (!isRecord2(raw.legend))
1256
- fail(`${at}.legend must be an object`);
2159
+ fail2(`${at}.legend must be an object`);
1257
2160
  const legend = {};
1258
2161
  for (const key of indices) {
1259
2162
  if (!Object.hasOwn(raw.legend, key))
1260
- fail(`${at}.legend is missing level ${key}`);
2163
+ fail2(`${at}.legend is missing level ${key}`);
1261
2164
  const entry = SystemOneEntrySchema.safeParse(raw.legend[key]);
1262
2165
  if (!entry.success)
1263
- fail(`${at}.legend["${key}"] must be a string, object, array, or null`);
2166
+ fail2(`${at}.legend["${key}"] must be a string, object, array, or null`);
1264
2167
  legend[key] = entry.data;
1265
2168
  }
1266
2169
  return {
@@ -1282,30 +2185,30 @@ function readRoutingModel(routing) {
1282
2185
  }
1283
2186
  function validateSystemOneResponse(questions, body, requestId, confidenceField = "confidence") {
1284
2187
  if (!isRecord2(body))
1285
- fail("response body must be a JSON object");
2188
+ fail2("response body must be a JSON object");
1286
2189
  if (typeof body.model !== "string" || body.model === "") {
1287
- fail("response is missing the model that produced the answers");
2190
+ fail2("response is missing the model that produced the answers");
1288
2191
  }
1289
2192
  if (!isRecord2(body.answers))
1290
- fail("response is missing the answers object");
2193
+ fail2("response is missing the answers object");
1291
2194
  const usage = object2({ input_tokens: number2().int().min(0), output_tokens: number2().int().min(0) }).safeParse(body.usage);
1292
2195
  if (!usage.success)
1293
- fail("response usage must report non-negative integer input_tokens and output_tokens");
2196
+ fail2("response usage must report non-negative integer input_tokens and output_tokens");
1294
2197
  const declared = Object.keys(questions);
1295
2198
  const unexpected = Object.keys(body.answers).filter((id) => !Object.hasOwn(questions, id));
1296
2199
  if (unexpected.length > 0)
1297
- fail(`response contains ${unexpected.length} answer(s) for undeclared questions`);
2200
+ fail2(`response contains ${unexpected.length} answer(s) for undeclared questions`);
1298
2201
  const answers = {};
1299
2202
  for (const id of declared) {
1300
2203
  if (!Object.hasOwn(body.answers, id))
1301
- fail(`response is missing the answer for question "${id}"`);
2204
+ fail2(`response is missing the answer for question "${id}"`);
1302
2205
  const question = questions[id];
1303
2206
  if (!question)
1304
- fail(`question "${id}" is not defined`);
1305
- answers[id] = validateAnswer(id, question, body.answers[id], confidenceField);
2207
+ fail2(`question "${id}" is not defined`);
2208
+ answers[id] = validateAnswer2(id, question, body.answers[id], confidenceField);
1306
2209
  }
1307
2210
  if (declared.length === 0)
1308
- fail("a system-one-decision call needs at least one question");
2211
+ fail2("a system-one-decision call needs at least one question");
1309
2212
  const routingModel = readRoutingModel(body.routing);
1310
2213
  return {
1311
2214
  model: body.model,
@@ -1456,59 +2359,6 @@ function resolveReviewedDecision(parkedOutput, approval) {
1456
2359
  }
1457
2360
  return finish("approved", answers, questions);
1458
2361
  }
1459
- var REQUEST_ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;
1460
- var ERROR_CODE_RE2 = /^[A-Za-z0-9._:-]{1,64}$/;
1461
- function readRequestId(headers) {
1462
- const value = headers.get("x-request-id") ?? headers.get("request-id") ?? headers.get("cf-ray");
1463
- return value && REQUEST_ID_RE.test(value) ? value : undefined;
1464
- }
1465
- function parseRetryAfter(value, now) {
1466
- if (!value)
1467
- return;
1468
- const trimmed = value.trim();
1469
- if (/^\d+(\.\d+)?$/.test(trimmed))
1470
- return Math.round(Number(trimmed) * 1000);
1471
- const at = Date.parse(trimmed);
1472
- return Number.isNaN(at) ? undefined : Math.max(0, at - now);
1473
- }
1474
- function readErrorCode(text) {
1475
- if (text.length === 0 || text.length > MAX_ERROR_BODY_CHARS)
1476
- return;
1477
- let parsed;
1478
- try {
1479
- parsed = JSON.parse(text);
1480
- } catch {
1481
- return;
1482
- }
1483
- if (!isRecord2(parsed))
1484
- return;
1485
- const nested = isRecord2(parsed.error) ? parsed.error : undefined;
1486
- const detail = isRecord2(parsed.detail) ? parsed.detail : undefined;
1487
- for (const candidate of [
1488
- nested?.code,
1489
- nested?.type,
1490
- detail?.error_type,
1491
- parsed.code,
1492
- parsed.type
1493
- ]) {
1494
- if (typeof candidate === "string" && ERROR_CODE_RE2.test(candidate))
1495
- return candidate;
1496
- }
1497
- return cloudflareErrorCode(parsed);
1498
- }
1499
- function defaultSleep(ms, signal) {
1500
- return new Promise((resolve) => {
1501
- if (signal.aborted)
1502
- return resolve();
1503
- const timer = setTimeout(done, ms);
1504
- function done() {
1505
- clearTimeout(timer);
1506
- signal.removeEventListener("abort", done);
1507
- resolve();
1508
- }
1509
- signal.addEventListener("abort", done, { once: true });
1510
- });
1511
- }
1512
2362
 
1513
2363
  class SystemOneDecisionExecutor extends BaseExecutor {
1514
2364
  options;
@@ -1569,11 +2419,21 @@ class SystemOneDecisionExecutor extends BaseExecutor {
1569
2419
  const target = await this.resolveTarget(providerId, model);
1570
2420
  if (!target.ok)
1571
2421
  return { ok: false, error: target.error };
1572
- const { apiKey, endpoint } = target;
2422
+ const { apiKey, endpoint, keyName } = target;
1573
2423
  const wire = provider.wire ?? SYSTEM_ONE_WIRE;
1574
2424
  const body = JSON.stringify(wire.buildRequest(config, model));
1575
2425
  const scrub = (message) => message.split(apiKey).join("[REDACTED]");
1576
- const sent = await this.send(providerId, endpoint, apiKey, body, config.timeoutMs, config.maxRetries);
2426
+ const sent = await sendSystemOneRequest({
2427
+ providerId,
2428
+ capability: "decide",
2429
+ endpoint,
2430
+ apiKey,
2431
+ keyName,
2432
+ body,
2433
+ timeoutMs: config.timeoutMs,
2434
+ maxRetries: config.maxRetries,
2435
+ env: this.providerContext().env
2436
+ }, this.options);
1577
2437
  if (!sent.ok)
1578
2438
  return { ok: false, error: scrub(sent.error) };
1579
2439
  let parsed;
@@ -1663,128 +2523,6 @@ class SystemOneDecisionExecutor extends BaseExecutor {
1663
2523
  resolveApproval(parkedOutput, approval) {
1664
2524
  return resolveReviewedDecision(parkedOutput, approval);
1665
2525
  }
1666
- async send(providerId, endpoint, apiKey, body, timeoutMs, maxRetries) {
1667
- const doFetch = this.options.fetch ?? fetch;
1668
- const sleep = this.options.sleep ?? defaultSleep;
1669
- const now = this.options.now ?? Date.now;
1670
- const random = this.options.random ?? Math.random;
1671
- const deadlineAt = now() + timeoutMs - DEADLINE_MARGIN_MS;
1672
- const controller = new AbortController;
1673
- const timer = setTimeout(() => controller.abort(), Math.max(0, deadlineAt - now()));
1674
- const timedOut = () => ({
1675
- ok: false,
1676
- error: `SystemOne request exceeded its ${timeoutMs}ms time budget`
1677
- });
1678
- try {
1679
- for (let attempt = 0;; attempt++) {
1680
- const result = await this.attemptOnce(doFetch, providerId, endpoint, apiKey, body, controller.signal, now);
1681
- if (result.kind === "ok") {
1682
- return { ok: true, text: result.text, requestId: result.requestId };
1683
- }
1684
- if (result.kind === "timeout" || controller.signal.aborted)
1685
- return timedOut();
1686
- const attempts = attempt + 1;
1687
- const suffix = attempts > 1 ? ` after ${attempts} attempts` : "";
1688
- if (!result.retryable || attempt >= maxRetries) {
1689
- return { ok: false, error: `${result.message}${suffix}` };
1690
- }
1691
- const backoff = Math.min(BACKOFF_CAP_MS, BACKOFF_BASE_MS * 2 ** attempt);
1692
- const delay = result.retryAfterMs ?? Math.round(backoff * (0.5 + random() * 0.5));
1693
- if (delay >= deadlineAt - now()) {
1694
- return {
1695
- ok: false,
1696
- error: `${result.message}${suffix}; the ${delay}ms retry delay does not fit the ${timeoutMs}ms time budget`
1697
- };
1698
- }
1699
- await sleep(delay, controller.signal);
1700
- if (controller.signal.aborted)
1701
- return timedOut();
1702
- }
1703
- } finally {
1704
- clearTimeout(timer);
1705
- }
1706
- }
1707
- async attemptOnce(doFetch, providerId, endpoint, apiKey, body, signal, now) {
1708
- let res;
1709
- try {
1710
- res = await doFetch(endpoint, {
1711
- method: "POST",
1712
- headers: {
1713
- ...getOpenRouterAttributionHeaders(endpoint, this.providerContext().env),
1714
- "content-type": "application/json",
1715
- accept: "application/json",
1716
- authorization: `Bearer ${apiKey}`
1717
- },
1718
- body,
1719
- redirect: "manual",
1720
- signal
1721
- });
1722
- } catch (err) {
1723
- if (signal.aborted)
1724
- return { kind: "timeout" };
1725
- const code = err?.code;
1726
- const detail = typeof code === "string" && /^[A-Z0-9_]{3,40}$/.test(code) ? ` (${code})` : "";
1727
- return {
1728
- kind: "fail",
1729
- message: `SystemOne request failed: network error${detail}`,
1730
- retryable: true
1731
- };
1732
- }
1733
- const requestId = readRequestId(res.headers);
1734
- const withId = (message) => requestId ? `${message} [request ${requestId}]` : message;
1735
- if (res.status >= 200 && res.status < 300) {
1736
- let text;
1737
- try {
1738
- text = await res.text();
1739
- } catch {
1740
- if (signal.aborted)
1741
- return { kind: "timeout" };
1742
- return {
1743
- kind: "fail",
1744
- message: withId("SystemOne response body could not be read"),
1745
- retryable: true
1746
- };
1747
- }
1748
- if (text.length > MAX_RESPONSE_CHARS) {
1749
- return {
1750
- kind: "fail",
1751
- message: withId("SystemOne response exceeded the size limit"),
1752
- retryable: false
1753
- };
1754
- }
1755
- return { kind: "ok", text, requestId };
1756
- }
1757
- if (res.status >= 300 && res.status < 400) {
1758
- return {
1759
- kind: "fail",
1760
- message: withId(`SystemOne request was redirected (HTTP ${res.status}); redirects are refused`),
1761
- retryable: false
1762
- };
1763
- }
1764
- let errorText = "";
1765
- try {
1766
- errorText = await res.text();
1767
- } catch {
1768
- if (signal.aborted)
1769
- return { kind: "timeout" };
1770
- }
1771
- const code = readErrorCode(errorText);
1772
- if (res.status === 401 || res.status === 403) {
1773
- return {
1774
- kind: "fail",
1775
- message: withId(systemOneKeyRejectedMessage(providerId, res.status, code)),
1776
- retryable: false
1777
- };
1778
- }
1779
- const detail = code ? ` (${code})` : "";
1780
- const retryable = res.status === 408 || res.status === 429 || res.status >= 500;
1781
- return {
1782
- kind: "fail",
1783
- message: withId(`SystemOne API returned HTTP ${res.status}${detail}`),
1784
- retryable,
1785
- retryAfterMs: retryable ? parseRetryAfter(res.headers.get("retry-after"), now()) : undefined
1786
- };
1787
- }
1788
2526
  }
1789
2527
 
1790
2528
  // src/workflows/definition.ts
@@ -1919,7 +2657,7 @@ function validateDefinition(def, registry, options = {}) {
1919
2657
  if (node.type === "human-in-the-loop" && typeof node.config.questions === "string" && !/^\{\{[^}]+\}\}$/.test(node.config.questions.trim())) {
1920
2658
  errors.push(`Node "${node.id}" human-in-the-loop config.questions must be an array or one exact {{interpolation}} token`);
1921
2659
  }
1922
- errors.push(...systemOneStaticShapeViolations(node), ...systemOneRetryViolations(node));
2660
+ errors.push(...systemOneStaticShapeViolations(node), ...systemOneExtractionStaticShapeViolations(node), ...systemOneRetryViolations(node));
1923
2661
  if (node.type === "foreach") {
1924
2662
  validateForeachNode(node, errors);
1925
2663
  if (node.id.includes("#")) {
@@ -2120,4 +2858,4 @@ function applyDefinitionPatch(def, patch) {
2120
2858
  return { definition: patchedDef, errors };
2121
2859
  }
2122
2860
 
2123
- export { BaseExecutor, postApprovalCancellationUpdates, createApprovalFollowUpTask, HumanInTheLoopExecutor, resolveWorkflowLlmConfig, SYSTEM_ONE_PROVIDER_IDS, SYSTEM_ONE_DECISION_NODE_TYPE, SystemOneDecisionConfigSchema, systemOneRetryViolations, systemOneUnresolvedError, SystemOneDecisionExecutor, getNextTargets, generateEdges, findEntryNodes, resolveValidationPort, getSuccessors, definitionNodeIds, validateDefinition, applyDefinitionPatch };
2861
+ export { BaseExecutor, postApprovalCancellationUpdates, createApprovalFollowUpTask, HumanInTheLoopExecutor, resolveWorkflowLlmConfig, SYSTEM_ONE_PROVIDER_IDS, SystemOneExtractionExecutor, SYSTEM_ONE_DECISION_NODE_TYPE, SystemOneDecisionConfigSchema, isSystemOneNodeType, systemOneRetryViolations, systemOneUnresolvedError, SystemOneDecisionExecutor, getNextTargets, generateEdges, findEntryNodes, resolveValidationPort, getSuccessors, definitionNodeIds, validateDefinition, applyDefinitionPatch };