@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,17 +1,21 @@
|
|
|
1
1
|
import { DEFAULT_OPENROUTER_BASE_URL, getOpenRouterBaseUrl } from "../../utils/openrouter-base-url";
|
|
2
2
|
import type { ExecutorDependencies } from "./base";
|
|
3
|
+
import { CLOUDFLARE_EXTRACT_WIRE, type SystemOneExtractWire } from "./system-one-extract-wire";
|
|
3
4
|
import { OPENAI_DECISIONS_WIRE } from "./system-one-openai-wire";
|
|
4
5
|
import { CLOUDFLARE_WIRE, type SystemOneWire } from "./system-one-wire";
|
|
5
6
|
import { resolveWorkflowLlmConfig } from "./workflow-llm";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
* Hosts
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* Hosts of System One models. One registry serves every capability: a host
|
|
10
|
+
* declares what it serves (`decide` for typed decisions: Jev, laya, OpenAI
|
|
11
|
+
* Decisions, Cloudflare Clef; `extract` for typed extraction: Cloudflare Clairic)
|
|
12
|
+
* and, per capability, its endpoint, models, limits, and wire. The credential, the
|
|
13
|
+
* deployment check, and every message are the host's, so a decision node and an
|
|
14
|
+
* extraction node on the same host fail with the same sentence.
|
|
15
|
+
*
|
|
16
|
+
* A node names a host by id (`config.provider`); the endpoint, header shape, and
|
|
17
|
+
* key name live here, never in a workflow definition. Adding a host, or a
|
|
18
|
+
* capability to one, is one entry in `SYSTEM_ONE_HOSTS`.
|
|
15
19
|
*
|
|
16
20
|
* Do not add a chat-completions route. OpenRouter's `typesafe/jev-router` is a
|
|
17
21
|
* router that forwards a chat request to another model, and `~typesafe/jev-latest`
|
|
@@ -19,6 +23,8 @@ import { resolveWorkflowLlmConfig } from "./workflow-llm";
|
|
|
19
23
|
* runs Jev itself.
|
|
20
24
|
*/
|
|
21
25
|
|
|
26
|
+
export type SystemOneCapability = "decide" | "extract";
|
|
27
|
+
|
|
22
28
|
export interface SystemOneProviderContext {
|
|
23
29
|
db: ExecutorDependencies["db"];
|
|
24
30
|
env: NodeJS.ProcessEnv;
|
|
@@ -48,13 +54,18 @@ export interface SystemOneBuiltEndpoint {
|
|
|
48
54
|
readonly build: (value: string, model: string) => string | null;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
export
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
export type SystemOneEndpoint = string | SystemOneConfiguredEndpoint | SystemOneBuiltEndpoint;
|
|
58
|
+
|
|
59
|
+
/** A credential read from config, with the name it was read under. */
|
|
60
|
+
export interface SystemOneCredential {
|
|
61
|
+
value: string;
|
|
62
|
+
keyName: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** What every capability of a host declares. */
|
|
66
|
+
interface SystemOneServiceBase {
|
|
56
67
|
/** A fixed URL, the global config key that names the server, or a URL built from config and model. */
|
|
57
|
-
readonly endpoint:
|
|
68
|
+
readonly endpoint: SystemOneEndpoint;
|
|
58
69
|
/**
|
|
59
70
|
* Model used when the node sets none. Absent means no `model` is sent and the
|
|
60
71
|
* host picks, for a host that ignores a model id it does not know.
|
|
@@ -62,6 +73,9 @@ export interface SystemOneProvider {
|
|
|
62
73
|
readonly defaultModel?: string;
|
|
63
74
|
/** When set, the only model ids the node may use (checked at save and at run). */
|
|
64
75
|
readonly allowedModels?: readonly string[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface SystemOneDecideService extends SystemOneServiceBase {
|
|
65
79
|
/** Host limits stricter than the node's own. */
|
|
66
80
|
readonly limits?: { readonly maxQuestions?: number };
|
|
67
81
|
/** Request and response translation. Absent means the SystemOne wire as is. */
|
|
@@ -71,18 +85,48 @@ export interface SystemOneProvider {
|
|
|
71
85
|
* the output reports and what a `humanReview` band is tested against.
|
|
72
86
|
*/
|
|
73
87
|
readonly confidenceField: "confidence" | "answer_confidence";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface SystemOneExtractService extends SystemOneServiceBase {
|
|
91
|
+
/** Host limits stricter than the node's own. */
|
|
92
|
+
readonly limits?: { readonly maxFields?: number; readonly maxImages?: number };
|
|
93
|
+
/** Request and response translation. */
|
|
94
|
+
readonly wire: SystemOneExtractWire;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface SystemOneHost {
|
|
98
|
+
/** Name used in messages, e.g. "TypeSafe". */
|
|
99
|
+
readonly label: string;
|
|
100
|
+
/** The credential this host needs: a global secret name, the one messages tell an author to set. */
|
|
101
|
+
readonly keyName: string;
|
|
102
|
+
/** Other secret names read, in order, when `keyName` is unset. Messages mention them. */
|
|
103
|
+
readonly fallbackKeyNames?: readonly string[];
|
|
74
104
|
/** A deployment setting that makes this host unusable, or null. */
|
|
75
105
|
readonly deploymentProblem?: (env: NodeJS.ProcessEnv) => string | null;
|
|
76
|
-
/**
|
|
77
|
-
|
|
106
|
+
/**
|
|
107
|
+
* The credential value (a string is read under `keyName`), or null/blank when
|
|
108
|
+
* absent. May throw; callers never relay the cause.
|
|
109
|
+
*/
|
|
110
|
+
readonly readKey: (
|
|
111
|
+
ctx: SystemOneProviderContext,
|
|
112
|
+
) => Promise<SystemOneCredential | string | null | undefined>;
|
|
113
|
+
readonly decide?: SystemOneDecideService;
|
|
114
|
+
readonly extract?: SystemOneExtractService;
|
|
78
115
|
}
|
|
79
116
|
|
|
117
|
+
/**
|
|
118
|
+
* A decision host as one flat record: the host's credential and messages plus its
|
|
119
|
+
* `decide` service. `system-one-decision` reads this shape.
|
|
120
|
+
*/
|
|
121
|
+
export type SystemOneProvider = Omit<SystemOneHost, "decide" | "extract"> & SystemOneDecideService;
|
|
122
|
+
|
|
80
123
|
const TYPESAFE_KEY = "TYPESAFE_API_KEY";
|
|
81
124
|
const OPENROUTER_KEY = "OPENROUTER_API_KEY";
|
|
82
125
|
const LAYA_KEY = "LAYA_API_KEY";
|
|
83
126
|
const LAYA_URL_KEY = "LAYA_URL";
|
|
84
127
|
const OPENAI_DECISIONS_KEY = "OPENAI_DECISIONS_API_KEY";
|
|
85
128
|
const CLOUDFLARE_TOKEN_KEY = "CLOUDFLARE_API_TOKEN";
|
|
129
|
+
const CLOUDFLARE_KEY_FALLBACK = "CLOUDFLARE_API_KEY";
|
|
86
130
|
const CLOUDFLARE_ACCOUNT_KEY = "CLOUDFLARE_ACCOUNT_ID";
|
|
87
131
|
const CLOUDFLARE_ACCOUNT_RE = /^[0-9a-f]{32}$/;
|
|
88
132
|
|
|
@@ -95,21 +139,44 @@ async function readGlobalConfig(
|
|
|
95
139
|
return rows.find((row) => row.scope === "global" && row.key === key)?.value;
|
|
96
140
|
}
|
|
97
141
|
|
|
98
|
-
|
|
142
|
+
/** The first of `keys` with a non-blank global value, with the key it came from. */
|
|
143
|
+
async function readFirstGlobalConfig(
|
|
144
|
+
db: ExecutorDependencies["db"],
|
|
145
|
+
keys: readonly string[],
|
|
146
|
+
): Promise<SystemOneCredential | undefined> {
|
|
147
|
+
for (const keyName of keys) {
|
|
148
|
+
const value = await readGlobalConfig(db, keyName);
|
|
149
|
+
if (value && value.trim() !== "") return { value, keyName };
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Every Workers AI model is reached at the same account-scoped route; only the model differs. */
|
|
155
|
+
const CLOUDFLARE_ENDPOINT: SystemOneBuiltEndpoint = {
|
|
156
|
+
configKey: CLOUDFLARE_ACCOUNT_KEY,
|
|
157
|
+
describe: "the 32-character id of a Cloudflare account",
|
|
158
|
+
build: (accountId, model) => {
|
|
159
|
+
const id = accountId.trim();
|
|
160
|
+
return CLOUDFLARE_ACCOUNT_RE.test(id)
|
|
161
|
+
? `https://api.cloudflare.com/client/v4/accounts/${id}/ai/run/@cf/cloudflare/${model}`
|
|
162
|
+
: null;
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export const SYSTEM_ONE_HOSTS = {
|
|
99
167
|
typesafe: {
|
|
100
168
|
label: "TypeSafe",
|
|
101
169
|
keyName: TYPESAFE_KEY,
|
|
102
|
-
endpoint: "https://api.typesafe.ai/v1/systemone",
|
|
103
|
-
defaultModel: "jev-latest",
|
|
104
|
-
confidenceField: "confidence",
|
|
105
170
|
readKey: ({ db }) => readGlobalConfig(db, TYPESAFE_KEY),
|
|
171
|
+
decide: {
|
|
172
|
+
endpoint: "https://api.typesafe.ai/v1/systemone",
|
|
173
|
+
defaultModel: "jev-latest",
|
|
174
|
+
confidenceField: "confidence",
|
|
175
|
+
},
|
|
106
176
|
},
|
|
107
177
|
openrouter: {
|
|
108
178
|
label: "OpenRouter",
|
|
109
179
|
keyName: OPENROUTER_KEY,
|
|
110
|
-
endpoint: "https://openrouter.ai/api/alpha/decisions",
|
|
111
|
-
defaultModel: "~typesafe/jev-latest",
|
|
112
|
-
confidenceField: "confidence",
|
|
113
180
|
deploymentProblem(env) {
|
|
114
181
|
// A gateway URL means OPENROUTER_API_KEY may be a gateway token, and the
|
|
115
182
|
// decisions API is a sibling of /v1 that a chat gateway need not forward.
|
|
@@ -127,158 +194,276 @@ export const SYSTEM_ONE_PROVIDERS = {
|
|
|
127
194
|
return null;
|
|
128
195
|
}
|
|
129
196
|
},
|
|
197
|
+
decide: {
|
|
198
|
+
endpoint: "https://openrouter.ai/api/alpha/decisions",
|
|
199
|
+
defaultModel: "~typesafe/jev-latest",
|
|
200
|
+
confidenceField: "confidence",
|
|
201
|
+
},
|
|
130
202
|
},
|
|
131
203
|
laya: {
|
|
132
204
|
label: "laya",
|
|
133
205
|
keyName: LAYA_KEY,
|
|
134
|
-
endpoint: { configKey: LAYA_URL_KEY, path: "/v1/systemone" },
|
|
135
|
-
// No default model. laya answers an id it does not know (`jev-latest` included) by
|
|
136
|
-
// routing on the text instead, with HTTP 200, so a default would be sent as if it meant something.
|
|
137
|
-
// `confidence` on a choice or score is entropy-based (0.47 top probability reads
|
|
138
|
-
// ~0.15). `answer_confidence` is the top probability, the number a `noul` band
|
|
139
|
-
// already uses, so one band means the same thing on every question type.
|
|
140
|
-
confidenceField: "answer_confidence",
|
|
141
206
|
readKey: ({ db }) => readGlobalConfig(db, LAYA_KEY),
|
|
207
|
+
decide: {
|
|
208
|
+
endpoint: { configKey: LAYA_URL_KEY, path: "/v1/systemone" },
|
|
209
|
+
// No default model. laya answers an id it does not know (`jev-latest` included) by
|
|
210
|
+
// routing on the text instead, with HTTP 200, so a default would be sent as if it meant something.
|
|
211
|
+
// `confidence` on a choice or score is entropy-based (0.47 top probability reads
|
|
212
|
+
// ~0.15). `answer_confidence` is the top probability, the number a `noul` band
|
|
213
|
+
// already uses, so one band means the same thing on every question type.
|
|
214
|
+
confidenceField: "answer_confidence",
|
|
215
|
+
},
|
|
142
216
|
},
|
|
143
217
|
openai: {
|
|
144
218
|
label: "OpenAI",
|
|
145
219
|
// Not OPENAI_API_KEY: a global row of that name reaches every worker's env and
|
|
146
220
|
// can change harness auth. This key serves this node only, and needs Decisions access.
|
|
147
221
|
keyName: OPENAI_DECISIONS_KEY,
|
|
148
|
-
endpoint: "https://api.openai.com/v1/decisions",
|
|
149
|
-
defaultModel: "gpt-6-luna",
|
|
150
|
-
// OpenAI's own field; it is not the top probability.
|
|
151
|
-
confidenceField: "confidence",
|
|
152
|
-
wire: OPENAI_DECISIONS_WIRE,
|
|
153
222
|
readKey: ({ db }) => readGlobalConfig(db, OPENAI_DECISIONS_KEY),
|
|
223
|
+
decide: {
|
|
224
|
+
endpoint: "https://api.openai.com/v1/decisions",
|
|
225
|
+
defaultModel: "gpt-6-luna",
|
|
226
|
+
// OpenAI's own field; it is not the top probability.
|
|
227
|
+
confidenceField: "confidence",
|
|
228
|
+
wire: OPENAI_DECISIONS_WIRE,
|
|
229
|
+
},
|
|
154
230
|
},
|
|
155
231
|
cloudflare: {
|
|
156
232
|
label: "Cloudflare Workers AI",
|
|
157
233
|
keyName: CLOUDFLARE_TOKEN_KEY,
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
234
|
+
// A Workers AI API token is a bearer token whatever the secret is called; a
|
|
235
|
+
// deployment that stored it as CLOUDFLARE_API_KEY works without a rename.
|
|
236
|
+
fallbackKeyNames: [CLOUDFLARE_KEY_FALLBACK],
|
|
237
|
+
readKey: ({ db }) => readFirstGlobalConfig(db, [CLOUDFLARE_TOKEN_KEY, CLOUDFLARE_KEY_FALLBACK]),
|
|
238
|
+
decide: {
|
|
239
|
+
endpoint: CLOUDFLARE_ENDPOINT,
|
|
240
|
+
defaultModel: "clef",
|
|
241
|
+
// The model is part of the URL, so only these ids are accepted.
|
|
242
|
+
allowedModels: ["clef", "clef-flash"],
|
|
243
|
+
limits: { maxQuestions: 64 },
|
|
244
|
+
confidenceField: "confidence",
|
|
245
|
+
wire: CLOUDFLARE_WIRE,
|
|
246
|
+
},
|
|
247
|
+
extract: {
|
|
248
|
+
endpoint: CLOUDFLARE_ENDPOINT,
|
|
249
|
+
defaultModel: "clairic",
|
|
250
|
+
allowedModels: ["clairic"],
|
|
251
|
+
limits: { maxFields: 64, maxImages: 4 },
|
|
252
|
+
wire: CLOUDFLARE_EXTRACT_WIRE,
|
|
167
253
|
},
|
|
168
|
-
defaultModel: "clef",
|
|
169
|
-
// The model is part of the URL, so only these ids are accepted.
|
|
170
|
-
allowedModels: ["clef", "clef-flash"],
|
|
171
|
-
limits: { maxQuestions: 64 },
|
|
172
|
-
confidenceField: "confidence",
|
|
173
|
-
wire: CLOUDFLARE_WIRE,
|
|
174
|
-
readKey: ({ db }) => readGlobalConfig(db, CLOUDFLARE_TOKEN_KEY),
|
|
175
254
|
},
|
|
176
|
-
} as const satisfies Record<string,
|
|
255
|
+
} as const satisfies Record<string, SystemOneHost>;
|
|
177
256
|
|
|
178
|
-
export type
|
|
257
|
+
export type SystemOneHostId = keyof typeof SYSTEM_ONE_HOSTS;
|
|
179
258
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
];
|
|
259
|
+
type HostsServing<C extends SystemOneCapability> = {
|
|
260
|
+
[K in SystemOneHostId]: (typeof SYSTEM_ONE_HOSTS)[K] extends Record<C, object> ? K : never;
|
|
261
|
+
}[SystemOneHostId];
|
|
184
262
|
|
|
263
|
+
/** A host that serves typed decisions. */
|
|
264
|
+
export type SystemOneProviderId = HostsServing<"decide">;
|
|
265
|
+
/** A host that serves typed extraction. */
|
|
266
|
+
export type SystemOneExtractProviderId = HostsServing<"extract">;
|
|
267
|
+
|
|
268
|
+
function hostsServing<C extends SystemOneCapability>(
|
|
269
|
+
capability: C,
|
|
270
|
+
): [HostsServing<C>, ...HostsServing<C>[]] {
|
|
271
|
+
const ids = (Object.keys(SYSTEM_ONE_HOSTS) as SystemOneHostId[]).filter(
|
|
272
|
+
(id) => (SYSTEM_ONE_HOSTS[id] as SystemOneHost)[capability] !== undefined,
|
|
273
|
+
);
|
|
274
|
+
return ids as [HostsServing<C>, ...HostsServing<C>[]];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** The decision view of every host that decides: the host's shared fields plus its `decide` service. */
|
|
278
|
+
export const SYSTEM_ONE_PROVIDERS = Object.fromEntries(
|
|
279
|
+
hostsServing("decide").map((id) => {
|
|
280
|
+
const { decide, extract: _extract, ...host } = SYSTEM_ONE_HOSTS[id] as SystemOneHost;
|
|
281
|
+
return [id, { ...host, ...decide }];
|
|
282
|
+
}),
|
|
283
|
+
) as {
|
|
284
|
+
[K in SystemOneProviderId]: Omit<(typeof SYSTEM_ONE_HOSTS)[K], "decide" | "extract"> &
|
|
285
|
+
(typeof SYSTEM_ONE_HOSTS)[K]["decide"];
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export const SYSTEM_ONE_PROVIDER_IDS = hostsServing("decide");
|
|
289
|
+
export const SYSTEM_ONE_EXTRACT_PROVIDER_IDS = hostsServing("extract");
|
|
290
|
+
|
|
291
|
+
/** The decision provider when neither the call nor `SYSTEM_ONE_DEFAULT_PROVIDER` names one. */
|
|
185
292
|
export const SYSTEM_ONE_DEFAULT_PROVIDER: SystemOneProviderId = "typesafe";
|
|
293
|
+
export const SYSTEM_ONE_EXTRACT_DEFAULT_PROVIDER: SystemOneExtractProviderId = "cloudflare";
|
|
294
|
+
|
|
295
|
+
/** Global config key naming the decision provider a node or call without `provider` uses. */
|
|
296
|
+
export const SYSTEM_ONE_DEFAULT_PROVIDER_CONFIG_KEY = "SYSTEM_ONE_DEFAULT_PROVIDER";
|
|
186
297
|
|
|
187
298
|
export function isSystemOneProviderId(value: unknown): value is SystemOneProviderId {
|
|
188
|
-
return
|
|
299
|
+
return (
|
|
300
|
+
typeof value === "string" && (SYSTEM_ONE_PROVIDER_IDS as readonly string[]).includes(value)
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* The decision provider for a node or call that names none: the global
|
|
306
|
+
* `SYSTEM_ONE_DEFAULT_PROVIDER` row, then the same key in the environment, then
|
|
307
|
+
* `typesafe`. Read at run time, so changing the setting needs no workflow edit.
|
|
308
|
+
* A value that is not a decide host is skipped; the config guard refuses it on save.
|
|
309
|
+
*/
|
|
310
|
+
export async function resolveSystemOneDefaultProvider(
|
|
311
|
+
ctx: SystemOneProviderContext,
|
|
312
|
+
): Promise<SystemOneProviderId> {
|
|
313
|
+
let stored: string | undefined;
|
|
314
|
+
try {
|
|
315
|
+
stored = await readGlobalConfig(ctx.db, SYSTEM_ONE_DEFAULT_PROVIDER_CONFIG_KEY);
|
|
316
|
+
} catch {
|
|
317
|
+
// An unreadable row falls back like an unset one: the default stays usable.
|
|
318
|
+
}
|
|
319
|
+
for (const raw of [stored, ctx.env[SYSTEM_ONE_DEFAULT_PROVIDER_CONFIG_KEY]]) {
|
|
320
|
+
const id = raw?.trim().toLowerCase();
|
|
321
|
+
if (isSystemOneProviderId(id)) return id;
|
|
322
|
+
}
|
|
323
|
+
return SYSTEM_ONE_DEFAULT_PROVIDER;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function isSystemOneExtractProviderId(value: unknown): value is SystemOneExtractProviderId {
|
|
327
|
+
return (
|
|
328
|
+
typeof value === "string" &&
|
|
329
|
+
(SYSTEM_ONE_EXTRACT_PROVIDER_IDS as readonly string[]).includes(value)
|
|
330
|
+
);
|
|
189
331
|
}
|
|
190
332
|
|
|
191
|
-
/**
|
|
333
|
+
/**
|
|
334
|
+
* Provider a raw node config selects, `defaultId` when it names none. Undefined
|
|
335
|
+
* when the field is present but not a known id.
|
|
336
|
+
*/
|
|
192
337
|
export function systemOneProviderOf(
|
|
193
338
|
config: Record<string, unknown>,
|
|
339
|
+
defaultId: SystemOneProviderId = SYSTEM_ONE_DEFAULT_PROVIDER,
|
|
194
340
|
): SystemOneProviderId | undefined {
|
|
195
|
-
if (config.provider === undefined) return
|
|
341
|
+
if (config.provider === undefined) return defaultId;
|
|
196
342
|
return isSystemOneProviderId(config.provider) ? config.provider : undefined;
|
|
197
343
|
}
|
|
198
344
|
|
|
345
|
+
/** Extraction provider a raw node config selects. Undefined when present but not a known id. */
|
|
346
|
+
export function systemOneExtractProviderOf(
|
|
347
|
+
config: Record<string, unknown>,
|
|
348
|
+
): SystemOneExtractProviderId | undefined {
|
|
349
|
+
if (config.provider === undefined) return SYSTEM_ONE_EXTRACT_DEFAULT_PROVIDER;
|
|
350
|
+
return isSystemOneExtractProviderId(config.provider) ? config.provider : undefined;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** The service a host runs for one capability. Callers pass an id that serves it. */
|
|
354
|
+
function serviceOf(id: SystemOneHostId, capability: SystemOneCapability): SystemOneServiceBase {
|
|
355
|
+
const service = (SYSTEM_ONE_HOSTS[id] as SystemOneHost)[capability];
|
|
356
|
+
if (!service) throw new Error(`SystemOne provider "${id}" does not serve ${capability}`);
|
|
357
|
+
return service;
|
|
358
|
+
}
|
|
359
|
+
|
|
199
360
|
// ─── Messages ───────────────────────────────────────────────
|
|
200
361
|
//
|
|
201
362
|
// Every message names the exact credential and where to set it. They are shared by
|
|
202
363
|
// the save-time warning, the run-start stop, and the step itself, so an author
|
|
203
364
|
// sees the same sentence wherever the problem surfaces.
|
|
204
365
|
|
|
366
|
+
/** What a call that failed did not produce, for the end of a message. */
|
|
367
|
+
const NOTHING_PRODUCED: Record<SystemOneCapability, string> = {
|
|
368
|
+
decide: "No decision was made.",
|
|
369
|
+
extract: "Nothing was extracted.",
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
export function systemOneNothingProduced(capability: SystemOneCapability): string {
|
|
373
|
+
return NOTHING_PRODUCED[capability];
|
|
374
|
+
}
|
|
375
|
+
|
|
205
376
|
const SET_IT = (keyName: string) =>
|
|
206
377
|
`Set ${keyName} as a global secret on the Secrets page (Settings > Secrets).`;
|
|
207
378
|
|
|
208
379
|
const SET_URL = (configKey: string) =>
|
|
209
380
|
`Set ${configKey} as a global config value (set-config with scope global).`;
|
|
210
381
|
|
|
211
|
-
export function systemOneKeyMissingMessage(id:
|
|
212
|
-
const { label, keyName } =
|
|
213
|
-
|
|
382
|
+
export function systemOneKeyMissingMessage(id: SystemOneHostId): string {
|
|
383
|
+
const { label, keyName, fallbackKeyNames }: SystemOneHost = SYSTEM_ONE_HOSTS[id];
|
|
384
|
+
const also = fallbackKeyNames?.length
|
|
385
|
+
? ` (${fallbackKeyNames.join(", ")} is read when it is unset, and is not configured either)`
|
|
386
|
+
: "";
|
|
387
|
+
return `${keyName} is not configured${also}. SystemOne provider "${id}" needs a working ${label} API key. ${SET_IT(keyName)}`;
|
|
214
388
|
}
|
|
215
389
|
|
|
216
|
-
export function systemOneKeyUnreadableMessage(id:
|
|
390
|
+
export function systemOneKeyUnreadableMessage(id: SystemOneHostId): string {
|
|
217
391
|
// The underlying error can name a config row id; keep the message generic.
|
|
218
|
-
return `Could not read ${
|
|
392
|
+
return `Could not read ${SYSTEM_ONE_HOSTS[id].keyName} from swarm config`;
|
|
219
393
|
}
|
|
220
394
|
|
|
221
|
-
export function systemOneKeyMalformedMessage(id:
|
|
222
|
-
|
|
395
|
+
export function systemOneKeyMalformedMessage(id: SystemOneHostId, keyName?: string): string {
|
|
396
|
+
const name = keyName ?? SYSTEM_ONE_HOSTS[id].keyName;
|
|
397
|
+
return `${name} is not a valid bearer token value. ${SET_IT(name)}`;
|
|
223
398
|
}
|
|
224
399
|
|
|
225
400
|
/** A 401 or 403 from the host: the key exists but the host does not accept it. */
|
|
226
401
|
export function systemOneKeyRejectedMessage(
|
|
227
|
-
id:
|
|
402
|
+
id: SystemOneHostId,
|
|
228
403
|
status: number,
|
|
229
404
|
code?: string,
|
|
405
|
+
options: { keyName?: string; capability?: SystemOneCapability } = {},
|
|
230
406
|
): string {
|
|
231
|
-
const { label
|
|
407
|
+
const { label }: SystemOneHost = SYSTEM_ONE_HOSTS[id];
|
|
408
|
+
const keyName = options.keyName ?? SYSTEM_ONE_HOSTS[id].keyName;
|
|
232
409
|
const detail = code ? ` (${code})` : "";
|
|
233
410
|
return (
|
|
234
411
|
`${keyName} was rejected by ${label} (HTTP ${status}${detail}). ` +
|
|
235
|
-
`The key is invalid, revoked, or lacks access to the model. Replace it on the Secrets page (Settings > Secrets).
|
|
412
|
+
`The key is invalid, revoked, or lacks access to the model. Replace it on the Secrets page (Settings > Secrets). ${NOTHING_PRODUCED[options.capability ?? "decide"]}`
|
|
236
413
|
);
|
|
237
414
|
}
|
|
238
415
|
|
|
239
|
-
function urlKeyOf(
|
|
240
|
-
return typeof
|
|
416
|
+
function urlKeyOf(endpoint: SystemOneEndpoint): string | null {
|
|
417
|
+
return typeof endpoint === "string" ? null : endpoint.configKey;
|
|
241
418
|
}
|
|
242
419
|
|
|
243
|
-
function isBuiltEndpoint(
|
|
244
|
-
endpoint: SystemOneProvider["endpoint"],
|
|
245
|
-
): endpoint is SystemOneBuiltEndpoint {
|
|
420
|
+
function isBuiltEndpoint(endpoint: SystemOneEndpoint): endpoint is SystemOneBuiltEndpoint {
|
|
246
421
|
return typeof endpoint !== "string" && "build" in endpoint;
|
|
247
422
|
}
|
|
248
423
|
|
|
249
424
|
/** What a provider's config value names, for messages. */
|
|
250
|
-
function urlDescriptionOf(
|
|
251
|
-
return isBuiltEndpoint(
|
|
252
|
-
?
|
|
253
|
-
: `the URL of a ${
|
|
425
|
+
function urlDescriptionOf(id: SystemOneHostId, endpoint: SystemOneEndpoint): string {
|
|
426
|
+
return isBuiltEndpoint(endpoint)
|
|
427
|
+
? endpoint.describe
|
|
428
|
+
: `the URL of a ${SYSTEM_ONE_HOSTS[id].label} server`;
|
|
254
429
|
}
|
|
255
430
|
|
|
256
|
-
export function systemOneUrlMissingMessage(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
431
|
+
export function systemOneUrlMissingMessage(
|
|
432
|
+
id: SystemOneHostId,
|
|
433
|
+
capability: SystemOneCapability = "decide",
|
|
434
|
+
): string {
|
|
435
|
+
const { endpoint } = serviceOf(id, capability);
|
|
436
|
+
const configKey = urlKeyOf(endpoint) ?? "";
|
|
437
|
+
return `${configKey} is not configured. SystemOne provider "${id}" needs ${urlDescriptionOf(id, endpoint)}. ${SET_URL(configKey)}`;
|
|
260
438
|
}
|
|
261
439
|
|
|
262
|
-
export function systemOneUrlUnreadableMessage(
|
|
263
|
-
|
|
440
|
+
export function systemOneUrlUnreadableMessage(
|
|
441
|
+
id: SystemOneHostId,
|
|
442
|
+
capability: SystemOneCapability = "decide",
|
|
443
|
+
): string {
|
|
444
|
+
return `Could not read ${urlKeyOf(serviceOf(id, capability).endpoint) ?? ""} from swarm config`;
|
|
264
445
|
}
|
|
265
446
|
|
|
266
|
-
export function systemOneUrlInvalidMessage(
|
|
267
|
-
|
|
268
|
-
|
|
447
|
+
export function systemOneUrlInvalidMessage(
|
|
448
|
+
id: SystemOneHostId,
|
|
449
|
+
capability: SystemOneCapability = "decide",
|
|
450
|
+
): string {
|
|
451
|
+
const { endpoint } = serviceOf(id, capability);
|
|
452
|
+
const configKey = urlKeyOf(endpoint) ?? "";
|
|
269
453
|
// The value is not echoed: a URL can carry a credential.
|
|
270
|
-
if (isBuiltEndpoint(
|
|
271
|
-
return `${configKey} is not ${
|
|
454
|
+
if (isBuiltEndpoint(endpoint)) {
|
|
455
|
+
return `${configKey} is not ${endpoint.describe}. ${SET_URL(configKey)}`;
|
|
272
456
|
}
|
|
273
|
-
return `${configKey} is not a usable ${
|
|
457
|
+
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)}`;
|
|
274
458
|
}
|
|
275
459
|
|
|
276
460
|
/** Why a provider cannot use this model, or null. The value is not echoed: it may come from run input. */
|
|
277
461
|
export function systemOneModelProblem(
|
|
278
|
-
id:
|
|
462
|
+
id: SystemOneHostId,
|
|
279
463
|
model: string | undefined,
|
|
464
|
+
capability: SystemOneCapability = "decide",
|
|
280
465
|
): string | null {
|
|
281
|
-
const { allowedModels }
|
|
466
|
+
const { allowedModels } = serviceOf(id, capability);
|
|
282
467
|
if (!allowedModels || (model !== undefined && allowedModels.includes(model))) return null;
|
|
283
468
|
return `SystemOne provider "${id}" serves only the models ${allowedModels.join(", ")}; set model to one of them or leave it unset.`;
|
|
284
469
|
}
|
|
@@ -327,16 +512,29 @@ export function systemOneEndpointFromBase(base: string, path: string): string |
|
|
|
327
512
|
}
|
|
328
513
|
|
|
329
514
|
export type SystemOneTarget =
|
|
330
|
-
| {
|
|
331
|
-
|
|
515
|
+
| {
|
|
516
|
+
ok: true;
|
|
517
|
+
apiKey: string;
|
|
518
|
+
endpoint: string;
|
|
519
|
+
/** The secret name the key was read under, for a rejection message. */
|
|
520
|
+
keyName: string;
|
|
521
|
+
}
|
|
522
|
+
| {
|
|
523
|
+
ok: false;
|
|
524
|
+
error: string;
|
|
525
|
+
/** This deployment refuses the host whatever its credential (`deploymentProblem`). */
|
|
526
|
+
deploymentRefused?: true;
|
|
527
|
+
};
|
|
332
528
|
|
|
333
529
|
export interface SystemOneTargetOverrides {
|
|
334
530
|
/** Replaces the credential lookup. */
|
|
335
|
-
apiKey?: (id:
|
|
531
|
+
apiKey?: (id: SystemOneHostId) => Promise<string | null | undefined>;
|
|
336
532
|
/** Replaces the lookup of a configured server's base URL (or account id). */
|
|
337
|
-
serverUrl?: (id:
|
|
338
|
-
/** The model the call uses, for a host whose URL names it. Defaults to the
|
|
533
|
+
serverUrl?: (id: SystemOneHostId) => Promise<string | null | undefined>;
|
|
534
|
+
/** The model the call uses, for a host whose URL names it. Defaults to the service's default. */
|
|
339
535
|
model?: string;
|
|
536
|
+
/** Which of the host's services is reached. Defaults to `decide`. */
|
|
537
|
+
capability?: SystemOneCapability;
|
|
340
538
|
}
|
|
341
539
|
|
|
342
540
|
/**
|
|
@@ -346,21 +544,23 @@ export interface SystemOneTargetOverrides {
|
|
|
346
544
|
* fakes); the deployment check and the blank / malformed / URL checks still apply.
|
|
347
545
|
*/
|
|
348
546
|
export async function resolveSystemOneTarget(
|
|
349
|
-
id:
|
|
547
|
+
id: SystemOneHostId,
|
|
350
548
|
ctx: SystemOneProviderContext,
|
|
351
549
|
overrides: SystemOneTargetOverrides = {},
|
|
352
550
|
): Promise<SystemOneTarget> {
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
|
|
551
|
+
const host: SystemOneHost = SYSTEM_ONE_HOSTS[id];
|
|
552
|
+
const capability = overrides.capability ?? "decide";
|
|
553
|
+
const service = serviceOf(id, capability);
|
|
554
|
+
const deploymentProblem = host.deploymentProblem?.(ctx.env);
|
|
555
|
+
if (deploymentProblem) return { ok: false, error: deploymentProblem, deploymentRefused: true };
|
|
356
556
|
|
|
357
557
|
const errors: string[] = [];
|
|
358
558
|
|
|
359
559
|
let endpoint: string | undefined;
|
|
360
|
-
if (typeof
|
|
361
|
-
endpoint =
|
|
560
|
+
if (typeof service.endpoint === "string") {
|
|
561
|
+
endpoint = service.endpoint;
|
|
362
562
|
} else {
|
|
363
|
-
const { configKey } =
|
|
563
|
+
const { configKey } = service.endpoint;
|
|
364
564
|
let base: string | null | undefined;
|
|
365
565
|
let readable = true;
|
|
366
566
|
try {
|
|
@@ -369,34 +569,36 @@ export async function resolveSystemOneTarget(
|
|
|
369
569
|
: readGlobalConfig(ctx.db, configKey));
|
|
370
570
|
} catch {
|
|
371
571
|
readable = false;
|
|
372
|
-
errors.push(systemOneUrlUnreadableMessage(id));
|
|
572
|
+
errors.push(systemOneUrlUnreadableMessage(id, capability));
|
|
373
573
|
}
|
|
374
574
|
if (readable) {
|
|
375
575
|
if (!base || base.trim() === "") {
|
|
376
|
-
errors.push(systemOneUrlMissingMessage(id));
|
|
576
|
+
errors.push(systemOneUrlMissingMessage(id, capability));
|
|
377
577
|
} else {
|
|
378
|
-
const resolved = isBuiltEndpoint(
|
|
379
|
-
?
|
|
380
|
-
: systemOneEndpointFromBase(base,
|
|
578
|
+
const resolved = isBuiltEndpoint(service.endpoint)
|
|
579
|
+
? service.endpoint.build(base, overrides.model ?? service.defaultModel ?? "")
|
|
580
|
+
: systemOneEndpointFromBase(base, service.endpoint.path);
|
|
381
581
|
if (resolved) endpoint = resolved;
|
|
382
|
-
else errors.push(systemOneUrlInvalidMessage(id));
|
|
582
|
+
else errors.push(systemOneUrlInvalidMessage(id, capability));
|
|
383
583
|
}
|
|
384
584
|
}
|
|
385
585
|
}
|
|
386
586
|
|
|
387
|
-
let
|
|
587
|
+
let read: SystemOneCredential | string | null | undefined;
|
|
388
588
|
let keyProblem: string | undefined;
|
|
389
589
|
try {
|
|
390
|
-
|
|
590
|
+
read = await (overrides.apiKey ? overrides.apiKey(id) : host.readKey(ctx));
|
|
391
591
|
} catch {
|
|
392
592
|
keyProblem = systemOneKeyUnreadableMessage(id);
|
|
393
593
|
}
|
|
594
|
+
const apiKey = typeof read === "string" ? read : read?.value;
|
|
595
|
+
const keyName = (typeof read === "object" && read?.keyName) || host.keyName;
|
|
394
596
|
if (!keyProblem) {
|
|
395
597
|
if (!apiKey || apiKey.trim() === "") keyProblem = systemOneKeyMissingMessage(id);
|
|
396
|
-
else if (hasWhitespaceOrControl(apiKey)) keyProblem = systemOneKeyMalformedMessage(id);
|
|
598
|
+
else if (hasWhitespaceOrControl(apiKey)) keyProblem = systemOneKeyMalformedMessage(id, keyName);
|
|
397
599
|
}
|
|
398
600
|
if (keyProblem) errors.push(keyProblem);
|
|
399
601
|
|
|
400
602
|
if (errors.length > 0 || !endpoint || !apiKey) return { ok: false, error: errors.join(" ") };
|
|
401
|
-
return { ok: true, apiKey, endpoint };
|
|
603
|
+
return { ok: true, apiKey, endpoint, keyName };
|
|
402
604
|
}
|