@keemakr/agent-sdk 0.10.0 → 0.11.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/README.md +24 -12
- package/dist/client.d.ts +34 -4
- package/dist/client.js +26 -1
- package/dist/connectors.generated.d.ts +1 -37
- package/dist/connectors.generated.js +0 -32
- package/dist/grant-auth.d.ts +13 -3
- package/dist/grant-auth.js +27 -14
- package/dist/index.d.ts +1 -1
- package/dist/jwks.d.ts +42 -0
- package/dist/jwks.js +97 -0
- package/dist/refresh.d.ts +11 -1
- package/dist/refresh.js +15 -2
- package/dist/verify-grant.d.ts +5 -3
- package/dist/verify-grant.js +15 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,13 +19,21 @@ Peer dependencies (match your eve agent): `eve@0.13.0`, `jose@^6.2.3`.
|
|
|
19
19
|
|
|
20
20
|
Set these in your deployed agent's environment:
|
|
21
21
|
|
|
22
|
-
| Variable | Purpose
|
|
23
|
-
| -------------------- |
|
|
24
|
-
| `
|
|
25
|
-
| `KEE_AGENT_AUDIENCE` | This deployment's audience — your runtime URL's origin, e.g. `https://my-agent.example.com`. Must match the audience the operator mints.
|
|
26
|
-
| `
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
| Variable | Purpose |
|
|
23
|
+
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
24
|
+
| `KEE_CORE_URL` | **Required.** The keemakr platform you installed on, e.g. `https://dash.dev.keemakr.ai`. Used both to verify grants and to call the capability API. |
|
|
25
|
+
| `KEE_AGENT_AUDIENCE` | This deployment's audience — your runtime URL's origin, e.g. `https://my-agent.example.com`. Must match the audience the operator mints. |
|
|
26
|
+
| `KEE_CORE_JWKS_URL` | Only when the JWKS is not at `<KEE_CORE_URL>/.well-known/jwks.json`. Takes precedence over `KEE_CORE_URL` for verification. |
|
|
27
|
+
|
|
28
|
+
**There is no default platform, deliberately.** `KEE_CORE_URL` names the keys your
|
|
29
|
+
agent trusts and the host it sends the tenant's grant token to. A compiled-in
|
|
30
|
+
default would mean this package picking your trust anchor for you — and since the
|
|
31
|
+
same published SDK is installed against dev, staging and production, whichever
|
|
32
|
+
origin it named would be wrong for the others. Set one variable per deployment.
|
|
33
|
+
|
|
34
|
+
With neither set, `grantAuth()` refuses every grant and logs once explaining why,
|
|
35
|
+
and `coreBaseUrl()` throws rather than posting a live credential somewhere you did
|
|
36
|
+
not choose. Both name the variables in their message.
|
|
29
37
|
|
|
30
38
|
## 1. Verify the grant in your channel
|
|
31
39
|
|
|
@@ -142,13 +150,17 @@ via `kee.kb`.
|
|
|
142
150
|
|
|
143
151
|
```ts
|
|
144
152
|
const hits = await kee.kb.search('what is our refund policy?', { k: 5 });
|
|
145
|
-
// → [{ text, score,
|
|
153
|
+
// → [{ text, score, artifact_id, version_id, chunk_id, title,
|
|
154
|
+
// audience, visibility, provenance }]
|
|
146
155
|
```
|
|
147
156
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
157
|
+
Core derives the retrieval actor from the verified grant. A Kee acting for a
|
|
158
|
+
user can retrieve that user's Personal Knowledge plus Organization and Platform
|
|
159
|
+
Knowledge. A System Kee can retrieve Organization and Platform Knowledge but
|
|
160
|
+
never Personal Knowledge. External Kees are additionally restricted to External
|
|
161
|
+
Safe knowledge. Collections organize artifacts but never grant access
|
|
162
|
+
(`kb:retrieve` scope, granted to every install). Retrieval is hybrid and may be
|
|
163
|
+
reranked in Core; `text` may be a wider parent context.
|
|
152
164
|
|
|
153
165
|
### Platform tools
|
|
154
166
|
|
package/dist/client.d.ts
CHANGED
|
@@ -112,13 +112,17 @@ export interface KeeMemory {
|
|
|
112
112
|
export interface KBHit {
|
|
113
113
|
text: string;
|
|
114
114
|
score: number;
|
|
115
|
+
artifact_id: string;
|
|
116
|
+
version_id: string;
|
|
117
|
+
chunk_id: string;
|
|
118
|
+
title: string;
|
|
119
|
+
audience: 'personal' | 'organization' | 'platform';
|
|
120
|
+
visibility: 'internal_only' | 'external_safe';
|
|
115
121
|
provenance: Record<string, unknown>;
|
|
116
122
|
}
|
|
117
123
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* enforced server-side from the grant. Requires the `kb:retrieve` scope
|
|
121
|
-
* (granted to every install).
|
|
124
|
+
* Governed Company Brain retrieval. Core derives User, System, or External Kee access from the
|
|
125
|
+
* verified grant and returns source identities suitable for citations. Requires `kb:retrieve`.
|
|
122
126
|
*/
|
|
123
127
|
export interface KeeKb {
|
|
124
128
|
/** Semantic + lexical + reranked search over the agent-visible knowledge. */
|
|
@@ -182,6 +186,31 @@ export interface KeeTools {
|
|
|
182
186
|
/** Run a registry tool by name and return its result. Requires `tools:run`. */
|
|
183
187
|
run(name: string, args?: Record<string, unknown>): Promise<unknown>;
|
|
184
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* The entry's STAFF-SET platform settings — one value per `kind:'env'`
|
|
191
|
+
* dependency it declares. Requires the `config:read` scope, which that same
|
|
192
|
+
* declaration mints.
|
|
193
|
+
*
|
|
194
|
+
* These are operator settings (a destination address, a shared endpoint), not
|
|
195
|
+
* tenant data and not secrets the agent may change: the surface is read-only by
|
|
196
|
+
* design. An operator sets them once in the platform console and they apply to
|
|
197
|
+
* every install of the entry.
|
|
198
|
+
*/
|
|
199
|
+
export interface KeeConfig {
|
|
200
|
+
/** One setting, or null when staff have not set it yet. */
|
|
201
|
+
get(key: string): Promise<string | null>;
|
|
202
|
+
/**
|
|
203
|
+
* Every declared setting at once — `{ config, declared }`. `declared` lists
|
|
204
|
+
* the keys this entry asks for, so "staff haven't set it" (declared, missing
|
|
205
|
+
* from config) stays distinguishable from "this entry has no such setting"
|
|
206
|
+
* (absent from declared). Collapsing those two is what makes a configuration
|
|
207
|
+
* gap look like a runtime bug.
|
|
208
|
+
*/
|
|
209
|
+
all(): Promise<{
|
|
210
|
+
config: Record<string, string>;
|
|
211
|
+
declared: string[];
|
|
212
|
+
}>;
|
|
213
|
+
}
|
|
185
214
|
export interface Kee {
|
|
186
215
|
tenantId: string;
|
|
187
216
|
scopes: string[];
|
|
@@ -193,6 +222,7 @@ export interface Kee {
|
|
|
193
222
|
records: KeeRecords;
|
|
194
223
|
kb: KeeKb;
|
|
195
224
|
tools: KeeTools;
|
|
225
|
+
config: KeeConfig;
|
|
196
226
|
}
|
|
197
227
|
/**
|
|
198
228
|
* Build a tenant-scoped capability client from a tool's context. Call inside a
|
package/dist/client.js
CHANGED
|
@@ -278,7 +278,7 @@ export function useKee(ctx) {
|
|
|
278
278
|
async search(query, opts) {
|
|
279
279
|
const json = (await capabilityFetch(grant, 'kb/retrieve', {
|
|
280
280
|
query,
|
|
281
|
-
|
|
281
|
+
limit: opts?.k,
|
|
282
282
|
}));
|
|
283
283
|
return json.hits ?? [];
|
|
284
284
|
},
|
|
@@ -295,6 +295,30 @@ export function useKee(ctx) {
|
|
|
295
295
|
return json.result;
|
|
296
296
|
},
|
|
297
297
|
};
|
|
298
|
+
// One fetch serves both accessors, and the result is cached for the life of
|
|
299
|
+
// this client: settings change at operator pace, not per tool call, and a
|
|
300
|
+
// `get()` per setting inside one tool would re-fetch the same map N times.
|
|
301
|
+
let configOnce = null;
|
|
302
|
+
const config = {
|
|
303
|
+
async all() {
|
|
304
|
+
configOnce ??= (async () => {
|
|
305
|
+
const json = (await capabilityFetch(grant, 'config', undefined, 'GET'));
|
|
306
|
+
return { config: json.config ?? {}, declared: json.declared ?? [] };
|
|
307
|
+
})();
|
|
308
|
+
try {
|
|
309
|
+
return await configOnce;
|
|
310
|
+
}
|
|
311
|
+
catch (e) {
|
|
312
|
+
// Don't cache a failure — a transient error must not poison every later
|
|
313
|
+
// read for the rest of the session.
|
|
314
|
+
configOnce = null;
|
|
315
|
+
throw e;
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
async get(key) {
|
|
319
|
+
return (await this.all()).config[key] ?? null;
|
|
320
|
+
},
|
|
321
|
+
};
|
|
298
322
|
return {
|
|
299
323
|
tenantId: grant.tenantId,
|
|
300
324
|
scopes: grant.scopes,
|
|
@@ -303,5 +327,6 @@ export function useKee(ctx) {
|
|
|
303
327
|
records,
|
|
304
328
|
kb,
|
|
305
329
|
tools,
|
|
330
|
+
config,
|
|
306
331
|
};
|
|
307
332
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
export type ConnectorName = "agent-sessions" | "apify" | "apollo" | "cal-com" | "
|
|
1
|
+
export type ConnectorName = "agent-sessions" | "apify" | "apollo" | "cal-com" | "clearbit" | "drip" | "elevenlabs" | "facebook" | "fal" | "hubspot" | "hunter" | "instagram" | "instantly" | "mailchimp" | "meta" | "pagespeed" | "posthog" | "sendgrid" | "stripe" | "twilio" | "wordpress";
|
|
2
2
|
/** Per-provider operation names (autocomplete for `.call(op)`). */
|
|
3
3
|
export interface ConnectorOps {
|
|
4
4
|
"agent-sessions": never;
|
|
5
5
|
"apify": never;
|
|
6
6
|
"apollo": never;
|
|
7
7
|
"cal-com": never;
|
|
8
|
-
"calendly": never;
|
|
9
8
|
"clearbit": never;
|
|
10
9
|
"drip": never;
|
|
11
10
|
"elevenlabs": never;
|
|
@@ -19,12 +18,9 @@ export interface ConnectorOps {
|
|
|
19
18
|
"meta": never;
|
|
20
19
|
"pagespeed": never;
|
|
21
20
|
"posthog": never;
|
|
22
|
-
"salesforce": never;
|
|
23
21
|
"sendgrid": never;
|
|
24
|
-
"slack": never;
|
|
25
22
|
"stripe": never;
|
|
26
23
|
"twilio": never;
|
|
27
|
-
"whatsapp": never;
|
|
28
24
|
"wordpress": never;
|
|
29
25
|
}
|
|
30
26
|
export interface ConnectorOp {
|
|
@@ -79,14 +75,6 @@ export declare const connectors: {
|
|
|
79
75
|
readonly docUrl: "https://cal.com/docs/api-reference";
|
|
80
76
|
readonly ops: {};
|
|
81
77
|
};
|
|
82
|
-
readonly calendly: {
|
|
83
|
-
readonly displayName: "Calendly";
|
|
84
|
-
readonly authKind: "api_key";
|
|
85
|
-
readonly category: "scheduling";
|
|
86
|
-
readonly maturity: "coming_soon";
|
|
87
|
-
readonly docUrl: "https://developer.calendly.com/api-docs";
|
|
88
|
-
readonly ops: {};
|
|
89
|
-
};
|
|
90
78
|
readonly clearbit: {
|
|
91
79
|
readonly displayName: "Clearbit";
|
|
92
80
|
readonly authKind: "api_key";
|
|
@@ -217,14 +205,6 @@ export declare const connectors: {
|
|
|
217
205
|
readonly docUrl: "https://posthog.com/docs/api";
|
|
218
206
|
readonly ops: {};
|
|
219
207
|
};
|
|
220
|
-
readonly salesforce: {
|
|
221
|
-
readonly displayName: "Salesforce";
|
|
222
|
-
readonly authKind: "api_key";
|
|
223
|
-
readonly category: "crm";
|
|
224
|
-
readonly maturity: "coming_soon";
|
|
225
|
-
readonly docUrl: "https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/";
|
|
226
|
-
readonly ops: {};
|
|
227
|
-
};
|
|
228
208
|
readonly sendgrid: {
|
|
229
209
|
readonly displayName: "SendGrid";
|
|
230
210
|
readonly authKind: "api_key";
|
|
@@ -233,14 +213,6 @@ export declare const connectors: {
|
|
|
233
213
|
readonly docUrl: "https://docs.sendgrid.com/api-reference";
|
|
234
214
|
readonly ops: {};
|
|
235
215
|
};
|
|
236
|
-
readonly slack: {
|
|
237
|
-
readonly displayName: "Slack";
|
|
238
|
-
readonly authKind: "api_key";
|
|
239
|
-
readonly category: "messaging";
|
|
240
|
-
readonly maturity: "coming_soon";
|
|
241
|
-
readonly docUrl: "https://api.slack.com/methods/chat.postMessage";
|
|
242
|
-
readonly ops: {};
|
|
243
|
-
};
|
|
244
216
|
readonly stripe: {
|
|
245
217
|
readonly displayName: "Stripe";
|
|
246
218
|
readonly authKind: "oauth2";
|
|
@@ -257,14 +229,6 @@ export declare const connectors: {
|
|
|
257
229
|
readonly docUrl: "https://www.twilio.com/docs/usage/api";
|
|
258
230
|
readonly ops: {};
|
|
259
231
|
};
|
|
260
|
-
readonly whatsapp: {
|
|
261
|
-
readonly displayName: "WhatsApp";
|
|
262
|
-
readonly authKind: "oauth2";
|
|
263
|
-
readonly category: "channel";
|
|
264
|
-
readonly maturity: "coming_soon";
|
|
265
|
-
readonly docUrl: "https://developers.facebook.com/docs/whatsapp";
|
|
266
|
-
readonly ops: {};
|
|
267
|
-
};
|
|
268
232
|
readonly wordpress: {
|
|
269
233
|
readonly displayName: "WordPress";
|
|
270
234
|
readonly authKind: "oauth2";
|
|
@@ -39,14 +39,6 @@ export const connectors = {
|
|
|
39
39
|
"docUrl": "https://cal.com/docs/api-reference",
|
|
40
40
|
"ops": {}
|
|
41
41
|
},
|
|
42
|
-
"calendly": {
|
|
43
|
-
"displayName": "Calendly",
|
|
44
|
-
"authKind": "api_key",
|
|
45
|
-
"category": "scheduling",
|
|
46
|
-
"maturity": "coming_soon",
|
|
47
|
-
"docUrl": "https://developer.calendly.com/api-docs",
|
|
48
|
-
"ops": {}
|
|
49
|
-
},
|
|
50
42
|
"clearbit": {
|
|
51
43
|
"displayName": "Clearbit",
|
|
52
44
|
"authKind": "api_key",
|
|
@@ -181,14 +173,6 @@ export const connectors = {
|
|
|
181
173
|
"docUrl": "https://posthog.com/docs/api",
|
|
182
174
|
"ops": {}
|
|
183
175
|
},
|
|
184
|
-
"salesforce": {
|
|
185
|
-
"displayName": "Salesforce",
|
|
186
|
-
"authKind": "api_key",
|
|
187
|
-
"category": "crm",
|
|
188
|
-
"maturity": "coming_soon",
|
|
189
|
-
"docUrl": "https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/",
|
|
190
|
-
"ops": {}
|
|
191
|
-
},
|
|
192
176
|
"sendgrid": {
|
|
193
177
|
"displayName": "SendGrid",
|
|
194
178
|
"authKind": "api_key",
|
|
@@ -197,14 +181,6 @@ export const connectors = {
|
|
|
197
181
|
"docUrl": "https://docs.sendgrid.com/api-reference",
|
|
198
182
|
"ops": {}
|
|
199
183
|
},
|
|
200
|
-
"slack": {
|
|
201
|
-
"displayName": "Slack",
|
|
202
|
-
"authKind": "api_key",
|
|
203
|
-
"category": "messaging",
|
|
204
|
-
"maturity": "coming_soon",
|
|
205
|
-
"docUrl": "https://api.slack.com/methods/chat.postMessage",
|
|
206
|
-
"ops": {}
|
|
207
|
-
},
|
|
208
184
|
"stripe": {
|
|
209
185
|
"displayName": "Stripe",
|
|
210
186
|
"authKind": "oauth2",
|
|
@@ -221,14 +197,6 @@ export const connectors = {
|
|
|
221
197
|
"docUrl": "https://www.twilio.com/docs/usage/api",
|
|
222
198
|
"ops": {}
|
|
223
199
|
},
|
|
224
|
-
"whatsapp": {
|
|
225
|
-
"displayName": "WhatsApp",
|
|
226
|
-
"authKind": "oauth2",
|
|
227
|
-
"category": "channel",
|
|
228
|
-
"maturity": "coming_soon",
|
|
229
|
-
"docUrl": "https://developers.facebook.com/docs/whatsapp",
|
|
230
|
-
"ops": {}
|
|
231
|
-
},
|
|
232
200
|
"wordpress": {
|
|
233
201
|
"displayName": "WordPress",
|
|
234
202
|
"authKind": "oauth2",
|
package/dist/grant-auth.d.ts
CHANGED
|
@@ -6,12 +6,22 @@ import { type AuthFn } from 'eve/channels/auth';
|
|
|
6
6
|
* fallback).
|
|
7
7
|
*
|
|
8
8
|
* Environment:
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* KEE_CORE_URL core's origin, e.g. https://dash.<env>.keemakr.ai — the
|
|
10
|
+
* platform this agent was installed on. REQUIRED (unless
|
|
11
|
+
* KEE_CORE_JWKS_URL or `opts.jwksUrl` says the same thing):
|
|
12
|
+
* it names the keys this agent trusts, and there is no
|
|
13
|
+
* default, because a default would mean trusting whichever
|
|
14
|
+
* platform the SDK author picked. Without it, every grant is
|
|
15
|
+
* refused and the reason is logged once.
|
|
16
|
+
* KEE_CORE_JWKS_URL the JWKS endpoint directly, when it is not
|
|
17
|
+
* `<KEE_CORE_URL>/.well-known/jwks.json`.
|
|
12
18
|
* KEE_AGENT_AUDIENCE this deployment's audience — the runtime URL's origin —
|
|
13
19
|
* matching the `aud` the operator mints. If unset, the
|
|
14
20
|
* audience check is skipped (dev convenience only).
|
|
21
|
+
*
|
|
22
|
+
* A missing configuration no longer switches grant verification off SILENTLY,
|
|
23
|
+
* which was the real defect: it used to return the same bare null as a bad token,
|
|
24
|
+
* so every capability call 401'd with nothing in the log to say why.
|
|
15
25
|
*/
|
|
16
26
|
export declare function grantAuth(opts?: {
|
|
17
27
|
jwksUrl?: string;
|
package/dist/grant-auth.js
CHANGED
|
@@ -8,16 +8,11 @@
|
|
|
8
8
|
// grantAuth() returns an eve AuthFn that verifies the grant against core's JWKS
|
|
9
9
|
// and surfaces the tenant + scopes (and the raw grant, for useKee to forward) on
|
|
10
10
|
// the session auth context. Use it as the PRIMARY inbound auth in your channel.
|
|
11
|
-
import {
|
|
11
|
+
import { jwtVerify } from 'jose';
|
|
12
12
|
import { extractBearerToken } from 'eve/channels/auth';
|
|
13
|
+
import { jwksFor, resolveJwksUrl, warnCoreUnconfigured, warnGrantVerifyFailed } from './jwks.js';
|
|
13
14
|
// The issuer keemakr-core mints grants with.
|
|
14
15
|
const GRANT_ISSUER = 'keemakr';
|
|
15
|
-
let jwks = null;
|
|
16
|
-
function jwksFor(url) {
|
|
17
|
-
if (!jwks)
|
|
18
|
-
jwks = createRemoteJWKSet(new URL(url));
|
|
19
|
-
return jwks;
|
|
20
|
-
}
|
|
21
16
|
/**
|
|
22
17
|
* An eve AuthFn that accepts a keemakr capability grant. Returns a principal
|
|
23
18
|
* carrying `tenant_id`, `scopes`, and the raw `grant_token` in attributes on
|
|
@@ -25,21 +20,35 @@ function jwksFor(url) {
|
|
|
25
20
|
* fallback).
|
|
26
21
|
*
|
|
27
22
|
* Environment:
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
23
|
+
* KEE_CORE_URL core's origin, e.g. https://dash.<env>.keemakr.ai — the
|
|
24
|
+
* platform this agent was installed on. REQUIRED (unless
|
|
25
|
+
* KEE_CORE_JWKS_URL or `opts.jwksUrl` says the same thing):
|
|
26
|
+
* it names the keys this agent trusts, and there is no
|
|
27
|
+
* default, because a default would mean trusting whichever
|
|
28
|
+
* platform the SDK author picked. Without it, every grant is
|
|
29
|
+
* refused and the reason is logged once.
|
|
30
|
+
* KEE_CORE_JWKS_URL the JWKS endpoint directly, when it is not
|
|
31
|
+
* `<KEE_CORE_URL>/.well-known/jwks.json`.
|
|
31
32
|
* KEE_AGENT_AUDIENCE this deployment's audience — the runtime URL's origin —
|
|
32
33
|
* matching the `aud` the operator mints. If unset, the
|
|
33
34
|
* audience check is skipped (dev convenience only).
|
|
35
|
+
*
|
|
36
|
+
* A missing configuration no longer switches grant verification off SILENTLY,
|
|
37
|
+
* which was the real defect: it used to return the same bare null as a bad token,
|
|
38
|
+
* so every capability call 401'd with nothing in the log to say why.
|
|
34
39
|
*/
|
|
35
40
|
export function grantAuth(opts) {
|
|
36
41
|
return async (request) => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return null;
|
|
42
|
+
// Token first: an unconfigured deployment should complain about requests that
|
|
43
|
+
// actually carry a grant, not about every anonymous hit on the channel.
|
|
40
44
|
const token = extractBearerToken(request.headers.get('authorization'));
|
|
41
45
|
if (!token)
|
|
42
46
|
return null;
|
|
47
|
+
const jwksUrl = resolveJwksUrl(opts?.jwksUrl);
|
|
48
|
+
if (!jwksUrl) {
|
|
49
|
+
warnCoreUnconfigured('verify the capability grant on this request');
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
43
52
|
const expectedAud = opts?.audience ?? process.env.KEE_AGENT_AUDIENCE;
|
|
44
53
|
try {
|
|
45
54
|
const { payload } = await jwtVerify(token, jwksFor(jwksUrl), {
|
|
@@ -67,7 +76,11 @@ export function grantAuth(opts) {
|
|
|
67
76
|
},
|
|
68
77
|
};
|
|
69
78
|
}
|
|
70
|
-
catch {
|
|
79
|
+
catch (error) {
|
|
80
|
+
// Still returns null — an unverifiable token must not authenticate — but no
|
|
81
|
+
// longer silently. A wrong JWKS URL and a genuinely bad token produced the
|
|
82
|
+
// same nothing before, which is what made the redirect trap so expensive.
|
|
83
|
+
warnGrantVerifyFailed(jwksUrl, error);
|
|
71
84
|
return null;
|
|
72
85
|
}
|
|
73
86
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { grantAuth } from './grant-auth.js';
|
|
2
2
|
export { verifyGrant, type VerifiedGrant } from './verify-grant.js';
|
|
3
|
-
export { useKee, MemoryConflictError, RecordConflictError, RecordValidationError, type Kee, type KeeConnection, type KeeContext, type KeeError, type KeeMemory, type KeeRecords, type KeeKb, type KBHit, type KeeTools, type MemoryEntry, type MemorySearchHit, type RecordEntry, } from './client.js';
|
|
3
|
+
export { useKee, MemoryConflictError, RecordConflictError, RecordValidationError, type Kee, type KeeConnection, type KeeContext, type KeeError, type KeeMemory, type KeeRecords, type KeeKb, type KBHit, type KeeTools, type KeeConfig, type MemoryEntry, type MemorySearchHit, type RecordEntry, } from './client.js';
|
|
4
4
|
export { keemakrToolDirectory } from './tool-directory.js';
|
|
5
5
|
export { refreshGrant, REFRESH_THRESHOLD_SECONDS } from './refresh.js';
|
package/dist/jwks.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* There is deliberately NO compiled-in default origin.
|
|
3
|
+
*
|
|
4
|
+
* This value is a TRUST ANCHOR: whatever it names gets to decide, for every
|
|
5
|
+
* tenant this agent serves, which grants are genuine. It cannot come from the
|
|
6
|
+
* token (a forged grant would name its own JWKS and verify against it), and it
|
|
7
|
+
* must not come from a constant baked into a published package either — this SDK
|
|
8
|
+
* ships to npm and is installed by agents on dev, staging and production alike,
|
|
9
|
+
* so ANY single origin compiled in here is the wrong one for at least two of
|
|
10
|
+
* them. A default pointing at a non-production platform is the worst case of all:
|
|
11
|
+
* an unconfigured production agent would accept grants signed by a platform
|
|
12
|
+
* anyone can get an account on.
|
|
13
|
+
*
|
|
14
|
+
* So an unconfigured deployment gets `null` and verifies nothing — but LOUDLY,
|
|
15
|
+
* via warnCoreUnconfigured(). That was the actual defect behind "every capability
|
|
16
|
+
* call 401s with nothing in the log": not the absence of a default, but the
|
|
17
|
+
* silence. One warning naming the two variables fixes that without inventing a
|
|
18
|
+
* trust anchor on the operator's behalf.
|
|
19
|
+
*/
|
|
20
|
+
/** `<origin>/.well-known/jwks.json`, tolerating a trailing slash. */
|
|
21
|
+
export declare function jwksUrlFor(origin: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve core's JWKS endpoint, most specific first, or `null` when the
|
|
24
|
+
* deployment has said nothing about which platform it belongs to.
|
|
25
|
+
*
|
|
26
|
+
* `KEE_CORE_URL` counts as a source in its own right: the SDK already derives
|
|
27
|
+
* that base URL FROM the JWKS URL, and supporting only that direction meant an
|
|
28
|
+
* agent could be configured to reach the capability API yet unable to verify the
|
|
29
|
+
* grant authorizing it — two variables for one fact, with a silent 401 as the
|
|
30
|
+
* penalty for setting the wrong one.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveJwksUrl(explicit?: string): string | null;
|
|
33
|
+
export declare function jwksFor(url: string): {
|
|
34
|
+
(protectedHeader?: import("jose").JWSHeaderParameters, token?: import("jose").FlattenedJWSInput): Promise<import("jose").CryptoKey>;
|
|
35
|
+
coolingDown: boolean;
|
|
36
|
+
fresh: boolean;
|
|
37
|
+
reloading: boolean;
|
|
38
|
+
reload: () => Promise<void>;
|
|
39
|
+
jwks: () => import("jose").JSONWebKeySet | undefined;
|
|
40
|
+
};
|
|
41
|
+
export declare function warnGrantVerifyFailed(jwksUrl: string, error: unknown): void;
|
|
42
|
+
export declare function warnCoreUnconfigured(attempting: string): void;
|
package/dist/jwks.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Where a keemakr agent looks for core's signing keys, and the one cache of them.
|
|
2
|
+
//
|
|
3
|
+
// Shared by both verification paths — the inbound channel auth (grant-auth) and
|
|
4
|
+
// the standalone machine-grant check (verify-grant) — because a deployment that
|
|
5
|
+
// can verify one and not the other is a configuration bug waiting to be
|
|
6
|
+
// diagnosed twice.
|
|
7
|
+
import { createRemoteJWKSet } from 'jose';
|
|
8
|
+
/**
|
|
9
|
+
* There is deliberately NO compiled-in default origin.
|
|
10
|
+
*
|
|
11
|
+
* This value is a TRUST ANCHOR: whatever it names gets to decide, for every
|
|
12
|
+
* tenant this agent serves, which grants are genuine. It cannot come from the
|
|
13
|
+
* token (a forged grant would name its own JWKS and verify against it), and it
|
|
14
|
+
* must not come from a constant baked into a published package either — this SDK
|
|
15
|
+
* ships to npm and is installed by agents on dev, staging and production alike,
|
|
16
|
+
* so ANY single origin compiled in here is the wrong one for at least two of
|
|
17
|
+
* them. A default pointing at a non-production platform is the worst case of all:
|
|
18
|
+
* an unconfigured production agent would accept grants signed by a platform
|
|
19
|
+
* anyone can get an account on.
|
|
20
|
+
*
|
|
21
|
+
* So an unconfigured deployment gets `null` and verifies nothing — but LOUDLY,
|
|
22
|
+
* via warnCoreUnconfigured(). That was the actual defect behind "every capability
|
|
23
|
+
* call 401s with nothing in the log": not the absence of a default, but the
|
|
24
|
+
* silence. One warning naming the two variables fixes that without inventing a
|
|
25
|
+
* trust anchor on the operator's behalf.
|
|
26
|
+
*/
|
|
27
|
+
/** `<origin>/.well-known/jwks.json`, tolerating a trailing slash. */
|
|
28
|
+
export function jwksUrlFor(origin) {
|
|
29
|
+
return `${origin.replace(/\/$/, '')}/.well-known/jwks.json`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve core's JWKS endpoint, most specific first, or `null` when the
|
|
33
|
+
* deployment has said nothing about which platform it belongs to.
|
|
34
|
+
*
|
|
35
|
+
* `KEE_CORE_URL` counts as a source in its own right: the SDK already derives
|
|
36
|
+
* that base URL FROM the JWKS URL, and supporting only that direction meant an
|
|
37
|
+
* agent could be configured to reach the capability API yet unable to verify the
|
|
38
|
+
* grant authorizing it — two variables for one fact, with a silent 401 as the
|
|
39
|
+
* penalty for setting the wrong one.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveJwksUrl(explicit) {
|
|
42
|
+
if (explicit)
|
|
43
|
+
return explicit;
|
|
44
|
+
if (process.env.KEE_CORE_JWKS_URL)
|
|
45
|
+
return process.env.KEE_CORE_JWKS_URL;
|
|
46
|
+
if (process.env.KEE_CORE_URL)
|
|
47
|
+
return jwksUrlFor(process.env.KEE_CORE_URL);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
// Keyed by URL. The previous single-slot cache handed back the FIRST keyset it
|
|
51
|
+
// ever built for every later call, so a process verifying against two cores used
|
|
52
|
+
// the wrong keys for one of them and reported nothing.
|
|
53
|
+
const jwksCache = new Map();
|
|
54
|
+
export function jwksFor(url) {
|
|
55
|
+
let set = jwksCache.get(url);
|
|
56
|
+
if (!set) {
|
|
57
|
+
set = createRemoteJWKSet(new URL(url));
|
|
58
|
+
jwksCache.set(url, set);
|
|
59
|
+
}
|
|
60
|
+
return set;
|
|
61
|
+
}
|
|
62
|
+
// A misconfigured JWKS URL was indistinguishable from an unauthorized caller: the
|
|
63
|
+
// verify threw, the caller returned null, and every capability call 401'd with
|
|
64
|
+
// nothing said about why. The classic cause is an origin that REDIRECTS (e.g.
|
|
65
|
+
// app.dev.keemakr.ai → dash.dev.keemakr.ai), where `jose` fetches HTML and finds
|
|
66
|
+
// no keys. Warn once per URL: enough to diagnose, not enough to flood a hot path.
|
|
67
|
+
const warned = new Set();
|
|
68
|
+
export function warnGrantVerifyFailed(jwksUrl, error) {
|
|
69
|
+
if (warned.has(jwksUrl))
|
|
70
|
+
return;
|
|
71
|
+
warned.add(jwksUrl);
|
|
72
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
73
|
+
console.warn(`[keemakr] could not verify a capability grant against ${jwksUrl}: ${reason}. ` +
|
|
74
|
+
'If that URL redirects or serves no keys, set KEE_CORE_JWKS_URL (or KEE_CORE_URL) ' +
|
|
75
|
+
'to the origin serving /.well-known/jwks.json for the platform you installed on.');
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The other half of the same diagnosis problem: a deployment that named no
|
|
79
|
+
* platform at all. It still refuses the token — an agent that does not know whose
|
|
80
|
+
* keys to trust must not decide that a grant is genuine — but it now says so
|
|
81
|
+
* instead of returning the same silent null a genuinely bad token gets.
|
|
82
|
+
*
|
|
83
|
+
* Warned once per process (not per request): this fires on a hot path, and after
|
|
84
|
+
* the first line the operator has everything they need.
|
|
85
|
+
*/
|
|
86
|
+
let warnedUnconfigured = false;
|
|
87
|
+
export function warnCoreUnconfigured(attempting) {
|
|
88
|
+
if (warnedUnconfigured)
|
|
89
|
+
return;
|
|
90
|
+
warnedUnconfigured = true;
|
|
91
|
+
console.warn(`[keemakr] cannot ${attempting}: this deployment has not been told which keemakr ` +
|
|
92
|
+
'platform it belongs to, so there are no signing keys to trust and no capability ' +
|
|
93
|
+
'API to call. Set KEE_CORE_URL to the origin you installed on (e.g. ' +
|
|
94
|
+
'https://dash.<env>.keemakr.ai), or KEE_CORE_JWKS_URL to its ' +
|
|
95
|
+
'/.well-known/jwks.json directly. There is no default on purpose — guessing ' +
|
|
96
|
+
'would mean trusting a platform you did not choose.');
|
|
97
|
+
}
|
package/dist/refresh.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
/** Refresh when the active token has less than this long left to live. */
|
|
2
2
|
export declare const REFRESH_THRESHOLD_SECONDS = 120;
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Resolve core's base URL: KEE_CORE_URL, else the origin of KEE_CORE_JWKS_URL.
|
|
5
|
+
*
|
|
6
|
+
* Throws when neither is set, and that is the safe answer rather than the
|
|
7
|
+
* unhelpful one. This URL is where the SDK POSTs the tenant's grant token, so a
|
|
8
|
+
* guessed default is not a convenience — it is a live credential sent to a host
|
|
9
|
+
* nobody chose, which on an unconfigured production agent would mean handing a
|
|
10
|
+
* production grant to whatever environment the constant happened to name. Failing
|
|
11
|
+
* at the first capability call, with both variable names in the message, costs one
|
|
12
|
+
* deploy; the other failure mode costs a credential.
|
|
13
|
+
*/
|
|
4
14
|
export declare function coreBaseUrl(): string;
|
|
5
15
|
/** The freshest token known for a delegation (the exchanged one, else the original). */
|
|
6
16
|
export declare function activeGrantToken(originalToken: string): string;
|
package/dist/refresh.js
CHANGED
|
@@ -19,7 +19,17 @@ export const REFRESH_THRESHOLD_SECONDS = 120;
|
|
|
19
19
|
const refreshed = new Map();
|
|
20
20
|
// original grant token → in-flight refresh, so N concurrent tool calls share one POST.
|
|
21
21
|
const inflight = new Map();
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Resolve core's base URL: KEE_CORE_URL, else the origin of KEE_CORE_JWKS_URL.
|
|
24
|
+
*
|
|
25
|
+
* Throws when neither is set, and that is the safe answer rather than the
|
|
26
|
+
* unhelpful one. This URL is where the SDK POSTs the tenant's grant token, so a
|
|
27
|
+
* guessed default is not a convenience — it is a live credential sent to a host
|
|
28
|
+
* nobody chose, which on an unconfigured production agent would mean handing a
|
|
29
|
+
* production grant to whatever environment the constant happened to name. Failing
|
|
30
|
+
* at the first capability call, with both variable names in the message, costs one
|
|
31
|
+
* deploy; the other failure mode costs a credential.
|
|
32
|
+
*/
|
|
23
33
|
export function coreBaseUrl() {
|
|
24
34
|
const explicit = process.env.KEE_CORE_URL;
|
|
25
35
|
if (explicit)
|
|
@@ -27,7 +37,10 @@ export function coreBaseUrl() {
|
|
|
27
37
|
const jwks = process.env.KEE_CORE_JWKS_URL;
|
|
28
38
|
if (jwks)
|
|
29
39
|
return jwks.replace(/\/\.well-known\/jwks\.json\/?$/, '');
|
|
30
|
-
const e = new Error('KEE_CORE_URL (or KEE_CORE_JWKS_URL) must be set to reach the Capability API'
|
|
40
|
+
const e = new Error('KEE_CORE_URL (or KEE_CORE_JWKS_URL) must be set to reach the Capability API — ' +
|
|
41
|
+
'it names the keemakr platform this agent was installed on, e.g. ' +
|
|
42
|
+
'https://dash.<env>.keemakr.ai. There is no default: the grant token is sent ' +
|
|
43
|
+
'to this URL, so guessing would leak it to a platform you did not choose.');
|
|
31
44
|
e.name = 'KeeError';
|
|
32
45
|
throw e;
|
|
33
46
|
}
|
package/dist/verify-grant.d.ts
CHANGED
|
@@ -10,9 +10,11 @@ export interface VerifiedGrant {
|
|
|
10
10
|
/**
|
|
11
11
|
* Verify a capability grant (session OR machine) against keemakr-core's JWKS.
|
|
12
12
|
* Returns the trusted claims, or `null` on any failure (bad signature, wrong
|
|
13
|
-
* issuer/audience, expired).
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* issuer/audience, expired). `jwksUrl` falls back to KEE_CORE_JWKS_URL, then to
|
|
14
|
+
* KEE_CORE_URL's origin — and to nothing after that, so a deployment that has not
|
|
15
|
+
* named its platform refuses every grant rather than trusting a compiled-in
|
|
16
|
+
* default it did not choose. `audience` falls back to KEE_AGENT_AUDIENCE and is
|
|
17
|
+
* strongly recommended: a grant is only valid for the remote it was minted for.
|
|
16
18
|
*/
|
|
17
19
|
export declare function verifyGrant(token: string, opts?: {
|
|
18
20
|
jwksUrl?: string;
|
package/dist/verify-grant.js
CHANGED
|
@@ -10,25 +10,24 @@
|
|
|
10
10
|
// A machine grant is the SAME token shape as a session grant (same issuer, aud,
|
|
11
11
|
// tenant_id, scopes) — only the TTL and the mint path differ — so this one
|
|
12
12
|
// verifier covers both.
|
|
13
|
-
import {
|
|
13
|
+
import { jwtVerify } from 'jose';
|
|
14
|
+
import { jwksFor, resolveJwksUrl, warnCoreUnconfigured, warnGrantVerifyFailed } from './jwks.js';
|
|
14
15
|
const GRANT_ISSUER = 'keemakr';
|
|
15
|
-
let jwks = null;
|
|
16
|
-
function jwksFor(url) {
|
|
17
|
-
if (!jwks)
|
|
18
|
-
jwks = createRemoteJWKSet(new URL(url));
|
|
19
|
-
return jwks;
|
|
20
|
-
}
|
|
21
16
|
/**
|
|
22
17
|
* Verify a capability grant (session OR machine) against keemakr-core's JWKS.
|
|
23
18
|
* Returns the trusted claims, or `null` on any failure (bad signature, wrong
|
|
24
|
-
* issuer/audience, expired).
|
|
25
|
-
*
|
|
26
|
-
*
|
|
19
|
+
* issuer/audience, expired). `jwksUrl` falls back to KEE_CORE_JWKS_URL, then to
|
|
20
|
+
* KEE_CORE_URL's origin — and to nothing after that, so a deployment that has not
|
|
21
|
+
* named its platform refuses every grant rather than trusting a compiled-in
|
|
22
|
+
* default it did not choose. `audience` falls back to KEE_AGENT_AUDIENCE and is
|
|
23
|
+
* strongly recommended: a grant is only valid for the remote it was minted for.
|
|
27
24
|
*/
|
|
28
25
|
export async function verifyGrant(token, opts) {
|
|
29
|
-
const jwksUrl = opts?.jwksUrl
|
|
30
|
-
if (!jwksUrl)
|
|
26
|
+
const jwksUrl = resolveJwksUrl(opts?.jwksUrl);
|
|
27
|
+
if (!jwksUrl) {
|
|
28
|
+
warnCoreUnconfigured('verify this capability grant');
|
|
31
29
|
return null;
|
|
30
|
+
}
|
|
32
31
|
const expectedAud = opts?.audience ?? process.env.KEE_AGENT_AUDIENCE;
|
|
33
32
|
try {
|
|
34
33
|
const { payload } = await jwtVerify(token, jwksFor(jwksUrl), {
|
|
@@ -46,7 +45,10 @@ export async function verifyGrant(token, opts) {
|
|
|
46
45
|
exp: typeof payload.exp === 'number' ? payload.exp : null,
|
|
47
46
|
};
|
|
48
47
|
}
|
|
49
|
-
catch {
|
|
48
|
+
catch (error) {
|
|
49
|
+
// Null either way — an unverifiable grant must not pass — but say why once, so
|
|
50
|
+
// a misconfigured JWKS URL is distinguishable from a genuinely bad token.
|
|
51
|
+
warnGrantVerifyFailed(jwksUrl, error);
|
|
50
52
|
return null;
|
|
51
53
|
}
|
|
52
54
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keemakr/agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "The floor for keemakr marketplace agents: verify the capability grant and reach tenant connections, memory, and shared platform tools through keemakr-core — without holding raw secrets or resolving the tenant yourself.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|