@byok-sdk/keys 0.1.0
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/LICENSE +21 -0
- package/README.md +150 -0
- package/dist/anthropic-client.d.ts +37 -0
- package/dist/command-runner.d.ts +22 -0
- package/dist/errors.d.ts +78 -0
- package/dist/headers.d.ts +21 -0
- package/dist/http.d.ts +44 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +1462 -0
- package/dist/index.js.map +1 -0
- package/dist/macos-keychain.d.ts +47 -0
- package/dist/openai-client.d.ts +55 -0
- package/dist/profile-store.d.ts +61 -0
- package/dist/provider-profile.d.ts +59 -0
- package/dist/registry.d.ts +103 -0
- package/dist/secret-name.d.ts +30 -0
- package/dist/secret-scope.d.ts +59 -0
- package/dist/secret-store.d.ts +103 -0
- package/dist/sqlite-profile-store.d.ts +35 -0
- package/dist/sqlite-support.d.ts +50 -0
- package/dist/url.d.ts +21 -0
- package/dist/windows-credential-manager.d.ts +31 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ancienttwo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# @byok-sdk/keys
|
|
2
|
+
|
|
3
|
+
Key-based BYOK: a validated provider profile, credential-backed auth headers, and
|
|
4
|
+
direct transports to OpenAI-compatible and Anthropic providers.
|
|
5
|
+
|
|
6
|
+
Status: **K3 done** — the pure-function layer (K0), the `SecretStore` layer
|
|
7
|
+
backed by the macOS Keychain and the Windows Credential Manager (K1), and the
|
|
8
|
+
configure/resolve registry with pluggable profile persistence (K2) have all
|
|
9
|
+
landed. K3 settled the settings-page question, recorded under
|
|
10
|
+
[Not in this package](#not-in-this-package). Next is K4, the aip-main-open swap.
|
|
11
|
+
|
|
12
|
+
## Security boundary
|
|
13
|
+
|
|
14
|
+
`@byok-sdk/keys` is a separate package with a separate security model from
|
|
15
|
+
`@byok/client` / `@byok/server` / `@byok/protocol`. Those three dispatch tasks to
|
|
16
|
+
agent runtimes the user already authenticated, and their credential-isolation
|
|
17
|
+
rule (`packages/client/src/types.ts:120-124`, audited in
|
|
18
|
+
`docs/security-review-m5-pilot-entry.md`) promises the dispatch path never
|
|
19
|
+
touches credentials. This package's job *is* to hold a provider API key, so it
|
|
20
|
+
lives on the other side of that line: `client`, `server`, and `protocol` must not
|
|
21
|
+
depend on `keys`.
|
|
22
|
+
|
|
23
|
+
Two consequences hold today and are the package's standing constraints:
|
|
24
|
+
|
|
25
|
+
1. `client`, `server`, and `protocol` must not gain a dependency on `keys`.
|
|
26
|
+
2. `@byok-sdk/keys` is outside the scope of the M5 credential-isolation claim.
|
|
27
|
+
Installing it is opting into a package that holds a provider API key, and
|
|
28
|
+
that choice is yours, not something the dispatch SDK does on your behalf.
|
|
29
|
+
|
|
30
|
+
The full declaration of the boundary between the two security models is
|
|
31
|
+
[`docs/security.md`](../../docs/security.md), section *Key management
|
|
32
|
+
(`@byok-sdk/keys`) is a separate package with a separate security model*.
|
|
33
|
+
|
|
34
|
+
## Not in this package
|
|
35
|
+
|
|
36
|
+
**There is no settings-page HTTP server here, and there will not be one.** The
|
|
37
|
+
source this package was ported from ships one — a localhost listener on a random
|
|
38
|
+
port, guarded by a token plus `Host`/`Origin`/CSP checks, serving
|
|
39
|
+
`/api/model/configure` and `/api/model/test`. It was evaluated for this package
|
|
40
|
+
at milestone K3 and deliberately excluded. Three reasons:
|
|
41
|
+
|
|
42
|
+
- **The host owns its own UI.** A settings page is product surface: its
|
|
43
|
+
branding, its routing, its invoke protocol, and its idea of what "configured"
|
|
44
|
+
should look like to a user. Shipping one from a library means every consumer
|
|
45
|
+
either accepts our product decisions or works around them.
|
|
46
|
+
- **This is a library, not a local web server.** A package you `npm install` to
|
|
47
|
+
hold a key should not decide to bind a socket. Anything that listens has a
|
|
48
|
+
lifecycle, a port, and an availability story that belongs to the application,
|
|
49
|
+
not to a dependency of it.
|
|
50
|
+
- **A key custodian does not open a listening port.** Every listener is an
|
|
51
|
+
entry point into the process that holds the API key. The narrowest defensible
|
|
52
|
+
posture for a component whose whole job is key custody is to expose no
|
|
53
|
+
network surface at all, so there is nothing to authenticate, rate-limit, or
|
|
54
|
+
CSRF-guard in the first place.
|
|
55
|
+
|
|
56
|
+
**The alternative: call `ProviderRegistry` directly.** Everything the settings
|
|
57
|
+
page did is available as a normal API. A host renders its own page and, in its
|
|
58
|
+
own request handler, calls `configure()`, `list()`, `get()`,
|
|
59
|
+
`setDefaultModelProvider()`, or `delete()`; to verify a key before committing to
|
|
60
|
+
it, resolve a client and call `testConnection()` on it. The registry never
|
|
61
|
+
returns the secret — `ProviderStatus` reports `secret_configured: boolean` and
|
|
62
|
+
nothing more — so a host can serve that object to its own UI without a
|
|
63
|
+
redaction step.
|
|
64
|
+
|
|
65
|
+
### What this transfers to you
|
|
66
|
+
|
|
67
|
+
This exclusion moves a security property, and the move is the point of this
|
|
68
|
+
section. In the source, the settings page was part of the same local process
|
|
69
|
+
and never sent the API key anywhere; **the package itself underwrote the
|
|
70
|
+
guarantee that the key does not leave the machine.**
|
|
71
|
+
|
|
72
|
+
With the page gone, `@byok-sdk/keys` guarantees only its own half: the key goes
|
|
73
|
+
into the OS credential store, it is never written to the profile store, it is
|
|
74
|
+
never present in any `ProviderStatus`, and it leaves the process only in the
|
|
75
|
+
`authorization` / `x-api-key` header of a request to the provider base URL the
|
|
76
|
+
profile declares. **Everything between the user's keystroke and
|
|
77
|
+
`configure(configuration, secret)` is now yours.** If your settings page posts
|
|
78
|
+
the key to your own backend before handing it to this package, or renders it
|
|
79
|
+
back into a response, or logs the request body, the key has left the machine —
|
|
80
|
+
and no property of this package prevents that. You are the custodian of that
|
|
81
|
+
path now.
|
|
82
|
+
|
|
83
|
+
## Node version and storage backends
|
|
84
|
+
|
|
85
|
+
`engines.node` is `>=20`, and that floor is deliberate: the package is fully
|
|
86
|
+
usable on Node 20.
|
|
87
|
+
|
|
88
|
+
| Backend | Requirement | Behaviour below it |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| `InMemoryProviderProfileStore` | Node 20+ | — the whole configure/resolve lifecycle works |
|
|
91
|
+
| `SqliteProviderProfileStore` | Node 22.5+ (`node:sqlite`) | fails closed with `PROVIDER_STORE_UNAVAILABLE` |
|
|
92
|
+
|
|
93
|
+
Only on-disk profile persistence needs the newer runtime. `node:sqlite` shipped
|
|
94
|
+
in Node 22.5 and spent part of the 22.x line behind `--experimental-sqlite`, so
|
|
95
|
+
a version-number comparison would be wrong in both directions; call
|
|
96
|
+
`isSqliteAvailable()` to branch, and constructing a `SqliteProviderProfileStore`
|
|
97
|
+
without it throws `ByokKeysError` with code `PROVIDER_STORE_UNAVAILABLE` rather
|
|
98
|
+
than degrading to a plaintext file.
|
|
99
|
+
|
|
100
|
+
The floor rises only when a consumer needs on-disk persistence as an
|
|
101
|
+
install-time guarantee rather than a runtime capability — that is, when
|
|
102
|
+
"`@byok-sdk/keys` installed successfully" must by itself imply
|
|
103
|
+
`SqliteProviderProfileStore` will construct. Until then, raising it would drop
|
|
104
|
+
Node 20 hosts that are served perfectly well by the in-memory store.
|
|
105
|
+
|
|
106
|
+
## Module inventory
|
|
107
|
+
|
|
108
|
+
Every module under `src/`, one line of responsibility each. The public surface is
|
|
109
|
+
whatever `index.ts` re-exports; nothing here is reachable by deep import.
|
|
110
|
+
|
|
111
|
+
| Module | Contents |
|
|
112
|
+
| --- | --- |
|
|
113
|
+
| `index.ts` | The package barrel — the single public entry point, and the only supported import path |
|
|
114
|
+
| `errors.ts` | `ByokKeysError` (`code` + message) and `BYOK_KEYS_ERROR_CODES`, the code strings consumers branch on |
|
|
115
|
+
| `provider-profile.ts` | zod schema for the model provider profile, including the adapter/auth-mode legality rules |
|
|
116
|
+
| `headers.ts` | `providerHeaders()` and fail-closed `requiredProviderSecret()` |
|
|
117
|
+
| `url.ts` | `normalizeProviderUrl()` with the HTTPS / loopback / private-network guard |
|
|
118
|
+
| `http.ts` | Shared transport guards: injectable `fetch`, timeout, bounded JSON, HTTP error classification |
|
|
119
|
+
| `openai-client.ts` | `OpenAiCompatibleChatClient` — chat/completions, injected `fetchImpl` |
|
|
120
|
+
| `anthropic-client.ts` | `AnthropicMessagesClient` — Messages API, injected `fetchImpl` |
|
|
121
|
+
| `secret-store.ts` | The `SecretStore` contract one credential entry is read and written through, plus the shared value/encoding guards |
|
|
122
|
+
| `secret-name.ts` | Runtime validation of secret entry names and namespaces, including the dot exclusion that stops one scope from spelling out another's storage key |
|
|
123
|
+
| `secret-scope.ts` | `SecretScope` and the envelope-scoped store that partitions a credential store by account and workspace |
|
|
124
|
+
| `macos-keychain.ts` | `SecretStore` backed by the macOS Keychain via the `security` CLI |
|
|
125
|
+
| `windows-credential-manager.ts` | `SecretStore` backed by the Win32 credential API via a PowerShell bridge |
|
|
126
|
+
| `command-runner.ts` | The `CommandRunner` injection seam both OS backends are written against, so no unit test touches a real credential store |
|
|
127
|
+
| `profile-store.ts` | The `ProviderProfileStore` persistence contract, plus the in-memory implementation |
|
|
128
|
+
| `sqlite-profile-store.ts` | `SqliteProviderProfileStore` — on-disk profile persistence on `node:sqlite` |
|
|
129
|
+
| `sqlite-support.ts` | Runtime `node:sqlite` capability detection and owner-only database file/directory creation |
|
|
130
|
+
| `registry.ts` | `ProviderRegistry` — the configure / list / resolve / delete lifecycle that binds a profile store to a secret store and hands back a ready client |
|
|
131
|
+
|
|
132
|
+
Auth modes map to headers as follows, and this mapping is the package's wire
|
|
133
|
+
contract:
|
|
134
|
+
|
|
135
|
+
| `auth_mode` | Headers added on top of `accept` / `content-type` |
|
|
136
|
+
| --- | --- |
|
|
137
|
+
| `bearer` | `authorization: Bearer <secret>` |
|
|
138
|
+
| `x_api_key` | `x-api-key: <secret>`, `anthropic-version: 2023-06-01` |
|
|
139
|
+
| `none` | none |
|
|
140
|
+
|
|
141
|
+
A profile declaring `bearer` or `x_api_key` without a secret fails closed with
|
|
142
|
+
`PROVIDER_SECRET_MISSING` instead of sending an unauthenticated request.
|
|
143
|
+
|
|
144
|
+
## Provenance
|
|
145
|
+
|
|
146
|
+
Ported symbol by symbol from `aip-main-open@c6a5385`
|
|
147
|
+
`apps/local-agent/src/providers.ts`. The AiphaBee narrative and finance domain
|
|
148
|
+
symbols listed in `docs/researches/HANDOFF-byok-keys.md` §4.5 deliberately stayed
|
|
149
|
+
behind; the clients here take generic `messages` / `max_tokens` / `system`
|
|
150
|
+
parameters instead of building domain prompts.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ModelProviderClientOptions } from './openai-client';
|
|
2
|
+
/** One Anthropic message. `content` is `unknown` so block arrays pass through. */
|
|
3
|
+
export interface AnthropicMessage {
|
|
4
|
+
role: 'user' | 'assistant';
|
|
5
|
+
content: unknown;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A Messages API request minus `model`, which the client fills from the
|
|
9
|
+
* profile. `max_tokens` is required because the API requires it.
|
|
10
|
+
*/
|
|
11
|
+
export interface AnthropicMessageRequest {
|
|
12
|
+
messages: readonly AnthropicMessage[];
|
|
13
|
+
max_tokens: number;
|
|
14
|
+
system?: string;
|
|
15
|
+
temperature?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Transport for the Anthropic Messages API, ported from
|
|
19
|
+
* `aip-main-open@c6a5385` `providers.ts:831-1067`.
|
|
20
|
+
*
|
|
21
|
+
* As with the OpenAI-compatible client, only `#createMessage`'s generic
|
|
22
|
+
* request/parse/error path travels; the narrative methods that wrapped it stay
|
|
23
|
+
* in aip-main-open per `docs/researches/HANDOFF-byok-keys.md` §4.5.
|
|
24
|
+
*/
|
|
25
|
+
export declare class AnthropicMessagesClient {
|
|
26
|
+
#private;
|
|
27
|
+
readonly model: string;
|
|
28
|
+
/** False only when the provider is loopback, i.e. no data leaves the machine. */
|
|
29
|
+
readonly remoteDataTransfer: boolean;
|
|
30
|
+
constructor(options: ModelProviderClientOptions);
|
|
31
|
+
/** POST `<base_url>/messages`; returns the parsed response object. */
|
|
32
|
+
createMessage(request: AnthropicMessageRequest, signal?: AbortSignal): Promise<Record<string, unknown>>;
|
|
33
|
+
/** Round-trip the configured key against the provider (`providers.ts:1037-1054`). */
|
|
34
|
+
testConnection(signal?: AbortSignal): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
/** Pull the assistant text out of a Messages API payload (`providers.ts:1084`). */
|
|
37
|
+
export declare function anthropicMessageText(payload: Record<string, unknown>): string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** A finished child process, ported from `aip-main-open@c6a5385` `index.ts:284-288`. */
|
|
2
|
+
export interface CommandResult {
|
|
3
|
+
exitCode: number;
|
|
4
|
+
stderr: string;
|
|
5
|
+
stdout: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The injection seam both OS backends are written against. Unit tests supply a
|
|
9
|
+
* fake so no test ever reads or writes a real credential store; production code
|
|
10
|
+
* gets {@link runCommand}.
|
|
11
|
+
*/
|
|
12
|
+
export type CommandRunner = (executable: string, args: string[], stdin?: string) => Promise<CommandResult>;
|
|
13
|
+
/**
|
|
14
|
+
* Spawn `executable`, feed it `stdin`, and collect its output
|
|
15
|
+
* (`index.ts:2498-2527`).
|
|
16
|
+
*
|
|
17
|
+
* It never rejects: a missing executable resolves as exit code 127 so the
|
|
18
|
+
* backends' `available()` probes can treat "no such binary" the same way they
|
|
19
|
+
* treat "binary said no". Stdin is always closed, otherwise a child that reads
|
|
20
|
+
* to EOF would hang forever.
|
|
21
|
+
*/
|
|
22
|
+
export declare function runCommand(executable: string, args: string[], stdin?: string): Promise<CommandResult>;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single error class for `@byok/keys`.
|
|
3
|
+
*
|
|
4
|
+
* The ported source (`aip-main-open@c6a5385`, `apps/local-agent/src/providers.ts`)
|
|
5
|
+
* raised two different classes — `LocalExecutionError` and
|
|
6
|
+
* `ResearchExecutionError` — that differed only in which subsystem owned the
|
|
7
|
+
* throw site; both carried a `code` string and consumers branched on that code.
|
|
8
|
+
* The narrative/research subsystem stays behind in aip-main-open, so this
|
|
9
|
+
* package keeps one class and preserves the `code` strings verbatim. K4's swap
|
|
10
|
+
* converts aip's two `instanceof` sites to structured code detection, so the
|
|
11
|
+
* strings — not the class identity — are the compatibility surface.
|
|
12
|
+
*/
|
|
13
|
+
export declare class ByokKeysError extends Error {
|
|
14
|
+
readonly code: string;
|
|
15
|
+
readonly httpStatus?: number;
|
|
16
|
+
constructor(code: string, message: string, options?: ErrorOptions & {
|
|
17
|
+
httpStatus?: number;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Error codes this package can throw, ported verbatim from the source. Kept as
|
|
22
|
+
* a named record so consumers (and K4's aip-main-open swap) can branch on
|
|
23
|
+
* `error.code` without retyping string literals.
|
|
24
|
+
*
|
|
25
|
+
* Four codes have no source counterpart, and each guards a check the source did
|
|
26
|
+
* not make: `KEYCHAIN_SECRET_DECODE_FAILED` (the source returned an undecodable
|
|
27
|
+
* stored value as-is), `SECRET_ENVELOPE_INVALID` (it reported a malformed
|
|
28
|
+
* envelope as an absent secret), and `SECRET_NAME_INVALID` /
|
|
29
|
+
* `SECRET_VALUE_INVALID` (the runtime replacements for the closed
|
|
30
|
+
* `KeychainSecretName` union). Everything else matches the source string for
|
|
31
|
+
* string.
|
|
32
|
+
*
|
|
33
|
+
* `SECRET_NAMESPACE_INVALID` is a separate case. The source does record it:
|
|
34
|
+
* `normalizeSecretNamespace` (`index.ts:748-757`) throws the verbatim string
|
|
35
|
+
* `'SECRET_NAMESPACE_INVALID'` at `index.ts:752`, and this package ports both
|
|
36
|
+
* that string and its pattern unchanged. It is still not a compatibility
|
|
37
|
+
* surface K4 must match, but for a different reason than the string: neither
|
|
38
|
+
* side has a branch consumer. In the source the only callers are the two OS
|
|
39
|
+
* stores' `scope()` methods, which just compose a service prefix; here the code
|
|
40
|
+
* is only ever asserted in tests. Matching it costs nothing and constrains
|
|
41
|
+
* nothing.
|
|
42
|
+
*/
|
|
43
|
+
export declare const BYOK_KEYS_ERROR_CODES: {
|
|
44
|
+
readonly CREDENTIAL_MANAGER_DELETE_FAILED: 'CREDENTIAL_MANAGER_DELETE_FAILED';
|
|
45
|
+
readonly CREDENTIAL_MANAGER_READ_FAILED: 'CREDENTIAL_MANAGER_READ_FAILED';
|
|
46
|
+
readonly CREDENTIAL_MANAGER_SECRET_INVALID: 'CREDENTIAL_MANAGER_SECRET_INVALID';
|
|
47
|
+
readonly CREDENTIAL_MANAGER_UNAVAILABLE: 'CREDENTIAL_MANAGER_UNAVAILABLE';
|
|
48
|
+
readonly CREDENTIAL_MANAGER_WRITE_FAILED: 'CREDENTIAL_MANAGER_WRITE_FAILED';
|
|
49
|
+
readonly KEYCHAIN_ARGUMENT_INVALID: 'KEYCHAIN_ARGUMENT_INVALID';
|
|
50
|
+
readonly KEYCHAIN_DELETE_FAILED: 'KEYCHAIN_DELETE_FAILED';
|
|
51
|
+
readonly KEYCHAIN_READ_FAILED: 'KEYCHAIN_READ_FAILED';
|
|
52
|
+
readonly KEYCHAIN_SECRET_DECODE_FAILED: 'KEYCHAIN_SECRET_DECODE_FAILED';
|
|
53
|
+
readonly KEYCHAIN_SECRET_INVALID: 'KEYCHAIN_SECRET_INVALID';
|
|
54
|
+
readonly KEYCHAIN_UNAVAILABLE: 'KEYCHAIN_UNAVAILABLE';
|
|
55
|
+
readonly KEYCHAIN_WRITE_FAILED: 'KEYCHAIN_WRITE_FAILED';
|
|
56
|
+
readonly LOCAL_ACCOUNT_SCOPE_INVALID: 'LOCAL_ACCOUNT_SCOPE_INVALID';
|
|
57
|
+
readonly MODEL_PROVIDER_AUTH_FAILED: 'MODEL_PROVIDER_AUTH_FAILED';
|
|
58
|
+
readonly MODEL_PROVIDER_BALANCE_INSUFFICIENT: 'MODEL_PROVIDER_BALANCE_INSUFFICIENT';
|
|
59
|
+
readonly MODEL_PROVIDER_HTTP_ERROR: 'MODEL_PROVIDER_HTTP_ERROR';
|
|
60
|
+
readonly MODEL_PROVIDER_MODEL_NOT_FOUND: 'MODEL_PROVIDER_MODEL_NOT_FOUND';
|
|
61
|
+
readonly MODEL_PROVIDER_RATE_LIMITED: 'MODEL_PROVIDER_RATE_LIMITED';
|
|
62
|
+
readonly MODEL_RESPONSE_INVALID: 'MODEL_RESPONSE_INVALID';
|
|
63
|
+
readonly PROVIDER_NOT_CONFIGURED: 'PROVIDER_NOT_CONFIGURED';
|
|
64
|
+
readonly PROVIDER_PROFILE_INVALID: 'PROVIDER_PROFILE_INVALID';
|
|
65
|
+
readonly PROVIDER_REQUEST_TIMEOUT: 'PROVIDER_REQUEST_TIMEOUT';
|
|
66
|
+
readonly PROVIDER_RESPONSE_INVALID: 'PROVIDER_RESPONSE_INVALID';
|
|
67
|
+
readonly PROVIDER_RESPONSE_TOO_LARGE: 'PROVIDER_RESPONSE_TOO_LARGE';
|
|
68
|
+
readonly PROVIDER_SECRET_EMPTY: 'PROVIDER_SECRET_EMPTY';
|
|
69
|
+
readonly PROVIDER_SECRET_MISSING: 'PROVIDER_SECRET_MISSING';
|
|
70
|
+
readonly PROVIDER_SECRET_NOT_ALLOWED: 'PROVIDER_SECRET_NOT_ALLOWED';
|
|
71
|
+
readonly PROVIDER_STORE_UNAVAILABLE: 'PROVIDER_STORE_UNAVAILABLE';
|
|
72
|
+
readonly PROVIDER_URL_INVALID: 'PROVIDER_URL_INVALID';
|
|
73
|
+
readonly SECRET_ENVELOPE_INVALID: 'SECRET_ENVELOPE_INVALID';
|
|
74
|
+
readonly SECRET_NAME_INVALID: 'SECRET_NAME_INVALID';
|
|
75
|
+
readonly SECRET_NAMESPACE_INVALID: 'SECRET_NAMESPACE_INVALID';
|
|
76
|
+
readonly SECRET_VALUE_INVALID: 'SECRET_VALUE_INVALID';
|
|
77
|
+
};
|
|
78
|
+
export type ByokKeysErrorCode = (typeof BYOK_KEYS_ERROR_CODES)[keyof typeof BYOK_KEYS_ERROR_CODES];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ProviderAuthMode } from './provider-profile';
|
|
2
|
+
/** The subset of a profile the header builder reads. */
|
|
3
|
+
export interface ProviderAuthProfile {
|
|
4
|
+
auth_mode: ProviderAuthMode;
|
|
5
|
+
kind: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Fail-closed secret check. Ported from `aip-main-open@c6a5385`
|
|
9
|
+
* `providers.ts:1657-1671`: a profile that declares `bearer` or `x_api_key`
|
|
10
|
+
* must have a secret, or the request is refused rather than sent unauthenticated.
|
|
11
|
+
*/
|
|
12
|
+
export declare function requiredProviderSecret(profile: ProviderAuthProfile, secret: string | undefined): string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Build the outbound request headers for a provider call. Key-for-key
|
|
15
|
+
* equivalent to `providers.ts:1680-1697`:
|
|
16
|
+
* - always `accept` and `content-type`;
|
|
17
|
+
* - `bearer` adds `authorization: Bearer <secret>`;
|
|
18
|
+
* - `x_api_key` adds `x-api-key: <secret>` and `anthropic-version: 2023-06-01`;
|
|
19
|
+
* - `none` adds nothing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function providerHeaders(profile: ProviderAuthProfile, secret: string | undefined): Record<string, string>;
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Injectable `fetch`. Defaults to `globalThis.fetch` at every call site. */
|
|
2
|
+
export type ProviderFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
3
|
+
/** Response body ceiling, ported from `providers.ts:106`. */
|
|
4
|
+
export declare const PROVIDER_RESPONSE_MAX_BYTES: number;
|
|
5
|
+
/** Per-request timeout, ported from `providers.ts:107`. */
|
|
6
|
+
export declare const PROVIDER_TIMEOUT_MS = 15000;
|
|
7
|
+
/**
|
|
8
|
+
* Issue a provider request under the source's guards
|
|
9
|
+
* (`providers.ts:1711-1743`): the URL is re-validated immediately before the
|
|
10
|
+
* call, the caller's abort signal is chained, and an internal timeout aborts
|
|
11
|
+
* with a distinguishable reason so a timeout maps to
|
|
12
|
+
* `PROVIDER_REQUEST_TIMEOUT` rather than a bare `AbortError`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function fetchWithProviderGuards(fetchImpl: ProviderFetch, url: string, init: RequestInit, signal: AbortSignal): Promise<Response>;
|
|
15
|
+
/**
|
|
16
|
+
* Read a JSON body with a size ceiling (`providers.ts:1825-1851`). The
|
|
17
|
+
* `content-length` check is an early exit; the decoded-byte check is the one
|
|
18
|
+
* that actually holds, since `content-length` is attacker-controlled.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseBoundedJsonResponse(response: Response): Promise<unknown>;
|
|
21
|
+
/**
|
|
22
|
+
* Parse a model-provider response, mapping non-2xx to a classified error
|
|
23
|
+
* (`providers.ts:1748-1769`). The source's `context` parameter only chose
|
|
24
|
+
* between two error classes; this package has one, so the parameter is gone.
|
|
25
|
+
*/
|
|
26
|
+
export declare function readModelProviderResponse(response: Response): Promise<unknown>;
|
|
27
|
+
/**
|
|
28
|
+
* Map an HTTP status plus error body onto a stable code
|
|
29
|
+
* (`providers.ts:1783-1815`). Providers disagree on status codes for billing
|
|
30
|
+
* and key problems, so the body text is inspected as well — the pattern lists
|
|
31
|
+
* are verbatim from the source, including the Chinese-language variants the
|
|
32
|
+
* source's providers actually return.
|
|
33
|
+
*/
|
|
34
|
+
export declare function classifyModelProviderHttpError(status: number, payload: unknown): string;
|
|
35
|
+
/** Join a normalized base URL with an API path, idempotently (`providers.ts:1949-1953`). */
|
|
36
|
+
export declare function modelApiUrl(baseUrl: string, suffix: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Flatten a message content field to text (`providers.ts:1955-1971`). Both
|
|
39
|
+
* dialects return either a string or an array of typed blocks.
|
|
40
|
+
*/
|
|
41
|
+
export declare function modelMessageText(value: unknown): string;
|
|
42
|
+
export declare function objectValue(value: unknown): Record<string, unknown> | undefined;
|
|
43
|
+
/** `providers.ts:2301-2308` — an empty completion does not prove a live key. */
|
|
44
|
+
export declare function assertLiveModelResponse(value: string): void;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export { ByokKeysError, BYOK_KEYS_ERROR_CODES } from './errors';
|
|
2
|
+
export type { ByokKeysErrorCode } from './errors';
|
|
3
|
+
export { MODEL_PROVIDER_IDS, MODEL_PROVIDER_ADAPTERS, PROVIDER_AUTH_MODES, ModelProviderProfileSchema, parseModelProviderProfile, } from './provider-profile';
|
|
4
|
+
export type { ModelProviderAdapter, ModelProviderId, ModelProviderProfile, ModelProviderProfileInput, ProviderAuthMode, } from './provider-profile';
|
|
5
|
+
export { providerHeaders, requiredProviderSecret } from './headers';
|
|
6
|
+
export type { ProviderAuthProfile } from './headers';
|
|
7
|
+
export { normalizeProviderUrl, isLoopbackHost, isLoopbackProviderUrl, isPrivateNetworkLiteral, } from './url';
|
|
8
|
+
export { assertLiveModelResponse, classifyModelProviderHttpError, fetchWithProviderGuards, modelApiUrl, modelMessageText, objectValue, parseBoundedJsonResponse, PROVIDER_RESPONSE_MAX_BYTES, PROVIDER_TIMEOUT_MS, readModelProviderResponse, } from './http';
|
|
9
|
+
export type { ProviderFetch } from './http';
|
|
10
|
+
export { OpenAiCompatibleChatClient, chatCompletionText } from './openai-client';
|
|
11
|
+
export type { ChatCompletionRequest, ChatMessage, ModelProviderClientOptions, } from './openai-client';
|
|
12
|
+
export { AnthropicMessagesClient, anthropicMessageText } from './anthropic-client';
|
|
13
|
+
export type { AnthropicMessage, AnthropicMessageRequest, } from './anthropic-client';
|
|
14
|
+
export { runCommand } from './command-runner';
|
|
15
|
+
export type { CommandResult, CommandRunner } from './command-runner';
|
|
16
|
+
export { SECRET_NAME_PATTERN, SECRET_NAMESPACE_PATTERN, assertSecretName, assertSecretNamespace, } from './secret-name';
|
|
17
|
+
export { DEFAULT_SECRET_SERVICE_PREFIX, InMemorySecretStore, MODEL_PROVIDER_SECRET_NAMES, assertSharedSecretValue, decodeStrictBase64Utf8, modelProviderSecretName, } from './secret-store';
|
|
18
|
+
export type { ModelProviderSecretName, SecretStore } from './secret-store';
|
|
19
|
+
export { DEFAULT_KEYCHAIN_SECRET_STORAGE_PREFIX, MacOsKeychainSecretStore, } from './macos-keychain';
|
|
20
|
+
export type { MacOsKeychainSecretStoreOptions } from './macos-keychain';
|
|
21
|
+
export { WindowsCredentialManagerSecretStore } from './windows-credential-manager';
|
|
22
|
+
export type { WindowsCredentialManagerSecretStoreOptions } from './windows-credential-manager';
|
|
23
|
+
export { DEFAULT_SECRET_ENVELOPE_PREFIX, EnvelopeScopedSecretStore, scopeSecretStore, secretScopeId, } from './secret-scope';
|
|
24
|
+
export type { SecretScope } from './secret-scope';
|
|
25
|
+
export { InMemoryProviderProfileStore } from './profile-store';
|
|
26
|
+
export type { ProviderProfileStore } from './profile-store';
|
|
27
|
+
export { isSqliteAvailable, loadSqliteModule, openSqliteDatabase, secureSqliteFilePermissions, } from './sqlite-support';
|
|
28
|
+
export { SqliteProviderProfileStore } from './sqlite-profile-store';
|
|
29
|
+
export type { SqliteProviderProfileStoreOptions } from './sqlite-profile-store';
|
|
30
|
+
export { ProviderRegistry } from './registry';
|
|
31
|
+
export type { ModelProviderClient, ProviderConfiguration, ProviderRegistryOptions, ProviderStatus, } from './registry';
|