@desplega.ai/agent-swarm 2.4.3 → 2.4.5

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 (270) hide show
  1. package/dist/{acp-adapter-1885dpr7.js → acp-adapter-2wdkktv6.js} +1 -2
  2. package/dist/{actions-pn76n32q.js → actions-c40ebcm4.js} +18 -18
  3. package/dist/{amp-adapter-af84rx1d.js → amp-adapter-wb67m95z.js} +2 -2
  4. package/dist/{app-6sv8mhzm.js → app-353vre8g.js} +3 -3
  5. package/dist/{assistant-sc2tf825.js → assistant-sj1njh23.js} +21 -21
  6. package/dist/{boot-reembed-y3m0ydzj.js → boot-reembed-b9c3az3c.js} +1 -1
  7. package/dist/{boot-reembed-gvxjs2py.js → boot-reembed-qdkvxeem.js} +1 -1
  8. package/dist/{boot-scrub-sweep-1j9mk05f.js → boot-scrub-sweep-m3wh8eks.js} +2 -2
  9. package/dist/{boot-seeds-37sp626b.js → boot-seeds-j2273s54.js} +4 -4
  10. package/dist/{claude-adapter-rp0jtnwr.js → claude-adapter-m7q0a085.js} +2 -2
  11. package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-5ptb6tzs.js} +2 -2
  12. package/dist/{claude-managed-setup-rdt248k7.js → claude-managed-setup-smrqk5j5.js} +3 -3
  13. package/dist/{claude-sdk-session-53rcw5we.js → claude-sdk-session-38xnz21e.js} +8284 -8267
  14. package/dist/{cli-6eyncg0y.js → cli-018r4jq6.js} +34 -6
  15. package/dist/{cli-sds5zzs8.js → cli-1f3qzw6y.js} +78 -12
  16. package/dist/{cli-f0p0f6ja.js → cli-1sp861s4.js} +7 -7
  17. package/dist/{cli-dm8p2dpf.js → cli-1syn2xy1.js} +10 -10
  18. package/dist/{cli-tkf4x6ha.js → cli-37j04k05.js} +11 -11
  19. package/dist/{cli-v87dr6e3.js → cli-3a45zcgm.js} +1 -1
  20. package/dist/{cli-1n9gdg56.js → cli-43x64hv3.js} +4 -4
  21. package/dist/{cli-p0qerd2r.js → cli-4zfqgjc5.js} +2 -2
  22. package/dist/{cli-aa3tb29r.js → cli-7c9qs530.js} +1 -1
  23. package/dist/{cli-9qtjfd60.js → cli-848hxdwj.js} +1 -1
  24. package/dist/{cli-1xsraagq.js → cli-94gwyg7m.js} +8 -8
  25. package/dist/{cli-0dxxwfkt.js → cli-9hstj09h.js} +17 -14
  26. package/dist/{cli-1012xb7j.js → cli-9yqgm9nd.js} +22 -22
  27. package/dist/{cli-pygdsgh8.js → cli-a92r1127.js} +1 -1
  28. package/dist/{cli-8vverzf7.js → cli-ba8ne1es.js} +5 -0
  29. package/dist/{cli-bxrqvpg5.js → cli-be2161h1.js} +7 -7
  30. package/dist/{cli-npfe9t1s.js → cli-bnes4z6b.js} +2 -2
  31. package/dist/{cli-aypbg9r2.js → cli-cxxe61f6.js} +1 -1
  32. package/dist/{cli-vq9gqarq.js → cli-dk44dxec.js} +1 -1
  33. package/dist/{cli-836ehxmt.js → cli-eww120g8.js} +3 -2
  34. package/dist/{cli-a0kcty51.js → cli-f01h25yt.js} +1 -1
  35. package/dist/{cli-mzkwkbdy.js → cli-f0g3pgvf.js} +1 -1
  36. package/dist/{cli-1ma335fz.js → cli-ftc1g5tj.js} +2 -2
  37. package/dist/{cli-4yef8ghv.js → cli-fv5hepyy.js} +3 -3
  38. package/dist/{cli-58vwn100.js → cli-g35m2xwm.js} +4 -4
  39. package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
  40. package/dist/{cli-gx0j2khs.js → cli-j99azg3g.js} +3 -3
  41. package/dist/{cli-er83cjbn.js → cli-k8g16gz3.js} +416 -116
  42. package/dist/{cli-qp2m4btv.js → cli-kpw04va0.js} +2 -2
  43. package/dist/{cli-8y99kfey.js → cli-ms85309d.js} +33 -15
  44. package/dist/{cli-kgnkyabk.js → cli-n72rg53p.js} +1555 -645
  45. package/dist/{cli-e4d78xsv.js → cli-p14m2cnp.js} +1 -1
  46. package/dist/{cli-1eszr343.js → cli-qfxc84gh.js} +1 -1
  47. package/dist/{cli-wk28hzqn.js → cli-qwhy2qma.js} +1 -1
  48. package/dist/{cli-73ezfhha.js → cli-st8aw1m6.js} +228 -11
  49. package/dist/{cli-zvbb4n0e.js → cli-tw4p90jb.js} +92 -192
  50. package/dist/{cli-038fga5k.js → cli-v0yrfmdh.js} +148 -183
  51. package/dist/{cli-6aen3ezb.js → cli-v8fjakwf.js} +1 -1
  52. package/dist/{cli-99jez72r.js → cli-w9etnnxk.js} +6 -1
  53. package/dist/{cli-vcgscaan.js → cli-wt0jf9hm.js} +217 -80
  54. package/dist/{cli-s9rr4gr2.js → cli-xpa64rek.js} +1065 -305
  55. package/dist/{cli-h0mgxann.js → cli-z7d29rw9.js} +2 -2
  56. package/dist/cli.js +45 -20
  57. package/dist/{codex-adapter-2zrmab9b.js → codex-adapter-tn6k5mkg.js} +1 -1
  58. package/dist/{codex-session-runner-kbkz62vc.js → codex-session-runner-7vjt7jbw.js} +1 -1
  59. package/dist/{commands-hd574th6.js → commands-t7rz9146.js} +2 -2
  60. package/dist/{controller-pz5mcfg4.js → controller-m2jasma5.js} +2 -2
  61. package/dist/{controllers-connect-5rgq2hfk.js → controllers-connect-9ftpex99.js} +19 -19
  62. package/dist/{cursor-adapter-mgaprq0j.js → cursor-adapter-mjv79e4z.js} +1 -1
  63. package/dist/{db-6nf1207q.js → db-6sfrn9g3.js} +1 -1
  64. package/dist/{db-h06dxp05.js → db-rn673r06.js} +3 -3
  65. package/dist/{dispatcher-zthpy4m1.js → dispatcher-c155xz0p.js} +17 -17
  66. package/dist/{dsh-adapter-64dhw0j7.js → dsh-adapter-3mmszc1x.js} +1 -1
  67. package/dist/{external-channel-x01t7ke6.js → external-channel-35k6tkv8.js} +2 -2
  68. package/dist/{grok-adapter-h21jv594.js → grok-adapter-tfrq7tg1.js} +2 -3
  69. package/dist/handlers-7gw64dxw.js +39 -0
  70. package/dist/{heartbeat-zzfksckz.js → heartbeat-dyg4npkp.js} +20 -20
  71. package/dist/{hook-z4zhzj1f.js → hook-drspbwpe.js} +1 -1
  72. package/dist/{hook-3xfqhh2s.js → hook-x26b31bg.js} +1 -1
  73. package/dist/{http-q813tmya.js → http-pk972xyw.js} +571 -294
  74. package/dist/{index-va3y63es.js → index-6srwvd5b.js} +1 -2
  75. package/dist/{index-x8xbyn1t.js → index-arytgj5q.js} +1 -1
  76. package/dist/{index-wbbs14c5.js → index-bgdyvcnv.js} +17 -17
  77. package/dist/{index-s98037v0.js → index-cbxkmfc5.js} +1 -2
  78. package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
  79. package/dist/{index-m0a4ydqj.js → index-gabbcdn2.js} +17 -17
  80. package/dist/{index-220tk9bw.js → index-jwxkkex2.js} +139 -76
  81. package/dist/index-tfb26zhy.js +41 -0
  82. package/dist/{keepalive-y5t1my7m.js → keepalive-7yrng7px.js} +3 -3
  83. package/dist/{lead-2xwtq66b.js → lead-7e02z839.js} +29 -30
  84. package/dist/{maintenance-ybfsaedv.js → maintenance-e6cemdqt.js} +1 -1
  85. package/dist/{node-3v3f08f7.js → node-de6akczr.js} +10 -9
  86. package/dist/{oauth-refresh-sweep-909jmn12.js → oauth-refresh-sweep-yw3xdbcj.js} +2 -2
  87. package/dist/{onboard-xn6fkkys.js → onboard-0a9y5yrc.js} +2 -2
  88. package/dist/{otel-impl-e6mn0z13.js → otel-impl-trjpkfb7.js} +1 -1
  89. package/dist/{pricing-refresh-ygd362ep.js → pricing-refresh-d2b5my0y.js} +3 -3
  90. package/dist/{rbac-roles-mb470p4e.js → rbac-roles-hey3yv9d.js} +2 -2
  91. package/dist/{rbac-roles-c9axvfnz.js → rbac-roles-nxh5ahwk.js} +2 -2
  92. package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
  93. package/dist/{render-v2-g0306nh1.js → render-v2-rr3p2atg.js} +3 -3
  94. package/dist/{resume-v315hnq6.js → resume-2efkf1s1.js} +5 -5
  95. package/dist/{session-access-2zg285a1.js → session-access-s2mttxpw.js} +6 -4
  96. package/dist/{setup-a4mtf02k.js → setup-vypma5yt.js} +1 -1
  97. package/dist/{work-objects-9fr0fgtn.js → work-objects-rvhckdkp.js} +20 -20
  98. package/dist/{worker-qae22jeb.js → worker-5snytpbd.js} +29 -30
  99. package/dist/worker-token-xf11yyds.js +136 -0
  100. package/openapi.json +303 -13
  101. package/package.json +8 -8
  102. package/src/apps/action-runner.ts +4 -0
  103. package/src/apps/catalog.generated.json +209 -4
  104. package/src/apps/page-validator.ts +21 -3
  105. package/src/be/chunking.ts +42 -9
  106. package/src/be/db/tasks/draft-text.ts +15 -0
  107. package/src/be/db/tasks/read.ts +14 -1
  108. package/src/be/db.ts +5 -1
  109. package/src/be/memory/index-content.ts +36 -17
  110. package/src/be/memory/providers/openai-embedding.ts +115 -61
  111. package/src/be/memory/providers/sqlite-store.ts +69 -23
  112. package/src/be/memory/reranker.ts +31 -1
  113. package/src/be/memory/search-candidates.ts +47 -0
  114. package/src/be/memory/types.ts +14 -0
  115. package/src/be/migrations/232_agent_session_tokens.sql +41 -0
  116. package/src/be/scripts/typecheck.ts +2 -0
  117. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +116 -55
  118. package/src/be/seed-scripts/index.ts +2 -2
  119. package/src/be/swarm-config-guard.ts +40 -1
  120. package/src/be/task-attachment-store.ts +30 -0
  121. package/src/be/users.ts +169 -12
  122. package/src/cli.tsx +24 -0
  123. package/src/commands/worker-token.ts +164 -0
  124. package/src/extensions/catalog.generated.json +4 -4
  125. package/src/extensions/contract-types.generated.ts +1 -1
  126. package/src/extensions/contract.ts +7 -0
  127. package/src/fs/provider.ts +13 -1
  128. package/src/heartbeat/heartbeat.ts +31 -11
  129. package/src/http/apps.ts +43 -5
  130. package/src/http/codex-oauth-keep-warm.ts +27 -9
  131. package/src/http/config.ts +5 -3
  132. package/src/http/core.ts +2 -0
  133. package/src/http/fs.ts +54 -34
  134. package/src/http/mcp-body.ts +37 -0
  135. package/src/http/mcp-user.ts +3 -5
  136. package/src/http/mcp.ts +6 -7
  137. package/src/http/memory.ts +26 -15
  138. package/src/http/page-proxy.ts +61 -5
  139. package/src/http/pages-public.ts +27 -44
  140. package/src/http/pages.ts +4 -4
  141. package/src/http/script-runs.ts +2 -0
  142. package/src/http/session-access.ts +51 -2
  143. package/src/http/sessions.ts +249 -10
  144. package/src/http/tasks.ts +4 -32
  145. package/src/http/users.ts +12 -1
  146. package/src/integrations/chat-sdk/adapters.ts +7 -3
  147. package/src/integrations/chat-sdk/attachments.ts +90 -12
  148. package/src/integrations/chat-sdk/facts.ts +120 -0
  149. package/src/integrations/chat-sdk/inbound.ts +156 -8
  150. package/src/integrations/chat-sdk/limits.ts +24 -0
  151. package/src/integrations/chat-sdk/outbound-files.ts +186 -0
  152. package/src/integrations/chat-sdk/outbound.ts +234 -4
  153. package/src/integrations/chat-sdk/relay.ts +4 -1
  154. package/src/integrations/chat-sdk/router.ts +28 -2
  155. package/src/integrations/chat-sdk/steering.ts +9 -0
  156. package/src/integrations/chat-sdk/templates.ts +14 -0
  157. package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
  158. package/src/mcp-limits.ts +20 -0
  159. package/src/pages/browser-sdk.ts +8 -3
  160. package/src/pages/feedback-overlay.ts +1 -1
  161. package/src/providers/acp-adapter.ts +33 -13
  162. package/src/providers/acp-swarm-events.ts +1 -1
  163. package/src/providers/amp-adapter.ts +1 -1
  164. package/src/providers/codex-adapter.ts +106 -119
  165. package/src/providers/codex-agents-md.ts +35 -229
  166. package/src/providers/codex-oauth/storage.ts +1 -1
  167. package/src/providers/cursor-adapter.ts +1 -1
  168. package/src/rbac/legacy-policy.ts +3 -1
  169. package/src/rbac/permissions.ts +4 -0
  170. package/src/realtime/transport.ts +1 -2
  171. package/src/script-workflows/workflow-ctx.ts +1 -0
  172. package/src/scripts-runtime/types/stdlib.d.ts +2 -0
  173. package/src/scripts-runtime/types/swarm-extension.d.ts +8 -0
  174. package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
  175. package/src/server-user.ts +2 -0
  176. package/src/server.ts +2 -0
  177. package/src/slack/assistant.ts +2 -2
  178. package/src/slack/files.ts +2 -0
  179. package/src/slack/handlers.ts +4 -4
  180. package/src/slack/inbound-files.ts +39 -6
  181. package/src/slack/thread-buffer.ts +2 -1
  182. package/src/tasks/task-terminal-effects.ts +21 -17
  183. package/src/tests/acp-adapter.test.ts +10 -23
  184. package/src/tests/acp-fake-agent.ts +36 -0
  185. package/src/tests/acp-swarm-events.test.ts +1 -1
  186. package/src/tests/agent-session-tokens.test.ts +452 -0
  187. package/src/tests/apps-action-parent-task.test.ts +295 -0
  188. package/src/tests/apps-card-grid.test.ts +197 -0
  189. package/src/tests/apps-catalog-additions.test.ts +120 -0
  190. package/src/tests/apps-tree.test.ts +129 -0
  191. package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
  192. package/src/tests/chat-sdk-config.test.ts +11 -0
  193. package/src/tests/chat-sdk-facts.test.ts +23 -0
  194. package/src/tests/chat-sdk-inbound.test.ts +558 -4
  195. package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
  196. package/src/tests/codex-adapter.test.ts +167 -242
  197. package/src/tests/codex-oauth-keep-warm.test.ts +85 -1
  198. package/src/tests/config-api-only-keys.test.ts +9 -0
  199. package/src/tests/config-session-auth.test.ts +14 -0
  200. package/src/tests/cors-allowlist.test.ts +1 -1
  201. package/src/tests/docs-index-footprint.test.js +2 -2
  202. package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
  203. package/src/tests/grok-adapter.test.ts +3 -3
  204. package/src/tests/heartbeat-checklist.test.ts +33 -0
  205. package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
  206. package/src/tests/heartbeat-jev-gate.test.ts +265 -1
  207. package/src/tests/kv-page-proxy.test.ts +91 -5
  208. package/src/tests/launch-password-rejection.test.ts +2 -2
  209. package/src/tests/mcp-input-caps.test.ts +294 -0
  210. package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
  211. package/src/tests/memory-embedding-input-cap.test.ts +114 -0
  212. package/src/tests/memory.test.ts +14 -0
  213. package/src/tests/migration-232-agent-session-tokens.test.ts +67 -0
  214. package/src/tests/page-guest-session.test.ts +19 -8
  215. package/src/tests/page-proxy-authed.test.ts +1 -1
  216. package/src/tests/page-proxy.test.ts +17 -10
  217. package/src/tests/page-session.test.ts +102 -1
  218. package/src/tests/pages-authed-mode.test.ts +82 -12
  219. package/src/tests/pages-password-mode.test.ts +5 -5
  220. package/src/tests/rbac-engine.test.ts +1 -0
  221. package/src/tests/realtime-transport.test.ts +13 -7
  222. package/src/tests/runtime-child-token-cleanup.test.ts +3 -3
  223. package/src/tests/script-connections.test.ts +2 -0
  224. package/src/tests/script-runs-http.test.ts +84 -0
  225. package/src/tests/scripts-http.test.ts +10 -1
  226. package/src/tests/session-route-posture.test.ts +2 -0
  227. package/src/tests/swarm-config-export-to-env.test.ts +22 -3
  228. package/src/tests/task-audio-transcription.test.ts +299 -0
  229. package/src/tests/task-completion-memory-chunking.test.ts +434 -0
  230. package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
  231. package/src/tests/transcription.test.ts +299 -0
  232. package/src/tests/worker-token-cli.test.ts +76 -0
  233. package/src/tests/workflow-executors.test.ts +4 -2
  234. package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
  235. package/src/tests/workflow-system-one-decision.test.ts +2 -1
  236. package/src/tests/workflow-system-one-extraction.test.ts +805 -0
  237. package/src/tools/chat-access.ts +10 -0
  238. package/src/tools/chat-post.ts +31 -11
  239. package/src/tools/chat-reply.ts +32 -10
  240. package/src/tools/memory-search.ts +3 -1
  241. package/src/tools/system-one-decide.ts +17 -4
  242. package/src/types.ts +8 -1
  243. package/src/utils/config-env-export.ts +3 -1
  244. package/src/utils/page-session.ts +133 -20
  245. package/src/utils/request-auth-context.ts +2 -1
  246. package/src/utils/transcription.ts +246 -0
  247. package/src/workflows/definition.ts +7 -1
  248. package/src/workflows/engine.ts +18 -12
  249. package/src/workflows/executors/index.ts +1 -0
  250. package/src/workflows/executors/registry.ts +2 -0
  251. package/src/workflows/executors/system-one-decision.ts +84 -262
  252. package/src/workflows/executors/system-one-extract-wire.ts +57 -0
  253. package/src/workflows/executors/system-one-extraction.ts +830 -0
  254. package/src/workflows/executors/system-one-providers.ts +316 -114
  255. package/src/workflows/executors/system-one-transport.ts +264 -0
  256. package/templates/extensions/slack-reply-gate/README.md +24 -22
  257. package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
  258. package/templates/extensions/slack-reply-gate/manifest.json +2 -2
  259. package/templates/official/lead/HEARTBEAT.md +1 -1
  260. package/templates/skills/apps/SKILL.md +31 -7
  261. package/templates/skills/apps/content.md +31 -7
  262. package/templates/skills/chat-sdk/content.md +6 -2
  263. package/templates/skills/code-quality/content.md +2 -1
  264. package/templates/skills/heartbeat-runbook/content.md +8 -8
  265. package/templates/skills/pages/content.md +6 -4
  266. package/templates/skills/swarm-scripts/SKILL.md +2 -2
  267. package/templates/skills/swarm-scripts/content.md +2 -2
  268. package/dist/cli-v1sjhn52.js +0 -7
  269. package/dist/handlers-2qjw434w.js +0 -39
  270. package/dist/index-d0g18bax.js +0 -41
@@ -5,10 +5,10 @@ import {
5
5
  completeApprovalCancellationNotificationClaim2,
6
6
  releaseApprovalCancellationNotificationClaim2,
7
7
  init_db3
8
- } from "./cli-vcgscaan.js";
8
+ } from "./cli-wt0jf9hm.js";
9
9
  import {
10
10
  getSlackApp2
11
- } from "./cli-tkf4x6ha.js";
11
+ } from "./cli-37j04k05.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-eww120g8.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-353vre8g.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,153 @@ 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";
906
+ var SYSTEM_ONE_DEFAULT_PROVIDER_CONFIG_KEY = "SYSTEM_ONE_DEFAULT_PROVIDER";
836
907
  function isSystemOneProviderId(value) {
837
- return typeof value === "string" && Object.hasOwn(SYSTEM_ONE_PROVIDERS, value);
908
+ return typeof value === "string" && SYSTEM_ONE_PROVIDER_IDS.includes(value);
909
+ }
910
+ async function resolveSystemOneDefaultProvider(ctx) {
911
+ let stored;
912
+ try {
913
+ stored = await readGlobalConfig(ctx.db, SYSTEM_ONE_DEFAULT_PROVIDER_CONFIG_KEY);
914
+ } catch {}
915
+ for (const raw of [stored, ctx.env[SYSTEM_ONE_DEFAULT_PROVIDER_CONFIG_KEY]]) {
916
+ const id = raw?.trim().toLowerCase();
917
+ if (isSystemOneProviderId(id))
918
+ return id;
919
+ }
920
+ return SYSTEM_ONE_DEFAULT_PROVIDER;
921
+ }
922
+ function isSystemOneExtractProviderId(value) {
923
+ return typeof value === "string" && SYSTEM_ONE_EXTRACT_PROVIDER_IDS.includes(value);
838
924
  }
839
- function systemOneProviderOf(config) {
925
+ function systemOneProviderOf(config, defaultId = SYSTEM_ONE_DEFAULT_PROVIDER) {
840
926
  if (config.provider === undefined)
841
- return SYSTEM_ONE_DEFAULT_PROVIDER;
927
+ return defaultId;
842
928
  return isSystemOneProviderId(config.provider) ? config.provider : undefined;
843
929
  }
930
+ function systemOneExtractProviderOf(config) {
931
+ if (config.provider === undefined)
932
+ return SYSTEM_ONE_EXTRACT_DEFAULT_PROVIDER;
933
+ return isSystemOneExtractProviderId(config.provider) ? config.provider : undefined;
934
+ }
935
+ function serviceOf(id, capability) {
936
+ const service = SYSTEM_ONE_HOSTS[id][capability];
937
+ if (!service)
938
+ throw new Error(`SystemOne provider "${id}" does not serve ${capability}`);
939
+ return service;
940
+ }
941
+ var NOTHING_PRODUCED = {
942
+ decide: "No decision was made.",
943
+ extract: "Nothing was extracted."
944
+ };
844
945
  var SET_IT = (keyName) => `Set ${keyName} as a global secret on the Secrets page (Settings > Secrets).`;
845
946
  var SET_URL = (configKey) => `Set ${configKey} as a global config value (set-config with scope global).`;
846
947
  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)}`;
948
+ const { label, keyName, fallbackKeyNames } = SYSTEM_ONE_HOSTS[id];
949
+ const also = fallbackKeyNames?.length ? ` (${fallbackKeyNames.join(", ")} is read when it is unset, and is not configured either)` : "";
950
+ return `${keyName} is not configured${also}. SystemOne provider "${id}" needs a working ${label} API key. ${SET_IT(keyName)}`;
849
951
  }
850
952
  function systemOneKeyUnreadableMessage(id) {
851
- return `Could not read ${SYSTEM_ONE_PROVIDERS[id].keyName} from swarm config`;
953
+ return `Could not read ${SYSTEM_ONE_HOSTS[id].keyName} from swarm config`;
852
954
  }
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)}`;
955
+ function systemOneKeyMalformedMessage(id, keyName) {
956
+ const name = keyName ?? SYSTEM_ONE_HOSTS[id].keyName;
957
+ return `${name} is not a valid bearer token value. ${SET_IT(name)}`;
855
958
  }
856
- function systemOneKeyRejectedMessage(id, status, code) {
857
- const { label, keyName } = SYSTEM_ONE_PROVIDERS[id];
959
+ function systemOneKeyRejectedMessage(id, status, code, options = {}) {
960
+ const { label } = SYSTEM_ONE_HOSTS[id];
961
+ const keyName = options.keyName ?? SYSTEM_ONE_HOSTS[id].keyName;
858
962
  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.`;
963
+ 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
964
  }
861
- function urlKeyOf(provider) {
862
- return typeof provider.endpoint === "string" ? null : provider.endpoint.configKey;
965
+ function urlKeyOf(endpoint) {
966
+ return typeof endpoint === "string" ? null : endpoint.configKey;
863
967
  }
864
968
  function isBuiltEndpoint(endpoint) {
865
969
  return typeof endpoint !== "string" && "build" in endpoint;
866
970
  }
867
- function urlDescriptionOf(provider) {
868
- return isBuiltEndpoint(provider.endpoint) ? provider.endpoint.describe : `the URL of a ${provider.label} server`;
971
+ function urlDescriptionOf(id, endpoint) {
972
+ return isBuiltEndpoint(endpoint) ? endpoint.describe : `the URL of a ${SYSTEM_ONE_HOSTS[id].label} server`;
869
973
  }
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)}`;
974
+ function systemOneUrlMissingMessage(id, capability = "decide") {
975
+ const { endpoint } = serviceOf(id, capability);
976
+ const configKey = urlKeyOf(endpoint) ?? "";
977
+ return `${configKey} is not configured. SystemOne provider "${id}" needs ${urlDescriptionOf(id, endpoint)}. ${SET_URL(configKey)}`;
874
978
  }
875
- function systemOneUrlUnreadableMessage(id) {
876
- return `Could not read ${urlKeyOf(SYSTEM_ONE_PROVIDERS[id]) ?? ""} from swarm config`;
979
+ function systemOneUrlUnreadableMessage(id, capability = "decide") {
980
+ return `Could not read ${urlKeyOf(serviceOf(id, capability).endpoint) ?? ""} from swarm config`;
877
981
  }
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)}`;
982
+ function systemOneUrlInvalidMessage(id, capability = "decide") {
983
+ const { endpoint } = serviceOf(id, capability);
984
+ const configKey = urlKeyOf(endpoint) ?? "";
985
+ if (isBuiltEndpoint(endpoint)) {
986
+ return `${configKey} is not ${endpoint.describe}. ${SET_URL(configKey)}`;
883
987
  }
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)}`;
988
+ 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
989
  }
886
- function systemOneModelProblem(id, model) {
887
- const { allowedModels } = SYSTEM_ONE_PROVIDERS[id];
990
+ function systemOneModelProblem(id, model, capability = "decide") {
991
+ const { allowedModels } = serviceOf(id, capability);
888
992
  if (!allowedModels || model !== undefined && allowedModels.includes(model))
889
993
  return null;
890
994
  return `SystemOne provider "${id}" serves only the models ${allowedModels.join(", ")}; set model to one of them or leave it unset.`;
@@ -918,75 +1022,879 @@ function systemOneEndpointFromBase(base, path) {
918
1022
  return `${url.origin}${url.pathname.replace(/\/+$/, "")}${path}`;
919
1023
  }
920
1024
  async function resolveSystemOneTarget(id, ctx, overrides = {}) {
921
- const provider = SYSTEM_ONE_PROVIDERS[id];
922
- const deploymentProblem = provider.deploymentProblem?.(ctx.env);
1025
+ const host = SYSTEM_ONE_HOSTS[id];
1026
+ const capability = overrides.capability ?? "decide";
1027
+ const service = serviceOf(id, capability);
1028
+ const deploymentProblem = host.deploymentProblem?.(ctx.env);
923
1029
  if (deploymentProblem)
924
- return { ok: false, error: deploymentProblem };
1030
+ return { ok: false, error: deploymentProblem, deploymentRefused: true };
925
1031
  const errors = [];
926
1032
  let endpoint;
927
- if (typeof provider.endpoint === "string") {
928
- endpoint = provider.endpoint;
1033
+ if (typeof service.endpoint === "string") {
1034
+ endpoint = service.endpoint;
929
1035
  } else {
930
- const { configKey } = provider.endpoint;
1036
+ const { configKey } = service.endpoint;
931
1037
  let base;
932
1038
  let readable = true;
933
1039
  try {
934
1040
  base = await (overrides.serverUrl ? overrides.serverUrl(id) : readGlobalConfig(ctx.db, configKey));
935
1041
  } catch {
936
1042
  readable = false;
937
- errors.push(systemOneUrlUnreadableMessage(id));
1043
+ errors.push(systemOneUrlUnreadableMessage(id, capability));
938
1044
  }
939
1045
  if (readable) {
940
1046
  if (!base || base.trim() === "") {
941
- errors.push(systemOneUrlMissingMessage(id));
1047
+ errors.push(systemOneUrlMissingMessage(id, capability));
942
1048
  } else {
943
- const resolved = isBuiltEndpoint(provider.endpoint) ? provider.endpoint.build(base, overrides.model ?? provider.defaultModel ?? "") : systemOneEndpointFromBase(base, provider.endpoint.path);
1049
+ const resolved = isBuiltEndpoint(service.endpoint) ? service.endpoint.build(base, overrides.model ?? service.defaultModel ?? "") : systemOneEndpointFromBase(base, service.endpoint.path);
944
1050
  if (resolved)
945
1051
  endpoint = resolved;
946
1052
  else
947
- errors.push(systemOneUrlInvalidMessage(id));
1053
+ errors.push(systemOneUrlInvalidMessage(id, capability));
948
1054
  }
949
1055
  }
950
1056
  }
951
- let apiKey;
1057
+ let read;
952
1058
  let keyProblem;
953
1059
  try {
954
- apiKey = await (overrides.apiKey ? overrides.apiKey(id) : provider.readKey(ctx));
1060
+ read = await (overrides.apiKey ? overrides.apiKey(id) : host.readKey(ctx));
955
1061
  } catch {
956
1062
  keyProblem = systemOneKeyUnreadableMessage(id);
957
1063
  }
1064
+ const apiKey = typeof read === "string" ? read : read?.value;
1065
+ const keyName = typeof read === "object" && read?.keyName || host.keyName;
958
1066
  if (!keyProblem) {
959
1067
  if (!apiKey || apiKey.trim() === "")
960
1068
  keyProblem = systemOneKeyMissingMessage(id);
961
1069
  else if (hasWhitespaceOrControl(apiKey))
962
- keyProblem = systemOneKeyMalformedMessage(id);
1070
+ keyProblem = systemOneKeyMalformedMessage(id, keyName);
963
1071
  }
964
1072
  if (keyProblem)
965
1073
  errors.push(keyProblem);
966
1074
  if (errors.length > 0 || !endpoint || !apiKey)
967
1075
  return { ok: false, error: errors.join(" ") };
968
- return { ok: true, apiKey, endpoint };
1076
+ return { ok: true, apiKey, endpoint, keyName };
969
1077
  }
970
1078
 
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;
1079
+ // src/workflows/executors/system-one-transport.ts
1080
+ init_openrouter_base_url();
975
1081
  var SYSTEM_ONE_DEFAULT_TIMEOUT_MS = 30000;
976
1082
  var SYSTEM_ONE_MIN_TIMEOUT_MS = 1000;
977
1083
  var SYSTEM_ONE_MAX_TIMEOUT_MS = 300000;
978
1084
  var SYSTEM_ONE_DEFAULT_MAX_RETRIES = 2;
979
1085
  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
1086
  var DEADLINE_MARGIN_MS = 250;
985
1087
  var BACKOFF_BASE_MS = 500;
986
1088
  var BACKOFF_CAP_MS = 8000;
987
- var DISTRIBUTION_TOLERANCE = 0.02;
988
1089
  var MAX_RESPONSE_CHARS = 1048576;
989
1090
  var MAX_ERROR_BODY_CHARS = 65536;
1091
+ var REQUEST_ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;
1092
+ var ERROR_CODE_RE2 = /^[A-Za-z0-9._:-]{1,64}$/;
1093
+ function readRequestId(headers) {
1094
+ const value = headers.get("x-request-id") ?? headers.get("request-id") ?? headers.get("cf-ray");
1095
+ return value && REQUEST_ID_RE.test(value) ? value : undefined;
1096
+ }
1097
+ function parseRetryAfter(value, now) {
1098
+ if (!value)
1099
+ return;
1100
+ const trimmed = value.trim();
1101
+ if (/^\d+(\.\d+)?$/.test(trimmed))
1102
+ return Math.round(Number(trimmed) * 1000);
1103
+ const at = Date.parse(trimmed);
1104
+ return Number.isNaN(at) ? undefined : Math.max(0, at - now);
1105
+ }
1106
+ function readErrorCode(text) {
1107
+ if (text.length === 0 || text.length > MAX_ERROR_BODY_CHARS)
1108
+ return;
1109
+ let parsed;
1110
+ try {
1111
+ parsed = JSON.parse(text);
1112
+ } catch {
1113
+ return;
1114
+ }
1115
+ if (!isRecord(parsed))
1116
+ return;
1117
+ const nested = isRecord(parsed.error) ? parsed.error : undefined;
1118
+ const detail = isRecord(parsed.detail) ? parsed.detail : undefined;
1119
+ for (const candidate of [
1120
+ nested?.code,
1121
+ nested?.type,
1122
+ detail?.error_type,
1123
+ parsed.code,
1124
+ parsed.type
1125
+ ]) {
1126
+ if (typeof candidate === "string" && ERROR_CODE_RE2.test(candidate))
1127
+ return candidate;
1128
+ }
1129
+ return cloudflareErrorCode(parsed);
1130
+ }
1131
+ function defaultSleep(ms, signal) {
1132
+ return new Promise((resolve) => {
1133
+ if (signal.aborted)
1134
+ return resolve();
1135
+ const timer = setTimeout(done, ms);
1136
+ function done() {
1137
+ clearTimeout(timer);
1138
+ signal.removeEventListener("abort", done);
1139
+ resolve();
1140
+ }
1141
+ signal.addEventListener("abort", done, { once: true });
1142
+ });
1143
+ }
1144
+ async function sendSystemOneRequest(request, options = {}) {
1145
+ const { timeoutMs, maxRetries } = request;
1146
+ const doFetch = options.fetch ?? fetch;
1147
+ const sleep = options.sleep ?? defaultSleep;
1148
+ const now = options.now ?? Date.now;
1149
+ const random = options.random ?? Math.random;
1150
+ const deadlineAt = now() + timeoutMs - DEADLINE_MARGIN_MS;
1151
+ const controller = new AbortController;
1152
+ const timer = setTimeout(() => controller.abort(), Math.max(0, deadlineAt - now()));
1153
+ const timedOut = () => ({
1154
+ ok: false,
1155
+ error: `SystemOne request exceeded its ${timeoutMs}ms time budget`
1156
+ });
1157
+ try {
1158
+ for (let attempt = 0;; attempt++) {
1159
+ const result = await attemptOnce(doFetch, request, controller.signal, now);
1160
+ if (result.kind === "ok") {
1161
+ return { ok: true, text: result.text, requestId: result.requestId };
1162
+ }
1163
+ if (result.kind === "timeout" || controller.signal.aborted)
1164
+ return timedOut();
1165
+ const attempts = attempt + 1;
1166
+ const suffix = attempts > 1 ? ` after ${attempts} attempts` : "";
1167
+ if (!result.retryable || attempt >= maxRetries) {
1168
+ return { ok: false, error: `${result.message}${suffix}` };
1169
+ }
1170
+ const backoff = Math.min(BACKOFF_CAP_MS, BACKOFF_BASE_MS * 2 ** attempt);
1171
+ const delay = result.retryAfterMs ?? Math.round(backoff * (0.5 + random() * 0.5));
1172
+ if (delay >= deadlineAt - now()) {
1173
+ return {
1174
+ ok: false,
1175
+ error: `${result.message}${suffix}; the ${delay}ms retry delay does not fit the ${timeoutMs}ms time budget`
1176
+ };
1177
+ }
1178
+ await sleep(delay, controller.signal);
1179
+ if (controller.signal.aborted)
1180
+ return timedOut();
1181
+ }
1182
+ } finally {
1183
+ clearTimeout(timer);
1184
+ }
1185
+ }
1186
+ async function attemptOnce(doFetch, request, signal, now) {
1187
+ const { endpoint, apiKey, body } = request;
1188
+ let res;
1189
+ try {
1190
+ res = await doFetch(endpoint, {
1191
+ method: "POST",
1192
+ headers: {
1193
+ ...getOpenRouterAttributionHeaders(endpoint, request.env),
1194
+ "content-type": "application/json",
1195
+ accept: "application/json",
1196
+ authorization: `Bearer ${apiKey}`
1197
+ },
1198
+ body,
1199
+ redirect: "manual",
1200
+ signal
1201
+ });
1202
+ } catch (err) {
1203
+ if (signal.aborted)
1204
+ return { kind: "timeout" };
1205
+ const code = err?.code;
1206
+ const detail = typeof code === "string" && /^[A-Z0-9_]{3,40}$/.test(code) ? ` (${code})` : "";
1207
+ return {
1208
+ kind: "fail",
1209
+ message: `SystemOne request failed: network error${detail}`,
1210
+ retryable: true
1211
+ };
1212
+ }
1213
+ const requestId = readRequestId(res.headers);
1214
+ const withId = (message) => requestId ? `${message} [request ${requestId}]` : message;
1215
+ if (res.status >= 200 && res.status < 300) {
1216
+ let text;
1217
+ try {
1218
+ text = await res.text();
1219
+ } catch {
1220
+ if (signal.aborted)
1221
+ return { kind: "timeout" };
1222
+ return {
1223
+ kind: "fail",
1224
+ message: withId("SystemOne response body could not be read"),
1225
+ retryable: true
1226
+ };
1227
+ }
1228
+ if (text.length > MAX_RESPONSE_CHARS) {
1229
+ return {
1230
+ kind: "fail",
1231
+ message: withId("SystemOne response exceeded the size limit"),
1232
+ retryable: false
1233
+ };
1234
+ }
1235
+ return { kind: "ok", text, requestId };
1236
+ }
1237
+ if (res.status >= 300 && res.status < 400) {
1238
+ return {
1239
+ kind: "fail",
1240
+ message: withId(`SystemOne request was redirected (HTTP ${res.status}); redirects are refused`),
1241
+ retryable: false
1242
+ };
1243
+ }
1244
+ let errorText = "";
1245
+ try {
1246
+ errorText = await res.text();
1247
+ } catch {
1248
+ if (signal.aborted)
1249
+ return { kind: "timeout" };
1250
+ }
1251
+ const code = readErrorCode(errorText);
1252
+ if (res.status === 401 || res.status === 403) {
1253
+ return {
1254
+ kind: "fail",
1255
+ message: withId(systemOneKeyRejectedMessage(request.providerId, res.status, code, {
1256
+ keyName: request.keyName,
1257
+ capability: request.capability
1258
+ })),
1259
+ retryable: false
1260
+ };
1261
+ }
1262
+ const detail = code ? ` (${code})` : "";
1263
+ const retryable = res.status === 408 || res.status === 429 || res.status >= 500;
1264
+ return {
1265
+ kind: "fail",
1266
+ message: withId(`SystemOne API returned HTTP ${res.status}${detail}`),
1267
+ retryable,
1268
+ retryAfterMs: retryable ? parseRetryAfter(res.headers.get("retry-after"), now()) : undefined
1269
+ };
1270
+ }
1271
+
1272
+ // src/workflows/executors/system-one-extraction.ts
1273
+ var SYSTEM_ONE_EXTRACTION_NODE_TYPE = "system-one-extraction";
1274
+ var EXTRACTION_FIELD_TYPES = [
1275
+ "text",
1276
+ "number",
1277
+ "date",
1278
+ "time",
1279
+ "datetime",
1280
+ "duration",
1281
+ "list",
1282
+ "estimate",
1283
+ "forecast"
1284
+ ];
1285
+ var DURATION_UNITS = ["seconds", "minutes", "hours", "days", "weeks", "months", "years"];
1286
+ var LIST_ITEMS = ["text", "number", "date"];
1287
+ var FORECAST_INTERVALS = [
1288
+ "step",
1289
+ "minute",
1290
+ "hour",
1291
+ "day",
1292
+ "week",
1293
+ "month",
1294
+ "quarter",
1295
+ "year"
1296
+ ];
1297
+ var FIELD_ID_RE = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
1298
+ var FIELD_ID_MESSAGE = "field ids must start with a letter or underscore, use only letters, digits, _ or -, and be at most 64 characters";
1299
+ var RESERVED_FIELD_IDS = new Set([...Object.getOwnPropertyNames(Object.prototype), "prototype"]);
1300
+ var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
1301
+ var MAX_IMAGES = 4;
1302
+ var MAX_IMAGE_BYTES = 4 * 1024 * 1024;
1303
+ var MAX_IMAGES_TOTAL_BYTES = 8 * 1024 * 1024;
1304
+ var IMAGE_TYPES = ["image/png", "image/jpeg", "image/webp"];
1305
+ var DATA_URL_RE = /^data:(image\/(?:png|jpeg|webp));base64,([A-Za-z0-9+/=\s]+)$/i;
1306
+ var Instructions = string2().trim().min(1, "instructions must not be blank");
1307
+ var NullThreshold = number2().gt(0, "null_threshold must be above 0").max(1, "null_threshold must be at most 1");
1308
+ var Decimals = number2().int().min(0).max(6);
1309
+ var Unit = string2().trim().min(1, "unit must not be blank");
1310
+ var Scale = _enum2(["linear", "log"]);
1311
+ var Bound = union2([
1312
+ number2(),
1313
+ string2().regex(ISO_DATE_RE, "must be a number or an ISO date (YYYY-MM-DD)")
1314
+ ]);
1315
+ var plain = { instructions: Instructions.optional(), null_threshold: NullThreshold.optional() };
1316
+ var TextField = strictObject2({ type: literal2("text"), ...plain });
1317
+ var NumberField = strictObject2({
1318
+ type: literal2("number"),
1319
+ ...plain,
1320
+ unit: Unit.optional(),
1321
+ integer: boolean().optional(),
1322
+ decimals: Decimals.optional()
1323
+ });
1324
+ var DateField = strictObject2({ type: literal2("date"), ...plain });
1325
+ var TimeField = strictObject2({ type: literal2("time"), ...plain });
1326
+ var DatetimeField = strictObject2({ type: literal2("datetime"), ...plain });
1327
+ var DurationField = strictObject2({
1328
+ type: literal2("duration"),
1329
+ ...plain,
1330
+ unit: _enum2(DURATION_UNITS).optional(),
1331
+ integer: boolean().optional(),
1332
+ decimals: Decimals.optional()
1333
+ });
1334
+ var ListField = strictObject2({
1335
+ type: literal2("list"),
1336
+ instructions: Instructions.optional(),
1337
+ items: _enum2(LIST_ITEMS),
1338
+ decimals: Decimals.optional()
1339
+ });
1340
+ var EstimateField = strictObject2({
1341
+ type: literal2("estimate"),
1342
+ instructions: Instructions.optional(),
1343
+ unit: Unit.optional(),
1344
+ min: Bound,
1345
+ max: Bound,
1346
+ step: union2([number2().positive(), _enum2(["month", "year"])]).optional(),
1347
+ scale: Scale.optional()
1348
+ });
1349
+ var ForecastField = strictObject2({
1350
+ type: literal2("forecast"),
1351
+ instructions: Instructions.optional(),
1352
+ unit: Unit.optional(),
1353
+ min: number2(),
1354
+ max: number2(),
1355
+ horizon: number2().int().min(1).max(64).optional(),
1356
+ interval: _enum2(FORECAST_INTERVALS).optional(),
1357
+ scale: Scale.optional()
1358
+ });
1359
+ var SystemOneExtractionFieldSchema = discriminatedUnion("type", [
1360
+ TextField,
1361
+ NumberField,
1362
+ DateField,
1363
+ TimeField,
1364
+ DatetimeField,
1365
+ DurationField,
1366
+ ListField,
1367
+ EstimateField,
1368
+ ForecastField
1369
+ ]).superRefine((field, ctx) => {
1370
+ if ("integer" in field && field.integer && field.decimals !== undefined && field.decimals > 0) {
1371
+ ctx.addIssue({
1372
+ code: "custom",
1373
+ path: ["decimals"],
1374
+ message: "an integer field cannot also set decimals above 0"
1375
+ });
1376
+ }
1377
+ if (field.type === "list" && field.decimals !== undefined && field.items !== "number") {
1378
+ ctx.addIssue({
1379
+ code: "custom",
1380
+ path: ["decimals"],
1381
+ message: 'decimals applies only to a list of items "number"'
1382
+ });
1383
+ }
1384
+ if (field.type === "estimate" || field.type === "forecast") {
1385
+ const { min, max } = field;
1386
+ if (typeof min !== typeof max) {
1387
+ ctx.addIssue({
1388
+ code: "custom",
1389
+ path: ["max"],
1390
+ message: "min and max must both be numbers or both be ISO dates"
1391
+ });
1392
+ } else if (min >= max) {
1393
+ ctx.addIssue({ code: "custom", path: ["max"], message: "max must be greater than min" });
1394
+ }
1395
+ if (field.scale === "log" && typeof min === "number" && min <= 0) {
1396
+ ctx.addIssue({
1397
+ code: "custom",
1398
+ path: ["min"],
1399
+ message: 'a "log" scale needs min above 0'
1400
+ });
1401
+ }
1402
+ if (field.type === "estimate" && typeof field.step === "string" && typeof field.min === "number") {
1403
+ ctx.addIssue({
1404
+ code: "custom",
1405
+ path: ["step"],
1406
+ message: `step "${field.step}" applies only to a date estimate (ISO date min and max)`
1407
+ });
1408
+ }
1409
+ }
1410
+ });
1411
+ var ImageSchema = union2([
1412
+ string2().regex(DATA_URL_RE, "an image must be a base64 data: URL of a PNG, JPEG or WebP"),
1413
+ strictObject2({ content_type: _enum2(IMAGE_TYPES), base64: string2().min(1) })
1414
+ ]);
1415
+ function imageBytes(image) {
1416
+ const base64 = (typeof image === "string" ? DATA_URL_RE.exec(image)?.[2] ?? "" : image.base64).replace(/\s+/g, "");
1417
+ const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
1418
+ return Math.floor(base64.length * 3 / 4) - padding;
1419
+ }
1420
+ var SystemOneExtractionConfigSchema = strictObject2({
1421
+ provider: _enum2(SYSTEM_ONE_EXTRACT_PROVIDER_IDS).default(SYSTEM_ONE_EXTRACT_DEFAULT_PROVIDER),
1422
+ model: string2().min(1).optional(),
1423
+ state: union2([string2(), array2(unknown()), record2(string2(), unknown())]).default(""),
1424
+ images: array2(ImageSchema).max(MAX_IMAGES).optional(),
1425
+ referenceDate: string2().regex(ISO_DATE_RE, "referenceDate must be an ISO date (YYYY-MM-DD)").optional(),
1426
+ timeoutMs: number2().int().min(SYSTEM_ONE_MIN_TIMEOUT_MS).max(SYSTEM_ONE_MAX_TIMEOUT_MS).default(SYSTEM_ONE_DEFAULT_TIMEOUT_MS),
1427
+ maxRetries: number2().int().min(0).max(SYSTEM_ONE_MAX_RETRIES_LIMIT).default(SYSTEM_ONE_DEFAULT_MAX_RETRIES),
1428
+ fields: record2(string2().regex(FIELD_ID_RE, FIELD_ID_MESSAGE), SystemOneExtractionFieldSchema)
1429
+ }).superRefine((config, ctx) => {
1430
+ const fieldIds = Object.keys(config.fields);
1431
+ if (fieldIds.length === 0) {
1432
+ ctx.addIssue({
1433
+ code: "custom",
1434
+ path: ["fields"],
1435
+ message: "system-one-extraction needs at least one field"
1436
+ });
1437
+ }
1438
+ for (const id of fieldIds) {
1439
+ if (RESERVED_FIELD_IDS.has(id)) {
1440
+ ctx.addIssue({
1441
+ code: "custom",
1442
+ path: ["fields", id],
1443
+ message: `"${id}" is reserved and cannot be a field id`
1444
+ });
1445
+ }
1446
+ }
1447
+ const stateText = typeof config.state === "string" ? config.state : "nonempty";
1448
+ if (stateText.trim() === "" && !config.images?.length) {
1449
+ ctx.addIssue({
1450
+ code: "custom",
1451
+ path: ["state"],
1452
+ message: "state must not be empty unless images are sent"
1453
+ });
1454
+ }
1455
+ const images = config.images ?? [];
1456
+ let total = 0;
1457
+ images.forEach((image, index) => {
1458
+ const bytes = imageBytes(image);
1459
+ total += bytes;
1460
+ if (bytes > MAX_IMAGE_BYTES) {
1461
+ ctx.addIssue({
1462
+ code: "custom",
1463
+ path: ["images", index],
1464
+ message: "an image must be at most 4 MiB decoded"
1465
+ });
1466
+ }
1467
+ });
1468
+ if (total > MAX_IMAGES_TOTAL_BYTES) {
1469
+ ctx.addIssue({
1470
+ code: "custom",
1471
+ path: ["images"],
1472
+ message: "images must total at most 8 MiB decoded"
1473
+ });
1474
+ }
1475
+ const host = SYSTEM_ONE_HOSTS[config.provider];
1476
+ const service = host.extract;
1477
+ const modelProblem = systemOneModelProblem(config.provider, config.model ?? service?.defaultModel, "extract");
1478
+ if (modelProblem)
1479
+ ctx.addIssue({ code: "custom", path: ["model"], message: modelProblem });
1480
+ const maxFields = service?.limits?.maxFields;
1481
+ if (maxFields !== undefined && fieldIds.length > maxFields) {
1482
+ ctx.addIssue({
1483
+ code: "custom",
1484
+ path: ["fields"],
1485
+ message: `SystemOne provider "${config.provider}" takes at most ${maxFields} fields per node; this node has ${fieldIds.length}.`
1486
+ });
1487
+ }
1488
+ const maxImages = service?.limits?.maxImages;
1489
+ if (maxImages !== undefined && images.length > maxImages) {
1490
+ ctx.addIssue({
1491
+ code: "custom",
1492
+ path: ["images"],
1493
+ message: `SystemOne provider "${config.provider}" takes at most ${maxImages} images per node.`
1494
+ });
1495
+ }
1496
+ });
1497
+ var UnitInterval = number2().min(0).max(1);
1498
+ var Span = tuple([number2().int().min(0), number2().int().min(0)]);
1499
+ var Point = union2([number2(), string2()]);
1500
+ var SystemOneExtractionAnswerSchema = strictObject2({
1501
+ type: _enum2(EXTRACTION_FIELD_TYPES),
1502
+ value: unknown(),
1503
+ guess: unknown().optional(),
1504
+ p_null: UnitInterval.optional(),
1505
+ confidence: UnitInterval.optional(),
1506
+ span: Span.optional(),
1507
+ spans: array2(Span).optional(),
1508
+ unit: string2().optional(),
1509
+ mean: union2([Point, array2(Point)]).optional(),
1510
+ median: union2([Point, array2(Point)]).optional(),
1511
+ p10: union2([Point, array2(Point)]).optional(),
1512
+ p90: union2([Point, array2(Point)]).optional(),
1513
+ p_below: UnitInterval.optional(),
1514
+ p_above: UnitInterval.optional()
1515
+ });
1516
+ var SystemOneExtractionOutputSchema = object2({
1517
+ model: string2().min(1),
1518
+ answers: record2(string2(), SystemOneExtractionAnswerSchema),
1519
+ usage: object2({
1520
+ input_tokens: number2().int().min(0),
1521
+ output_tokens: number2().int().min(0)
1522
+ }),
1523
+ truncated: boolean().optional(),
1524
+ requestId: string2().optional()
1525
+ });
1526
+ function hasToken(value) {
1527
+ if (typeof value === "string")
1528
+ return value.includes("{{") && value.includes("}}");
1529
+ if (Array.isArray(value))
1530
+ return value.some(hasToken);
1531
+ if (isRecord(value))
1532
+ return Object.values(value).some(hasToken);
1533
+ return false;
1534
+ }
1535
+ function templatedFieldViolations(nodeId, fields) {
1536
+ const violations = [];
1537
+ for (const [id, spec] of Object.entries(fields)) {
1538
+ if (!hasToken(spec))
1539
+ continue;
1540
+ const at = `Node "${nodeId}" (system-one-extraction) config.fields.${id}`;
1541
+ if (!FIELD_ID_RE.test(id)) {
1542
+ violations.push(`${at}: ${FIELD_ID_MESSAGE}`);
1543
+ } else if (RESERVED_FIELD_IDS.has(id)) {
1544
+ violations.push(`${at}: "${id}" is reserved and cannot be a field id`);
1545
+ }
1546
+ if (!isRecord(spec)) {
1547
+ violations.push(`${at} must be an object: field types and options are static, only instructions may use {{tokens}}`);
1548
+ continue;
1549
+ }
1550
+ const tokened = Object.keys(spec).filter((key) => key !== "instructions" && hasToken(spec[key]));
1551
+ for (const key of tokened) {
1552
+ violations.push(`${at}.${key} must be a literal: only instructions may use {{tokens}}`);
1553
+ }
1554
+ if (tokened.length > 0)
1555
+ continue;
1556
+ if (typeof spec.instructions !== "string") {
1557
+ violations.push(`${at}.instructions must be a string to use {{tokens}}`);
1558
+ continue;
1559
+ }
1560
+ const parsed = SystemOneExtractionFieldSchema.safeParse({ ...spec, instructions: "x" });
1561
+ if (parsed.success)
1562
+ continue;
1563
+ for (const issue of parsed.error.issues) {
1564
+ const path = issue.path.length > 0 ? `${at}.${issue.path.map(String).join(".")}` : at;
1565
+ violations.push(`${path}: ${issue.message}`);
1566
+ }
1567
+ }
1568
+ return violations;
1569
+ }
1570
+ var STAND_IN_IMAGE = "data:image/png;base64,AAAA";
1571
+ function standInConfig(config) {
1572
+ const known = new Set(Object.keys(SystemOneExtractionConfigSchema.shape));
1573
+ const out = {};
1574
+ for (const [key, value] of Object.entries(config)) {
1575
+ if (!hasToken(value) || key === "fields" || !known.has(key)) {
1576
+ out[key] = value;
1577
+ } else if (key === "state") {
1578
+ out.state = typeof value === "string" ? "x" : value;
1579
+ } else if (key === "images" && Array.isArray(value)) {
1580
+ out.images = value.map((image) => hasToken(image) ? STAND_IN_IMAGE : image);
1581
+ }
1582
+ }
1583
+ if (isRecord(config.fields)) {
1584
+ out.fields = Object.fromEntries(Object.entries(config.fields).map(([id, spec]) => [
1585
+ id,
1586
+ isRecord(spec) && hasToken(spec) ? { ...spec, instructions: "x" } : spec
1587
+ ]));
1588
+ }
1589
+ return out;
1590
+ }
1591
+ function templatedConfigViolations(nodeId, config) {
1592
+ if (!hasToken(config))
1593
+ return [];
1594
+ const parsed = SystemOneExtractionConfigSchema.safeParse(standInConfig(config));
1595
+ if (parsed.success)
1596
+ return [];
1597
+ const violations = [];
1598
+ for (const issue of parsed.error.issues) {
1599
+ let value = config;
1600
+ for (const segment of issue.path) {
1601
+ value = isRecord(value) || Array.isArray(value) ? value[segment] : undefined;
1602
+ }
1603
+ if (!hasToken(value) || typeof value === "string")
1604
+ continue;
1605
+ const path = ["config", ...issue.path.map(String)].join(".");
1606
+ violations.push(`Node "${nodeId}" (system-one-extraction) ${path}: ${issue.message}`);
1607
+ }
1608
+ return violations;
1609
+ }
1610
+ function systemOneExtractionStaticShapeViolations(node) {
1611
+ if (node.type !== SYSTEM_ONE_EXTRACTION_NODE_TYPE)
1612
+ return [];
1613
+ const violations = [];
1614
+ const fields = node.config.fields;
1615
+ if (fields !== undefined && (typeof fields !== "object" || fields === null || Array.isArray(fields))) {
1616
+ 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}}`);
1617
+ } else if (fields !== undefined) {
1618
+ violations.push(...templatedFieldViolations(node.id, fields));
1619
+ }
1620
+ const provider = node.config.provider;
1621
+ if (typeof provider === "string" && provider.includes("{{")) {
1622
+ 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`);
1623
+ }
1624
+ if (violations.length === 0) {
1625
+ violations.push(...templatedConfigViolations(node.id, node.config));
1626
+ }
1627
+ return violations;
1628
+ }
1629
+
1630
+ class ExtractionContractError extends Error {
1631
+ }
1632
+ function fail(message) {
1633
+ throw new ExtractionContractError(message);
1634
+ }
1635
+ function unitOrUndefined(value, what) {
1636
+ if (value === undefined)
1637
+ return;
1638
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
1639
+ fail(`${what} must be a finite number between 0 and 1`);
1640
+ }
1641
+ return value;
1642
+ }
1643
+ function isFiniteNumber(value) {
1644
+ return typeof value === "number" && Number.isFinite(value);
1645
+ }
1646
+ function isSpan(value) {
1647
+ return Array.isArray(value) && value.length === 2 && value.every((n) => Number.isInteger(n) && n >= 0) && value[0] <= value[1];
1648
+ }
1649
+ function isPoint(value, dates) {
1650
+ return dates ? typeof value === "string" : isFiniteNumber(value);
1651
+ }
1652
+ function checkScalar(at, field, value) {
1653
+ if (value === null)
1654
+ return;
1655
+ switch (field.type) {
1656
+ case "text":
1657
+ case "date":
1658
+ case "time":
1659
+ case "datetime":
1660
+ if (typeof value !== "string")
1661
+ fail(`${at}.value must be a string or null`);
1662
+ return;
1663
+ case "number":
1664
+ case "duration":
1665
+ if (!isFiniteNumber(value))
1666
+ fail(`${at}.value must be a finite number or null`);
1667
+ if (field.integer && !Number.isInteger(value))
1668
+ fail(`${at}.value must be an integer`);
1669
+ return;
1670
+ default:
1671
+ return;
1672
+ }
1673
+ }
1674
+ function validateAnswer(id, field, raw) {
1675
+ const at = `answer "${id}"`;
1676
+ if (!isRecord(raw))
1677
+ fail(`${at} must be an object`);
1678
+ if (raw.type !== field.type) {
1679
+ fail(`${at} has type ${JSON.stringify(raw.type)} but the field declares "${field.type}"`);
1680
+ }
1681
+ if (!Object.hasOwn(raw, "value"))
1682
+ fail(`${at} is missing its value`);
1683
+ const answer = { type: field.type, value: raw.value };
1684
+ switch (field.type) {
1685
+ case "list": {
1686
+ if (!Array.isArray(raw.value))
1687
+ fail(`${at}.value must be a list`);
1688
+ const ok = raw.value.every((item) => field.items === "number" ? isFiniteNumber(item) : typeof item === "string");
1689
+ if (!ok)
1690
+ fail(`${at}.value items must all be ${field.items === "number" ? "numbers" : "strings"}`);
1691
+ if (raw.spans !== undefined) {
1692
+ if (!Array.isArray(raw.spans) || !raw.spans.every(isSpan)) {
1693
+ fail(`${at}.spans must be a list of [start, end] offsets`);
1694
+ }
1695
+ answer.spans = raw.spans;
1696
+ }
1697
+ break;
1698
+ }
1699
+ case "estimate": {
1700
+ const dates = typeof field.min === "string";
1701
+ if (raw.value !== null && !isPoint(raw.value, dates)) {
1702
+ fail(`${at}.value must be ${dates ? "an ISO date" : "a finite number"} or null`);
1703
+ }
1704
+ for (const key of ["median", "p10", "p90"]) {
1705
+ if (!isPoint(raw[key], dates))
1706
+ fail(`${at}.${key} must be ${dates ? "an ISO date" : "a finite number"}`);
1707
+ answer[key] = raw[key];
1708
+ }
1709
+ if (raw.mean !== undefined && isPoint(raw.mean, dates))
1710
+ answer.mean = raw.mean;
1711
+ break;
1712
+ }
1713
+ case "forecast": {
1714
+ const steps = (value) => Array.isArray(value) && value.every(isFiniteNumber);
1715
+ if (!steps(raw.value))
1716
+ fail(`${at}.value must be a list of numbers, one per step`);
1717
+ const length = raw.value.length;
1718
+ if (field.horizon !== undefined && length !== field.horizon) {
1719
+ fail(`${at}.value must have ${field.horizon} step(s), got ${length}`);
1720
+ }
1721
+ for (const key of ["median", "p10", "p90"]) {
1722
+ if (!steps(raw[key]) || raw[key].length !== length) {
1723
+ fail(`${at}.${key} must be a list of ${length} number(s)`);
1724
+ }
1725
+ answer[key] = raw[key];
1726
+ }
1727
+ break;
1728
+ }
1729
+ default:
1730
+ checkScalar(at, field, raw.value);
1731
+ if (raw.span !== undefined) {
1732
+ if (!isSpan(raw.span))
1733
+ fail(`${at}.span must be [start, end] offsets`);
1734
+ answer.span = raw.span;
1735
+ }
1736
+ if (raw.guess !== undefined && raw.guess !== null) {
1737
+ checkScalar(`${at}.guess`, field, raw.guess);
1738
+ answer.guess = raw.guess;
1739
+ }
1740
+ }
1741
+ const pNull = unitOrUndefined(raw.p_null, `${at}.p_null`);
1742
+ if (pNull !== undefined)
1743
+ answer.p_null = pNull;
1744
+ const confidence = unitOrUndefined(raw.confidence, `${at}.confidence`);
1745
+ if (confidence !== undefined)
1746
+ answer.confidence = confidence;
1747
+ if (field.type === "estimate") {
1748
+ const below = unitOrUndefined(raw.p_below, `${at}.p_below`);
1749
+ const above = unitOrUndefined(raw.p_above, `${at}.p_above`);
1750
+ if (below !== undefined)
1751
+ answer.p_below = below;
1752
+ if (above !== undefined)
1753
+ answer.p_above = above;
1754
+ }
1755
+ if (typeof raw.unit === "string" && raw.unit !== "")
1756
+ answer.unit = raw.unit;
1757
+ return answer;
1758
+ }
1759
+ function validateSystemOneExtraction(fields, body, requestId) {
1760
+ if (!isRecord(body))
1761
+ fail("response body must be a JSON object");
1762
+ if (typeof body.model !== "string" || body.model === "") {
1763
+ fail("response is missing the model that produced the answers");
1764
+ }
1765
+ if (!isRecord(body.answers))
1766
+ fail("response is missing the answers object");
1767
+ const usage = object2({ input_tokens: number2().int().min(0), output_tokens: number2().int().min(0) }).safeParse(body.usage);
1768
+ if (!usage.success) {
1769
+ fail("response usage must report non-negative integer input_tokens and output_tokens");
1770
+ }
1771
+ const declared = Object.keys(fields);
1772
+ if (declared.length === 0)
1773
+ fail("a system-one-extraction call needs at least one field");
1774
+ const unexpected = Object.keys(body.answers).filter((id) => !Object.hasOwn(fields, id));
1775
+ if (unexpected.length > 0) {
1776
+ fail(`response contains ${unexpected.length} answer(s) for undeclared fields`);
1777
+ }
1778
+ const answers = {};
1779
+ for (const id of declared) {
1780
+ if (!Object.hasOwn(body.answers, id))
1781
+ fail(`response is missing the answer for field "${id}"`);
1782
+ const field = fields[id];
1783
+ if (!field)
1784
+ fail(`field "${id}" is not defined`);
1785
+ answers[id] = validateAnswer(id, field, body.answers[id]);
1786
+ }
1787
+ return {
1788
+ model: body.model,
1789
+ answers,
1790
+ usage: usage.data,
1791
+ ...typeof body.truncated === "boolean" ? { truncated: body.truncated } : {},
1792
+ ...requestId ? { requestId } : {}
1793
+ };
1794
+ }
1795
+
1796
+ class SystemOneExtractionExecutor extends BaseExecutor {
1797
+ options;
1798
+ type = SYSTEM_ONE_EXTRACTION_NODE_TYPE;
1799
+ mode = "instant";
1800
+ configSchema = SystemOneExtractionConfigSchema;
1801
+ outputSchema = SystemOneExtractionOutputSchema;
1802
+ constructor(deps, options = {}) {
1803
+ super(deps);
1804
+ this.options = options;
1805
+ }
1806
+ providerContext() {
1807
+ return { db: this.deps.db, env: this.options.env ?? process.env };
1808
+ }
1809
+ resolveTarget(id, model) {
1810
+ const { getApiKey, getServerUrl } = this.options;
1811
+ return resolveSystemOneTarget(id, this.providerContext(), {
1812
+ apiKey: getApiKey ? () => getApiKey(id) : undefined,
1813
+ serverUrl: getServerUrl ? () => getServerUrl(id) : undefined,
1814
+ model,
1815
+ capability: "extract"
1816
+ });
1817
+ }
1818
+ async checkReadiness(nodes) {
1819
+ const nodeIdsByProvider = new Map;
1820
+ for (const node of nodes) {
1821
+ const id = systemOneExtractProviderOf(node.config);
1822
+ if (id)
1823
+ nodeIdsByProvider.set(id, [...nodeIdsByProvider.get(id) ?? [], node.id]);
1824
+ }
1825
+ const problems = [];
1826
+ for (const [id, nodeIds] of nodeIdsByProvider) {
1827
+ const target = await this.resolveTarget(id);
1828
+ if (!target.ok)
1829
+ problems.push({ nodeIds, message: target.error });
1830
+ }
1831
+ return problems;
1832
+ }
1833
+ async execute(config, _context, _meta) {
1834
+ const extracted = await this.extract(config);
1835
+ return extracted.ok ? { status: "success", output: extracted.output } : { status: "failed", error: extracted.error };
1836
+ }
1837
+ async extract(config) {
1838
+ const providerId = config.provider;
1839
+ const service = SYSTEM_ONE_HOSTS[providerId].extract;
1840
+ if (!service) {
1841
+ return { ok: false, error: `SystemOne provider "${providerId}" does not serve extraction` };
1842
+ }
1843
+ const model = config.model ?? service.defaultModel;
1844
+ const modelProblem = systemOneModelProblem(providerId, model, "extract");
1845
+ if (modelProblem)
1846
+ return { ok: false, error: modelProblem };
1847
+ const target = await this.resolveTarget(providerId, model);
1848
+ if (!target.ok)
1849
+ return { ok: false, error: target.error };
1850
+ const { apiKey, endpoint, keyName } = target;
1851
+ const body = JSON.stringify(service.wire.buildRequest(config, model));
1852
+ const scrub = (message) => message.split(apiKey).join("[REDACTED]");
1853
+ const sent = await sendSystemOneRequest({
1854
+ providerId,
1855
+ capability: "extract",
1856
+ endpoint,
1857
+ apiKey,
1858
+ keyName,
1859
+ body,
1860
+ timeoutMs: config.timeoutMs,
1861
+ maxRetries: config.maxRetries,
1862
+ env: this.providerContext().env
1863
+ }, this.options);
1864
+ if (!sent.ok)
1865
+ return { ok: false, error: scrub(sent.error) };
1866
+ let parsed;
1867
+ try {
1868
+ parsed = JSON.parse(sent.text);
1869
+ } catch {
1870
+ return { ok: false, error: "SystemOne response was not valid JSON" };
1871
+ }
1872
+ try {
1873
+ return {
1874
+ ok: true,
1875
+ output: validateSystemOneExtraction(config.fields, service.wire.toExtraction(parsed), sent.requestId)
1876
+ };
1877
+ } catch (err) {
1878
+ const withId = (message) => sent.requestId ? `${message} [request ${sent.requestId}]` : message;
1879
+ if (err instanceof SystemOneWireError)
1880
+ return { ok: false, error: scrub(withId(err.message)) };
1881
+ if (err instanceof ExtractionContractError) {
1882
+ return { ok: false, error: scrub(withId(`${VALIDATION_PREFIX}${err.message}`)) };
1883
+ }
1884
+ throw err;
1885
+ }
1886
+ }
1887
+ }
1888
+
1889
+ // src/workflows/executors/system-one-decision.ts
1890
+ var SYSTEM_ONE_DECISION_NODE_TYPE = "system-one-decision";
1891
+ var SYSTEM_ONE_ENDPOINT = SYSTEM_ONE_PROVIDERS.typesafe.endpoint;
1892
+ var SYSTEM_ONE_API_KEY_CONFIG_KEY = SYSTEM_ONE_PROVIDERS.typesafe.keyName;
1893
+ var CHOICE_MIN_OPTIONS = 2;
1894
+ var CHOICE_MAX_OPTIONS = 255;
1895
+ var SCORE_MIN_LEVELS = 2;
1896
+ var SCORE_MAX_LEVELS = 10;
1897
+ var DISTRIBUTION_TOLERANCE = 0.02;
990
1898
  var REVIEW_PORTS = ["approved", "rejected", "timeout"];
991
1899
  var ENGINE_RETRY_DEFAULT = 3;
992
1900
  var SystemOneEntrySchema = union2([
@@ -1028,7 +1936,7 @@ var SystemOneQuestionSchema = discriminatedUnion("type", [
1028
1936
  ChoiceQuestionSchema,
1029
1937
  ScoreQuestionSchema
1030
1938
  ]);
1031
- var Unit = number2().min(0).max(1);
1939
+ var Unit2 = number2().min(0).max(1);
1032
1940
  var QUESTION_ID_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
1033
1941
  var RESERVED_QUESTION_IDS = new Set([
1034
1942
  ...Object.getOwnPropertyNames(Object.prototype),
@@ -1036,14 +1944,14 @@ var RESERVED_QUESTION_IDS = new Set([
1036
1944
  ]);
1037
1945
  var SystemOnePrimitiveTypeSchema = _enum2(["noul", "choice", "score"]);
1038
1946
  var HumanReviewSchema = strictObject2({
1039
- band: strictObject2({ min: Unit, max: Unit }).refine((band) => band.min <= band.max, { message: "band.min must not exceed band.max" }),
1947
+ band: strictObject2({ min: Unit2, max: Unit2 }).refine((band) => band.min <= band.max, { message: "band.min must not exceed band.max" }),
1040
1948
  approvers: ApproverConfigSchema,
1041
1949
  title: string2().min(1).optional(),
1042
1950
  timeout: HITLTimeoutSchema.optional(),
1043
1951
  notifications: array2(NotificationConfigSchema).optional()
1044
1952
  });
1045
1953
  var SystemOneDecisionConfigSchema = strictObject2({
1046
- provider: _enum2(SYSTEM_ONE_PROVIDER_IDS).default(SYSTEM_ONE_DEFAULT_PROVIDER),
1954
+ provider: _enum2(SYSTEM_ONE_PROVIDER_IDS).optional(),
1047
1955
  model: string2().min(1).optional(),
1048
1956
  state: SystemOneStateSchema,
1049
1957
  timeoutMs: number2().int().min(SYSTEM_ONE_MIN_TIMEOUT_MS).max(SYSTEM_ONE_MAX_TIMEOUT_MS).default(SYSTEM_ONE_DEFAULT_TIMEOUT_MS),
@@ -1092,6 +2000,8 @@ var SystemOneDecisionConfigSchema = strictObject2({
1092
2000
  });
1093
2001
  }
1094
2002
  }
2003
+ if (config.provider === undefined)
2004
+ return;
1095
2005
  const modelProblem = systemOneModelProblem(config.provider, config.model ?? SYSTEM_ONE_PROVIDERS[config.provider].defaultModel);
1096
2006
  if (modelProblem)
1097
2007
  ctx.addIssue({ code: "custom", path: ["model"], message: modelProblem });
@@ -1099,19 +2009,19 @@ var SystemOneDecisionConfigSchema = strictObject2({
1099
2009
  if (limitProblem)
1100
2010
  ctx.addIssue({ code: "custom", path: ["questions"], message: limitProblem });
1101
2011
  });
1102
- var NoulAnswerSchema = strictObject2({ type: literal2("noul"), noul: Unit });
2012
+ var NoulAnswerSchema = strictObject2({ type: literal2("noul"), noul: Unit2 });
1103
2013
  var ChoiceAnswerSchema = strictObject2({
1104
2014
  type: literal2("choice"),
1105
2015
  choice: string2(),
1106
- probabilities: record2(string2(), Unit),
1107
- confidence: Unit
2016
+ probabilities: record2(string2(), Unit2),
2017
+ confidence: Unit2
1108
2018
  });
1109
2019
  var ScoreAnswerSchema = strictObject2({
1110
2020
  type: literal2("score"),
1111
2021
  score: number2(),
1112
2022
  legend: record2(string2(), SystemOneEntrySchema),
1113
- probabilities: record2(string2(), Unit),
1114
- confidence: Unit
2023
+ probabilities: record2(string2(), Unit2),
2024
+ confidence: Unit2
1115
2025
  });
1116
2026
  var SystemOneAnswerSchema = discriminatedUnion("type", [
1117
2027
  NoulAnswerSchema,
@@ -1119,7 +2029,7 @@ var SystemOneAnswerSchema = discriminatedUnion("type", [
1119
2029
  ScoreAnswerSchema
1120
2030
  ]);
1121
2031
  var ReviewQuestionSchema = strictObject2({
1122
- confidence: Unit,
2032
+ confidence: Unit2,
1123
2033
  inBand: boolean(),
1124
2034
  decidedBy: _enum2(["model", "human"]),
1125
2035
  modelAnswer: union2([string2(), number2()]).optional()
@@ -1141,16 +2051,23 @@ var SystemOneDecisionOutputSchema = object2({
1141
2051
  routing: strictObject2({ model: string2().min(1) }).optional(),
1142
2052
  review: ReviewSchema.optional()
1143
2053
  });
2054
+ var SYSTEM_ONE_NODE_LABELS = {
2055
+ [SYSTEM_ONE_DECISION_NODE_TYPE]: "SystemOne Decision",
2056
+ [SYSTEM_ONE_EXTRACTION_NODE_TYPE]: "SystemOne Extraction"
2057
+ };
2058
+ function isSystemOneNodeType(type) {
2059
+ return Object.hasOwn(SYSTEM_ONE_NODE_LABELS, type);
2060
+ }
1144
2061
  function systemOneRetryViolations(node) {
1145
- if (node.type !== SYSTEM_ONE_DECISION_NODE_TYPE)
2062
+ if (!isSystemOneNodeType(node.type))
1146
2063
  return [];
1147
2064
  const violations = [];
1148
2065
  const attempts = (retry) => retry ? retry.maxRetries ?? ENGINE_RETRY_DEFAULT : 0;
1149
2066
  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`);
2067
+ 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
2068
  }
1152
2069
  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`);
2070
+ violations.push(`Node "${node.id}" (${node.type}) must not set validation.retry.maxRetries > 0: a validation-driven retry would send another paid request`);
1154
2071
  }
1155
2072
  return violations;
1156
2073
  }
@@ -1184,14 +2101,15 @@ function systemOneStaticShapeViolations(node) {
1184
2101
  }
1185
2102
  return violations;
1186
2103
  }
1187
- function systemOneUnresolvedError(nodeId, tokens) {
2104
+ function systemOneUnresolvedError(nodeId, tokens, nodeType = SYSTEM_ONE_DECISION_NODE_TYPE) {
1188
2105
  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.";
2106
+ const label = SYSTEM_ONE_NODE_LABELS[nodeType] ?? SYSTEM_ONE_NODE_LABELS[SYSTEM_ONE_DECISION_NODE_TYPE];
2107
+ 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
2108
  }
1191
2109
 
1192
2110
  class SystemOneContractError extends Error {
1193
2111
  }
1194
- function fail(message) {
2112
+ function fail2(message) {
1195
2113
  throw new SystemOneContractError(message);
1196
2114
  }
1197
2115
  function isRecord2(value) {
@@ -1199,18 +2117,18 @@ function isRecord2(value) {
1199
2117
  }
1200
2118
  function unit(value, what) {
1201
2119
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
1202
- fail(`${what} must be a finite number between 0 and 1`);
2120
+ fail2(`${what} must be a finite number between 0 and 1`);
1203
2121
  }
1204
2122
  return value;
1205
2123
  }
1206
2124
  function distribution(value, keys, what) {
1207
2125
  if (!isRecord2(value))
1208
- fail(`${what} must be an object`);
2126
+ fail2(`${what} must be an object`);
1209
2127
  const actual = Object.keys(value);
1210
2128
  const missing = keys.filter((key) => !Object.hasOwn(value, key));
1211
2129
  const extra = actual.filter((key) => !keys.includes(key));
1212
2130
  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` : ""));
2131
+ 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
2132
  }
1215
2133
  const out = {};
1216
2134
  let sum = 0;
@@ -1220,16 +2138,16 @@ function distribution(value, keys, what) {
1220
2138
  sum += p;
1221
2139
  }
1222
2140
  if (Math.abs(sum - 1) > DISTRIBUTION_TOLERANCE) {
1223
- fail(`${what} must sum to 1 (within ${DISTRIBUTION_TOLERANCE})`);
2141
+ fail2(`${what} must sum to 1 (within ${DISTRIBUTION_TOLERANCE})`);
1224
2142
  }
1225
2143
  return out;
1226
2144
  }
1227
- function validateAnswer(id, question, raw, confidenceField) {
2145
+ function validateAnswer2(id, question, raw, confidenceField) {
1228
2146
  const at = `answer "${id}"`;
1229
2147
  if (!isRecord2(raw))
1230
- fail(`${at} must be an object`);
2148
+ fail2(`${at} must be an object`);
1231
2149
  if (raw.type !== question.type) {
1232
- fail(`${at} has type ${JSON.stringify(raw.type)} but the question declares "${question.type}"`);
2150
+ fail2(`${at} has type ${JSON.stringify(raw.type)} but the question declares "${question.type}"`);
1233
2151
  }
1234
2152
  switch (question.type) {
1235
2153
  case "noul":
@@ -1237,7 +2155,7 @@ function validateAnswer(id, question, raw, confidenceField) {
1237
2155
  case "choice": {
1238
2156
  const options = Object.keys(question.criteria);
1239
2157
  if (typeof raw.choice !== "string" || !Object.hasOwn(question.criteria, raw.choice)) {
1240
- fail(`${at}.choice is not one of the declared options`);
2158
+ fail2(`${at}.choice is not one of the declared options`);
1241
2159
  }
1242
2160
  return {
1243
2161
  type: "choice",
@@ -1250,17 +2168,17 @@ function validateAnswer(id, question, raw, confidenceField) {
1250
2168
  const indices = question.criteria.map((_, index) => String(index));
1251
2169
  const top = indices.length - 1;
1252
2170
  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}`);
2171
+ fail2(`${at}.score must be a finite number between 0 and ${top}`);
1254
2172
  }
1255
2173
  if (!isRecord2(raw.legend))
1256
- fail(`${at}.legend must be an object`);
2174
+ fail2(`${at}.legend must be an object`);
1257
2175
  const legend = {};
1258
2176
  for (const key of indices) {
1259
2177
  if (!Object.hasOwn(raw.legend, key))
1260
- fail(`${at}.legend is missing level ${key}`);
2178
+ fail2(`${at}.legend is missing level ${key}`);
1261
2179
  const entry = SystemOneEntrySchema.safeParse(raw.legend[key]);
1262
2180
  if (!entry.success)
1263
- fail(`${at}.legend["${key}"] must be a string, object, array, or null`);
2181
+ fail2(`${at}.legend["${key}"] must be a string, object, array, or null`);
1264
2182
  legend[key] = entry.data;
1265
2183
  }
1266
2184
  return {
@@ -1282,30 +2200,30 @@ function readRoutingModel(routing) {
1282
2200
  }
1283
2201
  function validateSystemOneResponse(questions, body, requestId, confidenceField = "confidence") {
1284
2202
  if (!isRecord2(body))
1285
- fail("response body must be a JSON object");
2203
+ fail2("response body must be a JSON object");
1286
2204
  if (typeof body.model !== "string" || body.model === "") {
1287
- fail("response is missing the model that produced the answers");
2205
+ fail2("response is missing the model that produced the answers");
1288
2206
  }
1289
2207
  if (!isRecord2(body.answers))
1290
- fail("response is missing the answers object");
2208
+ fail2("response is missing the answers object");
1291
2209
  const usage = object2({ input_tokens: number2().int().min(0), output_tokens: number2().int().min(0) }).safeParse(body.usage);
1292
2210
  if (!usage.success)
1293
- fail("response usage must report non-negative integer input_tokens and output_tokens");
2211
+ fail2("response usage must report non-negative integer input_tokens and output_tokens");
1294
2212
  const declared = Object.keys(questions);
1295
2213
  const unexpected = Object.keys(body.answers).filter((id) => !Object.hasOwn(questions, id));
1296
2214
  if (unexpected.length > 0)
1297
- fail(`response contains ${unexpected.length} answer(s) for undeclared questions`);
2215
+ fail2(`response contains ${unexpected.length} answer(s) for undeclared questions`);
1298
2216
  const answers = {};
1299
2217
  for (const id of declared) {
1300
2218
  if (!Object.hasOwn(body.answers, id))
1301
- fail(`response is missing the answer for question "${id}"`);
2219
+ fail2(`response is missing the answer for question "${id}"`);
1302
2220
  const question = questions[id];
1303
2221
  if (!question)
1304
- fail(`question "${id}" is not defined`);
1305
- answers[id] = validateAnswer(id, question, body.answers[id], confidenceField);
2222
+ fail2(`question "${id}" is not defined`);
2223
+ answers[id] = validateAnswer2(id, question, body.answers[id], confidenceField);
1306
2224
  }
1307
2225
  if (declared.length === 0)
1308
- fail("a system-one-decision call needs at least one question");
2226
+ fail2("a system-one-decision call needs at least one question");
1309
2227
  const routingModel = readRoutingModel(body.routing);
1310
2228
  return {
1311
2229
  model: body.model,
@@ -1456,59 +2374,6 @@ function resolveReviewedDecision(parkedOutput, approval) {
1456
2374
  }
1457
2375
  return finish("approved", answers, questions);
1458
2376
  }
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
2377
 
1513
2378
  class SystemOneDecisionExecutor extends BaseExecutor {
1514
2379
  options;
@@ -1523,6 +2388,9 @@ class SystemOneDecisionExecutor extends BaseExecutor {
1523
2388
  providerContext() {
1524
2389
  return { db: this.deps.db, env: this.options.env ?? process.env };
1525
2390
  }
2391
+ defaultProvider() {
2392
+ return resolveSystemOneDefaultProvider(this.providerContext());
2393
+ }
1526
2394
  resolveTarget(id, model) {
1527
2395
  return resolveSystemOneTarget(id, this.providerContext(), {
1528
2396
  apiKey: this.options.getApiKey,
@@ -1532,8 +2400,9 @@ class SystemOneDecisionExecutor extends BaseExecutor {
1532
2400
  }
1533
2401
  async checkReadiness(nodes) {
1534
2402
  const nodeIdsByProvider = new Map;
2403
+ const defaultId = await this.defaultProvider();
1535
2404
  for (const node of nodes) {
1536
- const id = systemOneProviderOf(node.config);
2405
+ const id = systemOneProviderOf(node.config, defaultId);
1537
2406
  if (id)
1538
2407
  nodeIdsByProvider.set(id, [...nodeIdsByProvider.get(id) ?? [], node.id]);
1539
2408
  }
@@ -1560,20 +2429,33 @@ class SystemOneDecisionExecutor extends BaseExecutor {
1560
2429
  return this.reviewDecision(config, config.humanReview, decided.output, context, meta);
1561
2430
  }
1562
2431
  async decide(config) {
1563
- const providerId = config.provider;
2432
+ const providerId = config.provider ?? await this.defaultProvider();
1564
2433
  const provider = SYSTEM_ONE_PROVIDERS[providerId];
1565
2434
  const model = config.model ?? provider.defaultModel;
1566
2435
  const modelProblem = systemOneModelProblem(providerId, model);
1567
2436
  if (modelProblem)
1568
2437
  return { ok: false, error: modelProblem };
2438
+ const limitProblem = systemOneQuestionLimitProblem(providerId, Object.keys(config.questions).length);
2439
+ if (limitProblem)
2440
+ return { ok: false, error: limitProblem };
1569
2441
  const target = await this.resolveTarget(providerId, model);
1570
2442
  if (!target.ok)
1571
- return { ok: false, error: target.error };
1572
- const { apiKey, endpoint } = target;
2443
+ return target;
2444
+ const { apiKey, endpoint, keyName } = target;
1573
2445
  const wire = provider.wire ?? SYSTEM_ONE_WIRE;
1574
2446
  const body = JSON.stringify(wire.buildRequest(config, model));
1575
2447
  const scrub = (message) => message.split(apiKey).join("[REDACTED]");
1576
- const sent = await this.send(providerId, endpoint, apiKey, body, config.timeoutMs, config.maxRetries);
2448
+ const sent = await sendSystemOneRequest({
2449
+ providerId,
2450
+ capability: "decide",
2451
+ endpoint,
2452
+ apiKey,
2453
+ keyName,
2454
+ body,
2455
+ timeoutMs: config.timeoutMs,
2456
+ maxRetries: config.maxRetries,
2457
+ env: this.providerContext().env
2458
+ }, this.options);
1577
2459
  if (!sent.ok)
1578
2460
  return { ok: false, error: scrub(sent.error) };
1579
2461
  let parsed;
@@ -1663,128 +2545,6 @@ class SystemOneDecisionExecutor extends BaseExecutor {
1663
2545
  resolveApproval(parkedOutput, approval) {
1664
2546
  return resolveReviewedDecision(parkedOutput, approval);
1665
2547
  }
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
2548
  }
1789
2549
 
1790
2550
  // src/workflows/definition.ts
@@ -1919,7 +2679,7 @@ function validateDefinition(def, registry, options = {}) {
1919
2679
  if (node.type === "human-in-the-loop" && typeof node.config.questions === "string" && !/^\{\{[^}]+\}\}$/.test(node.config.questions.trim())) {
1920
2680
  errors.push(`Node "${node.id}" human-in-the-loop config.questions must be an array or one exact {{interpolation}} token`);
1921
2681
  }
1922
- errors.push(...systemOneStaticShapeViolations(node), ...systemOneRetryViolations(node));
2682
+ errors.push(...systemOneStaticShapeViolations(node), ...systemOneExtractionStaticShapeViolations(node), ...systemOneRetryViolations(node));
1923
2683
  if (node.type === "foreach") {
1924
2684
  validateForeachNode(node, errors);
1925
2685
  if (node.id.includes("#")) {
@@ -2120,4 +2880,4 @@ function applyDefinitionPatch(def, patch) {
2120
2880
  return { definition: patchedDef, errors };
2121
2881
  }
2122
2882
 
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 };
2883
+ 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 };