@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.
- package/dist/{acp-adapter-1885dpr7.js → acp-adapter-2wdkktv6.js} +1 -2
- package/dist/{actions-pn76n32q.js → actions-c40ebcm4.js} +18 -18
- package/dist/{amp-adapter-af84rx1d.js → amp-adapter-wb67m95z.js} +2 -2
- package/dist/{app-6sv8mhzm.js → app-353vre8g.js} +3 -3
- package/dist/{assistant-sc2tf825.js → assistant-sj1njh23.js} +21 -21
- package/dist/{boot-reembed-y3m0ydzj.js → boot-reembed-b9c3az3c.js} +1 -1
- package/dist/{boot-reembed-gvxjs2py.js → boot-reembed-qdkvxeem.js} +1 -1
- package/dist/{boot-scrub-sweep-1j9mk05f.js → boot-scrub-sweep-m3wh8eks.js} +2 -2
- package/dist/{boot-seeds-37sp626b.js → boot-seeds-j2273s54.js} +4 -4
- package/dist/{claude-adapter-rp0jtnwr.js → claude-adapter-m7q0a085.js} +2 -2
- package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-5ptb6tzs.js} +2 -2
- package/dist/{claude-managed-setup-rdt248k7.js → claude-managed-setup-smrqk5j5.js} +3 -3
- package/dist/{claude-sdk-session-53rcw5we.js → claude-sdk-session-38xnz21e.js} +8284 -8267
- package/dist/{cli-6eyncg0y.js → cli-018r4jq6.js} +34 -6
- package/dist/{cli-sds5zzs8.js → cli-1f3qzw6y.js} +78 -12
- package/dist/{cli-f0p0f6ja.js → cli-1sp861s4.js} +7 -7
- package/dist/{cli-dm8p2dpf.js → cli-1syn2xy1.js} +10 -10
- package/dist/{cli-tkf4x6ha.js → cli-37j04k05.js} +11 -11
- package/dist/{cli-v87dr6e3.js → cli-3a45zcgm.js} +1 -1
- package/dist/{cli-1n9gdg56.js → cli-43x64hv3.js} +4 -4
- package/dist/{cli-p0qerd2r.js → cli-4zfqgjc5.js} +2 -2
- package/dist/{cli-aa3tb29r.js → cli-7c9qs530.js} +1 -1
- package/dist/{cli-9qtjfd60.js → cli-848hxdwj.js} +1 -1
- package/dist/{cli-1xsraagq.js → cli-94gwyg7m.js} +8 -8
- package/dist/{cli-0dxxwfkt.js → cli-9hstj09h.js} +17 -14
- package/dist/{cli-1012xb7j.js → cli-9yqgm9nd.js} +22 -22
- package/dist/{cli-pygdsgh8.js → cli-a92r1127.js} +1 -1
- package/dist/{cli-8vverzf7.js → cli-ba8ne1es.js} +5 -0
- package/dist/{cli-bxrqvpg5.js → cli-be2161h1.js} +7 -7
- package/dist/{cli-npfe9t1s.js → cli-bnes4z6b.js} +2 -2
- package/dist/{cli-aypbg9r2.js → cli-cxxe61f6.js} +1 -1
- package/dist/{cli-vq9gqarq.js → cli-dk44dxec.js} +1 -1
- package/dist/{cli-836ehxmt.js → cli-eww120g8.js} +3 -2
- package/dist/{cli-a0kcty51.js → cli-f01h25yt.js} +1 -1
- package/dist/{cli-mzkwkbdy.js → cli-f0g3pgvf.js} +1 -1
- package/dist/{cli-1ma335fz.js → cli-ftc1g5tj.js} +2 -2
- package/dist/{cli-4yef8ghv.js → cli-fv5hepyy.js} +3 -3
- package/dist/{cli-58vwn100.js → cli-g35m2xwm.js} +4 -4
- package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
- package/dist/{cli-gx0j2khs.js → cli-j99azg3g.js} +3 -3
- package/dist/{cli-er83cjbn.js → cli-k8g16gz3.js} +416 -116
- package/dist/{cli-qp2m4btv.js → cli-kpw04va0.js} +2 -2
- package/dist/{cli-8y99kfey.js → cli-ms85309d.js} +33 -15
- package/dist/{cli-kgnkyabk.js → cli-n72rg53p.js} +1555 -645
- package/dist/{cli-e4d78xsv.js → cli-p14m2cnp.js} +1 -1
- package/dist/{cli-1eszr343.js → cli-qfxc84gh.js} +1 -1
- package/dist/{cli-wk28hzqn.js → cli-qwhy2qma.js} +1 -1
- package/dist/{cli-73ezfhha.js → cli-st8aw1m6.js} +228 -11
- package/dist/{cli-zvbb4n0e.js → cli-tw4p90jb.js} +92 -192
- package/dist/{cli-038fga5k.js → cli-v0yrfmdh.js} +148 -183
- package/dist/{cli-6aen3ezb.js → cli-v8fjakwf.js} +1 -1
- package/dist/{cli-99jez72r.js → cli-w9etnnxk.js} +6 -1
- package/dist/{cli-vcgscaan.js → cli-wt0jf9hm.js} +217 -80
- package/dist/{cli-s9rr4gr2.js → cli-xpa64rek.js} +1065 -305
- package/dist/{cli-h0mgxann.js → cli-z7d29rw9.js} +2 -2
- package/dist/cli.js +45 -20
- package/dist/{codex-adapter-2zrmab9b.js → codex-adapter-tn6k5mkg.js} +1 -1
- package/dist/{codex-session-runner-kbkz62vc.js → codex-session-runner-7vjt7jbw.js} +1 -1
- package/dist/{commands-hd574th6.js → commands-t7rz9146.js} +2 -2
- package/dist/{controller-pz5mcfg4.js → controller-m2jasma5.js} +2 -2
- package/dist/{controllers-connect-5rgq2hfk.js → controllers-connect-9ftpex99.js} +19 -19
- package/dist/{cursor-adapter-mgaprq0j.js → cursor-adapter-mjv79e4z.js} +1 -1
- package/dist/{db-6nf1207q.js → db-6sfrn9g3.js} +1 -1
- package/dist/{db-h06dxp05.js → db-rn673r06.js} +3 -3
- package/dist/{dispatcher-zthpy4m1.js → dispatcher-c155xz0p.js} +17 -17
- package/dist/{dsh-adapter-64dhw0j7.js → dsh-adapter-3mmszc1x.js} +1 -1
- package/dist/{external-channel-x01t7ke6.js → external-channel-35k6tkv8.js} +2 -2
- package/dist/{grok-adapter-h21jv594.js → grok-adapter-tfrq7tg1.js} +2 -3
- package/dist/handlers-7gw64dxw.js +39 -0
- package/dist/{heartbeat-zzfksckz.js → heartbeat-dyg4npkp.js} +20 -20
- package/dist/{hook-z4zhzj1f.js → hook-drspbwpe.js} +1 -1
- package/dist/{hook-3xfqhh2s.js → hook-x26b31bg.js} +1 -1
- package/dist/{http-q813tmya.js → http-pk972xyw.js} +571 -294
- package/dist/{index-va3y63es.js → index-6srwvd5b.js} +1 -2
- package/dist/{index-x8xbyn1t.js → index-arytgj5q.js} +1 -1
- package/dist/{index-wbbs14c5.js → index-bgdyvcnv.js} +17 -17
- package/dist/{index-s98037v0.js → index-cbxkmfc5.js} +1 -2
- package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
- package/dist/{index-m0a4ydqj.js → index-gabbcdn2.js} +17 -17
- package/dist/{index-220tk9bw.js → index-jwxkkex2.js} +139 -76
- package/dist/index-tfb26zhy.js +41 -0
- package/dist/{keepalive-y5t1my7m.js → keepalive-7yrng7px.js} +3 -3
- package/dist/{lead-2xwtq66b.js → lead-7e02z839.js} +29 -30
- package/dist/{maintenance-ybfsaedv.js → maintenance-e6cemdqt.js} +1 -1
- package/dist/{node-3v3f08f7.js → node-de6akczr.js} +10 -9
- package/dist/{oauth-refresh-sweep-909jmn12.js → oauth-refresh-sweep-yw3xdbcj.js} +2 -2
- package/dist/{onboard-xn6fkkys.js → onboard-0a9y5yrc.js} +2 -2
- package/dist/{otel-impl-e6mn0z13.js → otel-impl-trjpkfb7.js} +1 -1
- package/dist/{pricing-refresh-ygd362ep.js → pricing-refresh-d2b5my0y.js} +3 -3
- package/dist/{rbac-roles-mb470p4e.js → rbac-roles-hey3yv9d.js} +2 -2
- package/dist/{rbac-roles-c9axvfnz.js → rbac-roles-nxh5ahwk.js} +2 -2
- package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
- package/dist/{render-v2-g0306nh1.js → render-v2-rr3p2atg.js} +3 -3
- package/dist/{resume-v315hnq6.js → resume-2efkf1s1.js} +5 -5
- package/dist/{session-access-2zg285a1.js → session-access-s2mttxpw.js} +6 -4
- package/dist/{setup-a4mtf02k.js → setup-vypma5yt.js} +1 -1
- package/dist/{work-objects-9fr0fgtn.js → work-objects-rvhckdkp.js} +20 -20
- package/dist/{worker-qae22jeb.js → worker-5snytpbd.js} +29 -30
- package/dist/worker-token-xf11yyds.js +136 -0
- package/openapi.json +303 -13
- package/package.json +8 -8
- package/src/apps/action-runner.ts +4 -0
- package/src/apps/catalog.generated.json +209 -4
- package/src/apps/page-validator.ts +21 -3
- package/src/be/chunking.ts +42 -9
- package/src/be/db/tasks/draft-text.ts +15 -0
- package/src/be/db/tasks/read.ts +14 -1
- package/src/be/db.ts +5 -1
- package/src/be/memory/index-content.ts +36 -17
- package/src/be/memory/providers/openai-embedding.ts +115 -61
- package/src/be/memory/providers/sqlite-store.ts +69 -23
- package/src/be/memory/reranker.ts +31 -1
- package/src/be/memory/search-candidates.ts +47 -0
- package/src/be/memory/types.ts +14 -0
- package/src/be/migrations/232_agent_session_tokens.sql +41 -0
- package/src/be/scripts/typecheck.ts +2 -0
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +116 -55
- package/src/be/seed-scripts/index.ts +2 -2
- package/src/be/swarm-config-guard.ts +40 -1
- package/src/be/task-attachment-store.ts +30 -0
- package/src/be/users.ts +169 -12
- package/src/cli.tsx +24 -0
- package/src/commands/worker-token.ts +164 -0
- package/src/extensions/catalog.generated.json +4 -4
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +7 -0
- package/src/fs/provider.ts +13 -1
- package/src/heartbeat/heartbeat.ts +31 -11
- package/src/http/apps.ts +43 -5
- package/src/http/codex-oauth-keep-warm.ts +27 -9
- package/src/http/config.ts +5 -3
- package/src/http/core.ts +2 -0
- package/src/http/fs.ts +54 -34
- package/src/http/mcp-body.ts +37 -0
- package/src/http/mcp-user.ts +3 -5
- package/src/http/mcp.ts +6 -7
- package/src/http/memory.ts +26 -15
- package/src/http/page-proxy.ts +61 -5
- package/src/http/pages-public.ts +27 -44
- package/src/http/pages.ts +4 -4
- package/src/http/script-runs.ts +2 -0
- package/src/http/session-access.ts +51 -2
- package/src/http/sessions.ts +249 -10
- package/src/http/tasks.ts +4 -32
- package/src/http/users.ts +12 -1
- package/src/integrations/chat-sdk/adapters.ts +7 -3
- package/src/integrations/chat-sdk/attachments.ts +90 -12
- package/src/integrations/chat-sdk/facts.ts +120 -0
- package/src/integrations/chat-sdk/inbound.ts +156 -8
- package/src/integrations/chat-sdk/limits.ts +24 -0
- package/src/integrations/chat-sdk/outbound-files.ts +186 -0
- package/src/integrations/chat-sdk/outbound.ts +234 -4
- package/src/integrations/chat-sdk/relay.ts +4 -1
- package/src/integrations/chat-sdk/router.ts +28 -2
- package/src/integrations/chat-sdk/steering.ts +9 -0
- package/src/integrations/chat-sdk/templates.ts +14 -0
- package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
- package/src/mcp-limits.ts +20 -0
- package/src/pages/browser-sdk.ts +8 -3
- package/src/pages/feedback-overlay.ts +1 -1
- package/src/providers/acp-adapter.ts +33 -13
- package/src/providers/acp-swarm-events.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +106 -119
- package/src/providers/codex-agents-md.ts +35 -229
- package/src/providers/codex-oauth/storage.ts +1 -1
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/rbac/legacy-policy.ts +3 -1
- package/src/rbac/permissions.ts +4 -0
- package/src/realtime/transport.ts +1 -2
- package/src/script-workflows/workflow-ctx.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +2 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +8 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
- package/src/server-user.ts +2 -0
- package/src/server.ts +2 -0
- package/src/slack/assistant.ts +2 -2
- package/src/slack/files.ts +2 -0
- package/src/slack/handlers.ts +4 -4
- package/src/slack/inbound-files.ts +39 -6
- package/src/slack/thread-buffer.ts +2 -1
- package/src/tasks/task-terminal-effects.ts +21 -17
- package/src/tests/acp-adapter.test.ts +10 -23
- package/src/tests/acp-fake-agent.ts +36 -0
- package/src/tests/acp-swarm-events.test.ts +1 -1
- package/src/tests/agent-session-tokens.test.ts +452 -0
- package/src/tests/apps-action-parent-task.test.ts +295 -0
- package/src/tests/apps-card-grid.test.ts +197 -0
- package/src/tests/apps-catalog-additions.test.ts +120 -0
- package/src/tests/apps-tree.test.ts +129 -0
- package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
- package/src/tests/chat-sdk-config.test.ts +11 -0
- package/src/tests/chat-sdk-facts.test.ts +23 -0
- package/src/tests/chat-sdk-inbound.test.ts +558 -4
- package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
- package/src/tests/codex-adapter.test.ts +167 -242
- package/src/tests/codex-oauth-keep-warm.test.ts +85 -1
- package/src/tests/config-api-only-keys.test.ts +9 -0
- package/src/tests/config-session-auth.test.ts +14 -0
- package/src/tests/cors-allowlist.test.ts +1 -1
- package/src/tests/docs-index-footprint.test.js +2 -2
- package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
- package/src/tests/grok-adapter.test.ts +3 -3
- package/src/tests/heartbeat-checklist.test.ts +33 -0
- package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
- package/src/tests/heartbeat-jev-gate.test.ts +265 -1
- package/src/tests/kv-page-proxy.test.ts +91 -5
- package/src/tests/launch-password-rejection.test.ts +2 -2
- package/src/tests/mcp-input-caps.test.ts +294 -0
- package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
- package/src/tests/memory-embedding-input-cap.test.ts +114 -0
- package/src/tests/memory.test.ts +14 -0
- package/src/tests/migration-232-agent-session-tokens.test.ts +67 -0
- package/src/tests/page-guest-session.test.ts +19 -8
- package/src/tests/page-proxy-authed.test.ts +1 -1
- package/src/tests/page-proxy.test.ts +17 -10
- package/src/tests/page-session.test.ts +102 -1
- package/src/tests/pages-authed-mode.test.ts +82 -12
- package/src/tests/pages-password-mode.test.ts +5 -5
- package/src/tests/rbac-engine.test.ts +1 -0
- package/src/tests/realtime-transport.test.ts +13 -7
- package/src/tests/runtime-child-token-cleanup.test.ts +3 -3
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/script-runs-http.test.ts +84 -0
- package/src/tests/scripts-http.test.ts +10 -1
- package/src/tests/session-route-posture.test.ts +2 -0
- package/src/tests/swarm-config-export-to-env.test.ts +22 -3
- package/src/tests/task-audio-transcription.test.ts +299 -0
- package/src/tests/task-completion-memory-chunking.test.ts +434 -0
- package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
- package/src/tests/transcription.test.ts +299 -0
- package/src/tests/worker-token-cli.test.ts +76 -0
- package/src/tests/workflow-executors.test.ts +4 -2
- package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
- package/src/tests/workflow-system-one-decision.test.ts +2 -1
- package/src/tests/workflow-system-one-extraction.test.ts +805 -0
- package/src/tools/chat-access.ts +10 -0
- package/src/tools/chat-post.ts +31 -11
- package/src/tools/chat-reply.ts +32 -10
- package/src/tools/memory-search.ts +3 -1
- package/src/tools/system-one-decide.ts +17 -4
- package/src/types.ts +8 -1
- package/src/utils/config-env-export.ts +3 -1
- package/src/utils/page-session.ts +133 -20
- package/src/utils/request-auth-context.ts +2 -1
- package/src/utils/transcription.ts +246 -0
- package/src/workflows/definition.ts +7 -1
- package/src/workflows/engine.ts +18 -12
- package/src/workflows/executors/index.ts +1 -0
- package/src/workflows/executors/registry.ts +2 -0
- package/src/workflows/executors/system-one-decision.ts +84 -262
- package/src/workflows/executors/system-one-extract-wire.ts +57 -0
- package/src/workflows/executors/system-one-extraction.ts +830 -0
- package/src/workflows/executors/system-one-providers.ts +316 -114
- package/src/workflows/executors/system-one-transport.ts +264 -0
- package/templates/extensions/slack-reply-gate/README.md +24 -22
- package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
- package/templates/extensions/slack-reply-gate/manifest.json +2 -2
- package/templates/official/lead/HEARTBEAT.md +1 -1
- package/templates/skills/apps/SKILL.md +31 -7
- package/templates/skills/apps/content.md +31 -7
- package/templates/skills/chat-sdk/content.md +6 -2
- package/templates/skills/code-quality/content.md +2 -1
- package/templates/skills/heartbeat-runbook/content.md +8 -8
- package/templates/skills/pages/content.md +6 -4
- package/templates/skills/swarm-scripts/SKILL.md +2 -2
- package/templates/skills/swarm-scripts/content.md +2 -2
- package/dist/cli-v1sjhn52.js +0 -7
- package/dist/handlers-2qjw434w.js +0 -39
- 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-
|
|
8
|
+
} from "./cli-wt0jf9hm.js";
|
|
9
9
|
import {
|
|
10
10
|
getSlackApp2
|
|
11
|
-
} from "./cli-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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
|
-
|
|
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" &&
|
|
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
|
|
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 } =
|
|
848
|
-
|
|
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 ${
|
|
953
|
+
return `Could not read ${SYSTEM_ONE_HOSTS[id].keyName} from swarm config`;
|
|
852
954
|
}
|
|
853
|
-
function systemOneKeyMalformedMessage(id) {
|
|
854
|
-
|
|
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
|
|
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).
|
|
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(
|
|
862
|
-
return typeof
|
|
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(
|
|
868
|
-
return isBuiltEndpoint(
|
|
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
|
|
872
|
-
const configKey = urlKeyOf(
|
|
873
|
-
return `${configKey} is not configured. SystemOne provider "${id}" needs ${urlDescriptionOf(
|
|
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(
|
|
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
|
|
880
|
-
const configKey = urlKeyOf(
|
|
881
|
-
if (isBuiltEndpoint(
|
|
882
|
-
return `${configKey} is not ${
|
|
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 ${
|
|
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 } =
|
|
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
|
|
922
|
-
const
|
|
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
|
|
928
|
-
endpoint =
|
|
1033
|
+
if (typeof service.endpoint === "string") {
|
|
1034
|
+
endpoint = service.endpoint;
|
|
929
1035
|
} else {
|
|
930
|
-
const { configKey } =
|
|
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(
|
|
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
|
|
1057
|
+
let read;
|
|
952
1058
|
let keyProblem;
|
|
953
1059
|
try {
|
|
954
|
-
|
|
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-
|
|
972
|
-
|
|
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
|
|
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:
|
|
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).
|
|
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:
|
|
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(),
|
|
1107
|
-
confidence:
|
|
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(),
|
|
1114
|
-
confidence:
|
|
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:
|
|
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
|
|
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}" (
|
|
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}" (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2141
|
+
fail2(`${what} must sum to 1 (within ${DISTRIBUTION_TOLERANCE})`);
|
|
1224
2142
|
}
|
|
1225
2143
|
return out;
|
|
1226
2144
|
}
|
|
1227
|
-
function
|
|
2145
|
+
function validateAnswer2(id, question, raw, confidenceField) {
|
|
1228
2146
|
const at = `answer "${id}"`;
|
|
1229
2147
|
if (!isRecord2(raw))
|
|
1230
|
-
|
|
2148
|
+
fail2(`${at} must be an object`);
|
|
1231
2149
|
if (raw.type !== question.type) {
|
|
1232
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2171
|
+
fail2(`${at}.score must be a finite number between 0 and ${top}`);
|
|
1254
2172
|
}
|
|
1255
2173
|
if (!isRecord2(raw.legend))
|
|
1256
|
-
|
|
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
|
-
|
|
2178
|
+
fail2(`${at}.legend is missing level ${key}`);
|
|
1261
2179
|
const entry = SystemOneEntrySchema.safeParse(raw.legend[key]);
|
|
1262
2180
|
if (!entry.success)
|
|
1263
|
-
|
|
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
|
-
|
|
2203
|
+
fail2("response body must be a JSON object");
|
|
1286
2204
|
if (typeof body.model !== "string" || body.model === "") {
|
|
1287
|
-
|
|
2205
|
+
fail2("response is missing the model that produced the answers");
|
|
1288
2206
|
}
|
|
1289
2207
|
if (!isRecord2(body.answers))
|
|
1290
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2219
|
+
fail2(`response is missing the answer for question "${id}"`);
|
|
1302
2220
|
const question = questions[id];
|
|
1303
2221
|
if (!question)
|
|
1304
|
-
|
|
1305
|
-
answers[id] =
|
|
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
|
-
|
|
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
|
|
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
|
|
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 };
|