@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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Scott Goodwin
|
|
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,147 @@
|
|
|
1
|
+
# `@openkeyai/sdk`
|
|
2
|
+
|
|
3
|
+
The SDK every [OpenKey AI](https://openkeyai.com) tool installs. Provides JWT verification, the `SecureKey` key-fetch pattern, and typed errors mirroring the hub's frozen error contract.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm add @openkeyai/sdk
|
|
7
|
+
# or: npm i @openkeyai/sdk
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Status
|
|
11
|
+
|
|
12
|
+
Five-module surface defined in [`hub/docs/TOOL_SDK.md`](https://github.com/Scott-Builds-AI/hub/blob/main/docs/TOOL_SDK.md). Status by module in 0.1.0:
|
|
13
|
+
|
|
14
|
+
| Module | 0.1.0 | Notes |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `session.verify` | ✅ | Uses the hub's JWKS endpoint |
|
|
17
|
+
| `keys.get` → `SecureKey` | ✅ | Talks to `/api/tools/{slug}/keys/{provider}` |
|
|
18
|
+
| `user.profile` | ⏳ deferred | Needs hub to ship `/api/me` |
|
|
19
|
+
| `billing.status` | ⏳ deferred | Needs hub to ship `/api/billing/status` |
|
|
20
|
+
| `webhooks.handler` | ⏳ deferred | Lands with hub Phase 16 |
|
|
21
|
+
|
|
22
|
+
Deferred modules are simply absent from exports — your tool gets a TypeScript error if it tries to use them, not a runtime surprise.
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
A typical tool's request handler:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { session, keys, SubscriptionInactiveError, RateLimitedError } from "@openkeyai/sdk";
|
|
30
|
+
|
|
31
|
+
export async function handleRequest(req: Request) {
|
|
32
|
+
const jwt = req.headers.get("authorization")?.replace(/^Bearer\s+/i, "") ?? "";
|
|
33
|
+
|
|
34
|
+
// 1. Verify the JWT and read claims.
|
|
35
|
+
const claims = await session.verify(jwt, {
|
|
36
|
+
expectedAudience: "yt-thumbnails", // your tool's slug
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// 2. Fetch the user's API key for the provider you need.
|
|
40
|
+
try {
|
|
41
|
+
const k = await keys.get(jwt, "openai");
|
|
42
|
+
|
|
43
|
+
// 3. Use it inside a single callback. After this, the reference is scrubbed.
|
|
44
|
+
const response = await k.use(async (apiKey) => {
|
|
45
|
+
return fetch("https://api.openai.com/v1/responses", {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: {
|
|
48
|
+
"authorization": `Bearer ${apiKey}`,
|
|
49
|
+
"content-type": "application/json",
|
|
50
|
+
},
|
|
51
|
+
body: JSON.stringify({ model: "gpt-4o", input: "Hello!" }),
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return response;
|
|
56
|
+
} catch (e) {
|
|
57
|
+
if (e instanceof SubscriptionInactiveError) {
|
|
58
|
+
return new Response("Upgrade your subscription", { status: 402 });
|
|
59
|
+
}
|
|
60
|
+
if (e instanceof RateLimitedError) {
|
|
61
|
+
return new Response(`Too many calls. Retry in ${e.retryAfter}s.`, {
|
|
62
|
+
status: 429,
|
|
63
|
+
headers: { "retry-after": String(e.retryAfter) },
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
throw e;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## `SecureKey` — the never-leak pattern
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const k = await keys.get(jwt, "anthropic"); // SecureKey
|
|
75
|
+
|
|
76
|
+
await k.use(async (apiKey) => {
|
|
77
|
+
// `apiKey` is the plaintext string. Available ONLY inside this callback.
|
|
78
|
+
return fetch("https://api.anthropic.com/...", {
|
|
79
|
+
headers: { "x-api-key": apiKey },
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// After the callback resolves (or throws):
|
|
84
|
+
// - The internal reference is scrubbed
|
|
85
|
+
// - Subsequent `k.use(...)` throws SecureKeyConsumedError
|
|
86
|
+
// - Each instance is single-use; call keys.get() again for the next request
|
|
87
|
+
|
|
88
|
+
// Leakage guards (verified by the test suite):
|
|
89
|
+
k.toString() // → "[SecureKey]"
|
|
90
|
+
JSON.stringify(k) // → '"[SecureKey]"'
|
|
91
|
+
`${k}` // → "[SecureKey]"
|
|
92
|
+
console.log(k) // → [SecureKey]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The plaintext is held in a true private field (`#plaintext`) — not visible via `Object.keys`, bracket notation, or any serialiser.
|
|
96
|
+
|
|
97
|
+
## Typed errors
|
|
98
|
+
|
|
99
|
+
The hub's frozen error contract (defined in [`hub/docs/phases/05-tools-keyfetch.md`](https://github.com/Scott-Builds-AI/hub/blob/main/docs/phases/05-tools-keyfetch.md)) maps 1:1 onto SDK error classes:
|
|
100
|
+
|
|
101
|
+
| Code | Class | When |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `missing_token` | `MissingTokenError` | No Authorization header |
|
|
104
|
+
| `bad_token` | `BadTokenError` | Sig / iss / aud / exp / shape failed |
|
|
105
|
+
| `missing_scope` | `MissingScopeError` | Token lacks the scope this endpoint requires |
|
|
106
|
+
| `subscription_inactive` | `SubscriptionInactiveError` | User's subscription not active — show paywall, don't retry |
|
|
107
|
+
| `tool_not_found` | `ToolNotFoundError` | Slug unknown or suspended |
|
|
108
|
+
| `not_subscribed` | `NotSubscribedError` | User hasn't enabled the tool yet |
|
|
109
|
+
| `provider_not_granted` | `ProviderNotGrantedError` | Tool / subscription doesn't include the provider |
|
|
110
|
+
| `rate_limited` | `RateLimitedError` | `.retryAfter` carries the seconds value |
|
|
111
|
+
| `key_not_found` | `KeyNotFoundError` | User has no key for this provider |
|
|
112
|
+
| `internal` | `InternalError` | Hub-side error — retry-with-backoff is fine |
|
|
113
|
+
| `network` | `NetworkError` | Could not reach the hub at all |
|
|
114
|
+
| `secure_key_consumed` | `SecureKeyConsumedError` | `.use()` called more than once on the same SecureKey |
|
|
115
|
+
|
|
116
|
+
Every class extends `HubSdkError` so you can catch the base type if you don't care about the specific code.
|
|
117
|
+
|
|
118
|
+
## Configuration
|
|
119
|
+
|
|
120
|
+
The SDK has no configuration object and no environment variables. Pass overrides per call:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
await session.verify(jwt, { hubUrl: "https://staging.openkeyai.com" });
|
|
124
|
+
await keys.get(jwt, "openai", { hubUrl: "https://staging.openkeyai.com", signal });
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Defaults are `https://openkeyai.com` everywhere.
|
|
128
|
+
|
|
129
|
+
## Versioning
|
|
130
|
+
|
|
131
|
+
- Semver. Breaking changes to public surface = major bump with 60 days notice (per [`hub/CLAUDE.md`](https://github.com/Scott-Builds-AI/hub/blob/main/CLAUDE.md)).
|
|
132
|
+
- `SDK_VERSION` is exported so tools can log which build they shipped against.
|
|
133
|
+
- Don't import from `@openkeyai/sdk/_internal/*` — the tool-manifest scanner (Phase 9) treats it as a CI failure.
|
|
134
|
+
|
|
135
|
+
## Development
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pnpm install
|
|
139
|
+
pnpm dev # tsup --watch
|
|
140
|
+
pnpm build # tsup
|
|
141
|
+
pnpm typecheck
|
|
142
|
+
pnpm test # vitest (focused tests on SecureKey leakage guarantees)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT — see [LICENSE](./LICENSE).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jose = require('jose');
|
|
4
|
+
|
|
5
|
+
// src/session.ts
|
|
6
|
+
|
|
7
|
+
// src/errors.ts
|
|
8
|
+
var HubSdkError = class extends Error {
|
|
9
|
+
code;
|
|
10
|
+
/** HTTP status from the hub, when applicable. 0 for client-only errors. */
|
|
11
|
+
status;
|
|
12
|
+
constructor(code, message, status = 0) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "HubSdkError";
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.status = status;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var MissingTokenError = class extends HubSdkError {
|
|
20
|
+
constructor() {
|
|
21
|
+
super("missing_token", "No Authorization token was provided.", 401);
|
|
22
|
+
this.name = "MissingTokenError";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var BadTokenError = class extends HubSdkError {
|
|
26
|
+
constructor(message = "Token signature / issuer / audience / expiry check failed.") {
|
|
27
|
+
super("bad_token", message, 401);
|
|
28
|
+
this.name = "BadTokenError";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var MissingScopeError = class extends HubSdkError {
|
|
32
|
+
constructor(needed) {
|
|
33
|
+
super("missing_scope", `Token is missing the required scope: ${needed}.`, 403);
|
|
34
|
+
this.name = "MissingScopeError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var SubscriptionInactiveError = class extends HubSdkError {
|
|
38
|
+
constructor() {
|
|
39
|
+
super(
|
|
40
|
+
"subscription_inactive",
|
|
41
|
+
"The user's subscription is not active. Surface a paywall \u2014 don't retry.",
|
|
42
|
+
402
|
|
43
|
+
);
|
|
44
|
+
this.name = "SubscriptionInactiveError";
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var ToolNotFoundError = class extends HubSdkError {
|
|
48
|
+
constructor() {
|
|
49
|
+
super("tool_not_found", "Tool unknown or not approved.", 404);
|
|
50
|
+
this.name = "ToolNotFoundError";
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var NotSubscribedError = class extends HubSdkError {
|
|
54
|
+
constructor() {
|
|
55
|
+
super(
|
|
56
|
+
"not_subscribed",
|
|
57
|
+
"The user has not enabled this tool in their hub settings.",
|
|
58
|
+
403
|
|
59
|
+
);
|
|
60
|
+
this.name = "NotSubscribedError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var ProviderNotGrantedError = class extends HubSdkError {
|
|
64
|
+
constructor(provider) {
|
|
65
|
+
super(
|
|
66
|
+
"provider_not_granted",
|
|
67
|
+
`User has not granted this tool access to the ${provider} provider.`,
|
|
68
|
+
403
|
|
69
|
+
);
|
|
70
|
+
this.name = "ProviderNotGrantedError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var RateLimitedError = class extends HubSdkError {
|
|
74
|
+
retryAfter;
|
|
75
|
+
constructor(retryAfter) {
|
|
76
|
+
super(
|
|
77
|
+
"rate_limited",
|
|
78
|
+
`Rate limit hit. Retry after ${retryAfter} seconds.`,
|
|
79
|
+
429
|
|
80
|
+
);
|
|
81
|
+
this.name = "RateLimitedError";
|
|
82
|
+
this.retryAfter = retryAfter;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var KeyNotFoundError = class extends HubSdkError {
|
|
86
|
+
constructor(provider) {
|
|
87
|
+
super(
|
|
88
|
+
"key_not_found",
|
|
89
|
+
`No ${provider} key configured for this user. The tool should prompt the user to add one.`,
|
|
90
|
+
404
|
|
91
|
+
);
|
|
92
|
+
this.name = "KeyNotFoundError";
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var InternalError = class extends HubSdkError {
|
|
96
|
+
constructor(status, message = "The hub returned an internal error.") {
|
|
97
|
+
super("internal", message, status);
|
|
98
|
+
this.name = "InternalError";
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
var NetworkError = class extends HubSdkError {
|
|
102
|
+
constructor(cause) {
|
|
103
|
+
super(
|
|
104
|
+
"network",
|
|
105
|
+
`Could not reach the hub: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
106
|
+
0
|
|
107
|
+
);
|
|
108
|
+
this.name = "NetworkError";
|
|
109
|
+
this.cause = cause;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
var SecureKeyConsumedError = class extends HubSdkError {
|
|
113
|
+
constructor() {
|
|
114
|
+
super(
|
|
115
|
+
"secure_key_consumed",
|
|
116
|
+
"SecureKey has already been used. Each instance is single-use; call keys.get() again to fetch a fresh one.",
|
|
117
|
+
0
|
|
118
|
+
);
|
|
119
|
+
this.name = "SecureKeyConsumedError";
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
function errorFromResponse(status, body, context) {
|
|
123
|
+
const code = body?.error ?? "internal";
|
|
124
|
+
switch (code) {
|
|
125
|
+
case "missing_token":
|
|
126
|
+
return new MissingTokenError();
|
|
127
|
+
case "bad_token":
|
|
128
|
+
return new BadTokenError();
|
|
129
|
+
case "missing_scope":
|
|
130
|
+
return new MissingScopeError(context.scopeNeeded ?? "unknown");
|
|
131
|
+
case "subscription_inactive":
|
|
132
|
+
return new SubscriptionInactiveError();
|
|
133
|
+
case "tool_not_found":
|
|
134
|
+
return new ToolNotFoundError();
|
|
135
|
+
case "not_subscribed":
|
|
136
|
+
return new NotSubscribedError();
|
|
137
|
+
case "provider_not_granted":
|
|
138
|
+
return new ProviderNotGrantedError(context.provider ?? "unknown");
|
|
139
|
+
case "rate_limited":
|
|
140
|
+
return new RateLimitedError(context.retryAfter ?? 60);
|
|
141
|
+
case "key_not_found":
|
|
142
|
+
return new KeyNotFoundError(context.provider ?? "unknown");
|
|
143
|
+
case "internal":
|
|
144
|
+
default:
|
|
145
|
+
return new InternalError(status);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
var resolvers = /* @__PURE__ */ new Map();
|
|
149
|
+
function getJwksResolver(hubUrl) {
|
|
150
|
+
const cached = resolvers.get(hubUrl);
|
|
151
|
+
if (cached) return cached;
|
|
152
|
+
const url = new URL("/.well-known/jwks.json", hubUrl);
|
|
153
|
+
const resolver = jose.createRemoteJWKSet(url, {
|
|
154
|
+
// Respect the hub's stated cache window (5 min strong + 10 min SWR).
|
|
155
|
+
cacheMaxAge: 5 * 60 * 1e3,
|
|
156
|
+
cooldownDuration: 30 * 1e3
|
|
157
|
+
});
|
|
158
|
+
resolvers.set(hubUrl, resolver);
|
|
159
|
+
return resolver;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/session.ts
|
|
163
|
+
async function verify(jwt, opts = {}) {
|
|
164
|
+
if (!jwt || typeof jwt !== "string") {
|
|
165
|
+
throw new MissingTokenError();
|
|
166
|
+
}
|
|
167
|
+
const hubUrl = opts.hubUrl ?? "https://openkeyai.com";
|
|
168
|
+
const resolver = getJwksResolver(hubUrl);
|
|
169
|
+
let payload;
|
|
170
|
+
try {
|
|
171
|
+
const result = await jose.jwtVerify(jwt, resolver, {
|
|
172
|
+
issuer: "https://openkeyai.com",
|
|
173
|
+
algorithms: ["EdDSA"],
|
|
174
|
+
...opts.expectedAudience ? { audience: opts.expectedAudience } : {}
|
|
175
|
+
});
|
|
176
|
+
payload = result.payload;
|
|
177
|
+
} catch (err) {
|
|
178
|
+
if (err instanceof jose.errors.JWTExpired) {
|
|
179
|
+
throw new BadTokenError("Token expired.");
|
|
180
|
+
}
|
|
181
|
+
if (err instanceof jose.errors.JWTClaimValidationFailed) {
|
|
182
|
+
throw new BadTokenError(`Claim validation failed: ${err.message}`);
|
|
183
|
+
}
|
|
184
|
+
if (err instanceof jose.errors.JWSSignatureVerificationFailed) {
|
|
185
|
+
throw new BadTokenError("Signature did not verify.");
|
|
186
|
+
}
|
|
187
|
+
if (err instanceof jose.errors.JWKSNoMatchingKey) {
|
|
188
|
+
throw new BadTokenError("Token kid is not in the hub's active JWKS.");
|
|
189
|
+
}
|
|
190
|
+
if (err instanceof jose.errors.JOSEError) {
|
|
191
|
+
throw new BadTokenError(err.message);
|
|
192
|
+
}
|
|
193
|
+
throw new BadTokenError("Token verification failed.");
|
|
194
|
+
}
|
|
195
|
+
const sub = payload.sub;
|
|
196
|
+
const aud = payload.aud;
|
|
197
|
+
const scopes = payload.scopes;
|
|
198
|
+
const subscriptionActive = payload.subscription_active;
|
|
199
|
+
const jti = payload.jti;
|
|
200
|
+
const iat = payload.iat;
|
|
201
|
+
const exp = payload.exp;
|
|
202
|
+
if (typeof sub !== "string" || sub.length === 0) {
|
|
203
|
+
throw new BadTokenError("sub claim missing or invalid.");
|
|
204
|
+
}
|
|
205
|
+
if (typeof aud !== "string" || aud.length === 0) {
|
|
206
|
+
throw new BadTokenError("aud claim missing or invalid.");
|
|
207
|
+
}
|
|
208
|
+
if (!Array.isArray(scopes) || scopes.length === 0) {
|
|
209
|
+
throw new BadTokenError("scopes claim must be a non-empty array.");
|
|
210
|
+
}
|
|
211
|
+
if (typeof subscriptionActive !== "boolean") {
|
|
212
|
+
throw new BadTokenError("subscription_active claim must be boolean.");
|
|
213
|
+
}
|
|
214
|
+
if (typeof jti !== "string" || jti.length === 0) {
|
|
215
|
+
throw new BadTokenError("jti claim missing or invalid.");
|
|
216
|
+
}
|
|
217
|
+
if (typeof iat !== "number" || typeof exp !== "number") {
|
|
218
|
+
throw new BadTokenError("iat / exp claims missing or invalid.");
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
iss: "https://openkeyai.com",
|
|
222
|
+
sub,
|
|
223
|
+
aud,
|
|
224
|
+
scopes,
|
|
225
|
+
subscription_active: subscriptionActive,
|
|
226
|
+
iat,
|
|
227
|
+
exp,
|
|
228
|
+
jti
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/secure-key.ts
|
|
233
|
+
var SecureKey = class {
|
|
234
|
+
#plaintext;
|
|
235
|
+
/** Provider slug the key was fetched for. Public — not sensitive. */
|
|
236
|
+
provider;
|
|
237
|
+
/** @internal — tools should use `keys.get()`, never construct directly. */
|
|
238
|
+
constructor(plaintext, provider) {
|
|
239
|
+
this.#plaintext = plaintext;
|
|
240
|
+
this.provider = provider;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Pass the plaintext into a callback. Returns whatever the callback
|
|
244
|
+
* returns. After the callback resolves (or throws), the SecureKey is
|
|
245
|
+
* consumed — subsequent calls throw `SecureKeyConsumedError`.
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* const k = await keys.get(jwt, "openai");
|
|
249
|
+
* const response = await k.use((apiKey) => fetch("...", {
|
|
250
|
+
* headers: { "Authorization": `Bearer ${apiKey}` },
|
|
251
|
+
* }));
|
|
252
|
+
*/
|
|
253
|
+
async use(fn) {
|
|
254
|
+
const plaintext = this.#plaintext;
|
|
255
|
+
if (plaintext === null) {
|
|
256
|
+
throw new SecureKeyConsumedError();
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
return await fn(plaintext);
|
|
260
|
+
} finally {
|
|
261
|
+
this.#plaintext = null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/** Returns true once `use()` has been called and the reference cleared. */
|
|
265
|
+
get isConsumed() {
|
|
266
|
+
return this.#plaintext === null;
|
|
267
|
+
}
|
|
268
|
+
/** Always `[SecureKey]`. Never the underlying value. */
|
|
269
|
+
toString() {
|
|
270
|
+
return "[SecureKey]";
|
|
271
|
+
}
|
|
272
|
+
/** Always `[SecureKey]`. Catches `JSON.stringify` and friends. */
|
|
273
|
+
toJSON() {
|
|
274
|
+
return "[SecureKey]";
|
|
275
|
+
}
|
|
276
|
+
/** Catches `console.log` on Node and Workers (which use util.inspect). */
|
|
277
|
+
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
278
|
+
return "[SecureKey]";
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/keys.ts
|
|
283
|
+
async function get(jwt, provider, opts = {}) {
|
|
284
|
+
if (!jwt || typeof jwt !== "string") {
|
|
285
|
+
throw new BadTokenError("No JWT provided.");
|
|
286
|
+
}
|
|
287
|
+
if (!provider || typeof provider !== "string") {
|
|
288
|
+
throw new BadTokenError("Missing provider slug.");
|
|
289
|
+
}
|
|
290
|
+
let aud;
|
|
291
|
+
let scopes;
|
|
292
|
+
try {
|
|
293
|
+
const claims = jose.decodeJwt(jwt);
|
|
294
|
+
aud = String(claims.aud ?? "");
|
|
295
|
+
scopes = claims.scopes;
|
|
296
|
+
} catch (err) {
|
|
297
|
+
throw new BadTokenError(
|
|
298
|
+
`JWT could not be decoded: ${err instanceof Error ? err.message : "unknown"}`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
if (!aud) {
|
|
302
|
+
throw new BadTokenError("JWT is missing the aud claim.");
|
|
303
|
+
}
|
|
304
|
+
if (Array.isArray(scopes) && !scopes.includes("keys.read")) {
|
|
305
|
+
throw new MissingScopeError("keys.read");
|
|
306
|
+
}
|
|
307
|
+
const hubUrl = opts.hubUrl ?? "https://openkeyai.com";
|
|
308
|
+
const url = new URL(
|
|
309
|
+
`/api/tools/${encodeURIComponent(aud)}/keys/${encodeURIComponent(provider)}`,
|
|
310
|
+
hubUrl
|
|
311
|
+
);
|
|
312
|
+
let response;
|
|
313
|
+
try {
|
|
314
|
+
response = await fetch(url.toString(), {
|
|
315
|
+
method: "GET",
|
|
316
|
+
headers: {
|
|
317
|
+
accept: "application/json",
|
|
318
|
+
authorization: `Bearer ${jwt}`
|
|
319
|
+
},
|
|
320
|
+
signal: opts.signal
|
|
321
|
+
});
|
|
322
|
+
} catch (err) {
|
|
323
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
324
|
+
throw err;
|
|
325
|
+
}
|
|
326
|
+
throw new NetworkError(err);
|
|
327
|
+
}
|
|
328
|
+
if (response.ok) {
|
|
329
|
+
let body;
|
|
330
|
+
try {
|
|
331
|
+
body = await response.json();
|
|
332
|
+
} catch (err) {
|
|
333
|
+
throw new InternalError(
|
|
334
|
+
response.status,
|
|
335
|
+
`Could not parse hub response body: ${err instanceof Error ? err.message : "unknown"}`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
if (typeof body.secret !== "string" || body.secret.length === 0) {
|
|
339
|
+
throw new InternalError(
|
|
340
|
+
response.status,
|
|
341
|
+
"Hub returned 200 OK but no secret in the body."
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
return new SecureKey(body.secret, body.provider ?? provider);
|
|
345
|
+
}
|
|
346
|
+
let errorBody = null;
|
|
347
|
+
try {
|
|
348
|
+
errorBody = await response.json();
|
|
349
|
+
} catch {
|
|
350
|
+
}
|
|
351
|
+
const retryAfterHeader = response.headers.get("retry-after");
|
|
352
|
+
const retryAfter = retryAfterHeader ? parseInt(retryAfterHeader, 10) : 60;
|
|
353
|
+
throw errorFromResponse(response.status, errorBody, {
|
|
354
|
+
provider,
|
|
355
|
+
scopeNeeded: "keys.read",
|
|
356
|
+
retryAfter: Number.isFinite(retryAfter) ? retryAfter : 60
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// src/index.ts
|
|
361
|
+
var session = {
|
|
362
|
+
verify
|
|
363
|
+
};
|
|
364
|
+
var keys = {
|
|
365
|
+
get
|
|
366
|
+
};
|
|
367
|
+
var SDK_VERSION = "0.1.0";
|
|
368
|
+
|
|
369
|
+
exports.BadTokenError = BadTokenError;
|
|
370
|
+
exports.HubSdkError = HubSdkError;
|
|
371
|
+
exports.InternalError = InternalError;
|
|
372
|
+
exports.KeyNotFoundError = KeyNotFoundError;
|
|
373
|
+
exports.MissingScopeError = MissingScopeError;
|
|
374
|
+
exports.MissingTokenError = MissingTokenError;
|
|
375
|
+
exports.NetworkError = NetworkError;
|
|
376
|
+
exports.NotSubscribedError = NotSubscribedError;
|
|
377
|
+
exports.ProviderNotGrantedError = ProviderNotGrantedError;
|
|
378
|
+
exports.RateLimitedError = RateLimitedError;
|
|
379
|
+
exports.SDK_VERSION = SDK_VERSION;
|
|
380
|
+
exports.SecureKey = SecureKey;
|
|
381
|
+
exports.SecureKeyConsumedError = SecureKeyConsumedError;
|
|
382
|
+
exports.SubscriptionInactiveError = SubscriptionInactiveError;
|
|
383
|
+
exports.ToolNotFoundError = ToolNotFoundError;
|
|
384
|
+
exports.keys = keys;
|
|
385
|
+
exports.session = session;
|
|
386
|
+
//# sourceMappingURL=index.cjs.map
|
|
387
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/_internal/jwks.ts","../src/session.ts","../src/secure-key.ts","../src/keys.ts","../src/index.ts"],"names":["createRemoteJWKSet","jwtVerify","joseErrors","decodeJwt"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Typed errors.\n *\n * The error `code` strings are part of the FROZEN public contract — see the\n * key-fetch endpoint's documentation in\n * https://github.com/Scott-Builds-AI/hub/blob/main/docs/phases/05-tools-keyfetch.md\n *\n * Adding a new code is a minor-version bump. Renaming or removing one is a\n * major (with 60-day notice).\n *\n * Tools catch these to surface precise UX:\n *\n * try {\n * await keys.get(jwt, \"openai\");\n * } catch (e) {\n * if (e instanceof SubscriptionInactiveError) showPaywall();\n * else if (e instanceof RateLimitedError) showRetryToast(e.retryAfter);\n * else throw e;\n * }\n */\n\nexport type HubSdkErrorCode =\n | \"missing_token\"\n | \"bad_token\"\n | \"missing_scope\"\n | \"subscription_inactive\"\n | \"tool_not_found\"\n | \"not_subscribed\"\n | \"provider_not_granted\"\n | \"rate_limited\"\n | \"key_not_found\"\n | \"internal\"\n | \"network\"\n | \"secure_key_consumed\";\n\n/** Base class for every typed error in the SDK. */\nexport class HubSdkError extends Error {\n readonly code: HubSdkErrorCode;\n /** HTTP status from the hub, when applicable. 0 for client-only errors. */\n readonly status: number;\n\n constructor(code: HubSdkErrorCode, message: string, status = 0) {\n super(message);\n this.name = \"HubSdkError\";\n this.code = code;\n this.status = status;\n }\n}\n\n// ─── Wire-level errors (returned by the hub) ────────────────────────────────\n\nexport class MissingTokenError extends HubSdkError {\n constructor() {\n super(\"missing_token\", \"No Authorization token was provided.\", 401);\n this.name = \"MissingTokenError\";\n }\n}\n\nexport class BadTokenError extends HubSdkError {\n constructor(message = \"Token signature / issuer / audience / expiry check failed.\") {\n super(\"bad_token\", message, 401);\n this.name = \"BadTokenError\";\n }\n}\n\nexport class MissingScopeError extends HubSdkError {\n constructor(needed: string) {\n super(\"missing_scope\", `Token is missing the required scope: ${needed}.`, 403);\n this.name = \"MissingScopeError\";\n }\n}\n\nexport class SubscriptionInactiveError extends HubSdkError {\n constructor() {\n super(\n \"subscription_inactive\",\n \"The user's subscription is not active. Surface a paywall — don't retry.\",\n 402,\n );\n this.name = \"SubscriptionInactiveError\";\n }\n}\n\nexport class ToolNotFoundError extends HubSdkError {\n constructor() {\n super(\"tool_not_found\", \"Tool unknown or not approved.\", 404);\n this.name = \"ToolNotFoundError\";\n }\n}\n\nexport class NotSubscribedError extends HubSdkError {\n constructor() {\n super(\n \"not_subscribed\",\n \"The user has not enabled this tool in their hub settings.\",\n 403,\n );\n this.name = \"NotSubscribedError\";\n }\n}\n\nexport class ProviderNotGrantedError extends HubSdkError {\n constructor(provider: string) {\n super(\n \"provider_not_granted\",\n `User has not granted this tool access to the ${provider} provider.`,\n 403,\n );\n this.name = \"ProviderNotGrantedError\";\n }\n}\n\nexport class RateLimitedError extends HubSdkError {\n readonly retryAfter: number;\n constructor(retryAfter: number) {\n super(\n \"rate_limited\",\n `Rate limit hit. Retry after ${retryAfter} seconds.`,\n 429,\n );\n this.name = \"RateLimitedError\";\n this.retryAfter = retryAfter;\n }\n}\n\nexport class KeyNotFoundError extends HubSdkError {\n constructor(provider: string) {\n super(\n \"key_not_found\",\n `No ${provider} key configured for this user. The tool should prompt the user to add one.`,\n 404,\n );\n this.name = \"KeyNotFoundError\";\n }\n}\n\nexport class InternalError extends HubSdkError {\n constructor(status: number, message = \"The hub returned an internal error.\") {\n super(\"internal\", message, status);\n this.name = \"InternalError\";\n }\n}\n\n// ─── Client-only errors ──────────────────────────────────────────────────────\n\nexport class NetworkError extends HubSdkError {\n constructor(cause: unknown) {\n super(\n \"network\",\n `Could not reach the hub: ${cause instanceof Error ? cause.message : String(cause)}`,\n 0,\n );\n this.name = \"NetworkError\";\n this.cause = cause;\n }\n}\n\nexport class SecureKeyConsumedError extends HubSdkError {\n constructor() {\n super(\n \"secure_key_consumed\",\n \"SecureKey has already been used. Each instance is single-use; call keys.get() again to fetch a fresh one.\",\n 0,\n );\n this.name = \"SecureKeyConsumedError\";\n }\n}\n\n// ─── Wire → error mapping ────────────────────────────────────────────────────\n\n/**\n * Build the right HubSdkError subclass from a hub JSON error response. Used\n * by every fetch wrapper so the typed errors stay consistent.\n */\nexport function errorFromResponse(\n status: number,\n body: { error?: string } | null,\n context: { provider?: string; scopeNeeded?: string; retryAfter?: number },\n): HubSdkError {\n const code = body?.error ?? \"internal\";\n switch (code) {\n case \"missing_token\":\n return new MissingTokenError();\n case \"bad_token\":\n return new BadTokenError();\n case \"missing_scope\":\n return new MissingScopeError(context.scopeNeeded ?? \"unknown\");\n case \"subscription_inactive\":\n return new SubscriptionInactiveError();\n case \"tool_not_found\":\n return new ToolNotFoundError();\n case \"not_subscribed\":\n return new NotSubscribedError();\n case \"provider_not_granted\":\n return new ProviderNotGrantedError(context.provider ?? \"unknown\");\n case \"rate_limited\":\n return new RateLimitedError(context.retryAfter ?? 60);\n case \"key_not_found\":\n return new KeyNotFoundError(context.provider ?? \"unknown\");\n case \"internal\":\n default:\n return new InternalError(status);\n }\n}\n","import { createRemoteJWKSet, type JWTVerifyGetKey } from \"jose\";\n\n/**\n * Per-hub-URL JWKS cache.\n *\n * Each call to `keys.get` / `session.verify` needs a key-resolver\n * (`jose.JWTVerifyGetKey`) to feed into `jwtVerify`. Building a fresh\n * resolver per request would re-fetch the JWKS document every time.\n * `createRemoteJWKSet` handles caching internally, but only if we hand\n * back the SAME resolver instance for the same URL.\n *\n * Cache key is the hub URL because the same SDK build might be used by\n * tools that point at staging vs production hubs (different keys, different\n * kids).\n *\n * The cache has no eviction — there's at most a handful of distinct hub\n * URLs in any sane setup. If a tool author somehow constructs thousands of\n * URL variants this would leak memory; that's not a realistic path.\n */\nconst resolvers = new Map<string, JWTVerifyGetKey>();\n\nexport function getJwksResolver(hubUrl: string): JWTVerifyGetKey {\n const cached = resolvers.get(hubUrl);\n if (cached) return cached;\n const url = new URL(\"/.well-known/jwks.json\", hubUrl);\n // jose's createRemoteJWKSet:\n // - fetches lazily on the first verify\n // - caches for `cacheMaxAge` (default 10 minutes)\n // - revalidates after `cooldownDuration` (default 30s)\n // - re-fetches automatically when a verify hits an unknown kid (rotation)\n const resolver = createRemoteJWKSet(url, {\n // Respect the hub's stated cache window (5 min strong + 10 min SWR).\n cacheMaxAge: 5 * 60 * 1000,\n cooldownDuration: 30 * 1000,\n });\n resolvers.set(hubUrl, resolver);\n return resolver;\n}\n\n/** Test helper — drops the cache between cases. Not part of public API. */\nexport function _resetJwksCacheForTests(): void {\n resolvers.clear();\n}\n","import { jwtVerify, errors as joseErrors } from \"jose\";\nimport { BadTokenError, MissingTokenError } from \"./errors\";\nimport { getJwksResolver } from \"./_internal/jwks\";\nimport type { HubCallOptions, ToolJwtClaims, ToolJwtScope } from \"./types\";\n\n/**\n * `session.verify(jwt, opts?)`\n *\n * Verifies a hub-issued JWT against the hub's public JWKS, then validates\n * the claim shape (`iss`, `sub`, `aud`, `scopes`, `subscription_active`).\n *\n * Returns the typed claims on success.\n * Throws `BadTokenError` on any failure — signature, expiry, issuer,\n * audience, missing/malformed custom claims.\n * Throws `MissingTokenError` if `jwt` is empty / null.\n *\n * The verifier:\n * - fetches `${hubUrl}/.well-known/jwks.json` (cached per hub URL)\n * - re-fetches on `kid` rotation (jose handles this transparently)\n * - DOES NOT validate `aud` here — the caller specifies which audience\n * they expect (the tool's own slug, OR a wildcard for tools that\n * legitimately accept multiple audiences). `keys.get` validates\n * audience against the tool slug derived from the route.\n *\n * @param jwt The bearer token string (no `Bearer ` prefix).\n * @param opts.hubUrl override the hub root (default: `https://openkeyai.com`)\n * @param opts.expectedAudience tool slug the token must be addressed to.\n * When omitted, audience is not checked here — the caller MUST do it\n * themselves before granting any scope-derived privilege.\n */\nexport async function verify(\n jwt: string,\n opts: HubCallOptions & { expectedAudience?: string } = {},\n): Promise<ToolJwtClaims> {\n if (!jwt || typeof jwt !== \"string\") {\n throw new MissingTokenError();\n }\n\n const hubUrl = opts.hubUrl ?? \"https://openkeyai.com\";\n const resolver = getJwksResolver(hubUrl);\n\n let payload: Record<string, unknown>;\n try {\n const result = await jwtVerify(jwt, resolver, {\n issuer: \"https://openkeyai.com\",\n algorithms: [\"EdDSA\"],\n ...(opts.expectedAudience ? { audience: opts.expectedAudience } : {}),\n });\n payload = result.payload as Record<string, unknown>;\n } catch (err) {\n if (err instanceof joseErrors.JWTExpired) {\n throw new BadTokenError(\"Token expired.\");\n }\n if (err instanceof joseErrors.JWTClaimValidationFailed) {\n throw new BadTokenError(`Claim validation failed: ${err.message}`);\n }\n if (err instanceof joseErrors.JWSSignatureVerificationFailed) {\n throw new BadTokenError(\"Signature did not verify.\");\n }\n if (err instanceof joseErrors.JWKSNoMatchingKey) {\n throw new BadTokenError(\"Token kid is not in the hub's active JWKS.\");\n }\n if (err instanceof joseErrors.JOSEError) {\n throw new BadTokenError(err.message);\n }\n // Network / unknown — surface as BadToken too rather than leak details.\n throw new BadTokenError(\"Token verification failed.\");\n }\n\n // Custom-claim shape validation. `jose` only validates standard claims.\n const sub = payload.sub;\n const aud = payload.aud;\n const scopes = payload.scopes;\n const subscriptionActive = payload.subscription_active;\n const jti = payload.jti;\n const iat = payload.iat;\n const exp = payload.exp;\n\n if (typeof sub !== \"string\" || sub.length === 0) {\n throw new BadTokenError(\"sub claim missing or invalid.\");\n }\n if (typeof aud !== \"string\" || aud.length === 0) {\n throw new BadTokenError(\"aud claim missing or invalid.\");\n }\n if (!Array.isArray(scopes) || scopes.length === 0) {\n throw new BadTokenError(\"scopes claim must be a non-empty array.\");\n }\n if (typeof subscriptionActive !== \"boolean\") {\n throw new BadTokenError(\"subscription_active claim must be boolean.\");\n }\n if (typeof jti !== \"string\" || jti.length === 0) {\n throw new BadTokenError(\"jti claim missing or invalid.\");\n }\n if (typeof iat !== \"number\" || typeof exp !== \"number\") {\n throw new BadTokenError(\"iat / exp claims missing or invalid.\");\n }\n\n return {\n iss: \"https://openkeyai.com\",\n sub,\n aud,\n scopes: scopes as ToolJwtScope[],\n subscription_active: subscriptionActive,\n iat,\n exp,\n jti,\n };\n}\n","import { SecureKeyConsumedError } from \"./errors\";\n\n/**\n * SecureKey — the only way the SDK hands a plaintext credential to a tool.\n *\n * Design goals (per docs/SECURITY.md in the hub repo):\n *\n * 1. The plaintext is reachable from EXACTLY ONE place: inside a callback\n * passed to `use(fn)`. Outside that callback, no public method or\n * property returns the value.\n *\n * 2. After the callback runs (or throws), the held reference is set to\n * null. Subsequent `use()` calls throw `SecureKeyConsumedError`. The\n * pattern is one-shot — get a fresh SecureKey for the next request.\n *\n * 3. `JSON.stringify`, `toString`, `console.log` (via util.inspect), and\n * template-literal coercion all return the literal string\n * `\"[SecureKey]\"` — never the underlying value, even by accident.\n *\n * #plaintext is a true private field (Stage 3 syntax) so it doesn't appear\n * in `Object.keys()`, isn't accessible via bracket-notation, and is not\n * enumerable for serialisation. The frozen overrides below cover the\n * coercion paths.\n *\n * What we deliberately do NOT do:\n *\n * - WeakRef + FinalizationRegistry. They're observable from the same realm,\n * and we have no useful action to take on GC. Single-use + manual null\n * is the simpler, more deterministic guarantee.\n *\n * - Buffer.alloc + .fill(0) \"zeroising\". V8 and modern runtimes can keep\n * copies of strings in cache; we can't truly zeroise from JS. The\n * useful guarantee we CAN give is reference-clearing, which we do.\n */\nexport class SecureKey {\n #plaintext: string | null;\n\n /** Provider slug the key was fetched for. Public — not sensitive. */\n readonly provider: string;\n\n /** @internal — tools should use `keys.get()`, never construct directly. */\n constructor(plaintext: string, provider: string) {\n this.#plaintext = plaintext;\n this.provider = provider;\n }\n\n /**\n * Pass the plaintext into a callback. Returns whatever the callback\n * returns. After the callback resolves (or throws), the SecureKey is\n * consumed — subsequent calls throw `SecureKeyConsumedError`.\n *\n * @example\n * const k = await keys.get(jwt, \"openai\");\n * const response = await k.use((apiKey) => fetch(\"...\", {\n * headers: { \"Authorization\": `Bearer ${apiKey}` },\n * }));\n */\n async use<T>(fn: (plaintext: string) => T | Promise<T>): Promise<T> {\n const plaintext = this.#plaintext;\n if (plaintext === null) {\n throw new SecureKeyConsumedError();\n }\n try {\n return await fn(plaintext);\n } finally {\n // Always clear the held reference, even if `fn` threw. The single\n // legitimate consumer already had the value during the callback;\n // anything after that is leakage we don't want to enable.\n this.#plaintext = null;\n }\n }\n\n /** Returns true once `use()` has been called and the reference cleared. */\n get isConsumed(): boolean {\n return this.#plaintext === null;\n }\n\n /** Always `[SecureKey]`. Never the underlying value. */\n toString(): string {\n return \"[SecureKey]\";\n }\n\n /** Always `[SecureKey]`. Catches `JSON.stringify` and friends. */\n toJSON(): string {\n return \"[SecureKey]\";\n }\n\n /** Catches `console.log` on Node and Workers (which use util.inspect). */\n [Symbol.for(\"nodejs.util.inspect.custom\")](): string {\n return \"[SecureKey]\";\n }\n}\n","import { decodeJwt } from \"jose\";\nimport {\n BadTokenError,\n InternalError,\n MissingScopeError,\n NetworkError,\n errorFromResponse,\n} from \"./errors\";\nimport { SecureKey } from \"./secure-key\";\nimport type { HubCallOptions, ProviderSlug } from \"./types\";\n\n/**\n * `keys.get(jwt, provider, opts?)`\n *\n * Fetches a credential for the user identified by `jwt` and returns a\n * single-use SecureKey. The hub:\n *\n * 1. Verifies the JWT (signature + iss + aud + exp)\n * 2. Confirms `keys.read` scope, active subscription, and that the tool +\n * user are both subscribed to this provider\n * 3. Decrypts the user's stored API key via KMS\n * 4. Returns the plaintext, logged to the audit trail\n *\n * On the client side we:\n * - Decode (no verify) the JWT locally to read the `aud` claim — that's\n * the tool slug the hub will scope the lookup against\n * - Pre-check the `keys.read` scope so we can return a typed error\n * without a round-trip\n * - Issue the GET, map the hub's frozen error codes to typed exceptions\n * - Wrap the plaintext in a SecureKey and return\n *\n * IMPORTANT: a SecureKey is single-use. Don't call `.use()` more than once;\n * call `keys.get` again for the next request. The audit trail records every\n * fetch.\n */\nexport async function get(\n jwt: string,\n provider: ProviderSlug,\n opts: HubCallOptions = {},\n): Promise<SecureKey> {\n if (!jwt || typeof jwt !== \"string\") {\n throw new BadTokenError(\"No JWT provided.\");\n }\n if (!provider || typeof provider !== \"string\") {\n throw new BadTokenError(\"Missing provider slug.\");\n }\n\n // Decode (no verify) to learn the audience — that's the tool slug for the\n // route. The hub will re-verify; we don't need to here.\n let aud: string;\n let scopes: unknown;\n try {\n const claims = decodeJwt(jwt);\n aud = String(claims.aud ?? \"\");\n scopes = claims.scopes;\n } catch (err) {\n throw new BadTokenError(\n `JWT could not be decoded: ${err instanceof Error ? err.message : \"unknown\"}`,\n );\n }\n if (!aud) {\n throw new BadTokenError(\"JWT is missing the aud claim.\");\n }\n\n // Local scope pre-check. Cheap, and lets the tool surface the right UX\n // without a network round-trip when the scope is just missing.\n if (Array.isArray(scopes) && !scopes.includes(\"keys.read\")) {\n throw new MissingScopeError(\"keys.read\");\n }\n\n const hubUrl = opts.hubUrl ?? \"https://openkeyai.com\";\n const url = new URL(\n `/api/tools/${encodeURIComponent(aud)}/keys/${encodeURIComponent(provider)}`,\n hubUrl,\n );\n\n let response: Response;\n try {\n response = await fetch(url.toString(), {\n method: \"GET\",\n headers: {\n accept: \"application/json\",\n authorization: `Bearer ${jwt}`,\n },\n signal: opts.signal,\n });\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw err; // propagate as-is so callers can detect abort.\n }\n throw new NetworkError(err);\n }\n\n if (response.ok) {\n let body: { provider?: string; secret?: string };\n try {\n body = (await response.json()) as { provider?: string; secret?: string };\n } catch (err) {\n throw new InternalError(\n response.status,\n `Could not parse hub response body: ${err instanceof Error ? err.message : \"unknown\"}`,\n );\n }\n if (typeof body.secret !== \"string\" || body.secret.length === 0) {\n throw new InternalError(\n response.status,\n \"Hub returned 200 OK but no secret in the body.\",\n );\n }\n return new SecureKey(body.secret, body.provider ?? provider);\n }\n\n // Error path — let the typed-error builder do the right thing.\n let errorBody: { error?: string } | null = null;\n try {\n errorBody = (await response.json()) as { error?: string };\n } catch {\n // Body wasn't JSON. The status alone is informative; carry on with null.\n }\n\n const retryAfterHeader = response.headers.get(\"retry-after\");\n const retryAfter = retryAfterHeader ? parseInt(retryAfterHeader, 10) : 60;\n\n throw errorFromResponse(response.status, errorBody, {\n provider,\n scopeNeeded: \"keys.read\",\n retryAfter: Number.isFinite(retryAfter) ? retryAfter : 60,\n });\n}\n","/**\n * `@openkeyai/sdk` — public entry.\n *\n * Tools install this and import only from the package root:\n *\n * import { session, keys, SecureKey, SubscriptionInactiveError } from \"@openkeyai/sdk\";\n *\n * The five-module surface (session / keys / user / billing / webhooks) is\n * defined in\n * https://github.com/Scott-Builds-AI/hub/blob/main/docs/TOOL_SDK.md\n *\n * Status by module in 0.1.0:\n * - session.verify ✓\n * - keys.get → SecureKey ✓\n * - user — deferred until the hub ships /api/me (issue TBD)\n * - billing — deferred until the hub ships /api/billing/status\n * - webhooks — deferred until Phase 16 (the hub's webhook delivery layer)\n *\n * Internal helpers live under `_internal/`. They are NOT part of the\n * public API and may change without notice — the tool-manifest scanner\n * (Phase 9) treats `@openkeyai/sdk/_internal` imports as a CI failure.\n */\n\nimport { verify } from \"./session\";\nimport { get } from \"./keys\";\n\n/** session module — JWT verification + (future) refresh. */\nexport const session = {\n verify,\n} as const;\n\n/** keys module — credential fetch returning a single-use SecureKey. */\nexport const keys = {\n get,\n} as const;\n\n// Re-exports — the SecureKey class + every typed error, so tools can\n// `instanceof` check without reaching into submodules.\nexport { SecureKey } from \"./secure-key\";\nexport {\n HubSdkError,\n MissingTokenError,\n BadTokenError,\n MissingScopeError,\n SubscriptionInactiveError,\n ToolNotFoundError,\n NotSubscribedError,\n ProviderNotGrantedError,\n RateLimitedError,\n KeyNotFoundError,\n InternalError,\n NetworkError,\n SecureKeyConsumedError,\n} from \"./errors\";\nexport type { HubSdkErrorCode } from \"./errors\";\n\n// Type re-exports for tools that want to annotate their own helpers.\nexport type {\n ToolJwtClaims,\n ToolJwtScope,\n ProviderSlug,\n HubCallOptions,\n} from \"./types\";\n\n/** Bumped on each release. Tools log this on boot. */\nexport const SDK_VERSION = \"0.1.0\";\n"]}
|