@jini-ai/agent-runtime 0.3.10 → 0.4.1
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/CHANGELOG.md +108 -0
- package/README.md +34 -17
- package/dist/acp-model-probe.d.ts +6 -4
- package/dist/acp-model-probe.js +3 -3
- package/dist/agent-protocol/acp/account-failure.d.ts +4 -2
- package/dist/agent-protocol/acp/account-failure.js +1 -1
- package/dist/agent-protocol/acp/json.d.ts +6 -6
- package/dist/agent-protocol/acp/json.js +3 -3
- package/dist/agent-protocol/acp/models.d.ts +10 -5
- package/dist/agent-protocol/acp/models.js +32 -31
- package/dist/agent-protocol/acp/session-params.d.ts +3 -1
- package/dist/agent-protocol/acp/session-params.js +2 -1
- package/dist/agent-protocol/acp/session.d.ts +28 -22
- package/dist/agent-protocol/acp/session.js +181 -147
- package/dist/agent-protocol/acp/text-suppression.d.ts +1 -1
- package/dist/agent-protocol/acp/text-suppression.js +1 -1
- package/dist/agent-protocol/acp/types.d.ts +3 -2
- package/dist/agent-protocol/acp/updates.d.ts +11 -11
- package/dist/agent-protocol/acp/updates.js +2 -2
- package/dist/agent-protocol/core/json-line-stream.d.ts +24 -8
- package/dist/agent-protocol/core/json-line-stream.js +8 -8
- package/dist/agent-protocol/pi-rpc/events.d.ts +5 -1
- package/dist/agent-protocol/pi-rpc/events.js +36 -36
- package/dist/agent-protocol/pi-rpc/internal.d.ts +4 -1
- package/dist/agent-protocol/pi-rpc/models.d.ts +3 -1
- package/dist/agent-protocol/pi-rpc/models.js +1 -1
- package/dist/agent-protocol/pi-rpc/session.d.ts +1 -1
- package/dist/agent-protocol/pi-rpc/session.js +33 -32
- package/dist/amr-model-cache.d.ts +12 -4
- package/dist/amr-model-cache.js +17 -21
- package/dist/amr-profile-resolver.d.ts +3 -1
- package/dist/anthropic-live-models.js +0 -0
- package/dist/artifact-taxonomy.d.ts +6 -2
- package/dist/auth.d.ts +52 -15
- package/dist/auth.js +36 -36
- package/dist/claude-code-models.js +2 -2
- package/dist/claude-stream.d.ts +22 -3
- package/dist/claude-stream.js +24 -8
- package/dist/copilot-stream.d.ts +7 -3
- package/dist/copilot-stream.js +3 -3
- package/dist/defs/aider.d.ts +8 -1
- package/dist/defs/aider.js +2 -2
- package/dist/defs/amp.d.ts +8 -1
- package/dist/defs/amp.js +2 -2
- package/dist/defs/amr.d.ts +22 -6
- package/dist/defs/amr.js +23 -23
- package/dist/defs/antigravity.d.ts +15 -4
- package/dist/defs/antigravity.js +9 -8
- package/dist/defs/auggie.d.ts +4 -1
- package/dist/defs/auggie.js +1 -1
- package/dist/defs/claude.d.ts +12 -2
- package/dist/defs/claude.js +5 -5
- package/dist/defs/cline.d.ts +4 -1
- package/dist/defs/cline.js +1 -1
- package/dist/defs/codebuddy.d.ts +8 -1
- package/dist/defs/codebuddy.js +2 -2
- package/dist/defs/codex.d.ts +18 -4
- package/dist/defs/codex.js +7 -7
- package/dist/defs/copilot.d.ts +8 -1
- package/dist/defs/copilot.js +2 -2
- package/dist/defs/crush.d.ts +8 -1
- package/dist/defs/crush.js +1 -1
- package/dist/defs/cursor-agent.d.ts +14 -3
- package/dist/defs/cursor-agent.js +5 -5
- package/dist/defs/deepseek.d.ts +8 -1
- package/dist/defs/deepseek.js +2 -2
- package/dist/defs/devin.d.ts +12 -2
- package/dist/defs/devin.js +3 -3
- package/dist/defs/droid.d.ts +8 -1
- package/dist/defs/droid.js +1 -1
- package/dist/defs/gemini.d.ts +8 -1
- package/dist/defs/gemini.js +1 -1
- package/dist/defs/goose.d.ts +4 -1
- package/dist/defs/goose.js +1 -1
- package/dist/defs/grok-build.d.ts +11 -2
- package/dist/defs/grok-build.js +3 -3
- package/dist/defs/hermes.d.ts +4 -1
- package/dist/defs/hermes.js +2 -2
- package/dist/defs/kilo.d.ts +4 -1
- package/dist/defs/kilo.js +2 -2
- package/dist/defs/kimi.d.ts +4 -1
- package/dist/defs/kimi.js +2 -2
- package/dist/defs/kiro.d.ts +4 -1
- package/dist/defs/kiro.js +2 -2
- package/dist/defs/mimo.d.ts +8 -1
- package/dist/defs/mimo.js +2 -2
- package/dist/defs/opencode.d.ts +9 -2
- package/dist/defs/opencode.js +2 -2
- package/dist/defs/pi.d.ts +12 -2
- package/dist/defs/pi.js +9 -9
- package/dist/defs/qoder.d.ts +8 -1
- package/dist/defs/qoder.js +2 -2
- package/dist/defs/qwen.d.ts +8 -1
- package/dist/defs/qwen.js +2 -2
- package/dist/defs/reasonix.d.ts +4 -1
- package/dist/defs/reasonix.js +3 -3
- package/dist/defs/shared.d.ts +4 -2
- package/dist/defs/shared.js +2 -2
- package/dist/defs/trae-cli.d.ts +12 -2
- package/dist/defs/trae-cli.js +3 -3
- package/dist/defs/vibe.d.ts +4 -1
- package/dist/defs/vibe.js +2 -2
- package/dist/detection.d.ts +19 -4
- package/dist/detection.js +33 -33
- package/dist/diagnostics.d.ts +14 -3
- package/dist/diagnostics.js +5 -5
- package/dist/env.d.ts +16 -4
- package/dist/env.js +7 -7
- package/dist/executables.d.ts +24 -8
- package/dist/executables.js +13 -13
- package/dist/index.d.ts +228 -4
- package/dist/index.js +230 -6
- package/dist/invocation.d.ts +6 -1
- package/dist/invocation.js +2 -2
- package/dist/json-event-stream.d.ts +7 -2
- package/dist/json-event-stream.js +3 -3
- package/dist/launch.d.ts +12 -2
- package/dist/launch.js +3 -3
- package/dist/log-file.d.ts +4 -1
- package/dist/log-file.js +2 -2
- package/dist/mcp.d.ts +6 -3
- package/dist/mcp.js +1 -1
- package/dist/metadata.d.ts +7 -3
- package/dist/metadata.js +1 -1
- package/dist/mmd-routes.d.ts +22 -6
- package/dist/mmd-routes.js +14 -14
- package/dist/model-catalog-cache.d.ts +50 -0
- package/dist/model-catalog-cache.js +61 -0
- package/dist/model-loading-state.d.ts +10 -0
- package/dist/model-loading-state.js +23 -0
- package/dist/model-registry.d.ts +28 -7
- package/dist/model-registry.js +11 -8
- package/dist/models.d.ts +31 -6
- package/dist/models.js +6 -6
- package/dist/opencode-log.d.ts +12 -4
- package/dist/opencode-log.js +8 -8
- package/dist/paths.d.ts +6 -2
- package/dist/paths.js +3 -3
- package/dist/pi-models.d.ts +3 -1
- package/dist/pi-models.js +2 -2
- package/dist/prompt-budget.d.ts +17 -4
- package/dist/prompt-budget.js +3 -3
- package/dist/prompt-file.d.ts +5 -1
- package/dist/prompt-file.js +1 -1
- package/dist/providers/aihubmix.d.ts +34 -9
- package/dist/providers/aihubmix.js +26 -26
- package/dist/providers/anthropic-messages.d.ts +77 -20
- package/dist/providers/anthropic-messages.js +53 -50
- package/dist/providers/azure-chat.d.ts +21 -7
- package/dist/providers/azure-chat.js +18 -16
- package/dist/providers/connection-guard.d.ts +45 -31
- package/dist/providers/connection-guard.js +40 -143
- package/dist/providers/connection-test.d.ts +1 -1
- package/dist/providers/connection-test.js +13 -30
- package/dist/providers/elevenlabs.d.ts +7 -2
- package/dist/providers/elevenlabs.js +4 -4
- package/dist/providers/google-messages.d.ts +44 -10
- package/dist/providers/google-messages.js +36 -34
- package/dist/providers/google-schema.d.ts +26 -0
- package/dist/providers/google-schema.js +253 -0
- package/dist/providers/google.d.ts +21 -6
- package/dist/providers/google.js +10 -10
- package/dist/providers/index.d.ts +3 -2
- package/dist/providers/index.js +3 -2
- package/dist/providers/model-catalog.d.ts +1 -1
- package/dist/providers/model-catalog.js +14 -27
- package/dist/providers/oauth-callback-server.d.ts +1 -1
- package/dist/providers/oauth-callback-server.js +3 -2
- package/dist/providers/oauth-credentials.d.ts +7 -1
- package/dist/providers/oauth-credentials.js +5 -5
- package/dist/providers/oauth-provider.d.ts +2 -2
- package/dist/providers/oauth-provider.js +20 -25
- package/dist/providers/oauth-tokens.d.ts +26 -6
- package/dist/providers/oauth-tokens.js +9 -9
- package/dist/providers/ollama-chat.d.ts +66 -31
- package/dist/providers/ollama-chat.js +64 -61
- package/dist/providers/openai-chat.d.ts +77 -22
- package/dist/providers/openai-chat.js +71 -67
- package/dist/providers/pkce.d.ts +33 -20
- package/dist/providers/pkce.js +75 -146
- package/dist/providers/sse-decode.d.ts +3 -1
- package/dist/providers/sse-decode.js +1 -1
- package/dist/providers/token-params.d.ts +16 -5
- package/dist/providers/token-params.js +6 -6
- package/dist/providers/tool-turn-types.d.ts +94 -0
- package/dist/providers/tool-turn-types.js +2 -0
- package/dist/providers/tool-turn.d.ts +10 -0
- package/dist/providers/tool-turn.js +258 -0
- package/dist/qoder-stream.d.ts +7 -3
- package/dist/qoder-stream.js +3 -3
- package/dist/registry.d.ts +6 -2
- package/dist/registry.js +4 -4
- package/dist/resolution.d.ts +5 -1
- package/dist/resolution.js +3 -3
- package/dist/role-marker-guard.d.ts +9 -5
- package/dist/role-marker-guard.js +5 -5
- package/dist/terminal-launch.d.ts +6 -1
- package/dist/terminal-launch.js +2 -2
- package/dist/types.d.ts +22 -6
- package/package.json +43 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module providers/connection-guard
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* SSRF validation and pinned transport needed by
|
|
5
5
|
* this package's own `model-catalog.ts`. Vendored from the small generic
|
|
6
6
|
* subset of OD's `packages/contracts/src/api/connectionTest.ts` (the
|
|
7
7
|
* `isLoopbackApiHost`/`isBlockedExternalApiHostname`/`validateBaseUrl` triad)
|
|
@@ -10,17 +10,25 @@
|
|
|
10
10
|
* 2,600-line file, which is almost entirely OD's own agent-CLI
|
|
11
11
|
* connection-test orchestration (proxy dispatchers, product-specific
|
|
12
12
|
* executable-fallback copy and env-var names) and out of this task's
|
|
13
|
-
* scope; see `
|
|
14
|
-
*
|
|
13
|
+
* scope; see `archived provenance ledger` for the exact origin details. The shared classifiers now live in `@jini-ai/platform/net`, and redaction
|
|
14
|
+
* lives in core. Validation and transport here retain
|
|
15
15
|
* no product coupling in the origin — reject requests to loopback-disguised
|
|
16
16
|
* or RFC1918/link-local/CGNAT/metadata-service addresses, and strip bearer
|
|
17
17
|
* tokens / API-key headers / `?key=` query values out of free-form text
|
|
18
|
-
* before it is logged or surfaced to a caller.
|
|
18
|
+
* before it is logged or surfaced to a caller. Provider modules call core with
|
|
19
|
+
* explicit exactSecrets because upstream 401 bodies can echo configured credentials.
|
|
19
20
|
*
|
|
20
|
-
*
|
|
21
|
+
* The provider redaction contract removes bearer tokens,
|
|
22
|
+
* `x-api-key`/`api-key`/`x-goog-api-key` headers, `?key=` query values,
|
|
23
|
+
* and exact secret strings supplied by the caller from free-form text.
|
|
24
|
+
* It is used for logging or surfacing upstream failures without leaking
|
|
25
|
+
* credentials embedded in them (some providers echo the key in a 401 body).
|
|
26
|
+
* Core owns this union now; provider model/request IDs remain readable.
|
|
27
|
+
*
|
|
28
|
+
* Exports `pinnedFetch` — a `node:https`/`node:http`-based POST that dials the exact address
|
|
21
29
|
* `validateBaseUrlResolved` already validated, instead of leaving the transport to re-resolve DNS
|
|
22
30
|
* independently when it connects. Added later than the four functions above, for the same
|
|
23
|
-
*
|
|
31
|
+
* explicit network boundary; see `pinnedFetch`'s own doc for why `fetch()` itself cannot do this.
|
|
24
32
|
*
|
|
25
33
|
* ## Paired with `@jini-ai/ui`'s `utils/endpoint-policy.ts`
|
|
26
34
|
*
|
|
@@ -31,7 +39,7 @@
|
|
|
31
39
|
* imports by design and this package is Node-only (`validateBaseUrlResolved`
|
|
32
40
|
* needs `node:dns`); see that file's header for the full reasoning.
|
|
33
41
|
*
|
|
34
|
-
* **If the block-list
|
|
42
|
+
* **If the platform/net block-list changes, change the browser copy too.** A divergence means
|
|
35
43
|
* the UI accepts an endpoint this guard then refuses — or, in the direction
|
|
36
44
|
* that actually matters, the UI accepts one this guard would have refused.
|
|
37
45
|
*/
|
|
@@ -49,10 +57,6 @@ export interface BaseUrlValidationResult {
|
|
|
49
57
|
*/
|
|
50
58
|
pinnedAddress?: DnsLookupAddress;
|
|
51
59
|
}
|
|
52
|
-
/** True for `localhost`, `::1`, `127.0.0.0/8`, and their IPv4-mapped-IPv6 forms. */
|
|
53
|
-
export declare function isLoopbackApiHost(hostname: string): boolean;
|
|
54
|
-
/** True for RFC1918/link-local/CGNAT/multicast/unspecified/unique-local-IPv6 addresses — private network space a public caller should never be steered into. */
|
|
55
|
-
export declare function isBlockedExternalApiHostname(hostname: string): boolean;
|
|
56
60
|
/**
|
|
57
61
|
* Synchronous base-URL check: scheme allow-list + literal-hostname block-list.
|
|
58
62
|
* Does not resolve DNS — see {@link validateBaseUrlResolved}.
|
|
@@ -73,12 +77,16 @@ export declare function isBlockedExternalApiHostname(hostname: string): boolean;
|
|
|
73
77
|
* is the only shape that would let the block-list inspect one string while the
|
|
74
78
|
* network used another.
|
|
75
79
|
*/
|
|
76
|
-
export declare function validateBaseUrl(baseUrl
|
|
80
|
+
export declare function validateBaseUrl({ baseUrl }: {
|
|
81
|
+
baseUrl: string;
|
|
82
|
+
}): BaseUrlValidationResult;
|
|
77
83
|
export type DnsLookupAddress = {
|
|
78
84
|
address: string;
|
|
79
85
|
family: number;
|
|
80
86
|
};
|
|
81
|
-
export type DnsLookupFn = (
|
|
87
|
+
export type DnsLookupFn = (requiredArgs: {
|
|
88
|
+
hostname: string;
|
|
89
|
+
}) => Promise<DnsLookupAddress[]>;
|
|
82
90
|
/**
|
|
83
91
|
* DNS-aware companion to {@link validateBaseUrl}. The synchronous check only
|
|
84
92
|
* inspects the literal hostname string, so a public DNS name pointing at
|
|
@@ -110,15 +118,19 @@ export type DnsLookupFn = (hostname: string) => Promise<DnsLookupAddress[]>;
|
|
|
110
118
|
* unvalidated address; `pinnedFetch` never follows one regardless (`node:https`/`node:http` don't,
|
|
111
119
|
* unlike `fetch`'s default), but callers still pass `redirect: 'error'` for self-documentation.
|
|
112
120
|
*
|
|
113
|
-
* DNS lookup failures
|
|
114
|
-
*
|
|
115
|
-
*
|
|
121
|
+
* DNS lookup failures and empty answers fail closed: an unpinned fallback
|
|
122
|
+
* would let the transport resolve the host again without checking that answer.
|
|
123
|
+
* Earlier behavior deferred resolver hiccups to the transport; that bypassed
|
|
124
|
+
* validation if its independent lookup succeeded, so it is no longer allowed. The
|
|
116
125
|
* synchronous hostname check still rejects the obvious literal-IP cases
|
|
117
|
-
* before DNS is ever consulted. `lookup`
|
|
118
|
-
*
|
|
119
|
-
*
|
|
126
|
+
* before DNS is ever consulted. `lookup` is a required injected resolver;
|
|
127
|
+
* a Node host can wrap `node:dns`'s promise
|
|
128
|
+
* `lookup(hostname, { all: true, family: 0 })` in this port's object signature.
|
|
120
129
|
*/
|
|
121
|
-
export declare function validateBaseUrlResolved(baseUrl
|
|
130
|
+
export declare function validateBaseUrlResolved({ baseUrl, lookup }: {
|
|
131
|
+
baseUrl: string;
|
|
132
|
+
lookup: DnsLookupFn;
|
|
133
|
+
}): Promise<BaseUrlValidationResult>;
|
|
122
134
|
/** Request options accepted by {@link pinnedFetch} — the subset of `fetch`'s `init` this package's provider adapters actually pass. `redirect` is accepted only as documentation of intent: `pinnedFetch` never follows a redirect regardless of this field, so a caller cannot opt back into `fetch`'s default follow-redirects behavior. */
|
|
123
135
|
export interface PinnedFetchInit {
|
|
124
136
|
readonly method: string;
|
|
@@ -144,7 +156,11 @@ export interface PinnedFetchResponse {
|
|
|
144
156
|
* behavior, including the SSRF guard and DNS pinning, never changes unless a caller explicitly
|
|
145
157
|
* overrides it.
|
|
146
158
|
*/
|
|
147
|
-
export type PinnedFetch = (
|
|
159
|
+
export type PinnedFetch = (requiredArgs: {
|
|
160
|
+
url: string;
|
|
161
|
+
init: PinnedFetchInit;
|
|
162
|
+
pinnedAddress: DnsLookupAddress | undefined;
|
|
163
|
+
}) => Promise<PinnedFetchResponse>;
|
|
148
164
|
/**
|
|
149
165
|
* `fetch()`-shaped POST that dials `pinnedAddress` directly instead of letting the transport
|
|
150
166
|
* re-resolve DNS when it connects — see {@link validateBaseUrlResolved}'s doc for the TOCTOU this
|
|
@@ -173,15 +189,13 @@ export type PinnedFetch = (url: string, init: PinnedFetchInit, pinnedAddress: Dn
|
|
|
173
189
|
* (`lookup` option simply omitted). That is intentional, not a gap: an IP literal has nothing left
|
|
174
190
|
* to re-resolve, and loopback literals are the documented carve-out, outside this threat model.
|
|
175
191
|
*/
|
|
176
|
-
export declare function pinnedFetch(url
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
* upstream error message to a caller without leaking credentials embedded in
|
|
182
|
-
* it (some providers echo the key back in a 401 body).
|
|
183
|
-
*/
|
|
184
|
-
export declare function redactSecrets(text: string, exactSecrets?: ReadonlyArray<string | undefined | null>): string;
|
|
192
|
+
export declare function pinnedFetch({ url, init, pinnedAddress }: {
|
|
193
|
+
url: string;
|
|
194
|
+
init: PinnedFetchInit;
|
|
195
|
+
pinnedAddress: DnsLookupAddress | undefined;
|
|
196
|
+
}): Promise<PinnedFetchResponse>;
|
|
185
197
|
/** Default DNS lookup for {@link validateBaseUrlResolved} — `node:dns/promises`' `lookup(hostname, { all: true, family: 0 })`. */
|
|
186
|
-
export declare function defaultDnsLookup(hostname
|
|
198
|
+
export declare function defaultDnsLookup({ hostname }: {
|
|
199
|
+
hostname: string;
|
|
200
|
+
}): Promise<DnsLookupAddress[]>;
|
|
187
201
|
//# sourceMappingURL=connection-guard.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module providers/connection-guard
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* SSRF validation and pinned transport needed by
|
|
5
5
|
* this package's own `model-catalog.ts`. Vendored from the small generic
|
|
6
6
|
* subset of OD's `packages/contracts/src/api/connectionTest.ts` (the
|
|
7
7
|
* `isLoopbackApiHost`/`isBlockedExternalApiHostname`/`validateBaseUrl` triad)
|
|
@@ -10,17 +10,25 @@
|
|
|
10
10
|
* 2,600-line file, which is almost entirely OD's own agent-CLI
|
|
11
11
|
* connection-test orchestration (proxy dispatchers, product-specific
|
|
12
12
|
* executable-fallback copy and env-var names) and out of this task's
|
|
13
|
-
* scope; see `
|
|
14
|
-
*
|
|
13
|
+
* scope; see `archived provenance ledger` for the exact origin details. The shared classifiers now live in `@jini-ai/platform/net`, and redaction
|
|
14
|
+
* lives in core. Validation and transport here retain
|
|
15
15
|
* no product coupling in the origin — reject requests to loopback-disguised
|
|
16
16
|
* or RFC1918/link-local/CGNAT/metadata-service addresses, and strip bearer
|
|
17
17
|
* tokens / API-key headers / `?key=` query values out of free-form text
|
|
18
|
-
* before it is logged or surfaced to a caller.
|
|
18
|
+
* before it is logged or surfaced to a caller. Provider modules call core with
|
|
19
|
+
* explicit exactSecrets because upstream 401 bodies can echo configured credentials.
|
|
19
20
|
*
|
|
20
|
-
*
|
|
21
|
+
* The provider redaction contract removes bearer tokens,
|
|
22
|
+
* `x-api-key`/`api-key`/`x-goog-api-key` headers, `?key=` query values,
|
|
23
|
+
* and exact secret strings supplied by the caller from free-form text.
|
|
24
|
+
* It is used for logging or surfacing upstream failures without leaking
|
|
25
|
+
* credentials embedded in them (some providers echo the key in a 401 body).
|
|
26
|
+
* Core owns this union now; provider model/request IDs remain readable.
|
|
27
|
+
*
|
|
28
|
+
* Exports `pinnedFetch` — a `node:https`/`node:http`-based POST that dials the exact address
|
|
21
29
|
* `validateBaseUrlResolved` already validated, instead of leaving the transport to re-resolve DNS
|
|
22
30
|
* independently when it connects. Added later than the four functions above, for the same
|
|
23
|
-
*
|
|
31
|
+
* explicit network boundary; see `pinnedFetch`'s own doc for why `fetch()` itself cannot do this.
|
|
24
32
|
*
|
|
25
33
|
* ## Paired with `@jini-ai/ui`'s `utils/endpoint-policy.ts`
|
|
26
34
|
*
|
|
@@ -31,99 +39,11 @@
|
|
|
31
39
|
* imports by design and this package is Node-only (`validateBaseUrlResolved`
|
|
32
40
|
* needs `node:dns`); see that file's header for the full reasoning.
|
|
33
41
|
*
|
|
34
|
-
* **If the block-list
|
|
42
|
+
* **If the platform/net block-list changes, change the browser copy too.** A divergence means
|
|
35
43
|
* the UI accepts an endpoint this guard then refuses — or, in the direction
|
|
36
44
|
* that actually matters, the UI accepts one this guard would have refused.
|
|
37
45
|
*/
|
|
38
|
-
|
|
39
|
-
const stripped = hostname.startsWith('[') && hostname.endsWith(']')
|
|
40
|
-
? hostname.slice(1, -1)
|
|
41
|
-
: hostname;
|
|
42
|
-
// FQDN trailing-dot form (RFC 1034) resolves identically to the dotless
|
|
43
|
-
// form, so `localhost.` must normalize to `localhost` before the equality
|
|
44
|
-
// check below — and `0.0.0.0.`, `10.0.0.1.`, etc. must normalize before
|
|
45
|
-
// isBlockedIpv4 parses them. Strips one or more trailing dots.
|
|
46
|
-
return stripped.toLowerCase().replace(/\.+$/, '');
|
|
47
|
-
}
|
|
48
|
-
function parseIpv4(hostname) {
|
|
49
|
-
const parts = hostname.split('.');
|
|
50
|
-
if (parts.length !== 4)
|
|
51
|
-
return null;
|
|
52
|
-
const parsed = parts.map((part) => {
|
|
53
|
-
if (!/^\d{1,3}$/.test(part))
|
|
54
|
-
return null;
|
|
55
|
-
const value = Number(part);
|
|
56
|
-
return value >= 0 && value <= 255 ? value : null;
|
|
57
|
-
});
|
|
58
|
-
if (parsed.some((part) => part === null))
|
|
59
|
-
return null;
|
|
60
|
-
return parsed;
|
|
61
|
-
}
|
|
62
|
-
function isLoopbackIpv4(hostname) {
|
|
63
|
-
const parts = parseIpv4(hostname);
|
|
64
|
-
return Boolean(parts && parts[0] === 127);
|
|
65
|
-
}
|
|
66
|
-
function isBlockedIpv4(hostname) {
|
|
67
|
-
const parts = parseIpv4(hostname);
|
|
68
|
-
if (!parts)
|
|
69
|
-
return false;
|
|
70
|
-
const [a, b] = parts;
|
|
71
|
-
return (a === 0 ||
|
|
72
|
-
(a === 100 && b >= 64 && b <= 127) ||
|
|
73
|
-
(a === 169 && b === 254) ||
|
|
74
|
-
a === 10 ||
|
|
75
|
-
(a === 192 && b === 168) ||
|
|
76
|
-
(a === 172 && b >= 16 && b <= 31) ||
|
|
77
|
-
a >= 224);
|
|
78
|
-
}
|
|
79
|
-
function ipv4MappedToDotted(hostname) {
|
|
80
|
-
const host = normalizeBracketedIpv6(hostname);
|
|
81
|
-
const mapped = /^::ffff:(.+)$/i.exec(host)?.[1];
|
|
82
|
-
if (!mapped)
|
|
83
|
-
return null;
|
|
84
|
-
if (parseIpv4(mapped.toLowerCase()))
|
|
85
|
-
return mapped.toLowerCase();
|
|
86
|
-
const hexParts = mapped.split(':');
|
|
87
|
-
if (hexParts.length !== 2 ||
|
|
88
|
-
!hexParts.every((part) => /^[0-9a-f]{1,4}$/i.test(part))) {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
// Non-null assertions, not a runtime guard: the length/regex checks above
|
|
92
|
-
// already guarantee exactly two non-empty hex segments here.
|
|
93
|
-
const hi = hexParts[0];
|
|
94
|
-
const lo = hexParts[1];
|
|
95
|
-
const value = (Number.parseInt(hi, 16) << 16) | Number.parseInt(lo, 16);
|
|
96
|
-
return [
|
|
97
|
-
(value >>> 24) & 255,
|
|
98
|
-
(value >>> 16) & 255,
|
|
99
|
-
(value >>> 8) & 255,
|
|
100
|
-
value & 255,
|
|
101
|
-
].join('.');
|
|
102
|
-
}
|
|
103
|
-
/** True for `localhost`, `::1`, `127.0.0.0/8`, and their IPv4-mapped-IPv6 forms. */
|
|
104
|
-
export function isLoopbackApiHost(hostname) {
|
|
105
|
-
const host = normalizeBracketedIpv6(hostname);
|
|
106
|
-
if (host === 'localhost' || host === '::1')
|
|
107
|
-
return true;
|
|
108
|
-
if (isLoopbackIpv4(host))
|
|
109
|
-
return true;
|
|
110
|
-
const mapped = ipv4MappedToDotted(host);
|
|
111
|
-
return Boolean(mapped && isLoopbackIpv4(mapped));
|
|
112
|
-
}
|
|
113
|
-
/** True for RFC1918/link-local/CGNAT/multicast/unspecified/unique-local-IPv6 addresses — private network space a public caller should never be steered into. */
|
|
114
|
-
export function isBlockedExternalApiHostname(hostname) {
|
|
115
|
-
const host = normalizeBracketedIpv6(hostname);
|
|
116
|
-
if (host === '::')
|
|
117
|
-
return true;
|
|
118
|
-
if (isBlockedIpv4(host))
|
|
119
|
-
return true;
|
|
120
|
-
if (/^f[cd][0-9a-f]{2}:/i.test(host))
|
|
121
|
-
return true;
|
|
122
|
-
if (/^fe[89ab][0-9a-f]:/i.test(host))
|
|
123
|
-
return true;
|
|
124
|
-
const mapped = ipv4MappedToDotted(host);
|
|
125
|
-
return Boolean(mapped && isBlockedIpv4(mapped));
|
|
126
|
-
}
|
|
46
|
+
import { isLoopbackApiHost, isBlockedExternalApiHostname } from '@jini-ai/platform/net';
|
|
127
47
|
/**
|
|
128
48
|
* Synchronous base-URL check: scheme allow-list + literal-hostname block-list.
|
|
129
49
|
* Does not resolve DNS — see {@link validateBaseUrlResolved}.
|
|
@@ -144,7 +64,7 @@ export function isBlockedExternalApiHostname(hostname) {
|
|
|
144
64
|
* is the only shape that would let the block-list inspect one string while the
|
|
145
65
|
* network used another.
|
|
146
66
|
*/
|
|
147
|
-
export function validateBaseUrl(baseUrl) {
|
|
67
|
+
export function validateBaseUrl({ baseUrl }) {
|
|
148
68
|
let parsed;
|
|
149
69
|
try {
|
|
150
70
|
// Trim BEFORE stripping trailing slashes: in `"https://x/ "` the trailing
|
|
@@ -158,7 +78,7 @@ export function validateBaseUrl(baseUrl) {
|
|
|
158
78
|
return { error: 'Only http/https allowed' };
|
|
159
79
|
}
|
|
160
80
|
const hostname = parsed.hostname.toLowerCase();
|
|
161
|
-
if (!isLoopbackApiHost(hostname) && isBlockedExternalApiHostname(hostname)) {
|
|
81
|
+
if (!isLoopbackApiHost({ hostname: hostname }) && isBlockedExternalApiHostname({ hostname: hostname })) {
|
|
162
82
|
return { error: 'Internal IPs blocked', forbidden: true };
|
|
163
83
|
}
|
|
164
84
|
return { parsed };
|
|
@@ -202,46 +122,47 @@ function looksLikeIpLiteral(hostname) {
|
|
|
202
122
|
* unvalidated address; `pinnedFetch` never follows one regardless (`node:https`/`node:http` don't,
|
|
203
123
|
* unlike `fetch`'s default), but callers still pass `redirect: 'error'` for self-documentation.
|
|
204
124
|
*
|
|
205
|
-
* DNS lookup failures
|
|
206
|
-
*
|
|
207
|
-
*
|
|
125
|
+
* DNS lookup failures and empty answers fail closed: an unpinned fallback
|
|
126
|
+
* would let the transport resolve the host again without checking that answer.
|
|
127
|
+
* Earlier behavior deferred resolver hiccups to the transport; that bypassed
|
|
128
|
+
* validation if its independent lookup succeeded, so it is no longer allowed. The
|
|
208
129
|
* synchronous hostname check still rejects the obvious literal-IP cases
|
|
209
|
-
* before DNS is ever consulted. `lookup`
|
|
210
|
-
*
|
|
211
|
-
*
|
|
130
|
+
* before DNS is ever consulted. `lookup` is a required injected resolver;
|
|
131
|
+
* a Node host can wrap `node:dns`'s promise
|
|
132
|
+
* `lookup(hostname, { all: true, family: 0 })` in this port's object signature.
|
|
212
133
|
*/
|
|
213
|
-
export async function validateBaseUrlResolved(baseUrl, lookup) {
|
|
214
|
-
const sync = validateBaseUrl(baseUrl);
|
|
134
|
+
export async function validateBaseUrlResolved({ baseUrl, lookup }) {
|
|
135
|
+
const sync = validateBaseUrl({ baseUrl: baseUrl });
|
|
215
136
|
if (sync.error || !sync.parsed)
|
|
216
137
|
return sync;
|
|
217
138
|
const hostname = sync.parsed.hostname.toLowerCase();
|
|
218
|
-
if (isLoopbackApiHost(hostname))
|
|
139
|
+
if (isLoopbackApiHost({ hostname: hostname }))
|
|
219
140
|
return sync;
|
|
220
141
|
if (looksLikeIpLiteral(hostname))
|
|
221
142
|
return sync;
|
|
222
143
|
let addresses;
|
|
223
144
|
try {
|
|
224
|
-
addresses = await lookup(hostname);
|
|
145
|
+
addresses = await lookup({ hostname });
|
|
225
146
|
}
|
|
226
147
|
catch {
|
|
227
|
-
return
|
|
148
|
+
return { error: 'DNS lookup failed', forbidden: true };
|
|
228
149
|
}
|
|
150
|
+
if (addresses.length === 0)
|
|
151
|
+
return { error: 'DNS lookup returned no addresses', forbidden: true };
|
|
229
152
|
for (const addr of addresses) {
|
|
230
153
|
const ip = String(addr.address).toLowerCase();
|
|
231
|
-
if (isLoopbackApiHost(ip))
|
|
154
|
+
if (isLoopbackApiHost({ hostname: ip }))
|
|
232
155
|
continue;
|
|
233
|
-
if (isBlockedExternalApiHostname(ip)) {
|
|
156
|
+
if (isBlockedExternalApiHostname({ hostname: ip })) {
|
|
234
157
|
return { error: 'Internal IPs blocked', forbidden: true };
|
|
235
158
|
}
|
|
236
159
|
}
|
|
237
160
|
// Pin to the FIRST resolved address — the loop above already proved every address in this list
|
|
238
161
|
// cleared the block-list, so this is "the address the guard actually approved", singular, ready
|
|
239
|
-
// to hand to `pinnedFetch`.
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
const pinnedAddress = addresses[0];
|
|
244
|
-
return pinnedAddress ? { ...sync, pinnedAddress } : sync;
|
|
162
|
+
// to hand to `pinnedFetch`. A real `dns.lookup` throws ENOTFOUND rather than resolving to `[]`;
|
|
163
|
+
// injected resolvers can do either, and both fail closed above instead of reviving the earlier
|
|
164
|
+
// pre-pinning pass-through behavior. The nonempty check guarantees the first address exists.
|
|
165
|
+
return { ...sync, pinnedAddress: addresses[0] };
|
|
245
166
|
}
|
|
246
167
|
/**
|
|
247
168
|
* Idle-socket safety net, applied to every `pinnedFetch` request regardless of `init.signal`.
|
|
@@ -280,7 +201,7 @@ const PINNED_FETCH_IDLE_TIMEOUT_MS = 300_000;
|
|
|
280
201
|
* (`lookup` option simply omitted). That is intentional, not a gap: an IP literal has nothing left
|
|
281
202
|
* to re-resolve, and loopback literals are the documented carve-out, outside this threat model.
|
|
282
203
|
*/
|
|
283
|
-
export async function pinnedFetch(url, init, pinnedAddress) {
|
|
204
|
+
export async function pinnedFetch({ url, init, pinnedAddress }) {
|
|
284
205
|
const parsed = new URL(url);
|
|
285
206
|
const isHttps = parsed.protocol === 'https:';
|
|
286
207
|
const { request: httpsRequest } = await import('node:https');
|
|
@@ -380,32 +301,8 @@ export async function pinnedFetch(url, init, pinnedAddress) {
|
|
|
380
301
|
req.end(init.body);
|
|
381
302
|
});
|
|
382
303
|
}
|
|
383
|
-
function escapeRegExp(value) {
|
|
384
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Redacts bearer tokens, `x-api-key`/`api-key`/`x-goog-api-key` headers,
|
|
388
|
-
* `?key=` query values, and any exact secret strings supplied via
|
|
389
|
-
* `exactSecrets` out of free-form text — for logging or surfacing an
|
|
390
|
-
* upstream error message to a caller without leaking credentials embedded in
|
|
391
|
-
* it (some providers echo the key back in a 401 body).
|
|
392
|
-
*/
|
|
393
|
-
export function redactSecrets(text, exactSecrets = []) {
|
|
394
|
-
if (typeof text !== 'string' || text.length === 0)
|
|
395
|
-
return '';
|
|
396
|
-
let redacted = text
|
|
397
|
-
.replace(/Bearer\s+[A-Za-z0-9_\-.+/=]+/gi, 'Bearer [REDACTED]')
|
|
398
|
-
.replace(/(x-api-key|api-key|x-goog-api-key)\s*[:=]\s*[^\s,;"']+/gi, '$1: [REDACTED]')
|
|
399
|
-
.replace(/([?&]key=)[^&\s]+/gi, '$1[REDACTED]');
|
|
400
|
-
for (const secret of exactSecrets) {
|
|
401
|
-
if (typeof secret !== 'string' || secret.length === 0)
|
|
402
|
-
continue;
|
|
403
|
-
redacted = redacted.replace(new RegExp(escapeRegExp(secret), 'g'), '[REDACTED]');
|
|
404
|
-
}
|
|
405
|
-
return redacted;
|
|
406
|
-
}
|
|
407
304
|
/** Default DNS lookup for {@link validateBaseUrlResolved} — `node:dns/promises`' `lookup(hostname, { all: true, family: 0 })`. */
|
|
408
|
-
export async function defaultDnsLookup(hostname) {
|
|
305
|
+
export async function defaultDnsLookup({ hostname }) {
|
|
409
306
|
const { promises: dnsPromises } = await import('node:dns');
|
|
410
307
|
const result = await dnsPromises.lookup(hostname, { all: true, family: 0 });
|
|
411
308
|
return result.map(({ address, family }) => ({ address, family }));
|
|
@@ -58,5 +58,5 @@ export interface ConnectionTestResponse {
|
|
|
58
58
|
* @complexity O(1) — one bounded-size HTTP request with a hard timeout.
|
|
59
59
|
* @overallScore 100
|
|
60
60
|
*/
|
|
61
|
-
export declare function testProviderConnection(
|
|
61
|
+
export declare function testProviderConnection(requiredArgs: Pick<ProviderConnectionTestInput, "protocol" | "baseUrl" | "apiKey" | "model">, optionalArgs?: Omit<ProviderConnectionTestInput, "protocol" | "baseUrl" | "apiKey" | "model">): Promise<ConnectionTestResponse>;
|
|
62
62
|
//# sourceMappingURL=connection-test.d.ts.map
|
|
@@ -1,23 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* BYOK connection smoke test — sends a tiny "Reply with only: ok" completion
|
|
5
|
-
* request to the configured provider endpoint and classifies the outcome, so
|
|
6
|
-
* a settings UI can tell the operator "this endpoint works" without spending
|
|
7
|
-
* a real chat turn to find out. Ported from OD's `apps/daemon/src/connectionTest.ts`
|
|
8
|
-
* (`testProviderConnection` + `buildProviderCall` + `inspectProviderCompletion`),
|
|
9
|
-
* narrowed to the four protocols `@jini-ai/ui`'s execution-tab canary exposes
|
|
10
|
-
* (`anthropic` / `openai` / `azure` / `google`) — the origin also covers
|
|
11
|
-
* `ollama` / `senseaudio` / `aihubmix` / `bedrock`, which stay out of scope
|
|
12
|
-
* here the same way `model-catalog.ts` keeps Bedrock to a static seed and
|
|
13
|
-
* Azure to an explicit "unsupported" response for model discovery.
|
|
14
|
-
*
|
|
15
|
-
* Shares this package's own vendored SSRF guard / secret redaction
|
|
16
|
-
* (`connection-guard.ts`) and vendored connectivity types (`types.ts`) —
|
|
17
|
-
* no OD workspace-package imports, matching every other file in this
|
|
18
|
-
* directory.
|
|
19
|
-
*/
|
|
20
|
-
import { defaultDnsLookup, redactSecrets, validateBaseUrlResolved } from './connection-guard.js';
|
|
1
|
+
import { redactSecrets } from '@jini-ai/core';
|
|
2
|
+
import { defaultDnsLookup, validateBaseUrlResolved } from './connection-guard.js';
|
|
21
3
|
import { googleGenerateContentUrl } from './google.js';
|
|
22
4
|
import { buildOpenAIChatTokenParam, buildLegacyMaxTokensParam, buildMaxCompletionTokensParam } from './token-params.js';
|
|
23
5
|
const SMOKE_PROMPT = 'Reply with only: ok';
|
|
@@ -185,7 +167,7 @@ function buildProviderCall(input) {
|
|
|
185
167
|
},
|
|
186
168
|
body: {
|
|
187
169
|
model,
|
|
188
|
-
...buildOpenAIChatTokenParam(model, CONNECTION_TEST_MAX_TOKENS),
|
|
170
|
+
...buildOpenAIChatTokenParam({ model: model, maxTokens: CONNECTION_TEST_MAX_TOKENS }),
|
|
189
171
|
messages: [{ role: 'user', content: SMOKE_PROMPT }],
|
|
190
172
|
stream: false,
|
|
191
173
|
},
|
|
@@ -206,7 +188,7 @@ function buildProviderCall(input) {
|
|
|
206
188
|
headers: { 'content-type': 'application/json', 'api-key': apiKey },
|
|
207
189
|
body: {
|
|
208
190
|
...(usesVersionedOpenAIPath ? { model } : {}),
|
|
209
|
-
...buildLegacyMaxTokensParam(CONNECTION_TEST_MAX_TOKENS),
|
|
191
|
+
...buildLegacyMaxTokensParam({ maxTokens: CONNECTION_TEST_MAX_TOKENS }),
|
|
210
192
|
messages: [{ role: 'user', content: SMOKE_PROMPT }],
|
|
211
193
|
stream: false,
|
|
212
194
|
},
|
|
@@ -216,7 +198,7 @@ function buildProviderCall(input) {
|
|
|
216
198
|
case 'google': {
|
|
217
199
|
const effectiveBaseUrl = baseUrl.trim() || 'https://generativelanguage.googleapis.com';
|
|
218
200
|
return {
|
|
219
|
-
url: googleGenerateContentUrl(effectiveBaseUrl, model),
|
|
201
|
+
url: googleGenerateContentUrl({ baseUrl: effectiveBaseUrl, model: model }),
|
|
220
202
|
headers: { 'content-type': 'application/json', 'x-goog-api-key': apiKey },
|
|
221
203
|
body: {
|
|
222
204
|
contents: [{ role: 'user', parts: [{ text: SMOKE_PROMPT }] }],
|
|
@@ -247,7 +229,7 @@ function buildProviderCall(input) {
|
|
|
247
229
|
// rejected `max_tokens` param; this smoke test keeps the simpler single-shot
|
|
248
230
|
// legacy param (a connection test failing on a param-name mismatch on a
|
|
249
231
|
// reasoning-tier Azure deployment is a known, narrow gap — see this
|
|
250
|
-
// package's `
|
|
232
|
+
// package's `archived provenance ledger`) rather than porting OD's full retry-on-400 loop.
|
|
251
233
|
void buildMaxCompletionTokensParam;
|
|
252
234
|
/**
|
|
253
235
|
* Probes a BYOK provider endpoint with the supplied credentials by sending a
|
|
@@ -262,7 +244,8 @@ void buildMaxCompletionTokensParam;
|
|
|
262
244
|
* @complexity O(1) — one bounded-size HTTP request with a hard timeout.
|
|
263
245
|
* @overallScore 100
|
|
264
246
|
*/
|
|
265
|
-
export async function testProviderConnection(
|
|
247
|
+
export async function testProviderConnection(requiredArgs, optionalArgs = {}) {
|
|
248
|
+
const input = { ...optionalArgs, ...requiredArgs };
|
|
266
249
|
const start = Date.now();
|
|
267
250
|
const model = input.model.trim();
|
|
268
251
|
if (!isSupportedProtocol(input.protocol)) {
|
|
@@ -290,7 +273,7 @@ export async function testProviderConnection(input) {
|
|
|
290
273
|
detail: 'No API key saved. Save one to test the connection.',
|
|
291
274
|
};
|
|
292
275
|
}
|
|
293
|
-
const validated = await validateBaseUrlResolved(input.baseUrl, input.dnsLookup ?? defaultDnsLookup);
|
|
276
|
+
const validated = await validateBaseUrlResolved({ baseUrl: input.baseUrl, lookup: input.dnsLookup ?? defaultDnsLookup });
|
|
294
277
|
if (validated.error || !validated.parsed) {
|
|
295
278
|
return {
|
|
296
279
|
ok: false,
|
|
@@ -310,7 +293,7 @@ export async function testProviderConnection(input) {
|
|
|
310
293
|
kind: 'unknown',
|
|
311
294
|
latencyMs: Date.now() - start,
|
|
312
295
|
model,
|
|
313
|
-
detail: redactSecrets(err instanceof Error ? err.message : String(err), [input.apiKey]),
|
|
296
|
+
detail: redactSecrets({ input: err instanceof Error ? err.message : String(err) }, { exactSecrets: [input.apiKey] }),
|
|
314
297
|
};
|
|
315
298
|
}
|
|
316
299
|
const controller = new AbortController();
|
|
@@ -342,7 +325,7 @@ export async function testProviderConnection(input) {
|
|
|
342
325
|
// paths below, both of which treat an empty `data` as "no usable text".
|
|
343
326
|
}
|
|
344
327
|
if (!response.ok) {
|
|
345
|
-
const redactedDetail = redactSecrets(extractProviderErrorDetail(data, rawText).slice(0, 240), [input.apiKey]);
|
|
328
|
+
const redactedDetail = redactSecrets({ input: extractProviderErrorDetail(data, rawText).slice(0, 240) }, { exactSecrets: [input.apiKey] });
|
|
346
329
|
result = {
|
|
347
330
|
ok: false,
|
|
348
331
|
kind: statusToKind(response.status, redactedDetail),
|
|
@@ -366,7 +349,7 @@ export async function testProviderConnection(input) {
|
|
|
366
349
|
// header puts the operator's key in `detail` verbatim. Two independent
|
|
367
350
|
// audits reproduced that, each from a different call boundary; the
|
|
368
351
|
// redaction was simply missing on this one branch.
|
|
369
|
-
const sample = truncateSample(redactSecrets(text, [input.apiKey]));
|
|
352
|
+
const sample = truncateSample(redactSecrets({ input: text }, { exactSecrets: [input.apiKey] }));
|
|
370
353
|
// Naming the stop reason turns an unactionable "it returned nothing" into a diagnosis:
|
|
371
354
|
// `MAX_TOKENS` means the smoke budget was too small (usually a thinking model), a safety or
|
|
372
355
|
// content-filter reason means the probe was blocked, not that the endpoint is broken.
|
|
@@ -392,7 +375,7 @@ export async function testProviderConnection(input) {
|
|
|
392
375
|
kind: networkErrorToKind(err),
|
|
393
376
|
latencyMs: Date.now() - start,
|
|
394
377
|
model,
|
|
395
|
-
detail: redactSecrets(err instanceof Error ? err.message : String(err), [input.apiKey]),
|
|
378
|
+
detail: redactSecrets({ input: err instanceof Error ? err.message : String(err) }, { exactSecrets: [input.apiKey] }),
|
|
396
379
|
};
|
|
397
380
|
}
|
|
398
381
|
finally {
|
|
@@ -10,7 +10,9 @@ export interface ElevenLabsCredentials {
|
|
|
10
10
|
baseUrl?: string;
|
|
11
11
|
}
|
|
12
12
|
/** Host-supplied credential lookup — replaces OD's product-owned settings-store reader. */
|
|
13
|
-
export type ElevenLabsCredentialResolver = (
|
|
13
|
+
export type ElevenLabsCredentialResolver = (requiredArgs: {
|
|
14
|
+
workspaceKey: string;
|
|
15
|
+
}) => Promise<ElevenLabsCredentials>;
|
|
14
16
|
/** Thrown when {@link ElevenLabsCredentialResolver} resolves no API key. */
|
|
15
17
|
export declare class ElevenLabsCredentialMissingError extends Error {
|
|
16
18
|
constructor();
|
|
@@ -21,7 +23,10 @@ export declare class ElevenLabsCredentialMissingError extends Error {
|
|
|
21
23
|
* result for {@link ELEVENLABS_VOICE_CACHE_TTL_MS}. Throws
|
|
22
24
|
* {@link ElevenLabsCredentialMissingError} when no API key is configured.
|
|
23
25
|
*/
|
|
24
|
-
export declare function listElevenLabsVoiceOptions(workspaceKey
|
|
26
|
+
export declare function listElevenLabsVoiceOptions({ workspaceKey, resolveCredentials }: {
|
|
27
|
+
workspaceKey: string;
|
|
28
|
+
resolveCredentials: ElevenLabsCredentialResolver;
|
|
29
|
+
}, options?: {
|
|
25
30
|
limit?: number;
|
|
26
31
|
requestInit?: Pick<RequestInit, 'dispatcher'>;
|
|
27
32
|
}): Promise<ElevenLabsVoiceOption[]>;
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
* De-branded: the origin resolved the API key/base URL via OD's own
|
|
10
10
|
* `resolveProviderConfig` (`../media/config.js`, a product-owned
|
|
11
11
|
* settings-store reader entirely out of this package's scope) and threw a
|
|
12
|
-
* hardcoded product-prefixed env-var hint (see `
|
|
12
|
+
* hardcoded product-prefixed env-var hint (see `archived provenance ledger` for the
|
|
13
13
|
* exact original string). Both are replaced by an
|
|
14
14
|
* injected {@link ElevenLabsCredentialResolver} port — a host supplies its
|
|
15
15
|
* own settings-store/env lookup; this package only knows how to call the
|
|
16
16
|
* ElevenLabs wire API once it has a key.
|
|
17
17
|
*/
|
|
18
18
|
import { createHash } from 'node:crypto';
|
|
19
|
-
import { FETCH_TIMEOUT_MS, fetchWithTimeout } from '@jini-ai/platform';
|
|
19
|
+
import { FETCH_TIMEOUT_MS, fetchWithTimeout } from '@jini-ai/platform/fetch-with-timeout';
|
|
20
20
|
const ELEVENLABS_DEFAULT_BASE_URL = 'https://api.elevenlabs.io';
|
|
21
21
|
const ELEVENLABS_DEFAULT_VOICE_LIMIT = 100;
|
|
22
22
|
const ELEVENLABS_MAX_VOICE_LIMIT = 100;
|
|
@@ -93,8 +93,8 @@ function cloneVoiceOptions(voices) {
|
|
|
93
93
|
* result for {@link ELEVENLABS_VOICE_CACHE_TTL_MS}. Throws
|
|
94
94
|
* {@link ElevenLabsCredentialMissingError} when no API key is configured.
|
|
95
95
|
*/
|
|
96
|
-
export async function listElevenLabsVoiceOptions(workspaceKey, resolveCredentials, options = {}) {
|
|
97
|
-
const credentials = await resolveCredentials(workspaceKey);
|
|
96
|
+
export async function listElevenLabsVoiceOptions({ workspaceKey, resolveCredentials }, options = {}) {
|
|
97
|
+
const credentials = await resolveCredentials({ workspaceKey });
|
|
98
98
|
if (!credentials.apiKey) {
|
|
99
99
|
throw new ElevenLabsCredentialMissingError();
|
|
100
100
|
}
|