@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
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { ExecutorMeta } from "../../types";
|
|
3
|
-
import { getOpenRouterAttributionHeaders } from "../../utils/openrouter-base-url";
|
|
4
3
|
import { scrubSecrets } from "../../utils/secret-scrubber";
|
|
5
4
|
import {
|
|
6
5
|
type ApprovalOutcome,
|
|
@@ -17,19 +16,28 @@ import {
|
|
|
17
16
|
HumanInTheLoopExecutor,
|
|
18
17
|
NotificationConfigSchema,
|
|
19
18
|
} from "./human-in-the-loop";
|
|
19
|
+
import { SYSTEM_ONE_EXTRACTION_NODE_TYPE } from "./system-one-extraction";
|
|
20
20
|
import {
|
|
21
|
+
resolveSystemOneDefaultProvider,
|
|
21
22
|
resolveSystemOneTarget,
|
|
22
|
-
SYSTEM_ONE_DEFAULT_PROVIDER,
|
|
23
23
|
SYSTEM_ONE_PROVIDER_IDS,
|
|
24
24
|
SYSTEM_ONE_PROVIDERS,
|
|
25
25
|
type SystemOneProvider,
|
|
26
26
|
type SystemOneProviderId,
|
|
27
|
-
systemOneKeyRejectedMessage,
|
|
28
27
|
systemOneModelProblem,
|
|
29
28
|
systemOneProviderOf,
|
|
30
29
|
systemOneQuestionLimitProblem,
|
|
31
30
|
} from "./system-one-providers";
|
|
32
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
SYSTEM_ONE_DEFAULT_MAX_RETRIES,
|
|
33
|
+
SYSTEM_ONE_DEFAULT_TIMEOUT_MS,
|
|
34
|
+
SYSTEM_ONE_MAX_RETRIES_LIMIT,
|
|
35
|
+
SYSTEM_ONE_MAX_TIMEOUT_MS,
|
|
36
|
+
SYSTEM_ONE_MIN_TIMEOUT_MS,
|
|
37
|
+
type SystemOneTransportOptions,
|
|
38
|
+
sendSystemOneRequest,
|
|
39
|
+
} from "./system-one-transport";
|
|
40
|
+
import { SYSTEM_ONE_WIRE, SystemOneWireError } from "./system-one-wire";
|
|
33
41
|
|
|
34
42
|
// ─── Constants ──────────────────────────────────────────────
|
|
35
43
|
|
|
@@ -43,11 +51,13 @@ export const SYSTEM_ONE_DECISION_NODE_TYPE = "system-one-decision";
|
|
|
43
51
|
export const SYSTEM_ONE_ENDPOINT = SYSTEM_ONE_PROVIDERS.typesafe.endpoint;
|
|
44
52
|
export const SYSTEM_ONE_API_KEY_CONFIG_KEY = SYSTEM_ONE_PROVIDERS.typesafe.keyName;
|
|
45
53
|
|
|
46
|
-
export
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
export {
|
|
55
|
+
SYSTEM_ONE_DEFAULT_MAX_RETRIES,
|
|
56
|
+
SYSTEM_ONE_DEFAULT_TIMEOUT_MS,
|
|
57
|
+
SYSTEM_ONE_MAX_RETRIES_LIMIT,
|
|
58
|
+
SYSTEM_ONE_MAX_TIMEOUT_MS,
|
|
59
|
+
SYSTEM_ONE_MIN_TIMEOUT_MS,
|
|
60
|
+
};
|
|
51
61
|
|
|
52
62
|
/** Choice: 2-255 options, Score: 2-10 levels (TypeSafe primitive limits; 2 is our authoring floor). */
|
|
53
63
|
const CHOICE_MIN_OPTIONS = 2;
|
|
@@ -55,14 +65,8 @@ const CHOICE_MAX_OPTIONS = 255;
|
|
|
55
65
|
const SCORE_MIN_LEVELS = 2;
|
|
56
66
|
const SCORE_MAX_LEVELS = 10;
|
|
57
67
|
|
|
58
|
-
/** Room left before the engine watchdog fires so the executor can report its own failure. */
|
|
59
|
-
const DEADLINE_MARGIN_MS = 250;
|
|
60
|
-
const BACKOFF_BASE_MS = 500;
|
|
61
|
-
const BACKOFF_CAP_MS = 8_000;
|
|
62
68
|
/** A returned distribution must sum to 1 within this tolerance. It is checked, never normalized. */
|
|
63
69
|
const DISTRIBUTION_TOLERANCE = 0.02;
|
|
64
|
-
const MAX_RESPONSE_CHARS = 1_048_576;
|
|
65
|
-
const MAX_ERROR_BODY_CHARS = 65_536;
|
|
66
70
|
|
|
67
71
|
/** Output ports of a node that sets `humanReview`, the same names `human-in-the-loop` uses. */
|
|
68
72
|
const REVIEW_PORTS = ["approved", "rejected", "timeout"] as const;
|
|
@@ -160,7 +164,11 @@ export type SystemOneHumanReview = z.infer<typeof HumanReviewSchema>;
|
|
|
160
164
|
|
|
161
165
|
export const SystemOneDecisionConfigSchema = z
|
|
162
166
|
.strictObject({
|
|
163
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Decision host. Unset means the `SYSTEM_ONE_DEFAULT_PROVIDER` global config
|
|
169
|
+
* value, read at run time (`typesafe` when that is unset too).
|
|
170
|
+
*/
|
|
171
|
+
provider: z.enum(SYSTEM_ONE_PROVIDER_IDS).optional(),
|
|
164
172
|
/** Provider-specific model id. Unset means the provider's default. */
|
|
165
173
|
model: z.string().min(1).optional(),
|
|
166
174
|
state: SystemOneStateSchema,
|
|
@@ -231,7 +239,9 @@ export const SystemOneDecisionConfigSchema = z
|
|
|
231
239
|
}
|
|
232
240
|
}
|
|
233
241
|
// Provider limits. A {{token}} model is skipped at save and checked again at run,
|
|
234
|
-
// where the interpolated config is parsed by this same schema.
|
|
242
|
+
// where the interpolated config is parsed by this same schema. With no provider
|
|
243
|
+
// the default is read at run time, so `decide` checks the limits there.
|
|
244
|
+
if (config.provider === undefined) return;
|
|
235
245
|
const modelProblem = systemOneModelProblem(
|
|
236
246
|
config.provider,
|
|
237
247
|
config.model ?? (SYSTEM_ONE_PROVIDERS[config.provider] as SystemOneProvider).defaultModel,
|
|
@@ -316,8 +326,18 @@ interface RetryShape {
|
|
|
316
326
|
maxRetries?: number;
|
|
317
327
|
}
|
|
318
328
|
|
|
329
|
+
/** Node types that call a System One host: paid, non-idempotent, with their own transient retries. */
|
|
330
|
+
const SYSTEM_ONE_NODE_LABELS: Record<string, string> = {
|
|
331
|
+
[SYSTEM_ONE_DECISION_NODE_TYPE]: "SystemOne Decision",
|
|
332
|
+
[SYSTEM_ONE_EXTRACTION_NODE_TYPE]: "SystemOne Extraction",
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
export function isSystemOneNodeType(type: string): boolean {
|
|
336
|
+
return Object.hasOwn(SYSTEM_ONE_NODE_LABELS, type);
|
|
337
|
+
}
|
|
338
|
+
|
|
319
339
|
/**
|
|
320
|
-
* Why a `system-one-decision` node may not carry an engine retry policy.
|
|
340
|
+
* Why a `system-one-decision` (or `system-one-extraction`) node may not carry an engine retry policy.
|
|
321
341
|
*
|
|
322
342
|
* The executor owns its transient retries (`config.maxRetries`). Engine retries
|
|
323
343
|
* are not status-aware, so a node-level or validation retry would re-send 401 / 422
|
|
@@ -329,18 +349,18 @@ export function systemOneRetryViolations(node: {
|
|
|
329
349
|
retry?: RetryShape | null;
|
|
330
350
|
validation?: { retry?: RetryShape | null } | null;
|
|
331
351
|
}): string[] {
|
|
332
|
-
if (node.type
|
|
352
|
+
if (!isSystemOneNodeType(node.type)) return [];
|
|
333
353
|
const violations: string[] = [];
|
|
334
354
|
const attempts = (retry: RetryShape | null | undefined) =>
|
|
335
355
|
retry ? (retry.maxRetries ?? ENGINE_RETRY_DEFAULT) : 0;
|
|
336
356
|
if (attempts(node.retry) > 0) {
|
|
337
357
|
violations.push(
|
|
338
|
-
`Node "${node.id}" (
|
|
358
|
+
`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`,
|
|
339
359
|
);
|
|
340
360
|
}
|
|
341
361
|
if (attempts(node.validation?.retry) > 0) {
|
|
342
362
|
violations.push(
|
|
343
|
-
`Node "${node.id}" (
|
|
363
|
+
`Node "${node.id}" (${node.type}) must not set validation.retry.maxRetries > 0: a validation-driven retry would send another paid request`,
|
|
344
364
|
);
|
|
345
365
|
}
|
|
346
366
|
return violations;
|
|
@@ -397,11 +417,17 @@ export function systemOneStaticShapeViolations(node: {
|
|
|
397
417
|
return violations;
|
|
398
418
|
}
|
|
399
419
|
|
|
400
|
-
/** The engine fails a
|
|
401
|
-
export function systemOneUnresolvedError(
|
|
420
|
+
/** The engine fails a System One step before dispatch when any config token is unresolved. */
|
|
421
|
+
export function systemOneUnresolvedError(
|
|
422
|
+
nodeId: string,
|
|
423
|
+
tokens: string[],
|
|
424
|
+
nodeType: string = SYSTEM_ONE_DECISION_NODE_TYPE,
|
|
425
|
+
): string {
|
|
402
426
|
const rendered = [...new Set(tokens)].map((token) => `{{${token}}}`).join(", ");
|
|
427
|
+
const label =
|
|
428
|
+
SYSTEM_ONE_NODE_LABELS[nodeType] ?? SYSTEM_ONE_NODE_LABELS[SYSTEM_ONE_DECISION_NODE_TYPE];
|
|
403
429
|
return (
|
|
404
|
-
|
|
430
|
+
`${label} node "${nodeId}" has unresolved interpolation token(s): ${rendered}. ` +
|
|
405
431
|
"No request was sent. Declare the value in the node's inputs mapping or fix the path."
|
|
406
432
|
);
|
|
407
433
|
}
|
|
@@ -795,79 +821,13 @@ export function resolveReviewedDecision(
|
|
|
795
821
|
|
|
796
822
|
// ─── Transport ──────────────────────────────────────────────
|
|
797
823
|
|
|
798
|
-
export interface SystemOneDecisionExecutorOptions {
|
|
799
|
-
/** Transport. Tests inject a fake; production uses the global `fetch`. */
|
|
800
|
-
fetch?: typeof fetch;
|
|
824
|
+
export interface SystemOneDecisionExecutorOptions extends SystemOneTransportOptions {
|
|
801
825
|
/** Credential lookup for any provider. Defaults to each provider's own source (see `SYSTEM_ONE_PROVIDERS`). */
|
|
802
826
|
getApiKey?: (provider: SystemOneProviderId) => Promise<string | null | undefined>;
|
|
803
827
|
/** Base-URL lookup for a provider with no fixed host (`laya`). Defaults to its global config value. */
|
|
804
828
|
getServerUrl?: (provider: SystemOneProviderId) => Promise<string | null | undefined>;
|
|
805
829
|
/** Environment the OpenRouter credential is resolved from. Defaults to `process.env`. */
|
|
806
830
|
env?: NodeJS.ProcessEnv;
|
|
807
|
-
sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
808
|
-
now?: () => number;
|
|
809
|
-
random?: () => number;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
type Attempt =
|
|
813
|
-
| { kind: "ok"; text: string; requestId?: string }
|
|
814
|
-
| { kind: "timeout" }
|
|
815
|
-
| { kind: "fail"; message: string; retryable: boolean; retryAfterMs?: number };
|
|
816
|
-
|
|
817
|
-
const REQUEST_ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
818
|
-
const ERROR_CODE_RE = /^[A-Za-z0-9._:-]{1,64}$/;
|
|
819
|
-
|
|
820
|
-
function readRequestId(headers: Headers): string | undefined {
|
|
821
|
-
// Cloudflare marks every response with `cf-ray` and sends no request id.
|
|
822
|
-
const value = headers.get("x-request-id") ?? headers.get("request-id") ?? headers.get("cf-ray");
|
|
823
|
-
return value && REQUEST_ID_RE.test(value) ? value : undefined;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
/** Retry-After as delta-seconds or an HTTP date, in ms. Undefined when absent or unparseable. */
|
|
827
|
-
function parseRetryAfter(value: string | null, now: number): number | undefined {
|
|
828
|
-
if (!value) return undefined;
|
|
829
|
-
const trimmed = value.trim();
|
|
830
|
-
if (/^\d+(\.\d+)?$/.test(trimmed)) return Math.round(Number(trimmed) * 1000);
|
|
831
|
-
const at = Date.parse(trimmed);
|
|
832
|
-
return Number.isNaN(at) ? undefined : Math.max(0, at - now);
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
/** Only a short identifier from the provider's error envelope. Never its message or echoed request. */
|
|
836
|
-
function readErrorCode(text: string): string | undefined {
|
|
837
|
-
if (text.length === 0 || text.length > MAX_ERROR_BODY_CHARS) return undefined;
|
|
838
|
-
let parsed: unknown;
|
|
839
|
-
try {
|
|
840
|
-
parsed = JSON.parse(text);
|
|
841
|
-
} catch {
|
|
842
|
-
return undefined;
|
|
843
|
-
}
|
|
844
|
-
if (!isRecord(parsed)) return undefined;
|
|
845
|
-
const nested = isRecord(parsed.error) ? parsed.error : undefined;
|
|
846
|
-
const detail = isRecord(parsed.detail) ? parsed.detail : undefined;
|
|
847
|
-
for (const candidate of [
|
|
848
|
-
nested?.code,
|
|
849
|
-
nested?.type,
|
|
850
|
-
detail?.error_type,
|
|
851
|
-
parsed.code,
|
|
852
|
-
parsed.type,
|
|
853
|
-
]) {
|
|
854
|
-
if (typeof candidate === "string" && ERROR_CODE_RE.test(candidate)) return candidate;
|
|
855
|
-
}
|
|
856
|
-
// Cloudflare's envelope: `{ success: false, errors: [{ code: 10000, message }] }`.
|
|
857
|
-
return cloudflareErrorCode(parsed);
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
function defaultSleep(ms: number, signal: AbortSignal): Promise<void> {
|
|
861
|
-
return new Promise((resolve) => {
|
|
862
|
-
if (signal.aborted) return resolve();
|
|
863
|
-
const timer = setTimeout(done, ms);
|
|
864
|
-
function done() {
|
|
865
|
-
clearTimeout(timer);
|
|
866
|
-
signal.removeEventListener("abort", done);
|
|
867
|
-
resolve();
|
|
868
|
-
}
|
|
869
|
-
signal.addEventListener("abort", done, { once: true });
|
|
870
|
-
});
|
|
871
831
|
}
|
|
872
832
|
|
|
873
833
|
// ─── Executor ───────────────────────────────────────────────
|
|
@@ -893,6 +853,11 @@ export class SystemOneDecisionExecutor extends BaseExecutor<
|
|
|
893
853
|
return { db: this.deps.db, env: this.options.env ?? process.env };
|
|
894
854
|
}
|
|
895
855
|
|
|
856
|
+
/** The provider a config without `provider` uses, read now so a settings change applies at once. */
|
|
857
|
+
private defaultProvider(): Promise<SystemOneProviderId> {
|
|
858
|
+
return resolveSystemOneDefaultProvider(this.providerContext());
|
|
859
|
+
}
|
|
860
|
+
|
|
896
861
|
private resolveTarget(id: SystemOneProviderId, model?: string) {
|
|
897
862
|
return resolveSystemOneTarget(id, this.providerContext(), {
|
|
898
863
|
apiKey: this.options.getApiKey,
|
|
@@ -909,9 +874,10 @@ export class SystemOneDecisionExecutor extends BaseExecutor<
|
|
|
909
874
|
nodes: readonly ExecutorReadinessNode[],
|
|
910
875
|
): Promise<ExecutorReadinessProblem[]> {
|
|
911
876
|
const nodeIdsByProvider = new Map<SystemOneProviderId, string[]>();
|
|
877
|
+
const defaultId = await this.defaultProvider();
|
|
912
878
|
for (const node of nodes) {
|
|
913
879
|
// An unknown provider is a config error the schema reports; it names no credential.
|
|
914
|
-
const id = systemOneProviderOf(node.config);
|
|
880
|
+
const id = systemOneProviderOf(node.config, defaultId);
|
|
915
881
|
if (id) nodeIdsByProvider.set(id, [...(nodeIdsByProvider.get(id) ?? []), node.id]);
|
|
916
882
|
}
|
|
917
883
|
const problems: ExecutorReadinessProblem[] = [];
|
|
@@ -949,29 +915,44 @@ export class SystemOneDecisionExecutor extends BaseExecutor<
|
|
|
949
915
|
*/
|
|
950
916
|
async decide(
|
|
951
917
|
config: SystemOneDecisionConfig,
|
|
952
|
-
): Promise<
|
|
953
|
-
|
|
918
|
+
): Promise<
|
|
919
|
+
| { ok: true; output: SystemOneDecisionOutput }
|
|
920
|
+
| { ok: false; error: string; deploymentRefused?: true }
|
|
921
|
+
> {
|
|
922
|
+
const providerId = config.provider ?? (await this.defaultProvider());
|
|
954
923
|
const provider: SystemOneProvider = SYSTEM_ONE_PROVIDERS[providerId];
|
|
955
924
|
|
|
956
925
|
// A provider with no default sends no `model` when the node sets none.
|
|
957
926
|
const model = config.model ?? provider.defaultModel;
|
|
958
927
|
const modelProblem = systemOneModelProblem(providerId, model);
|
|
959
928
|
if (modelProblem) return { ok: false, error: modelProblem };
|
|
929
|
+
// Checked at save only for a named provider; a defaulted one is checked here.
|
|
930
|
+
const limitProblem = systemOneQuestionLimitProblem(
|
|
931
|
+
providerId,
|
|
932
|
+
Object.keys(config.questions).length,
|
|
933
|
+
);
|
|
934
|
+
if (limitProblem) return { ok: false, error: limitProblem };
|
|
960
935
|
const target = await this.resolveTarget(providerId, model);
|
|
961
|
-
if (!target.ok) return
|
|
962
|
-
const { apiKey, endpoint } = target;
|
|
936
|
+
if (!target.ok) return target;
|
|
937
|
+
const { apiKey, endpoint, keyName } = target;
|
|
963
938
|
|
|
964
939
|
const wire = provider.wire ?? SYSTEM_ONE_WIRE;
|
|
965
940
|
const body = JSON.stringify(wire.buildRequest(config, model));
|
|
966
941
|
|
|
967
942
|
const scrub = (message: string) => message.split(apiKey).join("[REDACTED]");
|
|
968
|
-
const sent = await
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
943
|
+
const sent = await sendSystemOneRequest(
|
|
944
|
+
{
|
|
945
|
+
providerId,
|
|
946
|
+
capability: "decide",
|
|
947
|
+
endpoint,
|
|
948
|
+
apiKey,
|
|
949
|
+
keyName,
|
|
950
|
+
body,
|
|
951
|
+
timeoutMs: config.timeoutMs,
|
|
952
|
+
maxRetries: config.maxRetries,
|
|
953
|
+
env: this.providerContext().env,
|
|
954
|
+
},
|
|
955
|
+
this.options,
|
|
975
956
|
);
|
|
976
957
|
if (!sent.ok) return { ok: false, error: scrub(sent.error) };
|
|
977
958
|
|
|
@@ -1101,163 +1082,4 @@ export class SystemOneDecisionExecutor extends BaseExecutor<
|
|
|
1101
1082
|
): ApprovalOutcome | null {
|
|
1102
1083
|
return resolveReviewedDecision(parkedOutput, approval);
|
|
1103
1084
|
}
|
|
1104
|
-
|
|
1105
|
-
/**
|
|
1106
|
-
* POST once, retrying only transient failures inside one time budget.
|
|
1107
|
-
* Retries: connection errors, HTTP 408 / 429 / 5xx (incl. 529). Never: 401, 422,
|
|
1108
|
-
* other 4xx, redirects, or a bad success body (the caller validates that).
|
|
1109
|
-
*/
|
|
1110
|
-
private async send(
|
|
1111
|
-
providerId: SystemOneProviderId,
|
|
1112
|
-
endpoint: string,
|
|
1113
|
-
apiKey: string,
|
|
1114
|
-
body: string,
|
|
1115
|
-
timeoutMs: number,
|
|
1116
|
-
maxRetries: number,
|
|
1117
|
-
): Promise<{ ok: true; text: string; requestId?: string } | { ok: false; error: string }> {
|
|
1118
|
-
const doFetch = this.options.fetch ?? fetch;
|
|
1119
|
-
const sleep = this.options.sleep ?? defaultSleep;
|
|
1120
|
-
const now = this.options.now ?? Date.now;
|
|
1121
|
-
const random = this.options.random ?? Math.random;
|
|
1122
|
-
|
|
1123
|
-
const deadlineAt = now() + timeoutMs - DEADLINE_MARGIN_MS;
|
|
1124
|
-
const controller = new AbortController();
|
|
1125
|
-
const timer = setTimeout(() => controller.abort(), Math.max(0, deadlineAt - now()));
|
|
1126
|
-
const timedOut = () =>
|
|
1127
|
-
({
|
|
1128
|
-
ok: false,
|
|
1129
|
-
error: `SystemOne request exceeded its ${timeoutMs}ms time budget`,
|
|
1130
|
-
}) as const;
|
|
1131
|
-
|
|
1132
|
-
try {
|
|
1133
|
-
for (let attempt = 0; ; attempt++) {
|
|
1134
|
-
const result = await this.attemptOnce(
|
|
1135
|
-
doFetch,
|
|
1136
|
-
providerId,
|
|
1137
|
-
endpoint,
|
|
1138
|
-
apiKey,
|
|
1139
|
-
body,
|
|
1140
|
-
controller.signal,
|
|
1141
|
-
now,
|
|
1142
|
-
);
|
|
1143
|
-
if (result.kind === "ok") {
|
|
1144
|
-
return { ok: true, text: result.text, requestId: result.requestId };
|
|
1145
|
-
}
|
|
1146
|
-
if (result.kind === "timeout" || controller.signal.aborted) return timedOut();
|
|
1147
|
-
|
|
1148
|
-
const attempts = attempt + 1;
|
|
1149
|
-
const suffix = attempts > 1 ? ` after ${attempts} attempts` : "";
|
|
1150
|
-
if (!result.retryable || attempt >= maxRetries) {
|
|
1151
|
-
return { ok: false, error: `${result.message}${suffix}` };
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
const backoff = Math.min(BACKOFF_CAP_MS, BACKOFF_BASE_MS * 2 ** attempt);
|
|
1155
|
-
const delay = result.retryAfterMs ?? Math.round(backoff * (0.5 + random() * 0.5));
|
|
1156
|
-
if (delay >= deadlineAt - now()) {
|
|
1157
|
-
return {
|
|
1158
|
-
ok: false,
|
|
1159
|
-
error: `${result.message}${suffix}; the ${delay}ms retry delay does not fit the ${timeoutMs}ms time budget`,
|
|
1160
|
-
};
|
|
1161
|
-
}
|
|
1162
|
-
await sleep(delay, controller.signal);
|
|
1163
|
-
if (controller.signal.aborted) return timedOut();
|
|
1164
|
-
}
|
|
1165
|
-
} finally {
|
|
1166
|
-
clearTimeout(timer);
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
private async attemptOnce(
|
|
1171
|
-
doFetch: typeof fetch,
|
|
1172
|
-
providerId: SystemOneProviderId,
|
|
1173
|
-
endpoint: string,
|
|
1174
|
-
apiKey: string,
|
|
1175
|
-
body: string,
|
|
1176
|
-
signal: AbortSignal,
|
|
1177
|
-
now: () => number,
|
|
1178
|
-
): Promise<Attempt> {
|
|
1179
|
-
let res: Response;
|
|
1180
|
-
try {
|
|
1181
|
-
res = await doFetch(endpoint, {
|
|
1182
|
-
method: "POST",
|
|
1183
|
-
headers: {
|
|
1184
|
-
...getOpenRouterAttributionHeaders(endpoint, this.providerContext().env),
|
|
1185
|
-
"content-type": "application/json",
|
|
1186
|
-
accept: "application/json",
|
|
1187
|
-
authorization: `Bearer ${apiKey}`,
|
|
1188
|
-
},
|
|
1189
|
-
body,
|
|
1190
|
-
redirect: "manual",
|
|
1191
|
-
signal,
|
|
1192
|
-
});
|
|
1193
|
-
} catch (err) {
|
|
1194
|
-
if (signal.aborted) return { kind: "timeout" };
|
|
1195
|
-
const code = (err as { code?: unknown })?.code;
|
|
1196
|
-
const detail = typeof code === "string" && /^[A-Z0-9_]{3,40}$/.test(code) ? ` (${code})` : "";
|
|
1197
|
-
return {
|
|
1198
|
-
kind: "fail",
|
|
1199
|
-
message: `SystemOne request failed: network error${detail}`,
|
|
1200
|
-
retryable: true,
|
|
1201
|
-
};
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
const requestId = readRequestId(res.headers);
|
|
1205
|
-
const withId = (message: string) => (requestId ? `${message} [request ${requestId}]` : message);
|
|
1206
|
-
|
|
1207
|
-
if (res.status >= 200 && res.status < 300) {
|
|
1208
|
-
let text: string;
|
|
1209
|
-
try {
|
|
1210
|
-
text = await res.text();
|
|
1211
|
-
} catch {
|
|
1212
|
-
if (signal.aborted) return { kind: "timeout" };
|
|
1213
|
-
return {
|
|
1214
|
-
kind: "fail",
|
|
1215
|
-
message: withId("SystemOne response body could not be read"),
|
|
1216
|
-
retryable: true,
|
|
1217
|
-
};
|
|
1218
|
-
}
|
|
1219
|
-
if (text.length > MAX_RESPONSE_CHARS) {
|
|
1220
|
-
return {
|
|
1221
|
-
kind: "fail",
|
|
1222
|
-
message: withId("SystemOne response exceeded the size limit"),
|
|
1223
|
-
retryable: false,
|
|
1224
|
-
};
|
|
1225
|
-
}
|
|
1226
|
-
return { kind: "ok", text, requestId };
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
if (res.status >= 300 && res.status < 400) {
|
|
1230
|
-
return {
|
|
1231
|
-
kind: "fail",
|
|
1232
|
-
message: withId(
|
|
1233
|
-
`SystemOne request was redirected (HTTP ${res.status}); redirects are refused`,
|
|
1234
|
-
),
|
|
1235
|
-
retryable: false,
|
|
1236
|
-
};
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
let errorText = "";
|
|
1240
|
-
try {
|
|
1241
|
-
errorText = await res.text();
|
|
1242
|
-
} catch {
|
|
1243
|
-
if (signal.aborted) return { kind: "timeout" };
|
|
1244
|
-
}
|
|
1245
|
-
const code = readErrorCode(errorText);
|
|
1246
|
-
// The host answered but does not accept the key: name it, never retry.
|
|
1247
|
-
if (res.status === 401 || res.status === 403) {
|
|
1248
|
-
return {
|
|
1249
|
-
kind: "fail",
|
|
1250
|
-
message: withId(systemOneKeyRejectedMessage(providerId, res.status, code)),
|
|
1251
|
-
retryable: false,
|
|
1252
|
-
};
|
|
1253
|
-
}
|
|
1254
|
-
const detail = code ? ` (${code})` : "";
|
|
1255
|
-
const retryable = res.status === 408 || res.status === 429 || res.status >= 500;
|
|
1256
|
-
return {
|
|
1257
|
-
kind: "fail",
|
|
1258
|
-
message: withId(`SystemOne API returned HTTP ${res.status}${detail}`),
|
|
1259
|
-
retryable,
|
|
1260
|
-
retryAfterMs: retryable ? parseRetryAfter(res.headers.get("retry-after"), now()) : undefined,
|
|
1261
|
-
};
|
|
1262
|
-
}
|
|
1263
1085
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { SystemOneExtractionConfig } from "./system-one-extraction";
|
|
2
|
+
import {
|
|
3
|
+
cloudflareErrorCode,
|
|
4
|
+
isRecord,
|
|
5
|
+
SystemOneWireError,
|
|
6
|
+
wireContractError,
|
|
7
|
+
} from "./system-one-wire";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* How one host's extraction request and response map onto the `system-one-extraction`
|
|
11
|
+
* contract: Clairic's `{ model, state, fields, images?, reference_date? }` request and
|
|
12
|
+
* `{ model, answers, truncated, usage }` body. A host with another shape supplies a
|
|
13
|
+
* wire that builds its body and returns that shape, which
|
|
14
|
+
* `validateSystemOneExtraction` then checks.
|
|
15
|
+
*/
|
|
16
|
+
export interface SystemOneExtractWire {
|
|
17
|
+
/** Request body for one call. */
|
|
18
|
+
buildRequest(config: SystemOneExtractionConfig, model: string | undefined): unknown;
|
|
19
|
+
/**
|
|
20
|
+
* Turn a parsed success body into the Clairic `{ model, answers, usage }` shape, or
|
|
21
|
+
* throw a `SystemOneWireError`. The result is still validated afterwards.
|
|
22
|
+
*/
|
|
23
|
+
toExtraction(body: unknown): unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Clairic reads one text; structured state is sent as JSON text, which spans then index into. */
|
|
27
|
+
export function extractionStateText(state: SystemOneExtractionConfig["state"]): string {
|
|
28
|
+
return typeof state === "string" ? state : JSON.stringify(state);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The Clairic request as is: field specs already use Clairic's option names. */
|
|
32
|
+
export const CLAIRIC_WIRE: SystemOneExtractWire = {
|
|
33
|
+
buildRequest: (config, model) => ({
|
|
34
|
+
...(model === undefined ? {} : { model }),
|
|
35
|
+
state: extractionStateText(config.state),
|
|
36
|
+
fields: config.fields,
|
|
37
|
+
...(config.images?.length ? { images: config.images } : {}),
|
|
38
|
+
...(config.referenceDate ? { reference_date: config.referenceDate } : {}),
|
|
39
|
+
}),
|
|
40
|
+
toExtraction: (body) => body,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Cloudflare Workers AI: the Clairic request, the response wrapped in `{ result, success, errors }`. */
|
|
44
|
+
export const CLOUDFLARE_EXTRACT_WIRE: SystemOneExtractWire = {
|
|
45
|
+
buildRequest: CLAIRIC_WIRE.buildRequest,
|
|
46
|
+
toExtraction(body) {
|
|
47
|
+
if (!isRecord(body)) wireContractError("response body must be a JSON object");
|
|
48
|
+
if (body.success !== true) {
|
|
49
|
+
const code = cloudflareErrorCode(body);
|
|
50
|
+
throw new SystemOneWireError(
|
|
51
|
+
`Cloudflare Workers AI reported a failed run${code ? ` (${code})` : ""}. Nothing was extracted.`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (!isRecord(body.result)) wireContractError("Cloudflare response is missing its result");
|
|
55
|
+
return body.result;
|
|
56
|
+
},
|
|
57
|
+
};
|