@openkeyai/sdk 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 +147 -0
- package/dist/index.cjs +387 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +267 -0
- package/dist/index.d.ts +267 -0
- package/dist/index.js +369 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types — kept in their own module so they can be imported without
|
|
3
|
+
* pulling the verifier / fetcher implementations into a tool's bundle.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the FROZEN claim contract in
|
|
6
|
+
* https://github.com/Scott-Builds-AI/hub/blob/main/docs/ARCHITECTURE.md#appendix-b
|
|
7
|
+
* Changes here require coordinated bumps across hub + SDK + every deployed
|
|
8
|
+
* tool.
|
|
9
|
+
*/
|
|
10
|
+
/** Scopes a tool may request and a JWT may carry. */
|
|
11
|
+
type ToolJwtScope = "keys.read" | "user.read" | "billing.read";
|
|
12
|
+
/** Decoded + verified claim payload. */
|
|
13
|
+
type ToolJwtClaims = {
|
|
14
|
+
/** Always exactly "https://openkeyai.com". */
|
|
15
|
+
iss: "https://openkeyai.com";
|
|
16
|
+
/** Hub user_id (Supabase auth.users.id, UUID v4). */
|
|
17
|
+
sub: string;
|
|
18
|
+
/** Tool slug the token was minted for. Must match the calling tool's slug. */
|
|
19
|
+
aud: string;
|
|
20
|
+
/** Subset of the scope set above. */
|
|
21
|
+
scopes: ToolJwtScope[];
|
|
22
|
+
/** Whether the user has an active subscription at issuance time. */
|
|
23
|
+
subscription_active: boolean;
|
|
24
|
+
/** Unix seconds. */
|
|
25
|
+
iat: number;
|
|
26
|
+
/** Unix seconds. Tokens default to iat + 900 (15 min). */
|
|
27
|
+
exp: number;
|
|
28
|
+
/** Unique token id, used for future revocation. */
|
|
29
|
+
jti: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Provider slug as seen by the hub vault. Kept as a string union rather
|
|
33
|
+
* than an `unknown` so editor autocomplete is useful; the hub will reject
|
|
34
|
+
* any value not in its registry, regardless of what TS thinks.
|
|
35
|
+
*/
|
|
36
|
+
type ProviderSlug = "openai" | "anthropic" | "google" | "replicate" | "elevenlabs" | "fal" | (string & {});
|
|
37
|
+
/**
|
|
38
|
+
* Optional overrides for any SDK call. All defaults match production.
|
|
39
|
+
*/
|
|
40
|
+
type HubCallOptions = {
|
|
41
|
+
/**
|
|
42
|
+
* Root URL of the hub. Defaults to `https://openkeyai.com`. Override only
|
|
43
|
+
* for staging or local-dev tools.
|
|
44
|
+
*/
|
|
45
|
+
hubUrl?: string;
|
|
46
|
+
/**
|
|
47
|
+
* AbortSignal to cancel the call. SDK never sets a default timeout — use
|
|
48
|
+
* the caller's request signal.
|
|
49
|
+
*/
|
|
50
|
+
signal?: AbortSignal;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `session.verify(jwt, opts?)`
|
|
55
|
+
*
|
|
56
|
+
* Verifies a hub-issued JWT against the hub's public JWKS, then validates
|
|
57
|
+
* the claim shape (`iss`, `sub`, `aud`, `scopes`, `subscription_active`).
|
|
58
|
+
*
|
|
59
|
+
* Returns the typed claims on success.
|
|
60
|
+
* Throws `BadTokenError` on any failure — signature, expiry, issuer,
|
|
61
|
+
* audience, missing/malformed custom claims.
|
|
62
|
+
* Throws `MissingTokenError` if `jwt` is empty / null.
|
|
63
|
+
*
|
|
64
|
+
* The verifier:
|
|
65
|
+
* - fetches `${hubUrl}/.well-known/jwks.json` (cached per hub URL)
|
|
66
|
+
* - re-fetches on `kid` rotation (jose handles this transparently)
|
|
67
|
+
* - DOES NOT validate `aud` here — the caller specifies which audience
|
|
68
|
+
* they expect (the tool's own slug, OR a wildcard for tools that
|
|
69
|
+
* legitimately accept multiple audiences). `keys.get` validates
|
|
70
|
+
* audience against the tool slug derived from the route.
|
|
71
|
+
*
|
|
72
|
+
* @param jwt The bearer token string (no `Bearer ` prefix).
|
|
73
|
+
* @param opts.hubUrl override the hub root (default: `https://openkeyai.com`)
|
|
74
|
+
* @param opts.expectedAudience tool slug the token must be addressed to.
|
|
75
|
+
* When omitted, audience is not checked here — the caller MUST do it
|
|
76
|
+
* themselves before granting any scope-derived privilege.
|
|
77
|
+
*/
|
|
78
|
+
declare function verify(jwt: string, opts?: HubCallOptions & {
|
|
79
|
+
expectedAudience?: string;
|
|
80
|
+
}): Promise<ToolJwtClaims>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* SecureKey — the only way the SDK hands a plaintext credential to a tool.
|
|
84
|
+
*
|
|
85
|
+
* Design goals (per docs/SECURITY.md in the hub repo):
|
|
86
|
+
*
|
|
87
|
+
* 1. The plaintext is reachable from EXACTLY ONE place: inside a callback
|
|
88
|
+
* passed to `use(fn)`. Outside that callback, no public method or
|
|
89
|
+
* property returns the value.
|
|
90
|
+
*
|
|
91
|
+
* 2. After the callback runs (or throws), the held reference is set to
|
|
92
|
+
* null. Subsequent `use()` calls throw `SecureKeyConsumedError`. The
|
|
93
|
+
* pattern is one-shot — get a fresh SecureKey for the next request.
|
|
94
|
+
*
|
|
95
|
+
* 3. `JSON.stringify`, `toString`, `console.log` (via util.inspect), and
|
|
96
|
+
* template-literal coercion all return the literal string
|
|
97
|
+
* `"[SecureKey]"` — never the underlying value, even by accident.
|
|
98
|
+
*
|
|
99
|
+
* #plaintext is a true private field (Stage 3 syntax) so it doesn't appear
|
|
100
|
+
* in `Object.keys()`, isn't accessible via bracket-notation, and is not
|
|
101
|
+
* enumerable for serialisation. The frozen overrides below cover the
|
|
102
|
+
* coercion paths.
|
|
103
|
+
*
|
|
104
|
+
* What we deliberately do NOT do:
|
|
105
|
+
*
|
|
106
|
+
* - WeakRef + FinalizationRegistry. They're observable from the same realm,
|
|
107
|
+
* and we have no useful action to take on GC. Single-use + manual null
|
|
108
|
+
* is the simpler, more deterministic guarantee.
|
|
109
|
+
*
|
|
110
|
+
* - Buffer.alloc + .fill(0) "zeroising". V8 and modern runtimes can keep
|
|
111
|
+
* copies of strings in cache; we can't truly zeroise from JS. The
|
|
112
|
+
* useful guarantee we CAN give is reference-clearing, which we do.
|
|
113
|
+
*/
|
|
114
|
+
declare class SecureKey {
|
|
115
|
+
#private;
|
|
116
|
+
/** Provider slug the key was fetched for. Public — not sensitive. */
|
|
117
|
+
readonly provider: string;
|
|
118
|
+
/** @internal — tools should use `keys.get()`, never construct directly. */
|
|
119
|
+
constructor(plaintext: string, provider: string);
|
|
120
|
+
/**
|
|
121
|
+
* Pass the plaintext into a callback. Returns whatever the callback
|
|
122
|
+
* returns. After the callback resolves (or throws), the SecureKey is
|
|
123
|
+
* consumed — subsequent calls throw `SecureKeyConsumedError`.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* const k = await keys.get(jwt, "openai");
|
|
127
|
+
* const response = await k.use((apiKey) => fetch("...", {
|
|
128
|
+
* headers: { "Authorization": `Bearer ${apiKey}` },
|
|
129
|
+
* }));
|
|
130
|
+
*/
|
|
131
|
+
use<T>(fn: (plaintext: string) => T | Promise<T>): Promise<T>;
|
|
132
|
+
/** Returns true once `use()` has been called and the reference cleared. */
|
|
133
|
+
get isConsumed(): boolean;
|
|
134
|
+
/** Always `[SecureKey]`. Never the underlying value. */
|
|
135
|
+
toString(): string;
|
|
136
|
+
/** Always `[SecureKey]`. Catches `JSON.stringify` and friends. */
|
|
137
|
+
toJSON(): string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* `keys.get(jwt, provider, opts?)`
|
|
142
|
+
*
|
|
143
|
+
* Fetches a credential for the user identified by `jwt` and returns a
|
|
144
|
+
* single-use SecureKey. The hub:
|
|
145
|
+
*
|
|
146
|
+
* 1. Verifies the JWT (signature + iss + aud + exp)
|
|
147
|
+
* 2. Confirms `keys.read` scope, active subscription, and that the tool +
|
|
148
|
+
* user are both subscribed to this provider
|
|
149
|
+
* 3. Decrypts the user's stored API key via KMS
|
|
150
|
+
* 4. Returns the plaintext, logged to the audit trail
|
|
151
|
+
*
|
|
152
|
+
* On the client side we:
|
|
153
|
+
* - Decode (no verify) the JWT locally to read the `aud` claim — that's
|
|
154
|
+
* the tool slug the hub will scope the lookup against
|
|
155
|
+
* - Pre-check the `keys.read` scope so we can return a typed error
|
|
156
|
+
* without a round-trip
|
|
157
|
+
* - Issue the GET, map the hub's frozen error codes to typed exceptions
|
|
158
|
+
* - Wrap the plaintext in a SecureKey and return
|
|
159
|
+
*
|
|
160
|
+
* IMPORTANT: a SecureKey is single-use. Don't call `.use()` more than once;
|
|
161
|
+
* call `keys.get` again for the next request. The audit trail records every
|
|
162
|
+
* fetch.
|
|
163
|
+
*/
|
|
164
|
+
declare function get(jwt: string, provider: ProviderSlug, opts?: HubCallOptions): Promise<SecureKey>;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Typed errors.
|
|
168
|
+
*
|
|
169
|
+
* The error `code` strings are part of the FROZEN public contract — see the
|
|
170
|
+
* key-fetch endpoint's documentation in
|
|
171
|
+
* https://github.com/Scott-Builds-AI/hub/blob/main/docs/phases/05-tools-keyfetch.md
|
|
172
|
+
*
|
|
173
|
+
* Adding a new code is a minor-version bump. Renaming or removing one is a
|
|
174
|
+
* major (with 60-day notice).
|
|
175
|
+
*
|
|
176
|
+
* Tools catch these to surface precise UX:
|
|
177
|
+
*
|
|
178
|
+
* try {
|
|
179
|
+
* await keys.get(jwt, "openai");
|
|
180
|
+
* } catch (e) {
|
|
181
|
+
* if (e instanceof SubscriptionInactiveError) showPaywall();
|
|
182
|
+
* else if (e instanceof RateLimitedError) showRetryToast(e.retryAfter);
|
|
183
|
+
* else throw e;
|
|
184
|
+
* }
|
|
185
|
+
*/
|
|
186
|
+
type HubSdkErrorCode = "missing_token" | "bad_token" | "missing_scope" | "subscription_inactive" | "tool_not_found" | "not_subscribed" | "provider_not_granted" | "rate_limited" | "key_not_found" | "internal" | "network" | "secure_key_consumed";
|
|
187
|
+
/** Base class for every typed error in the SDK. */
|
|
188
|
+
declare class HubSdkError extends Error {
|
|
189
|
+
readonly code: HubSdkErrorCode;
|
|
190
|
+
/** HTTP status from the hub, when applicable. 0 for client-only errors. */
|
|
191
|
+
readonly status: number;
|
|
192
|
+
constructor(code: HubSdkErrorCode, message: string, status?: number);
|
|
193
|
+
}
|
|
194
|
+
declare class MissingTokenError extends HubSdkError {
|
|
195
|
+
constructor();
|
|
196
|
+
}
|
|
197
|
+
declare class BadTokenError extends HubSdkError {
|
|
198
|
+
constructor(message?: string);
|
|
199
|
+
}
|
|
200
|
+
declare class MissingScopeError extends HubSdkError {
|
|
201
|
+
constructor(needed: string);
|
|
202
|
+
}
|
|
203
|
+
declare class SubscriptionInactiveError extends HubSdkError {
|
|
204
|
+
constructor();
|
|
205
|
+
}
|
|
206
|
+
declare class ToolNotFoundError extends HubSdkError {
|
|
207
|
+
constructor();
|
|
208
|
+
}
|
|
209
|
+
declare class NotSubscribedError extends HubSdkError {
|
|
210
|
+
constructor();
|
|
211
|
+
}
|
|
212
|
+
declare class ProviderNotGrantedError extends HubSdkError {
|
|
213
|
+
constructor(provider: string);
|
|
214
|
+
}
|
|
215
|
+
declare class RateLimitedError extends HubSdkError {
|
|
216
|
+
readonly retryAfter: number;
|
|
217
|
+
constructor(retryAfter: number);
|
|
218
|
+
}
|
|
219
|
+
declare class KeyNotFoundError extends HubSdkError {
|
|
220
|
+
constructor(provider: string);
|
|
221
|
+
}
|
|
222
|
+
declare class InternalError extends HubSdkError {
|
|
223
|
+
constructor(status: number, message?: string);
|
|
224
|
+
}
|
|
225
|
+
declare class NetworkError extends HubSdkError {
|
|
226
|
+
constructor(cause: unknown);
|
|
227
|
+
}
|
|
228
|
+
declare class SecureKeyConsumedError extends HubSdkError {
|
|
229
|
+
constructor();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* `@openkeyai/sdk` — public entry.
|
|
234
|
+
*
|
|
235
|
+
* Tools install this and import only from the package root:
|
|
236
|
+
*
|
|
237
|
+
* import { session, keys, SecureKey, SubscriptionInactiveError } from "@openkeyai/sdk";
|
|
238
|
+
*
|
|
239
|
+
* The five-module surface (session / keys / user / billing / webhooks) is
|
|
240
|
+
* defined in
|
|
241
|
+
* https://github.com/Scott-Builds-AI/hub/blob/main/docs/TOOL_SDK.md
|
|
242
|
+
*
|
|
243
|
+
* Status by module in 0.1.0:
|
|
244
|
+
* - session.verify ✓
|
|
245
|
+
* - keys.get → SecureKey ✓
|
|
246
|
+
* - user — deferred until the hub ships /api/me (issue TBD)
|
|
247
|
+
* - billing — deferred until the hub ships /api/billing/status
|
|
248
|
+
* - webhooks — deferred until Phase 16 (the hub's webhook delivery layer)
|
|
249
|
+
*
|
|
250
|
+
* Internal helpers live under `_internal/`. They are NOT part of the
|
|
251
|
+
* public API and may change without notice — the tool-manifest scanner
|
|
252
|
+
* (Phase 9) treats `@openkeyai/sdk/_internal` imports as a CI failure.
|
|
253
|
+
*/
|
|
254
|
+
|
|
255
|
+
/** session module — JWT verification + (future) refresh. */
|
|
256
|
+
declare const session: {
|
|
257
|
+
readonly verify: typeof verify;
|
|
258
|
+
};
|
|
259
|
+
/** keys module — credential fetch returning a single-use SecureKey. */
|
|
260
|
+
declare const keys: {
|
|
261
|
+
readonly get: typeof get;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/** Bumped on each release. Tools log this on boot. */
|
|
265
|
+
declare const SDK_VERSION = "0.1.0";
|
|
266
|
+
|
|
267
|
+
export { BadTokenError, type HubCallOptions, HubSdkError, type HubSdkErrorCode, InternalError, KeyNotFoundError, MissingScopeError, MissingTokenError, NetworkError, NotSubscribedError, ProviderNotGrantedError, type ProviderSlug, RateLimitedError, SDK_VERSION, SecureKey, SecureKeyConsumedError, SubscriptionInactiveError, type ToolJwtClaims, type ToolJwtScope, ToolNotFoundError, keys, session };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types — kept in their own module so they can be imported without
|
|
3
|
+
* pulling the verifier / fetcher implementations into a tool's bundle.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the FROZEN claim contract in
|
|
6
|
+
* https://github.com/Scott-Builds-AI/hub/blob/main/docs/ARCHITECTURE.md#appendix-b
|
|
7
|
+
* Changes here require coordinated bumps across hub + SDK + every deployed
|
|
8
|
+
* tool.
|
|
9
|
+
*/
|
|
10
|
+
/** Scopes a tool may request and a JWT may carry. */
|
|
11
|
+
type ToolJwtScope = "keys.read" | "user.read" | "billing.read";
|
|
12
|
+
/** Decoded + verified claim payload. */
|
|
13
|
+
type ToolJwtClaims = {
|
|
14
|
+
/** Always exactly "https://openkeyai.com". */
|
|
15
|
+
iss: "https://openkeyai.com";
|
|
16
|
+
/** Hub user_id (Supabase auth.users.id, UUID v4). */
|
|
17
|
+
sub: string;
|
|
18
|
+
/** Tool slug the token was minted for. Must match the calling tool's slug. */
|
|
19
|
+
aud: string;
|
|
20
|
+
/** Subset of the scope set above. */
|
|
21
|
+
scopes: ToolJwtScope[];
|
|
22
|
+
/** Whether the user has an active subscription at issuance time. */
|
|
23
|
+
subscription_active: boolean;
|
|
24
|
+
/** Unix seconds. */
|
|
25
|
+
iat: number;
|
|
26
|
+
/** Unix seconds. Tokens default to iat + 900 (15 min). */
|
|
27
|
+
exp: number;
|
|
28
|
+
/** Unique token id, used for future revocation. */
|
|
29
|
+
jti: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Provider slug as seen by the hub vault. Kept as a string union rather
|
|
33
|
+
* than an `unknown` so editor autocomplete is useful; the hub will reject
|
|
34
|
+
* any value not in its registry, regardless of what TS thinks.
|
|
35
|
+
*/
|
|
36
|
+
type ProviderSlug = "openai" | "anthropic" | "google" | "replicate" | "elevenlabs" | "fal" | (string & {});
|
|
37
|
+
/**
|
|
38
|
+
* Optional overrides for any SDK call. All defaults match production.
|
|
39
|
+
*/
|
|
40
|
+
type HubCallOptions = {
|
|
41
|
+
/**
|
|
42
|
+
* Root URL of the hub. Defaults to `https://openkeyai.com`. Override only
|
|
43
|
+
* for staging or local-dev tools.
|
|
44
|
+
*/
|
|
45
|
+
hubUrl?: string;
|
|
46
|
+
/**
|
|
47
|
+
* AbortSignal to cancel the call. SDK never sets a default timeout — use
|
|
48
|
+
* the caller's request signal.
|
|
49
|
+
*/
|
|
50
|
+
signal?: AbortSignal;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `session.verify(jwt, opts?)`
|
|
55
|
+
*
|
|
56
|
+
* Verifies a hub-issued JWT against the hub's public JWKS, then validates
|
|
57
|
+
* the claim shape (`iss`, `sub`, `aud`, `scopes`, `subscription_active`).
|
|
58
|
+
*
|
|
59
|
+
* Returns the typed claims on success.
|
|
60
|
+
* Throws `BadTokenError` on any failure — signature, expiry, issuer,
|
|
61
|
+
* audience, missing/malformed custom claims.
|
|
62
|
+
* Throws `MissingTokenError` if `jwt` is empty / null.
|
|
63
|
+
*
|
|
64
|
+
* The verifier:
|
|
65
|
+
* - fetches `${hubUrl}/.well-known/jwks.json` (cached per hub URL)
|
|
66
|
+
* - re-fetches on `kid` rotation (jose handles this transparently)
|
|
67
|
+
* - DOES NOT validate `aud` here — the caller specifies which audience
|
|
68
|
+
* they expect (the tool's own slug, OR a wildcard for tools that
|
|
69
|
+
* legitimately accept multiple audiences). `keys.get` validates
|
|
70
|
+
* audience against the tool slug derived from the route.
|
|
71
|
+
*
|
|
72
|
+
* @param jwt The bearer token string (no `Bearer ` prefix).
|
|
73
|
+
* @param opts.hubUrl override the hub root (default: `https://openkeyai.com`)
|
|
74
|
+
* @param opts.expectedAudience tool slug the token must be addressed to.
|
|
75
|
+
* When omitted, audience is not checked here — the caller MUST do it
|
|
76
|
+
* themselves before granting any scope-derived privilege.
|
|
77
|
+
*/
|
|
78
|
+
declare function verify(jwt: string, opts?: HubCallOptions & {
|
|
79
|
+
expectedAudience?: string;
|
|
80
|
+
}): Promise<ToolJwtClaims>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* SecureKey — the only way the SDK hands a plaintext credential to a tool.
|
|
84
|
+
*
|
|
85
|
+
* Design goals (per docs/SECURITY.md in the hub repo):
|
|
86
|
+
*
|
|
87
|
+
* 1. The plaintext is reachable from EXACTLY ONE place: inside a callback
|
|
88
|
+
* passed to `use(fn)`. Outside that callback, no public method or
|
|
89
|
+
* property returns the value.
|
|
90
|
+
*
|
|
91
|
+
* 2. After the callback runs (or throws), the held reference is set to
|
|
92
|
+
* null. Subsequent `use()` calls throw `SecureKeyConsumedError`. The
|
|
93
|
+
* pattern is one-shot — get a fresh SecureKey for the next request.
|
|
94
|
+
*
|
|
95
|
+
* 3. `JSON.stringify`, `toString`, `console.log` (via util.inspect), and
|
|
96
|
+
* template-literal coercion all return the literal string
|
|
97
|
+
* `"[SecureKey]"` — never the underlying value, even by accident.
|
|
98
|
+
*
|
|
99
|
+
* #plaintext is a true private field (Stage 3 syntax) so it doesn't appear
|
|
100
|
+
* in `Object.keys()`, isn't accessible via bracket-notation, and is not
|
|
101
|
+
* enumerable for serialisation. The frozen overrides below cover the
|
|
102
|
+
* coercion paths.
|
|
103
|
+
*
|
|
104
|
+
* What we deliberately do NOT do:
|
|
105
|
+
*
|
|
106
|
+
* - WeakRef + FinalizationRegistry. They're observable from the same realm,
|
|
107
|
+
* and we have no useful action to take on GC. Single-use + manual null
|
|
108
|
+
* is the simpler, more deterministic guarantee.
|
|
109
|
+
*
|
|
110
|
+
* - Buffer.alloc + .fill(0) "zeroising". V8 and modern runtimes can keep
|
|
111
|
+
* copies of strings in cache; we can't truly zeroise from JS. The
|
|
112
|
+
* useful guarantee we CAN give is reference-clearing, which we do.
|
|
113
|
+
*/
|
|
114
|
+
declare class SecureKey {
|
|
115
|
+
#private;
|
|
116
|
+
/** Provider slug the key was fetched for. Public — not sensitive. */
|
|
117
|
+
readonly provider: string;
|
|
118
|
+
/** @internal — tools should use `keys.get()`, never construct directly. */
|
|
119
|
+
constructor(plaintext: string, provider: string);
|
|
120
|
+
/**
|
|
121
|
+
* Pass the plaintext into a callback. Returns whatever the callback
|
|
122
|
+
* returns. After the callback resolves (or throws), the SecureKey is
|
|
123
|
+
* consumed — subsequent calls throw `SecureKeyConsumedError`.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* const k = await keys.get(jwt, "openai");
|
|
127
|
+
* const response = await k.use((apiKey) => fetch("...", {
|
|
128
|
+
* headers: { "Authorization": `Bearer ${apiKey}` },
|
|
129
|
+
* }));
|
|
130
|
+
*/
|
|
131
|
+
use<T>(fn: (plaintext: string) => T | Promise<T>): Promise<T>;
|
|
132
|
+
/** Returns true once `use()` has been called and the reference cleared. */
|
|
133
|
+
get isConsumed(): boolean;
|
|
134
|
+
/** Always `[SecureKey]`. Never the underlying value. */
|
|
135
|
+
toString(): string;
|
|
136
|
+
/** Always `[SecureKey]`. Catches `JSON.stringify` and friends. */
|
|
137
|
+
toJSON(): string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* `keys.get(jwt, provider, opts?)`
|
|
142
|
+
*
|
|
143
|
+
* Fetches a credential for the user identified by `jwt` and returns a
|
|
144
|
+
* single-use SecureKey. The hub:
|
|
145
|
+
*
|
|
146
|
+
* 1. Verifies the JWT (signature + iss + aud + exp)
|
|
147
|
+
* 2. Confirms `keys.read` scope, active subscription, and that the tool +
|
|
148
|
+
* user are both subscribed to this provider
|
|
149
|
+
* 3. Decrypts the user's stored API key via KMS
|
|
150
|
+
* 4. Returns the plaintext, logged to the audit trail
|
|
151
|
+
*
|
|
152
|
+
* On the client side we:
|
|
153
|
+
* - Decode (no verify) the JWT locally to read the `aud` claim — that's
|
|
154
|
+
* the tool slug the hub will scope the lookup against
|
|
155
|
+
* - Pre-check the `keys.read` scope so we can return a typed error
|
|
156
|
+
* without a round-trip
|
|
157
|
+
* - Issue the GET, map the hub's frozen error codes to typed exceptions
|
|
158
|
+
* - Wrap the plaintext in a SecureKey and return
|
|
159
|
+
*
|
|
160
|
+
* IMPORTANT: a SecureKey is single-use. Don't call `.use()` more than once;
|
|
161
|
+
* call `keys.get` again for the next request. The audit trail records every
|
|
162
|
+
* fetch.
|
|
163
|
+
*/
|
|
164
|
+
declare function get(jwt: string, provider: ProviderSlug, opts?: HubCallOptions): Promise<SecureKey>;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Typed errors.
|
|
168
|
+
*
|
|
169
|
+
* The error `code` strings are part of the FROZEN public contract — see the
|
|
170
|
+
* key-fetch endpoint's documentation in
|
|
171
|
+
* https://github.com/Scott-Builds-AI/hub/blob/main/docs/phases/05-tools-keyfetch.md
|
|
172
|
+
*
|
|
173
|
+
* Adding a new code is a minor-version bump. Renaming or removing one is a
|
|
174
|
+
* major (with 60-day notice).
|
|
175
|
+
*
|
|
176
|
+
* Tools catch these to surface precise UX:
|
|
177
|
+
*
|
|
178
|
+
* try {
|
|
179
|
+
* await keys.get(jwt, "openai");
|
|
180
|
+
* } catch (e) {
|
|
181
|
+
* if (e instanceof SubscriptionInactiveError) showPaywall();
|
|
182
|
+
* else if (e instanceof RateLimitedError) showRetryToast(e.retryAfter);
|
|
183
|
+
* else throw e;
|
|
184
|
+
* }
|
|
185
|
+
*/
|
|
186
|
+
type HubSdkErrorCode = "missing_token" | "bad_token" | "missing_scope" | "subscription_inactive" | "tool_not_found" | "not_subscribed" | "provider_not_granted" | "rate_limited" | "key_not_found" | "internal" | "network" | "secure_key_consumed";
|
|
187
|
+
/** Base class for every typed error in the SDK. */
|
|
188
|
+
declare class HubSdkError extends Error {
|
|
189
|
+
readonly code: HubSdkErrorCode;
|
|
190
|
+
/** HTTP status from the hub, when applicable. 0 for client-only errors. */
|
|
191
|
+
readonly status: number;
|
|
192
|
+
constructor(code: HubSdkErrorCode, message: string, status?: number);
|
|
193
|
+
}
|
|
194
|
+
declare class MissingTokenError extends HubSdkError {
|
|
195
|
+
constructor();
|
|
196
|
+
}
|
|
197
|
+
declare class BadTokenError extends HubSdkError {
|
|
198
|
+
constructor(message?: string);
|
|
199
|
+
}
|
|
200
|
+
declare class MissingScopeError extends HubSdkError {
|
|
201
|
+
constructor(needed: string);
|
|
202
|
+
}
|
|
203
|
+
declare class SubscriptionInactiveError extends HubSdkError {
|
|
204
|
+
constructor();
|
|
205
|
+
}
|
|
206
|
+
declare class ToolNotFoundError extends HubSdkError {
|
|
207
|
+
constructor();
|
|
208
|
+
}
|
|
209
|
+
declare class NotSubscribedError extends HubSdkError {
|
|
210
|
+
constructor();
|
|
211
|
+
}
|
|
212
|
+
declare class ProviderNotGrantedError extends HubSdkError {
|
|
213
|
+
constructor(provider: string);
|
|
214
|
+
}
|
|
215
|
+
declare class RateLimitedError extends HubSdkError {
|
|
216
|
+
readonly retryAfter: number;
|
|
217
|
+
constructor(retryAfter: number);
|
|
218
|
+
}
|
|
219
|
+
declare class KeyNotFoundError extends HubSdkError {
|
|
220
|
+
constructor(provider: string);
|
|
221
|
+
}
|
|
222
|
+
declare class InternalError extends HubSdkError {
|
|
223
|
+
constructor(status: number, message?: string);
|
|
224
|
+
}
|
|
225
|
+
declare class NetworkError extends HubSdkError {
|
|
226
|
+
constructor(cause: unknown);
|
|
227
|
+
}
|
|
228
|
+
declare class SecureKeyConsumedError extends HubSdkError {
|
|
229
|
+
constructor();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* `@openkeyai/sdk` — public entry.
|
|
234
|
+
*
|
|
235
|
+
* Tools install this and import only from the package root:
|
|
236
|
+
*
|
|
237
|
+
* import { session, keys, SecureKey, SubscriptionInactiveError } from "@openkeyai/sdk";
|
|
238
|
+
*
|
|
239
|
+
* The five-module surface (session / keys / user / billing / webhooks) is
|
|
240
|
+
* defined in
|
|
241
|
+
* https://github.com/Scott-Builds-AI/hub/blob/main/docs/TOOL_SDK.md
|
|
242
|
+
*
|
|
243
|
+
* Status by module in 0.1.0:
|
|
244
|
+
* - session.verify ✓
|
|
245
|
+
* - keys.get → SecureKey ✓
|
|
246
|
+
* - user — deferred until the hub ships /api/me (issue TBD)
|
|
247
|
+
* - billing — deferred until the hub ships /api/billing/status
|
|
248
|
+
* - webhooks — deferred until Phase 16 (the hub's webhook delivery layer)
|
|
249
|
+
*
|
|
250
|
+
* Internal helpers live under `_internal/`. They are NOT part of the
|
|
251
|
+
* public API and may change without notice — the tool-manifest scanner
|
|
252
|
+
* (Phase 9) treats `@openkeyai/sdk/_internal` imports as a CI failure.
|
|
253
|
+
*/
|
|
254
|
+
|
|
255
|
+
/** session module — JWT verification + (future) refresh. */
|
|
256
|
+
declare const session: {
|
|
257
|
+
readonly verify: typeof verify;
|
|
258
|
+
};
|
|
259
|
+
/** keys module — credential fetch returning a single-use SecureKey. */
|
|
260
|
+
declare const keys: {
|
|
261
|
+
readonly get: typeof get;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/** Bumped on each release. Tools log this on boot. */
|
|
265
|
+
declare const SDK_VERSION = "0.1.0";
|
|
266
|
+
|
|
267
|
+
export { BadTokenError, type HubCallOptions, HubSdkError, type HubSdkErrorCode, InternalError, KeyNotFoundError, MissingScopeError, MissingTokenError, NetworkError, NotSubscribedError, ProviderNotGrantedError, type ProviderSlug, RateLimitedError, SDK_VERSION, SecureKey, SecureKeyConsumedError, SubscriptionInactiveError, type ToolJwtClaims, type ToolJwtScope, ToolNotFoundError, keys, session };
|