@desplega.ai/agent-swarm 2.4.2 → 2.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-adapter-dv7hmx4r.js → acp-adapter-thq04adh.js} +4 -5
- package/dist/{actions-51nc1gny.js → actions-j6ywh6x5.js} +18 -18
- package/dist/{amp-adapter-abp5tdx8.js → amp-adapter-k8b5xmj8.js} +3 -3
- package/dist/{app-9h14qvr6.js → app-rb7vc02s.js} +3 -3
- package/dist/{assistant-wv1gz3mt.js → assistant-ftta1zjf.js} +20 -20
- package/dist/{boot-reembed-6yn53ms9.js → boot-reembed-srp6v6af.js} +1 -1
- package/dist/{boot-reembed-7fcka4z5.js → boot-reembed-xns7h5h9.js} +1 -1
- package/dist/{boot-scrub-sweep-pm1580ax.js → boot-scrub-sweep-3qqrh2mz.js} +3 -3
- package/dist/{boot-seeds-g5hfc1x8.js → boot-seeds-9ks8hce7.js} +4 -4
- package/dist/{claude-adapter-gf3kxhf0.js → claude-adapter-k5ksyres.js} +4 -4
- package/dist/{claude-managed-setup-h00hsgtz.js → claude-managed-setup-n5a5eqks.js} +1 -1
- package/dist/{claude-sdk-session-s2k1qd8y.js → claude-sdk-session-2j3j4kwn.js} +9822 -9749
- package/dist/{cli-pkht5hap.js → cli-1d70crsy.js} +2 -2
- package/dist/{cli-r99kc9yq.js → cli-1ephgwnq.js} +3 -3
- package/dist/{cli-7225gwj7.js → cli-1m17dc9d.js} +1 -1
- package/dist/{cli-bcj753sq.js → cli-2v045xyf.js} +3 -3
- package/dist/{cli-h38rjv87.js → cli-2y91wnbh.js} +12 -7
- package/dist/{cli-sv5gb0yb.js → cli-2zqpkvtt.js} +1 -1
- package/dist/{cli-xbzvet96.js → cli-36ckw0wx.js} +5 -4
- package/dist/{cli-8sxqsbbq.js → cli-37yb7307.js} +1 -1
- package/dist/{cli-hyfw2j1e.js → cli-3cgngyd8.js} +11 -11
- package/dist/{cli-a4gvwttf.js → cli-44159k28.js} +2 -2
- package/dist/{cli-7awf1fp8.js → cli-4dg55wsk.js} +3 -3
- package/dist/{cli-mhywgrrz.js → cli-4sydckyj.js} +1 -1
- package/dist/{cli-y2x52gs2.js → cli-51y09c6p.js} +20 -17
- package/dist/{cli-ahtpy9kf.js → cli-55qmbp6z.js} +10 -5
- package/dist/{cli-pw7v69vp.js → cli-625tb7aq.js} +1 -1
- package/dist/{cli-x5mq1fyq.js → cli-74c1wpm0.js} +3 -3
- package/dist/{cli-b1dfr1m4.js → cli-7a6p4e65.js} +1 -1
- package/dist/{cli-hqhzg1qq.js → cli-82d3nkc3.js} +1 -1
- package/dist/{cli-nmqdc2t3.js → cli-9fhkkpj5.js} +78 -12
- package/dist/{cli-h93c9vb3.js → cli-amyhm3gj.js} +1 -1
- package/dist/{cli-0qvtffe9.js → cli-b8k7epj1.js} +6 -6
- package/dist/{cli-1pa3rz69.js → cli-ba8ne1es.js} +6 -1
- package/dist/{cli-g0hgtz4p.js → cli-bnz0r3kc.js} +3 -3
- package/dist/{cli-0hyhdkdk.js → cli-cebvbc9t.js} +4 -1
- package/dist/{cli-efaqv1c7.js → cli-cynvc9fj.js} +35 -7
- package/dist/{cli-8zwe9mpk.js → cli-dqb4n629.js} +10 -10
- package/dist/{cli-7a91w22y.js → cli-e8xyjvfc.js} +1 -1
- package/dist/{cli-s6vr7r2d.js → cli-esxerzg7.js} +1 -1
- package/dist/{cli-w9g4sz81.js → cli-gf2850q7.js} +1 -1
- package/dist/{cli-10hhg0km.js → cli-gqvh8d8a.js} +7 -7
- package/dist/{cli-t30yj95q.js → cli-hj3debgw.js} +1 -1
- package/dist/{cli-fpmsqq59.js → cli-nc35pxb7.js} +3 -3
- package/dist/{cli-d3qqdy4c.js → cli-p20r8w42.js} +16 -14
- package/dist/{cli-shvfedvm.js → cli-pccfmtb5.js} +5 -5
- package/dist/{cli-02qm7src.js → cli-q6r7t299.js} +1037 -299
- package/dist/{cli-2wag8973.js → cli-qeaq3j6t.js} +1 -1
- package/dist/{cli-nn47d7e8.js → cli-qkhc5aqy.js} +15 -15
- package/dist/{cli-faypxz7q.js → cli-t7cyef5g.js} +4 -4
- package/dist/{cli-z4wd2ftv.js → cli-te6b5wvg.js} +149 -184
- package/dist/{cli-5rk36sjf.js → cli-v3yr2gbg.js} +119 -44
- package/dist/{cli-239yjk27.js → cli-vdm20wej.js} +5 -5
- package/dist/{cli-zfg9enh9.js → cli-vfha21c9.js} +6 -1
- package/dist/{cli-rdyw3mct.js → cli-vq97g1jq.js} +6 -5
- package/dist/{cli-n85dqg6d.js → cli-w6nadkh4.js} +4 -4
- package/dist/{cli-99jez72r.js → cli-w9etnnxk.js} +6 -1
- package/dist/{cli-saw9a6f6.js → cli-xc27fwzg.js} +2 -2
- package/dist/{cli-ftyypbg5.js → cli-xj0re5tm.js} +1 -1
- package/dist/{cli-fyzjmseb.js → cli-zrk6jxv8.js} +1 -1
- package/dist/{cli-y8n5k515.js → cli-zrwpctp4.js} +28 -28
- package/dist/cli.js +44 -19
- package/dist/{codex-adapter-ttygw382.js → codex-adapter-968xwfy7.js} +2 -2
- package/dist/{codex-hook-x7nejyrr.js → codex-hook-e2he7aca.js} +2 -2
- package/dist/{codex-session-runner-60b58vv3.js → codex-session-runner-ht4z99fa.js} +2 -2
- package/dist/{commands-edmdwa4x.js → commands-pcymaeg9.js} +3 -3
- package/dist/{controller-m263fg43.js → controller-fpjyk46r.js} +3 -3
- package/dist/{controllers-connect-5fym8sw1.js → controllers-connect-mwzx1fh6.js} +19 -19
- package/dist/{cursor-adapter-cz6nm6mj.js → cursor-adapter-cp4s2e2f.js} +2 -2
- package/dist/{db-2y0mgstc.js → db-sgd12bt9.js} +3 -3
- package/dist/{db-nn0hcmx5.js → db-xsjg9jqq.js} +2 -2
- package/dist/{dispatcher-0a9wwjcx.js → dispatcher-x0jfw2c9.js} +17 -17
- package/dist/{dsh-adapter-3n3net33.js → dsh-adapter-jrq7ma7h.js} +2 -2
- package/dist/{e2b-thdcxtjd.js → e2b-16d6ch39.js} +1 -1
- package/dist/{e2b-7am6tmq6.js → e2b-zn0b4w5w.js} +1 -1
- package/dist/{external-channel-zhvt95n3.js → external-channel-8njj4h3b.js} +2 -2
- package/dist/{grok-adapter-qnr1hp2y.js → grok-adapter-kkr97v2f.js} +5 -6
- package/dist/handlers-0pvee9nw.js +39 -0
- package/dist/{heartbeat-gz97r7ac.js → heartbeat-wd03zhn0.js} +20 -20
- package/dist/{hook-1mmdgjqs.js → hook-f0vghj42.js} +10 -5
- package/dist/{hook-x8rthca4.js → hook-xzj2ts2p.js} +2 -2
- package/dist/{http-r8f85xr9.js → http-4hwxv62p.js} +261 -123
- package/dist/{index-va3y63es.js → index-6srwvd5b.js} +1 -2
- package/dist/{index-y7rbcrc1.js → index-97q4xn03.js} +20 -11
- package/dist/{index-s98037v0.js → index-cbxkmfc5.js} +1 -2
- package/dist/{index-vzvfqzfj.js → index-gdh8jrdm.js} +17 -17
- package/dist/{index-aex0eba9.js → index-hg0x2cdv.js} +17 -17
- package/dist/index-thm0y6a8.js +41 -0
- package/dist/{index-smxyk04s.js → index-ww2vznkp.js} +1 -1
- package/dist/{keepalive-64c0f34c.js → keepalive-hrh7xnr9.js} +3 -3
- package/dist/{lead-b1da0f97.js → lead-ee8vazz2.js} +30 -31
- package/dist/{maintenance-mnxpar12.js → maintenance-e8wnwnqx.js} +1 -1
- package/dist/{oauth-refresh-sweep-36hs2v4h.js → oauth-refresh-sweep-zm0yj4sj.js} +2 -2
- package/dist/{onboard-xes8de61.js → onboard-dtwcq603.js} +3 -3
- package/dist/{opencode-adapter-3s7ax02e.js → opencode-adapter-x4tqzhkh.js} +3 -3
- package/dist/{otel-impl-fzc57kcq.js → otel-impl-w3fbhpn0.js} +1 -1
- package/dist/{pi-mono-adapter-ytp7gz8b.js → pi-mono-adapter-mcfkn9vf.js} +3 -3
- package/dist/{pricing-refresh-1nek2w7b.js → pricing-refresh-j38dehfs.js} +3 -3
- package/dist/{rbac-roles-ttyfaep2.js → rbac-roles-ae8yttq4.js} +3 -3
- package/dist/{rbac-roles-sqefb5hw.js → rbac-roles-ejxx9b35.js} +2 -2
- package/dist/{render-v2-vb527jwn.js → render-v2-n16n0phe.js} +3 -3
- package/dist/{resume-jcdzg1bn.js → resume-z0fgjc38.js} +5 -5
- package/dist/{session-access-zsp4wsp0.js → session-access-cv3mq6mc.js} +6 -4
- package/dist/{setup-1nb2zqd3.js → setup-mafhj3yt.js} +1 -1
- package/dist/{work-objects-61w08fwr.js → work-objects-8b86mgjp.js} +21 -21
- package/dist/{worker-c1e68hpa.js → worker-16s75hm5.js} +30 -31
- package/dist/worker-token-xf11yyds.js +136 -0
- package/openapi.json +295 -12
- package/package.json +5 -5
- package/src/apps/action-runner.ts +4 -0
- package/src/apps/catalog.generated.json +3 -0
- package/src/apps/page-validator.ts +8 -2
- package/src/be/db.ts +7 -4
- package/src/be/migrations/231_slack_tasks_last_updated_index.sql +5 -0
- package/src/be/migrations/232_agent_session_tokens.sql +41 -0
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +10 -1
- package/src/be/swarm-config-guard.ts +16 -0
- package/src/be/users.ts +169 -12
- package/src/cli.tsx +24 -0
- package/src/commands/worker-token.ts +164 -0
- package/src/github/handlers.ts +6 -5
- package/src/hooks/hook.ts +2 -1
- 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/session-access.ts +51 -2
- package/src/http/sessions.ts +249 -10
- package/src/http/tasks.ts +4 -32
- 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 +4 -4
- package/src/providers/codex-oauth/storage.ts +1 -1
- package/src/providers/opencode-adapter.ts +2 -2
- package/src/providers/pi-mono-extension.ts +2 -1
- package/src/rbac/legacy-policy.ts +3 -1
- package/src/rbac/permissions.ts +4 -0
- 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/claude-stop-hook.test.ts +47 -0
- package/src/tests/codex-adapter.test.ts +42 -0
- package/src/tests/codex-oauth-keep-warm.test.ts +85 -1
- package/src/tests/config-session-auth.test.ts +14 -0
- package/src/tests/docs-index-footprint.test.js +2 -2
- package/src/tests/env-flag.test.ts +15 -1
- package/src/tests/github-handlers-cancel-config.test.ts +29 -8
- package/src/tests/github-handlers-inline-comments.test.ts +9 -2
- package/src/tests/grok-adapter.test.ts +3 -3
- package/src/tests/heartbeat-jev-gate.test.ts +23 -0
- package/src/tests/migration-232-agent-session-tokens.test.ts +67 -0
- package/src/tests/rbac-engine.test.ts +1 -0
- package/src/tests/runtime-child-token-cleanup.test.ts +3 -3
- package/src/tests/script-connections.test.ts +4 -0
- package/src/tests/session-route-posture.test.ts +2 -0
- package/src/tests/skill-script-refs.test.ts +63 -0
- package/src/tests/slack-render-v2-query-plans.test.ts +147 -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-extraction.test.ts +805 -0
- package/src/types.ts +2 -1
- package/src/utils/env-flag.ts +9 -0
- package/src/utils/request-auth-context.ts +2 -1
- 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 +56 -255
- 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 +277 -110
- package/src/workflows/executors/system-one-transport.ts +264 -0
- package/templates/official/lead/HEARTBEAT.md +1 -1
- package/templates/skills/apps/SKILL.md +4 -1
- package/templates/skills/apps/content.md +4 -1
- package/templates/skills/code-quality/content.md +9 -14
- package/templates/skills/heartbeat-runbook/content.md +3 -3
- package/dist/cli-v1sjhn52.js +0 -7
- package/dist/handlers-dkcarkd0.js +0 -39
- package/dist/index-5jecgp3n.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,65 +194,114 @@ 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>;
|
|
256
|
+
|
|
257
|
+
export type SystemOneHostId = keyof typeof SYSTEM_ONE_HOSTS;
|
|
177
258
|
|
|
178
|
-
|
|
259
|
+
type HostsServing<C extends SystemOneCapability> = {
|
|
260
|
+
[K in SystemOneHostId]: (typeof SYSTEM_ONE_HOSTS)[K] extends Record<C, object> ? K : never;
|
|
261
|
+
}[SystemOneHostId];
|
|
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
|
+
}
|
|
179
276
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
];
|
|
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");
|
|
184
290
|
|
|
185
291
|
export const SYSTEM_ONE_DEFAULT_PROVIDER: SystemOneProviderId = "typesafe";
|
|
292
|
+
export const SYSTEM_ONE_EXTRACT_DEFAULT_PROVIDER: SystemOneExtractProviderId = "cloudflare";
|
|
186
293
|
|
|
187
294
|
export function isSystemOneProviderId(value: unknown): value is SystemOneProviderId {
|
|
188
|
-
return
|
|
295
|
+
return (
|
|
296
|
+
typeof value === "string" && (SYSTEM_ONE_PROVIDER_IDS as readonly string[]).includes(value)
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function isSystemOneExtractProviderId(value: unknown): value is SystemOneExtractProviderId {
|
|
301
|
+
return (
|
|
302
|
+
typeof value === "string" &&
|
|
303
|
+
(SYSTEM_ONE_EXTRACT_PROVIDER_IDS as readonly string[]).includes(value)
|
|
304
|
+
);
|
|
189
305
|
}
|
|
190
306
|
|
|
191
307
|
/** Provider a raw node config selects. Undefined when the field is present but not a known id. */
|
|
@@ -196,89 +312,128 @@ export function systemOneProviderOf(
|
|
|
196
312
|
return isSystemOneProviderId(config.provider) ? config.provider : undefined;
|
|
197
313
|
}
|
|
198
314
|
|
|
315
|
+
/** Extraction provider a raw node config selects. Undefined when present but not a known id. */
|
|
316
|
+
export function systemOneExtractProviderOf(
|
|
317
|
+
config: Record<string, unknown>,
|
|
318
|
+
): SystemOneExtractProviderId | undefined {
|
|
319
|
+
if (config.provider === undefined) return SYSTEM_ONE_EXTRACT_DEFAULT_PROVIDER;
|
|
320
|
+
return isSystemOneExtractProviderId(config.provider) ? config.provider : undefined;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** The service a host runs for one capability. Callers pass an id that serves it. */
|
|
324
|
+
function serviceOf(id: SystemOneHostId, capability: SystemOneCapability): SystemOneServiceBase {
|
|
325
|
+
const service = (SYSTEM_ONE_HOSTS[id] as SystemOneHost)[capability];
|
|
326
|
+
if (!service) throw new Error(`SystemOne provider "${id}" does not serve ${capability}`);
|
|
327
|
+
return service;
|
|
328
|
+
}
|
|
329
|
+
|
|
199
330
|
// ─── Messages ───────────────────────────────────────────────
|
|
200
331
|
//
|
|
201
332
|
// Every message names the exact credential and where to set it. They are shared by
|
|
202
333
|
// the save-time warning, the run-start stop, and the step itself, so an author
|
|
203
334
|
// sees the same sentence wherever the problem surfaces.
|
|
204
335
|
|
|
336
|
+
/** What a call that failed did not produce, for the end of a message. */
|
|
337
|
+
const NOTHING_PRODUCED: Record<SystemOneCapability, string> = {
|
|
338
|
+
decide: "No decision was made.",
|
|
339
|
+
extract: "Nothing was extracted.",
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
export function systemOneNothingProduced(capability: SystemOneCapability): string {
|
|
343
|
+
return NOTHING_PRODUCED[capability];
|
|
344
|
+
}
|
|
345
|
+
|
|
205
346
|
const SET_IT = (keyName: string) =>
|
|
206
347
|
`Set ${keyName} as a global secret on the Secrets page (Settings > Secrets).`;
|
|
207
348
|
|
|
208
349
|
const SET_URL = (configKey: string) =>
|
|
209
350
|
`Set ${configKey} as a global config value (set-config with scope global).`;
|
|
210
351
|
|
|
211
|
-
export function systemOneKeyMissingMessage(id:
|
|
212
|
-
const { label, keyName } =
|
|
213
|
-
|
|
352
|
+
export function systemOneKeyMissingMessage(id: SystemOneHostId): string {
|
|
353
|
+
const { label, keyName, fallbackKeyNames }: SystemOneHost = SYSTEM_ONE_HOSTS[id];
|
|
354
|
+
const also = fallbackKeyNames?.length
|
|
355
|
+
? ` (${fallbackKeyNames.join(", ")} is read when it is unset, and is not configured either)`
|
|
356
|
+
: "";
|
|
357
|
+
return `${keyName} is not configured${also}. SystemOne provider "${id}" needs a working ${label} API key. ${SET_IT(keyName)}`;
|
|
214
358
|
}
|
|
215
359
|
|
|
216
|
-
export function systemOneKeyUnreadableMessage(id:
|
|
360
|
+
export function systemOneKeyUnreadableMessage(id: SystemOneHostId): string {
|
|
217
361
|
// The underlying error can name a config row id; keep the message generic.
|
|
218
|
-
return `Could not read ${
|
|
362
|
+
return `Could not read ${SYSTEM_ONE_HOSTS[id].keyName} from swarm config`;
|
|
219
363
|
}
|
|
220
364
|
|
|
221
|
-
export function systemOneKeyMalformedMessage(id:
|
|
222
|
-
|
|
365
|
+
export function systemOneKeyMalformedMessage(id: SystemOneHostId, keyName?: string): string {
|
|
366
|
+
const name = keyName ?? SYSTEM_ONE_HOSTS[id].keyName;
|
|
367
|
+
return `${name} is not a valid bearer token value. ${SET_IT(name)}`;
|
|
223
368
|
}
|
|
224
369
|
|
|
225
370
|
/** A 401 or 403 from the host: the key exists but the host does not accept it. */
|
|
226
371
|
export function systemOneKeyRejectedMessage(
|
|
227
|
-
id:
|
|
372
|
+
id: SystemOneHostId,
|
|
228
373
|
status: number,
|
|
229
374
|
code?: string,
|
|
375
|
+
options: { keyName?: string; capability?: SystemOneCapability } = {},
|
|
230
376
|
): string {
|
|
231
|
-
const { label
|
|
377
|
+
const { label }: SystemOneHost = SYSTEM_ONE_HOSTS[id];
|
|
378
|
+
const keyName = options.keyName ?? SYSTEM_ONE_HOSTS[id].keyName;
|
|
232
379
|
const detail = code ? ` (${code})` : "";
|
|
233
380
|
return (
|
|
234
381
|
`${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).
|
|
382
|
+
`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
383
|
);
|
|
237
384
|
}
|
|
238
385
|
|
|
239
|
-
function urlKeyOf(
|
|
240
|
-
return typeof
|
|
386
|
+
function urlKeyOf(endpoint: SystemOneEndpoint): string | null {
|
|
387
|
+
return typeof endpoint === "string" ? null : endpoint.configKey;
|
|
241
388
|
}
|
|
242
389
|
|
|
243
|
-
function isBuiltEndpoint(
|
|
244
|
-
endpoint: SystemOneProvider["endpoint"],
|
|
245
|
-
): endpoint is SystemOneBuiltEndpoint {
|
|
390
|
+
function isBuiltEndpoint(endpoint: SystemOneEndpoint): endpoint is SystemOneBuiltEndpoint {
|
|
246
391
|
return typeof endpoint !== "string" && "build" in endpoint;
|
|
247
392
|
}
|
|
248
393
|
|
|
249
394
|
/** What a provider's config value names, for messages. */
|
|
250
|
-
function urlDescriptionOf(
|
|
251
|
-
return isBuiltEndpoint(
|
|
252
|
-
?
|
|
253
|
-
: `the URL of a ${
|
|
395
|
+
function urlDescriptionOf(id: SystemOneHostId, endpoint: SystemOneEndpoint): string {
|
|
396
|
+
return isBuiltEndpoint(endpoint)
|
|
397
|
+
? endpoint.describe
|
|
398
|
+
: `the URL of a ${SYSTEM_ONE_HOSTS[id].label} server`;
|
|
254
399
|
}
|
|
255
400
|
|
|
256
|
-
export function systemOneUrlMissingMessage(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
401
|
+
export function systemOneUrlMissingMessage(
|
|
402
|
+
id: SystemOneHostId,
|
|
403
|
+
capability: SystemOneCapability = "decide",
|
|
404
|
+
): string {
|
|
405
|
+
const { endpoint } = serviceOf(id, capability);
|
|
406
|
+
const configKey = urlKeyOf(endpoint) ?? "";
|
|
407
|
+
return `${configKey} is not configured. SystemOne provider "${id}" needs ${urlDescriptionOf(id, endpoint)}. ${SET_URL(configKey)}`;
|
|
260
408
|
}
|
|
261
409
|
|
|
262
|
-
export function systemOneUrlUnreadableMessage(
|
|
263
|
-
|
|
410
|
+
export function systemOneUrlUnreadableMessage(
|
|
411
|
+
id: SystemOneHostId,
|
|
412
|
+
capability: SystemOneCapability = "decide",
|
|
413
|
+
): string {
|
|
414
|
+
return `Could not read ${urlKeyOf(serviceOf(id, capability).endpoint) ?? ""} from swarm config`;
|
|
264
415
|
}
|
|
265
416
|
|
|
266
|
-
export function systemOneUrlInvalidMessage(
|
|
267
|
-
|
|
268
|
-
|
|
417
|
+
export function systemOneUrlInvalidMessage(
|
|
418
|
+
id: SystemOneHostId,
|
|
419
|
+
capability: SystemOneCapability = "decide",
|
|
420
|
+
): string {
|
|
421
|
+
const { endpoint } = serviceOf(id, capability);
|
|
422
|
+
const configKey = urlKeyOf(endpoint) ?? "";
|
|
269
423
|
// The value is not echoed: a URL can carry a credential.
|
|
270
|
-
if (isBuiltEndpoint(
|
|
271
|
-
return `${configKey} is not ${
|
|
424
|
+
if (isBuiltEndpoint(endpoint)) {
|
|
425
|
+
return `${configKey} is not ${endpoint.describe}. ${SET_URL(configKey)}`;
|
|
272
426
|
}
|
|
273
|
-
return `${configKey} is not a usable ${
|
|
427
|
+
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
428
|
}
|
|
275
429
|
|
|
276
430
|
/** Why a provider cannot use this model, or null. The value is not echoed: it may come from run input. */
|
|
277
431
|
export function systemOneModelProblem(
|
|
278
|
-
id:
|
|
432
|
+
id: SystemOneHostId,
|
|
279
433
|
model: string | undefined,
|
|
434
|
+
capability: SystemOneCapability = "decide",
|
|
280
435
|
): string | null {
|
|
281
|
-
const { allowedModels }
|
|
436
|
+
const { allowedModels } = serviceOf(id, capability);
|
|
282
437
|
if (!allowedModels || (model !== undefined && allowedModels.includes(model))) return null;
|
|
283
438
|
return `SystemOne provider "${id}" serves only the models ${allowedModels.join(", ")}; set model to one of them or leave it unset.`;
|
|
284
439
|
}
|
|
@@ -327,16 +482,24 @@ export function systemOneEndpointFromBase(base: string, path: string): string |
|
|
|
327
482
|
}
|
|
328
483
|
|
|
329
484
|
export type SystemOneTarget =
|
|
330
|
-
| {
|
|
485
|
+
| {
|
|
486
|
+
ok: true;
|
|
487
|
+
apiKey: string;
|
|
488
|
+
endpoint: string;
|
|
489
|
+
/** The secret name the key was read under, for a rejection message. */
|
|
490
|
+
keyName: string;
|
|
491
|
+
}
|
|
331
492
|
| { ok: false; error: string };
|
|
332
493
|
|
|
333
494
|
export interface SystemOneTargetOverrides {
|
|
334
495
|
/** Replaces the credential lookup. */
|
|
335
|
-
apiKey?: (id:
|
|
496
|
+
apiKey?: (id: SystemOneHostId) => Promise<string | null | undefined>;
|
|
336
497
|
/** 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
|
|
498
|
+
serverUrl?: (id: SystemOneHostId) => Promise<string | null | undefined>;
|
|
499
|
+
/** The model the call uses, for a host whose URL names it. Defaults to the service's default. */
|
|
339
500
|
model?: string;
|
|
501
|
+
/** Which of the host's services is reached. Defaults to `decide`. */
|
|
502
|
+
capability?: SystemOneCapability;
|
|
340
503
|
}
|
|
341
504
|
|
|
342
505
|
/**
|
|
@@ -346,21 +509,23 @@ export interface SystemOneTargetOverrides {
|
|
|
346
509
|
* fakes); the deployment check and the blank / malformed / URL checks still apply.
|
|
347
510
|
*/
|
|
348
511
|
export async function resolveSystemOneTarget(
|
|
349
|
-
id:
|
|
512
|
+
id: SystemOneHostId,
|
|
350
513
|
ctx: SystemOneProviderContext,
|
|
351
514
|
overrides: SystemOneTargetOverrides = {},
|
|
352
515
|
): Promise<SystemOneTarget> {
|
|
353
|
-
const
|
|
354
|
-
const
|
|
516
|
+
const host: SystemOneHost = SYSTEM_ONE_HOSTS[id];
|
|
517
|
+
const capability = overrides.capability ?? "decide";
|
|
518
|
+
const service = serviceOf(id, capability);
|
|
519
|
+
const deploymentProblem = host.deploymentProblem?.(ctx.env);
|
|
355
520
|
if (deploymentProblem) return { ok: false, error: deploymentProblem };
|
|
356
521
|
|
|
357
522
|
const errors: string[] = [];
|
|
358
523
|
|
|
359
524
|
let endpoint: string | undefined;
|
|
360
|
-
if (typeof
|
|
361
|
-
endpoint =
|
|
525
|
+
if (typeof service.endpoint === "string") {
|
|
526
|
+
endpoint = service.endpoint;
|
|
362
527
|
} else {
|
|
363
|
-
const { configKey } =
|
|
528
|
+
const { configKey } = service.endpoint;
|
|
364
529
|
let base: string | null | undefined;
|
|
365
530
|
let readable = true;
|
|
366
531
|
try {
|
|
@@ -369,34 +534,36 @@ export async function resolveSystemOneTarget(
|
|
|
369
534
|
: readGlobalConfig(ctx.db, configKey));
|
|
370
535
|
} catch {
|
|
371
536
|
readable = false;
|
|
372
|
-
errors.push(systemOneUrlUnreadableMessage(id));
|
|
537
|
+
errors.push(systemOneUrlUnreadableMessage(id, capability));
|
|
373
538
|
}
|
|
374
539
|
if (readable) {
|
|
375
540
|
if (!base || base.trim() === "") {
|
|
376
|
-
errors.push(systemOneUrlMissingMessage(id));
|
|
541
|
+
errors.push(systemOneUrlMissingMessage(id, capability));
|
|
377
542
|
} else {
|
|
378
|
-
const resolved = isBuiltEndpoint(
|
|
379
|
-
?
|
|
380
|
-
: systemOneEndpointFromBase(base,
|
|
543
|
+
const resolved = isBuiltEndpoint(service.endpoint)
|
|
544
|
+
? service.endpoint.build(base, overrides.model ?? service.defaultModel ?? "")
|
|
545
|
+
: systemOneEndpointFromBase(base, service.endpoint.path);
|
|
381
546
|
if (resolved) endpoint = resolved;
|
|
382
|
-
else errors.push(systemOneUrlInvalidMessage(id));
|
|
547
|
+
else errors.push(systemOneUrlInvalidMessage(id, capability));
|
|
383
548
|
}
|
|
384
549
|
}
|
|
385
550
|
}
|
|
386
551
|
|
|
387
|
-
let
|
|
552
|
+
let read: SystemOneCredential | string | null | undefined;
|
|
388
553
|
let keyProblem: string | undefined;
|
|
389
554
|
try {
|
|
390
|
-
|
|
555
|
+
read = await (overrides.apiKey ? overrides.apiKey(id) : host.readKey(ctx));
|
|
391
556
|
} catch {
|
|
392
557
|
keyProblem = systemOneKeyUnreadableMessage(id);
|
|
393
558
|
}
|
|
559
|
+
const apiKey = typeof read === "string" ? read : read?.value;
|
|
560
|
+
const keyName = (typeof read === "object" && read?.keyName) || host.keyName;
|
|
394
561
|
if (!keyProblem) {
|
|
395
562
|
if (!apiKey || apiKey.trim() === "") keyProblem = systemOneKeyMissingMessage(id);
|
|
396
|
-
else if (hasWhitespaceOrControl(apiKey)) keyProblem = systemOneKeyMalformedMessage(id);
|
|
563
|
+
else if (hasWhitespaceOrControl(apiKey)) keyProblem = systemOneKeyMalformedMessage(id, keyName);
|
|
397
564
|
}
|
|
398
565
|
if (keyProblem) errors.push(keyProblem);
|
|
399
566
|
|
|
400
567
|
if (errors.length > 0 || !endpoint || !apiKey) return { ok: false, error: errors.join(" ") };
|
|
401
|
-
return { ok: true, apiKey, endpoint };
|
|
568
|
+
return { ok: true, apiKey, endpoint, keyName };
|
|
402
569
|
}
|