@keemakr/agent-sdk 0.9.0 → 0.10.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/dist/client.d.ts CHANGED
@@ -13,6 +13,26 @@ export declare class MemoryConflictError extends Error {
13
13
  current: MemoryEntry | null;
14
14
  constructor(current: MemoryEntry | null);
15
15
  }
16
+ /**
17
+ * A conditional record write (`ifVersion`) lost the race — another agent wrote
18
+ * the record first. `current` is the record as it now stands (null when it was
19
+ * deleted concurrently). Re-read, re-derive, retry.
20
+ */
21
+ export declare class RecordConflictError extends Error {
22
+ status: 409;
23
+ current: RecordEntry | null;
24
+ constructor(current: RecordEntry | null);
25
+ }
26
+ /**
27
+ * The record didn't match the field spec the entry declared for the collection
28
+ * — core validates every write server-side against what the tenant consented
29
+ * to at install. `issues` lists the exact mismatches.
30
+ */
31
+ export declare class RecordValidationError extends Error {
32
+ status: 422;
33
+ issues: string[];
34
+ constructor(issues: string[]);
35
+ }
16
36
  export interface KeeContext {
17
37
  session?: {
18
38
  auth?: {
@@ -106,6 +126,51 @@ export interface KeeKb {
106
126
  k?: number;
107
127
  }): Promise<KBHit[]>;
108
128
  }
129
+ /** One stored typed record. */
130
+ export interface RecordEntry {
131
+ collection: string;
132
+ record_id: string;
133
+ data: Record<string, unknown>;
134
+ written_by_agent: string | null;
135
+ /** Monotonic write counter — pass as `ifVersion` for compare-and-swap writes. */
136
+ version: number;
137
+ created_at: string;
138
+ updated_at: string;
139
+ }
140
+ /**
141
+ * Typed record collections — the structured counterpart to memory (leads,
142
+ * tickets, anything row-shaped). A collection must be declared in the entry's
143
+ * entry.json as a kind:'records' dependency (collection + field spec); the
144
+ * tenant consents at install, and the grant then carries the per-collection
145
+ * `records:<collection>:read` / `records:<collection>:write` scopes. Core
146
+ * validates every write against the declared spec — a mismatch throws
147
+ * RecordValidationError with the exact issues. TENANT-SHARED like memory.
148
+ */
149
+ export interface KeeRecords {
150
+ /**
151
+ * Write a record. When the spec declares an upsertKey, the record id derives
152
+ * from that field's value — saving the same lead email twice updates one
153
+ * record (idempotent). Pass `id` to address a specific record (PUT), and
154
+ * `ifVersion` (from a prior get/list) for compare-and-swap: throws
155
+ * RecordConflictError when another agent wrote it in between.
156
+ */
157
+ upsert(collection: string, data: Record<string, unknown>, opts?: {
158
+ id?: string;
159
+ ifVersion?: number;
160
+ }): Promise<RecordEntry>;
161
+ /** Read one record, or null if absent. */
162
+ get(collection: string, id: string): Promise<RecordEntry | null>;
163
+ /** List a collection page by page (keyset cursor — pass back `nextCursor`). */
164
+ list(collection: string, opts?: {
165
+ limit?: number;
166
+ cursor?: string;
167
+ }): Promise<{
168
+ records: RecordEntry[];
169
+ nextCursor: string | null;
170
+ }>;
171
+ /** Delete one record. Returns whether it existed. */
172
+ delete(collection: string, id: string): Promise<boolean>;
173
+ }
109
174
  /** Platform registry tools (Shape B) — defined in core, run server-side. */
110
175
  export interface KeeTools {
111
176
  /** List the registry tools this grant is entitled to. */
@@ -125,6 +190,7 @@ export interface Kee {
125
190
  get(provider: string): KeeConnection;
126
191
  };
127
192
  memory: KeeMemory;
193
+ records: KeeRecords;
128
194
  kb: KeeKb;
129
195
  tools: KeeTools;
130
196
  }
package/dist/client.js CHANGED
@@ -31,6 +31,34 @@ export class MemoryConflictError extends Error {
31
31
  this.current = current;
32
32
  }
33
33
  }
34
+ /**
35
+ * A conditional record write (`ifVersion`) lost the race — another agent wrote
36
+ * the record first. `current` is the record as it now stands (null when it was
37
+ * deleted concurrently). Re-read, re-derive, retry.
38
+ */
39
+ export class RecordConflictError extends Error {
40
+ status = 409;
41
+ current;
42
+ constructor(current) {
43
+ super('record version conflict — the record changed since it was read');
44
+ this.name = 'RecordConflictError';
45
+ this.current = current;
46
+ }
47
+ }
48
+ /**
49
+ * The record didn't match the field spec the entry declared for the collection
50
+ * — core validates every write server-side against what the tenant consented
51
+ * to at install. `issues` lists the exact mismatches.
52
+ */
53
+ export class RecordValidationError extends Error {
54
+ status = 422;
55
+ issues;
56
+ constructor(issues) {
57
+ super(`record does not match the declared spec: ${issues.join('; ')}`);
58
+ this.name = 'RecordValidationError';
59
+ this.issues = issues;
60
+ }
61
+ }
34
62
  function readGrant(ctx) {
35
63
  const attrs = ctx?.session?.auth?.current?.attributes ?? {};
36
64
  const token = typeof attrs.grant_token === 'string' ? attrs.grant_token : undefined;
@@ -193,6 +221,59 @@ export function useKee(ctx) {
193
221
  return json.hits ?? [];
194
222
  },
195
223
  };
224
+ // Shared 409/422 mapping for record writes (POST to the collection or PUT to
225
+ // a specific id) — typed errors the agent's model can act on.
226
+ const recordWrite = async (path, method, body) => {
227
+ try {
228
+ const json = (await capabilityFetch(grant, path, body, method));
229
+ return json.entry;
230
+ }
231
+ catch (e) {
232
+ const err = e;
233
+ const body409 = err.body;
234
+ if (err.status === 409 && body409?.error === 'version_conflict') {
235
+ throw new RecordConflictError(body409.entry ?? null);
236
+ }
237
+ if (err.status === 422 && body409?.issues) {
238
+ throw new RecordValidationError(body409.issues);
239
+ }
240
+ throw e;
241
+ }
242
+ };
243
+ const records = {
244
+ async upsert(collection, data, opts) {
245
+ const payload = { data, expected_version: opts?.ifVersion };
246
+ if (opts?.id) {
247
+ return recordWrite(`records/${enc(collection)}/${enc(opts.id)}`, 'PUT', payload);
248
+ }
249
+ return recordWrite(`records/${enc(collection)}`, 'POST', payload);
250
+ },
251
+ async get(collection, id) {
252
+ try {
253
+ const json = (await capabilityFetch(grant, `records/${enc(collection)}/${enc(id)}`, undefined, 'GET'));
254
+ return json.entry ?? null;
255
+ }
256
+ catch (e) {
257
+ if (e.status === 404)
258
+ return null;
259
+ throw e;
260
+ }
261
+ },
262
+ async list(collection, opts) {
263
+ const qs = new URLSearchParams();
264
+ if (opts?.limit)
265
+ qs.set('limit', String(opts.limit));
266
+ if (opts?.cursor)
267
+ qs.set('cursor', opts.cursor);
268
+ const suffix = qs.size ? `?${qs}` : '';
269
+ const json = (await capabilityFetch(grant, `records/${enc(collection)}${suffix}`, undefined, 'GET'));
270
+ return { records: json.records ?? [], nextCursor: json.next_cursor ?? null };
271
+ },
272
+ async delete(collection, id) {
273
+ const json = (await capabilityFetch(grant, `records/${enc(collection)}/${enc(id)}`, undefined, 'DELETE'));
274
+ return !!json.deleted;
275
+ },
276
+ };
196
277
  const kb = {
197
278
  async search(query, opts) {
198
279
  const json = (await capabilityFetch(grant, 'kb/retrieve', {
@@ -214,5 +295,13 @@ export function useKee(ctx) {
214
295
  return json.result;
215
296
  },
216
297
  };
217
- return { tenantId: grant.tenantId, scopes: grant.scopes, connections, memory, kb, tools };
298
+ return {
299
+ tenantId: grant.tenantId,
300
+ scopes: grant.scopes,
301
+ connections,
302
+ memory,
303
+ records,
304
+ kb,
305
+ tools,
306
+ };
218
307
  }
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, type Kee, type KeeConnection, type KeeContext, type KeeError, type KeeMemory, type KeeKb, type KBHit, type KeeTools, type MemoryEntry, type MemorySearchHit, } 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 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/index.js CHANGED
@@ -10,6 +10,6 @@
10
10
  // const r = await kee.connections.hunter.call('email-finder', { domain, first_name, last_name });
11
11
  export { grantAuth } from './grant-auth.js';
12
12
  export { verifyGrant } from './verify-grant.js';
13
- export { useKee, MemoryConflictError, } from './client.js';
13
+ export { useKee, MemoryConflictError, RecordConflictError, RecordValidationError, } from './client.js';
14
14
  export { keemakrToolDirectory } from './tool-directory.js';
15
15
  export { refreshGrant, REFRESH_THRESHOLD_SECONDS } from './refresh.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keemakr/agent-sdk",
3
- "version": "0.9.0",
3
+ "version": "0.10.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",