@persistmemory/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/dist/index.js ADDED
@@ -0,0 +1,1015 @@
1
+ // src/query.ts
2
+ function encodeQuery(params) {
3
+ if (!params) return "";
4
+ const search = new URLSearchParams();
5
+ for (const [key, value] of Object.entries(params)) {
6
+ if (value === void 0) continue;
7
+ if (Array.isArray(value)) {
8
+ for (const one of value) search.append(key, String(one));
9
+ continue;
10
+ }
11
+ search.append(key, String(value));
12
+ }
13
+ const encoded = search.toString();
14
+ return encoded ? `?${encoded}` : "";
15
+ }
16
+ function pageQuery(params, cursor, extra) {
17
+ return {
18
+ ...params.limit !== void 0 ? { limit: params.limit } : {},
19
+ ...cursor !== void 0 ? { cursor } : params.cursor !== void 0 ? { cursor: params.cursor } : {},
20
+ ...extra
21
+ };
22
+ }
23
+
24
+ // src/backoff.ts
25
+ var DEFAULT_BACKOFF = { baseMs: 250, maxMs: 8e3, factor: 2, jitter: 1 };
26
+ function backoffMs(attempt, options = {}) {
27
+ const base = options.baseMs ?? DEFAULT_BACKOFF.baseMs;
28
+ const max = options.maxMs ?? DEFAULT_BACKOFF.maxMs;
29
+ const factor = options.factor ?? DEFAULT_BACKOFF.factor;
30
+ const jitter = clamp01(options.jitter ?? DEFAULT_BACKOFF.jitter);
31
+ const random = options.random ?? Math.random;
32
+ const exponent = Math.max(0, Math.floor(attempt) - 1);
33
+ const ceiling = Math.min(max, base * Math.pow(factor, exponent));
34
+ if (jitter <= 0) return Math.round(ceiling);
35
+ const fixed = ceiling * (1 - jitter);
36
+ return Math.round(fixed + random() * (ceiling - fixed));
37
+ }
38
+ function delayFor(args) {
39
+ const computed = backoffMs(args.attempt, args.options ?? {});
40
+ if (args.retryAfterSeconds === void 0 || !Number.isFinite(args.retryAfterSeconds)) {
41
+ return computed;
42
+ }
43
+ return Math.max(computed, Math.max(0, args.retryAfterSeconds) * 1e3);
44
+ }
45
+ function clamp01(value) {
46
+ if (!Number.isFinite(value)) return 0;
47
+ return Math.min(1, Math.max(0, value));
48
+ }
49
+
50
+ // src/errors.ts
51
+ var PersistMemoryError = class extends Error {
52
+ status;
53
+ code;
54
+ fields;
55
+ requestId;
56
+ retryAfterSeconds;
57
+ /** Retrying this exact request could plausibly succeed. */
58
+ retryable = false;
59
+ constructor(init) {
60
+ super(redact(init.message));
61
+ this.name = new.target.name;
62
+ this.status = init.status;
63
+ this.code = init.code;
64
+ if (init.fields) this.fields = init.fields;
65
+ if (init.requestId) this.requestId = init.requestId;
66
+ if (init.retryAfterSeconds !== void 0) this.retryAfterSeconds = init.retryAfterSeconds;
67
+ }
68
+ /**
69
+ * A one-line summary safe to log.
70
+ *
71
+ * Provided so callers reach for this instead of `JSON.stringify(error)`,
72
+ * which walks own properties and would pick up anything a future field
73
+ * holds. Everything here is already server-supplied and key-free.
74
+ */
75
+ toString() {
76
+ const id = this.requestId ? ` requestId=${this.requestId}` : "";
77
+ return `${this.name}: [${this.status} ${this.code}] ${this.message}${id}`;
78
+ }
79
+ };
80
+ var AuthenticationError = class extends PersistMemoryError {
81
+ };
82
+ var PermissionDeniedError = class extends PersistMemoryError {
83
+ };
84
+ var NotFoundError = class extends PersistMemoryError {
85
+ };
86
+ var ValidationError = class extends PersistMemoryError {
87
+ };
88
+ var ConflictError = class extends PersistMemoryError {
89
+ };
90
+ var RateLimitError = class extends PersistMemoryError {
91
+ retryable = true;
92
+ };
93
+ var ServerError = class extends PersistMemoryError {
94
+ retryable = true;
95
+ };
96
+ var ConnectionError = class extends PersistMemoryError {
97
+ retryable = true;
98
+ constructor(message) {
99
+ super({ status: 0, code: "CONNECTION_ERROR", message });
100
+ }
101
+ };
102
+ var TimeoutError = class extends PersistMemoryError {
103
+ retryable = true;
104
+ constructor(message) {
105
+ super({ status: 0, code: "TIMEOUT", message });
106
+ }
107
+ };
108
+ var AbortError = class extends PersistMemoryError {
109
+ constructor(message = "The request was aborted by the caller.") {
110
+ super({ status: 0, code: "ABORTED", message });
111
+ }
112
+ };
113
+ function errorFromResponse(status, body, headers) {
114
+ const envelope = body ?? {};
115
+ const code = typeof envelope.error?.code === "string" ? envelope.error.code : codeForStatus(status);
116
+ const message = typeof envelope.error?.message === "string" && envelope.error.message.length > 0 ? envelope.error.message : defaultMessage(status);
117
+ const retryAfterSeconds = status === 429 || status === 503 ? retryAfterOf(headers) : void 0;
118
+ const init = {
119
+ status,
120
+ code,
121
+ message,
122
+ ...isFieldMap(envelope.error?.fields) ? { fields: envelope.error.fields } : {},
123
+ ...typeof envelope.error?.requestId === "string" ? { requestId: envelope.error.requestId } : {},
124
+ ...retryAfterSeconds !== void 0 ? { retryAfterSeconds } : {}
125
+ };
126
+ if (status === 429 || code === "RATE_LIMITED") return new RateLimitError(init);
127
+ if (status === 401 || code === "UNAUTHORIZED") return new AuthenticationError(init);
128
+ if (status === 403 || code === "FORBIDDEN") return new PermissionDeniedError(init);
129
+ if (status === 404 || code === "NOT_FOUND") return new NotFoundError(init);
130
+ if (status === 409 || code === "CONFLICT") return new ConflictError(init);
131
+ if (status === 400 || status === 413 || status === 422) return new ValidationError(init);
132
+ if (status >= 500) return new ServerError(init);
133
+ return new PersistMemoryError(init);
134
+ }
135
+ function retryAfterOf(headers) {
136
+ const raw = headers.get("retry-after");
137
+ if (!raw) return void 0;
138
+ const seconds = Number(raw.trim());
139
+ if (!Number.isFinite(seconds) || seconds < 0) return void 0;
140
+ return Math.min(seconds, 300);
141
+ }
142
+ function isFieldMap(value) {
143
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
144
+ return Object.values(value).every((one) => typeof one === "string");
145
+ }
146
+ function codeForStatus(status) {
147
+ if (status === 401) return "UNAUTHORIZED";
148
+ if (status === 403) return "FORBIDDEN";
149
+ if (status === 404) return "NOT_FOUND";
150
+ if (status === 409) return "CONFLICT";
151
+ if (status === 429) return "RATE_LIMITED";
152
+ if (status === 413) return "PAYLOAD_TOO_LARGE";
153
+ if (status >= 500) return "INTERNAL_ERROR";
154
+ return "VALIDATION_ERROR";
155
+ }
156
+ function defaultMessage(status) {
157
+ return `The API returned ${status} with no readable error body.`;
158
+ }
159
+ function redact(text) {
160
+ return text.replace(/pm_(live|test)_[A-Za-z0-9_-]+/g, "pm_$1_[redacted]");
161
+ }
162
+
163
+ // src/http.ts
164
+ var DEFAULT_BASE_URL = "https://api.persistmemory.com";
165
+ var DEFAULT_TIMEOUT_MS = 3e4;
166
+ var DEFAULT_MAX_ATTEMPTS = 3;
167
+ var RETRY_WITHOUT_ASKING = /* @__PURE__ */ new Set(["GET", "HEAD", "PATCH", "DELETE"]);
168
+ var HttpClient = class {
169
+ /**
170
+ * The credential, in a private field, and never anywhere else.
171
+ *
172
+ * Private (`#`) rather than `readonly`: a public field is enumerable, so
173
+ * `JSON.stringify(client)` and every structured logger that walks own
174
+ * properties would write the key into a log line. `toJSON` and the inspect
175
+ * hook below close the two remaining paths - a bug report pasted from
176
+ * `console.log(client)` is exactly how a key gets shared with strangers.
177
+ */
178
+ #apiKey;
179
+ #baseUrl;
180
+ #fetch;
181
+ #timeoutMs;
182
+ #maxAttempts;
183
+ #backoff;
184
+ #sleep;
185
+ #userAgent;
186
+ constructor(options) {
187
+ if (typeof options.apiKey !== "string" || options.apiKey.trim().length === 0) {
188
+ throw new PersistMemoryError({
189
+ status: 0,
190
+ code: "VALIDATION_ERROR",
191
+ message: "An API key is required. Pass `apiKey`, or set PERSISTMEMORY_API_KEY."
192
+ });
193
+ }
194
+ const key = options.apiKey.trim();
195
+ if (/[\r\n\0]/.test(key)) {
196
+ throw new PersistMemoryError({
197
+ status: 0,
198
+ code: "VALIDATION_ERROR",
199
+ message: "The API key contains characters that cannot go in a header."
200
+ });
201
+ }
202
+ this.#apiKey = key;
203
+ this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
204
+ this.#fetch = options.fetch ?? globalThis.fetch;
205
+ this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
206
+ this.#maxAttempts = Math.max(1, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
207
+ this.#backoff = options.backoff ?? {};
208
+ this.#sleep = options.sleep ?? defaultSleep;
209
+ this.#userAgent = options.userAgent ?? "persistmemory-sdk-js/0.1.0";
210
+ }
211
+ /**
212
+ * What this object looks like when something serialises it.
213
+ *
214
+ * Both hooks return the same key-free shape. `toJSON` covers
215
+ * `JSON.stringify`, the inspect symbol covers `console.log` under Node, and
216
+ * between them they cover how a credential actually escapes: not through a
217
+ * deliberate log line, but through an object dumped into a bug report.
218
+ */
219
+ toJSON() {
220
+ return { baseUrl: this.#baseUrl, apiKey: "[redacted]" };
221
+ }
222
+ [Symbol.for("nodejs.util.inspect.custom")]() {
223
+ return this.toJSON();
224
+ }
225
+ async get(path, query, options) {
226
+ return this.#request({
227
+ method: "GET",
228
+ path,
229
+ ...query ? { query } : {},
230
+ ...options ? { options } : {}
231
+ });
232
+ }
233
+ async post(path, body, options) {
234
+ return this.#request({
235
+ method: "POST",
236
+ path,
237
+ ...body !== void 0 ? { body } : {},
238
+ ...options ? { options } : {}
239
+ });
240
+ }
241
+ async patch(path, body, options) {
242
+ return this.#request({
243
+ method: "PATCH",
244
+ path,
245
+ ...body !== void 0 ? { body } : {},
246
+ ...options ? { options } : {}
247
+ });
248
+ }
249
+ async delete(path, body, options) {
250
+ return this.#request({
251
+ method: "DELETE",
252
+ path,
253
+ ...body !== void 0 ? { body } : {},
254
+ ...options ? { options } : {}
255
+ });
256
+ }
257
+ async #request(request) {
258
+ const maxAttempts = Math.max(1, request.options?.maxAttempts ?? this.#maxAttempts);
259
+ const url = this.#baseUrl + request.path + encodeQuery(request.query);
260
+ let attempt = 0;
261
+ for (; ; ) {
262
+ attempt += 1;
263
+ let error;
264
+ try {
265
+ return await this.#attempt(request, url);
266
+ } catch (thrown) {
267
+ if (!(thrown instanceof PersistMemoryError)) throw thrown;
268
+ error = thrown;
269
+ }
270
+ if (attempt >= maxAttempts) throw error;
271
+ if (!mayRetry(error, request)) throw error;
272
+ const delay = delayFor({
273
+ attempt,
274
+ ...error.retryAfterSeconds !== void 0 ? { retryAfterSeconds: error.retryAfterSeconds } : {},
275
+ options: this.#backoff
276
+ });
277
+ await this.#sleep(delay, request.options?.signal);
278
+ }
279
+ }
280
+ async #attempt(request, url) {
281
+ const timeoutMs = request.options?.timeoutMs ?? this.#timeoutMs;
282
+ const deadline = new AbortController();
283
+ const timer = setTimeout(() => deadline.abort(), timeoutMs);
284
+ const onCallerAbort = () => deadline.abort();
285
+ const caller = request.options?.signal;
286
+ caller?.addEventListener("abort", onCallerAbort, { once: true });
287
+ try {
288
+ if (caller?.aborted) throw new AbortError();
289
+ const response = await untilAborted(
290
+ this.#fetch(url, {
291
+ method: request.method,
292
+ headers: this.#headers(request),
293
+ ...request.body !== void 0 ? { body: JSON.stringify(request.body) } : {},
294
+ signal: deadline.signal
295
+ }),
296
+ deadline.signal
297
+ );
298
+ const payload = await readBody(response);
299
+ if (!response.ok) throw errorFromResponse(response.status, payload, response.headers);
300
+ return payload;
301
+ } catch (thrown) {
302
+ if (thrown instanceof PersistMemoryError) throw thrown;
303
+ if (caller?.aborted) throw new AbortError();
304
+ if (deadline.signal.aborted) {
305
+ throw new TimeoutError(`The request did not complete within ${timeoutMs}ms.`);
306
+ }
307
+ const detail = thrown instanceof Error ? redact(thrown.message) : "transport failure";
308
+ throw new ConnectionError(`Could not reach the API: ${detail}`);
309
+ } finally {
310
+ clearTimeout(timer);
311
+ caller?.removeEventListener("abort", onCallerAbort);
312
+ }
313
+ }
314
+ #headers(request) {
315
+ return {
316
+ // The only place the key is ever read.
317
+ authorization: `Bearer ${this.#apiKey}`,
318
+ accept: "application/json",
319
+ "user-agent": this.#userAgent,
320
+ ...request.body !== void 0 ? { "content-type": "application/json" } : {},
321
+ ...request.options?.idempotencyKey ? { "idempotency-key": request.options.idempotencyKey } : {}
322
+ };
323
+ }
324
+ };
325
+ function mayRetry(error, request) {
326
+ if (!error.retryable) return false;
327
+ if (RETRY_WITHOUT_ASKING.has(request.method)) return true;
328
+ if (request.options?.idempotencyKey) return true;
329
+ return error.status === 429;
330
+ }
331
+ async function readBody(response) {
332
+ if (response.status === 204) return void 0;
333
+ const text = await response.text().catch(() => "");
334
+ if (text.length === 0) return void 0;
335
+ const type = response.headers.get("content-type") ?? "";
336
+ if (!type.includes("json")) return { raw: text.slice(0, 500) };
337
+ try {
338
+ return JSON.parse(text);
339
+ } catch {
340
+ return { raw: text.slice(0, 500) };
341
+ }
342
+ }
343
+ var SignalFired = class extends Error {
344
+ constructor() {
345
+ super("signal fired");
346
+ this.name = "SignalFired";
347
+ }
348
+ };
349
+ function untilAborted(work, signal) {
350
+ work.catch(() => void 0);
351
+ return new Promise((resolve, reject) => {
352
+ if (signal.aborted) {
353
+ reject(new SignalFired());
354
+ return;
355
+ }
356
+ const onAbort = () => reject(new SignalFired());
357
+ signal.addEventListener("abort", onAbort, { once: true });
358
+ work.then(
359
+ (value) => {
360
+ signal.removeEventListener("abort", onAbort);
361
+ resolve(value);
362
+ },
363
+ (error) => {
364
+ signal.removeEventListener("abort", onAbort);
365
+ reject(error instanceof Error ? error : new Error(String(error)));
366
+ }
367
+ );
368
+ });
369
+ }
370
+ function defaultSleep(ms, signal) {
371
+ return new Promise((resolve, reject) => {
372
+ if (signal?.aborted) {
373
+ reject(new AbortError());
374
+ return;
375
+ }
376
+ const timer = setTimeout(() => {
377
+ signal?.removeEventListener("abort", onAbort);
378
+ resolve();
379
+ }, ms);
380
+ function onAbort() {
381
+ clearTimeout(timer);
382
+ reject(new AbortError());
383
+ }
384
+ signal?.addEventListener("abort", onAbort, { once: true });
385
+ });
386
+ }
387
+
388
+ // src/pagination.ts
389
+ var Paginated = class {
390
+ #fetchPage;
391
+ constructor(fetchPage) {
392
+ this.#fetchPage = fetchPage;
393
+ }
394
+ /** The first page, and nothing more. For a UI that renders one page at a time. */
395
+ async first() {
396
+ return this.#fetchPage(void 0);
397
+ }
398
+ /**
399
+ * Page by page, for a caller that wants the cursors or wants to stop early.
400
+ *
401
+ * A generator rather than an array of pages: fetching them all up front
402
+ * would make "show me the first ten" cost every page in the account.
403
+ */
404
+ async *pages() {
405
+ let cursor;
406
+ const seen = /* @__PURE__ */ new Set();
407
+ for (; ; ) {
408
+ const page = await this.#fetchPage(cursor);
409
+ yield page;
410
+ const next = page.pagination.nextCursor;
411
+ if (!next) return;
412
+ if (seen.has(next)) return;
413
+ seen.add(next);
414
+ cursor = next;
415
+ }
416
+ }
417
+ /** Every item across every page. `for await (const memory of ...)`. */
418
+ async *[Symbol.asyncIterator]() {
419
+ for await (const page of this.pages()) {
420
+ for (const item of page.data) yield item;
421
+ }
422
+ }
423
+ /**
424
+ * Everything, in one array.
425
+ *
426
+ * `maxItems` is not optional, and that is the point. An unbounded `all()` on
427
+ * an account with two hundred thousand memories is a request loop that runs
428
+ * for minutes and an array that exhausts the heap, and the call site that
429
+ * does it reads as innocently as any other. Ask for a number you can hold.
430
+ */
431
+ async all(maxItems) {
432
+ const collected = [];
433
+ if (maxItems <= 0) return collected;
434
+ for await (const item of this) {
435
+ collected.push(item);
436
+ if (collected.length >= maxItems) break;
437
+ }
438
+ return collected;
439
+ }
440
+ };
441
+
442
+ // src/resources/memories.ts
443
+ var Memories = class {
444
+ #http;
445
+ constructor(http) {
446
+ this.#http = http;
447
+ }
448
+ /**
449
+ * One page, plus the cursor loop.
450
+ *
451
+ * Returns a `Paginated`, so `await memories.list().first()` gets a page and
452
+ * `for await (const memory of memories.list())` walks the lot. The filters
453
+ * are carried into every page automatically - a caller re-passing them per
454
+ * page is a caller who will eventually forget one, and the pages after that
455
+ * come from a differently filtered list.
456
+ */
457
+ list(params = {}, options) {
458
+ return new Paginated(
459
+ (cursor) => this.#http.get(
460
+ "/api/v1/memories",
461
+ pageQuery(params, cursor, toQuery(params)),
462
+ options
463
+ )
464
+ );
465
+ }
466
+ async get(id, options) {
467
+ return this.#http.get(`/api/v1/memories/${encodeURIComponent(id)}`, void 0, options);
468
+ }
469
+ /**
470
+ * Hands material to the ingestion pipeline. Needs a key with `write` scope.
471
+ *
472
+ * This does NOT create a memory, and the return type says so: it answers 202
473
+ * with a job id. Extraction, entity resolution, deduplication and conflict
474
+ * detection all run afterwards and may produce one memory, several, or none.
475
+ * Poll `client.jobs.get(result.jobId)` to find out which.
476
+ *
477
+ * Pass an `idempotencyKey` if this can be retried by anything - a queue, a
478
+ * user pressing a button twice, or this client's own retry loop, which
479
+ * refuses to repeat a POST without one. `remember:note-42`, not a fresh
480
+ * random value per call.
481
+ */
482
+ async remember(params, options) {
483
+ return this.#http.post("/api/v1/remember", params, options);
484
+ }
485
+ };
486
+ function toQuery(params) {
487
+ return {
488
+ ...params.type !== void 0 ? { type: params.type } : {},
489
+ ...params.state !== void 0 ? { state: params.state } : {},
490
+ ...params.scope !== void 0 ? { scope: params.scope } : {},
491
+ ...params.spaceIds !== void 0 ? { spaceIds: params.spaceIds } : {},
492
+ ...params.createdAfter !== void 0 ? { createdAfter: params.createdAfter } : {},
493
+ ...params.createdBefore !== void 0 ? { createdBefore: params.createdBefore } : {},
494
+ ...params.minConfidence !== void 0 ? { minConfidence: params.minConfidence } : {},
495
+ ...params.includeHistorical !== void 0 ? { includeHistorical: params.includeHistorical } : {}
496
+ };
497
+ }
498
+
499
+ // src/resources/search.ts
500
+ var Search = class {
501
+ #http;
502
+ constructor(http) {
503
+ this.#http = http;
504
+ }
505
+ /**
506
+ * Ranked results, with an account of how they were found.
507
+ *
508
+ * Read `diagnostics.degraded` before showing the results. Search degrades
509
+ * rather than fails - with embeddings unavailable it falls back to
510
+ * deterministic retrieval and still answers - and a UI that cannot tell
511
+ * degraded from healthy tells the user the system knows nothing when it is
512
+ * merely looking with one eye.
513
+ */
514
+ async query(params, options) {
515
+ const query = {
516
+ query: params.query,
517
+ ...params.limit !== void 0 ? { limit: params.limit } : {},
518
+ ...params.scope !== void 0 ? { scope: params.scope } : {},
519
+ ...params.spaceIds !== void 0 ? { spaceIds: params.spaceIds } : {},
520
+ ...params.types !== void 0 ? { types: params.types } : {},
521
+ ...params.minScore !== void 0 ? { minScore: params.minScore } : {},
522
+ ...params.asOf !== void 0 ? { asOf: params.asOf } : {},
523
+ ...params.includeHistorical !== void 0 ? { includeHistorical: params.includeHistorical } : {},
524
+ ...params.includeEvidence !== void 0 ? { includeEvidence: params.includeEvidence } : {},
525
+ ...params.explain !== void 0 ? { explain: params.explain } : {}
526
+ };
527
+ return this.#http.get("/api/v1/search", query, options);
528
+ }
529
+ /**
530
+ * A context window, assembled and ready to paste into a prompt.
531
+ *
532
+ * A POST that is safe to repeat - it reads and returns, it writes nothing -
533
+ * so this is one of the few places where retrying without an idempotency key
534
+ * would be harmless. It still is not retried by default: the request loop
535
+ * decides by METHOD, and a per-endpoint exception is a rule that holds until
536
+ * someone adds a POST next to it that does write.
537
+ */
538
+ async context(params, options) {
539
+ return this.#http.post("/api/v1/context", params, options);
540
+ }
541
+ };
542
+
543
+ // src/resources/spaces.ts
544
+ var Spaces = class {
545
+ #http;
546
+ constructor(http) {
547
+ this.#http = http;
548
+ }
549
+ list(params = {}, options) {
550
+ return new Paginated(
551
+ (cursor) => this.#http.get(
552
+ "/api/v1/spaces",
553
+ pageQuery(params, cursor, {
554
+ ...params.includeArchived !== void 0 ? { includeArchived: params.includeArchived } : {}
555
+ }),
556
+ options
557
+ )
558
+ );
559
+ }
560
+ async get(id, options) {
561
+ return this.#http.get(`/api/v1/spaces/${encodeURIComponent(id)}`, void 0, options);
562
+ }
563
+ /**
564
+ * Creates a Space. Answers 201.
565
+ *
566
+ * Worth an idempotency key when a person is behind it: a double-clicked
567
+ * "create Space" button makes two Spaces called Work, and nothing later can
568
+ * tell which of them memories should have gone into.
569
+ */
570
+ async create(params, options) {
571
+ return this.#http.post("/api/v1/spaces", params, options);
572
+ }
573
+ /** Renaming, retention, and archiving - `archived` is a field, not a verb. */
574
+ async update(id, params, options) {
575
+ return this.#http.patch(`/api/v1/spaces/${encodeURIComponent(id)}`, params, options);
576
+ }
577
+ /**
578
+ * The memories filed in a Space.
579
+ *
580
+ * This endpoint answers `{ data, pagination: { limit } }` with no cursor: it
581
+ * returns the first `limit` members and stops. Wrapped in a `Paginated`
582
+ * anyway so it reads like every other list, and it simply yields one page -
583
+ * a caller who needs more should filter `memories.list` by `spaceIds`, which
584
+ * is the endpoint that actually pages.
585
+ */
586
+ memories(id, params = {}, options) {
587
+ return new Paginated(
588
+ () => this.#http.get(
589
+ `/api/v1/spaces/${encodeURIComponent(id)}/memories`,
590
+ { ...params.limit !== void 0 ? { limit: params.limit } : {} },
591
+ options
592
+ )
593
+ );
594
+ }
595
+ async addMemories(id, memoryIds, options) {
596
+ return this.#http.post(
597
+ `/api/v1/spaces/${encodeURIComponent(id)}/memories`,
598
+ { memoryIds },
599
+ options
600
+ );
601
+ }
602
+ /**
603
+ * Removes memberships. The memories themselves are untouched.
604
+ *
605
+ * A body on a DELETE, which is unusual and is what the API takes: the
606
+ * alternative is five hundred ids in a query string, and every proxy in
607
+ * between has its own limit on how long a URL may be.
608
+ */
609
+ async removeMemories(id, memoryIds, options) {
610
+ return this.#http.delete(
611
+ `/api/v1/spaces/${encodeURIComponent(id)}/memories`,
612
+ { memoryIds },
613
+ options
614
+ );
615
+ }
616
+ };
617
+
618
+ // src/resources/ingestion.ts
619
+ var Sources = class {
620
+ #http;
621
+ constructor(http) {
622
+ this.#http = http;
623
+ }
624
+ list(params = {}, options) {
625
+ return new Paginated(
626
+ (cursor) => this.#http.get(
627
+ "/api/v1/sources",
628
+ pageQuery(params, cursor, {
629
+ ...params.provider !== void 0 ? { provider: params.provider } : {}
630
+ }),
631
+ options
632
+ )
633
+ );
634
+ }
635
+ async get(id, options) {
636
+ return this.#http.get(`/api/v1/sources/${encodeURIComponent(id)}`, void 0, options);
637
+ }
638
+ };
639
+ var Documents = class {
640
+ #http;
641
+ constructor(http) {
642
+ this.#http = http;
643
+ }
644
+ list(params = {}, options) {
645
+ return new Paginated(
646
+ (cursor) => this.#http.get(
647
+ "/api/v1/documents",
648
+ pageQuery(params, cursor, {
649
+ ...params.sourceId !== void 0 ? { sourceId: params.sourceId } : {},
650
+ ...params.status !== void 0 ? { status: params.status } : {}
651
+ }),
652
+ options
653
+ )
654
+ );
655
+ }
656
+ async get(id, options) {
657
+ return this.#http.get(
658
+ `/api/v1/documents/${encodeURIComponent(id)}`,
659
+ void 0,
660
+ options
661
+ );
662
+ }
663
+ };
664
+ var Jobs = class {
665
+ #http;
666
+ constructor(http) {
667
+ this.#http = http;
668
+ }
669
+ list(params = {}, options) {
670
+ return new Paginated(
671
+ (cursor) => this.#http.get(
672
+ "/api/v1/jobs",
673
+ pageQuery(params, cursor, {
674
+ ...params.status !== void 0 ? { status: params.status } : {},
675
+ ...params.type !== void 0 ? { type: params.type } : {}
676
+ }),
677
+ options
678
+ )
679
+ );
680
+ }
681
+ /**
682
+ * One job, by id. This is what `remember` hands back a reference to.
683
+ *
684
+ * `completed` is the terminal success state - the store's own word, not
685
+ * `succeeded`. A caller polling for a state the API never writes waits
686
+ * forever with nothing to show why.
687
+ */
688
+ async get(id, options) {
689
+ return this.#http.get(`/api/v1/jobs/${encodeURIComponent(id)}`, void 0, options);
690
+ }
691
+ };
692
+
693
+ // src/resources/knowledge.ts
694
+ var Entities = class {
695
+ #http;
696
+ constructor(http) {
697
+ this.#http = http;
698
+ }
699
+ list(params = {}, options) {
700
+ return new Paginated(
701
+ (cursor) => this.#http.get(
702
+ "/api/v1/entities",
703
+ pageQuery(params, cursor, {
704
+ ...params.type !== void 0 ? { type: params.type } : {},
705
+ ...params.q !== void 0 ? { q: params.q } : {}
706
+ }),
707
+ options
708
+ )
709
+ );
710
+ }
711
+ async get(id, options) {
712
+ return this.#http.get(`/api/v1/entities/${encodeURIComponent(id)}`, void 0, options);
713
+ }
714
+ /**
715
+ * Which memories mention this entity, and how.
716
+ *
717
+ * Returns MENTIONS - a memory id, a role and a confidence - not the memories
718
+ * themselves. "Memories about Sam" and "memories Sam appears in" are
719
+ * different questions, and `role` is what separates them.
720
+ *
721
+ * Like the Space membership list, this endpoint answers with a `pagination`
722
+ * block that carries no cursor, so it yields one page and stops.
723
+ */
724
+ memories(id, params = {}, options) {
725
+ return new Paginated(
726
+ (cursor) => this.#http.get(
727
+ `/api/v1/entities/${encodeURIComponent(id)}/memories`,
728
+ pageQuery(params, cursor, {
729
+ ...params.role !== void 0 ? { role: params.role } : {},
730
+ ...params.minConfidence !== void 0 ? { minConfidence: params.minConfidence } : {}
731
+ }),
732
+ options
733
+ )
734
+ );
735
+ }
736
+ };
737
+ var Graph = class {
738
+ #http;
739
+ constructor(http) {
740
+ this.#http = http;
741
+ }
742
+ async traverse(params, options) {
743
+ return this.#http.get(
744
+ "/api/v1/graph",
745
+ {
746
+ from: params.from,
747
+ ...params.depth !== void 0 ? { depth: params.depth } : {},
748
+ ...params.maxNodes !== void 0 ? { maxNodes: params.maxNodes } : {},
749
+ ...params.memoryLimit !== void 0 ? { memoryLimit: params.memoryLimit } : {}
750
+ },
751
+ options
752
+ );
753
+ }
754
+ };
755
+ var Conflicts = class {
756
+ #http;
757
+ constructor(http) {
758
+ this.#http = http;
759
+ }
760
+ list(params = {}, options) {
761
+ return new Paginated(
762
+ (cursor) => this.#http.get(
763
+ "/api/v1/conflicts",
764
+ pageQuery(params, cursor, {
765
+ ...params.includeResolved !== void 0 ? { includeResolved: params.includeResolved } : {},
766
+ ...params.type !== void 0 ? { type: params.type } : {}
767
+ }),
768
+ options
769
+ )
770
+ );
771
+ }
772
+ async get(id, options) {
773
+ return this.#http.get(
774
+ `/api/v1/conflicts/${encodeURIComponent(id)}`,
775
+ void 0,
776
+ options
777
+ );
778
+ }
779
+ /**
780
+ * Settles one.
781
+ *
782
+ * `keep` names the winner and supersedes the loser; it does not delete it.
783
+ * `dismiss` records that the detector was wrong, which is worth knowing when
784
+ * the same pair trips it again.
785
+ *
786
+ * The parameter type is a union, so `keep` without a `keepId` does not
787
+ * compile. The server rejects it too - this just moves the failure from a
788
+ * 400 in production to a red squiggle.
789
+ */
790
+ async resolve(id, params, options) {
791
+ return this.#http.post(
792
+ `/api/v1/conflicts/${encodeURIComponent(id)}/resolve`,
793
+ params,
794
+ options
795
+ );
796
+ }
797
+ };
798
+
799
+ // src/resources/conversations.ts
800
+ var Conversations = class {
801
+ #http;
802
+ constructor(http) {
803
+ this.#http = http;
804
+ }
805
+ list(params = {}, options) {
806
+ return new Paginated(
807
+ (cursor) => this.#http.get(
808
+ "/api/v1/conversations",
809
+ pageQuery(params, cursor, {
810
+ ...params.channel !== void 0 ? { channel: params.channel } : {}
811
+ }),
812
+ options
813
+ )
814
+ );
815
+ }
816
+ async get(id, options) {
817
+ return this.#http.get(
818
+ `/api/v1/conversations/${encodeURIComponent(id)}`,
819
+ void 0,
820
+ options
821
+ );
822
+ }
823
+ async create(params = {}, options) {
824
+ return this.#http.post("/api/v1/conversations", params, options);
825
+ }
826
+ messages(id, params = {}, options) {
827
+ return new Paginated(
828
+ (cursor) => this.#http.get(
829
+ `/api/v1/conversations/${encodeURIComponent(id)}/messages`,
830
+ pageQuery(params, cursor, {}),
831
+ options
832
+ )
833
+ );
834
+ }
835
+ /**
836
+ * Appends turns, and by default extracts memories from them.
837
+ *
838
+ * Check `extracting` on the result. With no queue configured the turns are
839
+ * stored and never become memory, and the API says so in `note` rather than
840
+ * reporting a success - a caller that ignores it believes a memory is on its
841
+ * way that never arrives.
842
+ *
843
+ * `system` and `tool` turns are stored but never extracted: a system prompt
844
+ * is configuration, and remembering it would file our own instructions as
845
+ * the user's facts.
846
+ *
847
+ * Give this an `idempotencyKey`. Appending the same turn twice is the most
848
+ * likely duplicate in the whole API - a client reconnecting after a dropped
849
+ * response has no other way to tell whether its last write landed.
850
+ */
851
+ async append(id, params, options) {
852
+ return this.#http.post(
853
+ `/api/v1/conversations/${encodeURIComponent(id)}/messages`,
854
+ params,
855
+ options
856
+ );
857
+ }
858
+ };
859
+
860
+ // src/resources/integrations.ts
861
+ var Integrations = class {
862
+ #http;
863
+ constructor(http) {
864
+ this.#http = http;
865
+ }
866
+ list(params = {}, options) {
867
+ return new Paginated(
868
+ (cursor) => this.#http.get(
869
+ "/api/v1/integrations",
870
+ pageQuery(params, cursor, {
871
+ ...params.provider !== void 0 ? { provider: params.provider } : {},
872
+ ...params.status !== void 0 ? { status: params.status } : {}
873
+ }),
874
+ options
875
+ )
876
+ );
877
+ }
878
+ /** Providers that can be connected at all. Not the user's own connections. */
879
+ async available(options) {
880
+ return this.#http.get("/api/v1/integrations/available", void 0, options);
881
+ }
882
+ async get(id, options) {
883
+ return this.#http.get(
884
+ `/api/v1/integrations/${encodeURIComponent(id)}`,
885
+ void 0,
886
+ options
887
+ );
888
+ }
889
+ async connect(params, options) {
890
+ return this.#http.post(
891
+ "/api/v1/integrations/connect",
892
+ params,
893
+ options
894
+ );
895
+ }
896
+ async update(id, params, options) {
897
+ return this.#http.patch(
898
+ `/api/v1/integrations/${encodeURIComponent(id)}`,
899
+ params,
900
+ options
901
+ );
902
+ }
903
+ /**
904
+ * Asks for a sync now rather than waiting for the schedule. Answers 202.
905
+ *
906
+ * `full` re-reads everything and is deliberately opt-in: on a large Drive
907
+ * that is thousands of documents and a real bill. The incremental default is
908
+ * what should run almost always.
909
+ */
910
+ async sync(id, params = {}, options) {
911
+ return this.#http.post(
912
+ `/api/v1/integrations/${encodeURIComponent(id)}/sync`,
913
+ params,
914
+ options
915
+ );
916
+ }
917
+ /**
918
+ * Destroys the credentials. The row stays.
919
+ *
920
+ * History still has to attribute the memories this connection produced, and
921
+ * deleting the row would leave them pointing at nothing.
922
+ */
923
+ async disconnect(id, options) {
924
+ return this.#http.delete(
925
+ `/api/v1/integrations/${encodeURIComponent(id)}`,
926
+ void 0,
927
+ options
928
+ );
929
+ }
930
+ };
931
+
932
+ // src/resources/health.ts
933
+ var Health = class {
934
+ #http;
935
+ constructor(http) {
936
+ this.#http = http;
937
+ }
938
+ async live(options) {
939
+ return this.#http.get("/health/live", void 0, options);
940
+ }
941
+ async ready(options) {
942
+ return this.#http.get("/health/ready", void 0, options);
943
+ }
944
+ };
945
+
946
+ // src/client.ts
947
+ var PersistMemory = class {
948
+ memories;
949
+ search;
950
+ spaces;
951
+ sources;
952
+ documents;
953
+ jobs;
954
+ entities;
955
+ graph;
956
+ conflicts;
957
+ conversations;
958
+ integrations;
959
+ health;
960
+ #http;
961
+ constructor(options) {
962
+ this.#http = new HttpClient(options);
963
+ this.memories = new Memories(this.#http);
964
+ this.search = new Search(this.#http);
965
+ this.spaces = new Spaces(this.#http);
966
+ this.sources = new Sources(this.#http);
967
+ this.documents = new Documents(this.#http);
968
+ this.jobs = new Jobs(this.#http);
969
+ this.entities = new Entities(this.#http);
970
+ this.graph = new Graph(this.#http);
971
+ this.conflicts = new Conflicts(this.#http);
972
+ this.conversations = new Conversations(this.#http);
973
+ this.integrations = new Integrations(this.#http);
974
+ this.health = new Health(this.#http);
975
+ }
976
+ /**
977
+ * An escape hatch for an endpoint this package has not caught up with.
978
+ *
979
+ * Typed as `unknown` on purpose: a caller reaching past the typed surface is
980
+ * taking responsibility for the shape, and handing them `any` would let that
981
+ * responsibility spread silently through their codebase.
982
+ */
983
+ async request(method, path, body, options) {
984
+ if (method === "GET") return this.#http.get(path, void 0, options);
985
+ if (method === "POST") return this.#http.post(path, body, options);
986
+ if (method === "PATCH") return this.#http.patch(path, body, options);
987
+ return this.#http.delete(path, body, options);
988
+ }
989
+ /** Never the key. See `HttpClient.toJSON`, which this delegates to. */
990
+ toJSON() {
991
+ return this.#http.toJSON();
992
+ }
993
+ [Symbol.for("nodejs.util.inspect.custom")]() {
994
+ return this.#http.toJSON();
995
+ }
996
+ };
997
+ export {
998
+ AbortError,
999
+ AuthenticationError,
1000
+ ConflictError,
1001
+ ConnectionError,
1002
+ DEFAULT_BACKOFF,
1003
+ NotFoundError,
1004
+ Paginated,
1005
+ PermissionDeniedError,
1006
+ PersistMemory,
1007
+ PersistMemoryError,
1008
+ RateLimitError,
1009
+ ServerError,
1010
+ TimeoutError,
1011
+ ValidationError,
1012
+ backoffMs,
1013
+ delayFor
1014
+ };
1015
+ //# sourceMappingURL=index.js.map