wontopos 2.2.37 → 2.2.39

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.
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WME = exports.Client = exports.ServerError = exports.RateLimitError = exports.ConflictError = exports.NotFoundError = exports.PermissionDeniedError = exports.PaymentRequiredError = exports.AuthenticationError = exports.BadRequestError = exports.APIConnectionError = exports.WosError = exports.CONTEXT_LIMIT_MAX = exports.CONTEXT_LIMIT_MIN = exports.SEARCH_LIMIT_MAX = exports.SEARCH_LIMIT_MIN = void 0;
4
4
  exports._resetStoreIdWarnings = _resetStoreIdWarnings;
5
5
  exports._resetFilterWarnings = _resetFilterWarnings;
6
- const VERSION = "2.2.37";
6
+ const VERSION = "2.2.39";
7
7
  const RUNTIME = (() => {
8
8
  const p = globalThis?.process;
9
9
  return p?.version ? ` (node/${p.version}; ${p.platform}-${p.arch})` : "";
@@ -18,7 +18,7 @@ function logDebug(msg) {
18
18
  const DEFAULT_BASE_URL = "https://api.wontopos.com";
19
19
  const DEFAULT_MODEL = "tablet-2";
20
20
  const RETRY_ALWAYS = new Set([429]);
21
- const RETRY_IF_IDEMPOTENT = new Set([502, 503]);
21
+ const RETRY_IF_IDEMPOTENT = new Set([408, 502, 503, 504]);
22
22
  const IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
23
23
  const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "0.0.0.0"]);
24
24
  const MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
@@ -29,6 +29,12 @@ function normalizeStoreId(id) {
29
29
  return id.toLowerCase().replace(/[^a-z0-9_]/g, "_");
30
30
  }
31
31
  const WARNED_STORE_IDS_MAX = 1024;
32
+ function assertUsableStoreId(userId) {
33
+ if (userId !== undefined && (typeof userId !== "string" || !userId.trim())) {
34
+ throw new Error(`userId must be a non-blank string; got ${JSON.stringify(userId)}. Omit it to use the client's ` +
35
+ "default store, or pass a real store id — anything else would silently write into the default store.");
36
+ }
37
+ }
32
38
  const WARNED_FILTER_KEYS_MAX = 1024;
33
39
  const warnedStoreIds = new Set();
34
40
  function warnIfStoreIdCollapses(id) {
@@ -212,6 +218,47 @@ const KNOWN_FILTER_KEYS = new Set([
212
218
  "min_importance",
213
219
  ]);
214
220
  const warnedFilterKeys = new Set();
221
+ const KNOWN_SEARCH_KEYS = new Set([
222
+ "cache_control", "speaker", "filters", "verify", "max_images", "extra",
223
+ ]);
224
+ const RESERVED_SEARCH_KEYS = new Set(["user_id", "query", "max_results"]);
225
+ const KNOWN_RECALL_KEYS = new Set(["form", "tz", "limit", "context_limit"]);
226
+ const KNOWN_ENGRAM_KEYS = new Set(["form", "tz"]);
227
+ function checkSearchOpts(opts) {
228
+ checkOpts(opts, KNOWN_SEARCH_KEYS, "search");
229
+ }
230
+ function checkOpts(opts, known, what) {
231
+ for (const k of Object.keys(opts)) {
232
+ if (RESERVED_SEARCH_KEYS.has(k))
233
+ throw new Error(`${JSON.stringify(k)} is set by the call, not by options — pass it as an argument. ` +
234
+ `An app forwarding untrusted input as options cannot steer the store, the query ` +
235
+ `or the count, and this says so rather than dropping it silently.`);
236
+ if (known.has(k))
237
+ continue;
238
+ const near = [...known].find((n) => n !== "extra" && editWithin(k, n, 2));
239
+ throw new Error(`unknown ${what} option ${JSON.stringify(k)}` +
240
+ (near ? ` — did you mean ${JSON.stringify(near)}?` : "") +
241
+ `. It would have had no effect, and the call would have looked like it worked.` +
242
+ (known.has("extra")
243
+ ? ` Pass it under \`extra\` if the service accepts it and this client does not know it yet.`
244
+ : ""));
245
+ }
246
+ }
247
+ function editWithin(a, b, max) {
248
+ if (Math.abs(a.length - b.length) > max)
249
+ return false;
250
+ const prev = Array.from({ length: b.length + 1 }, (_, j) => j);
251
+ for (let i = 1; i <= a.length; i++) {
252
+ let diag = prev[0];
253
+ prev[0] = i;
254
+ for (let j = 1; j <= b.length; j++) {
255
+ const tmp = prev[j];
256
+ prev[j] = Math.min(prev[j] + 1, prev[j - 1] + 1, diag + (a[i - 1] === b[j - 1] ? 0 : 1));
257
+ diag = tmp;
258
+ }
259
+ }
260
+ return prev[b.length] <= max;
261
+ }
215
262
  function warnOnUnknownFilters(filters) {
216
263
  if (!filters || typeof filters !== "object" || Array.isArray(filters))
217
264
  return;
@@ -257,6 +304,11 @@ class Client {
257
304
  throw new Error("apiKey contains whitespace - check for a stray newline or paste error");
258
305
  if (/[\x00-\x1f\x7f]/.test(key))
259
306
  throw new Error("apiKey contains a control character - check for a stray byte or paste error");
307
+ if (/[^\x00-\x7f]/.test(key)) {
308
+ const bad = key.match(/[^\x00-\x7f]/)?.[0];
309
+ throw new Error(`apiKey contains a non-ASCII character (${JSON.stringify(bad)}) - rich text turns '-' into an ` +
310
+ "en dash; copy the key from a plain-text field");
311
+ }
260
312
  this.apiKey = key;
261
313
  this.base = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
262
314
  const tm = opts.timeoutMs;
@@ -265,7 +317,8 @@ class Client {
265
317
  if (this.model && !MODEL_RE.test(this.model)) {
266
318
  throw new Error(`invalid model name: ${JSON.stringify(this.model)} (letters, digits, '.', '_', '-' only)`);
267
319
  }
268
- this.defaultUser = opts.userId || DEFAULT_USER;
320
+ assertUsableStoreId(opts.userId);
321
+ this.defaultUser = opts.userId ?? DEFAULT_USER;
269
322
  const mr = opts.maxRetries ?? opts.retries;
270
323
  this.maxRetries = typeof mr === "number" && Number.isFinite(mr) ? Math.max(0, Math.floor(mr)) : 2;
271
324
  const dl = opts.deadlineMs;
@@ -313,11 +366,8 @@ class Client {
313
366
  return `Client(${this.base}, model=${this.model}, userId=${this.defaultUser}, apiKey=${maskKey(this.apiKey)})`;
314
367
  }
315
368
  uid(userId) {
316
- if (userId !== undefined && (typeof userId !== "string" || !userId.trim())) {
317
- throw new Error(`userId must be a non-blank string; got ${JSON.stringify(userId)}. Omit it to use the client's ` +
318
- "default store, or pass a real store id — anything else would silently write into the default store.");
319
- }
320
- const id = userId || this.defaultUser;
369
+ assertUsableStoreId(userId);
370
+ const id = userId ?? this.defaultUser;
321
371
  warnIfStoreIdCollapses(id);
322
372
  return id;
323
373
  }
@@ -339,6 +389,10 @@ class Client {
339
389
  return this.clone({ model });
340
390
  }
341
391
  withUser(userId) {
392
+ if (userId === undefined) {
393
+ throw new Error("withUser() needs a store id. It was called with undefined — usually a lookup that " +
394
+ "found nothing. Use the client's own default instead of calling withUser at all.");
395
+ }
342
396
  return this.clone({ userId });
343
397
  }
344
398
  withSignal(signal) {
@@ -353,43 +407,49 @@ class Client {
353
407
  withRetries(maxRetries) {
354
408
  return this.clone({ maxRetries });
355
409
  }
356
- add(content, userId, metadata = {}, opts = {}) {
410
+ async add(content, userId, metadata = {}, opts = {}) {
357
411
  const body = { user_id: this.uid(userId), content, metadata };
358
412
  if (opts.image !== undefined)
359
413
  body.image = normalizeImage(opts.image);
360
414
  return this.post("/api/v1/memory/store", body, opts.idempotencyKey);
361
415
  }
362
- store(content, userId, metadata = {}, opts = {}) {
416
+ async store(content, userId, metadata = {}, opts = {}) {
363
417
  return this.add(content, userId, metadata, opts);
364
418
  }
365
- addTurn(userMsg, assistantMsg, userId, opts = {}) {
419
+ async addTurn(userMsg, assistantMsg, userId, opts = {}) {
366
420
  return this.post("/api/v1/memory/store-turn", { user_id: this.uid(userId), user_msg: userMsg, assistant_msg: assistantMsg }, opts.idempotencyKey);
367
421
  }
368
- addBulk(content, userId, category = "general", timestamp, opts = {}) {
422
+ async addBulk(content, userId, category = "general", timestamp, opts = {}) {
369
423
  const body = { user_id: this.uid(userId), content, category };
370
424
  if (timestamp)
371
425
  body.timestamp = timestamp;
372
426
  return this.post("/api/v1/memory/bulk-store", body, opts.idempotencyKey);
373
427
  }
374
- update(oldMemoryId, newContent, userId, opts = {}) {
428
+ async update(oldMemoryId, newContent, userId, opts = {}) {
375
429
  return this.post("/api/v1/memory/supersede", { user_id: this.uid(userId), old_memory_id: oldMemoryId, new_content: newContent }, opts.idempotencyKey);
376
430
  }
377
431
  async search(query, userId, limit = 10, opts = {}) {
432
+ checkSearchOpts(opts);
378
433
  warnOnUnknownFilters(opts.filters);
379
434
  checkCount(limit, "limit");
380
- const r = await this.post("/api/v1/memory/search", { ...opts, user_id: this.uid(userId), query, max_results: limit });
435
+ const { extra, ...known } = opts;
436
+ const r = await this.post("/api/v1/memory/search", { ...extra, ...known, user_id: this.uid(userId), query, max_results: limit });
381
437
  return mergeResults(r);
382
438
  }
383
439
  async searchSelf(query, userId, limit = 10, opts = {}) {
440
+ checkSearchOpts(opts);
384
441
  warnOnUnknownFilters(opts.filters);
385
442
  checkCount(limit, "limit");
386
- const r = await this.post("/api/v1/memory/search", { ...opts, user_id: this.uid(userId), query, max_results: limit });
443
+ const { extra, ...known } = opts;
444
+ const r = await this.post("/api/v1/memory/search", { ...extra, ...known, user_id: this.uid(userId), query, max_results: limit });
387
445
  return { memories: asRecords(r.memories), self_memories: asRecords(r.self_memories) };
388
446
  }
389
447
  async searchFull(query, userId, limit = 10, opts = {}) {
448
+ checkSearchOpts(opts);
390
449
  warnOnUnknownFilters(opts.filters);
391
450
  checkCount(limit, "limit");
392
- const r = await this.post("/api/v1/memory/search", { ...opts, user_id: this.uid(userId), query, max_results: limit });
451
+ const { extra, ...known } = opts;
452
+ const r = await this.post("/api/v1/memory/search", { ...extra, ...known, user_id: this.uid(userId), query, max_results: limit });
393
453
  return {
394
454
  ...r,
395
455
  memories: asRecords(r.memories),
@@ -398,6 +458,7 @@ class Client {
398
458
  };
399
459
  }
400
460
  async recall(query, userId, opts = {}) {
461
+ checkOpts(opts, KNOWN_RECALL_KEYS, "recall");
401
462
  const body = { user_id: this.uid(userId), query };
402
463
  if (opts.limit !== undefined) {
403
464
  checkCount(opts.limit, "limit");
@@ -409,14 +470,15 @@ class Client {
409
470
  }
410
471
  return this.post("/api/v1/memory/recall", withForm(body, opts.form, opts.tz));
411
472
  }
412
- engram(name, query, userId, opts = {}) {
473
+ async engram(name, query, userId, opts = {}) {
474
+ checkOpts(opts, KNOWN_ENGRAM_KEYS, "engram");
413
475
  return this.post("/api/v1/engram/run", withForm({ name, user_id: this.uid(userId), query }, opts.form, opts.tz));
414
476
  }
415
477
  async history(userId) {
416
478
  const r = await this.post("/api/v1/memory/history", { user_id: this.uid(userId) });
417
479
  return asRecords(r.turns);
418
480
  }
419
- stats(userId) {
481
+ async stats(userId) {
420
482
  return this.post("/api/v1/memory/stats", { user_id: this.uid(userId) });
421
483
  }
422
484
  async usage(days = 7) {
@@ -425,13 +487,13 @@ class Client {
425
487
  }
426
488
  return this.request("GET", `/api/v1/won/usage?days=${days}`);
427
489
  }
428
- get(userId, memoryId) {
429
- if (!memoryId || typeof memoryId !== "string") {
490
+ async get(userId, memoryId) {
491
+ if (typeof memoryId !== "string" || !memoryId.trim()) {
430
492
  throw new Error("memoryId is required — the id that add/store or listMemories returned.");
431
493
  }
432
- return this.post("/api/v1/memory/get", { user_id: this.uid(userId), memory_id: memoryId }).then((r) => asObj(r.memory));
494
+ return this.post("/api/v1/memory/get", { user_id: this.uid(userId), memory_id: memoryId.trim() }).then((r) => asObj(r.memory));
433
495
  }
434
- listMemories(userId, opts = {}) {
496
+ async listMemories(userId, opts = {}) {
435
497
  const body = { user_id: this.uid(userId), limit: opts.limit ?? 100 };
436
498
  if (opts.cursor)
437
499
  body.cursor = opts.cursor;
@@ -459,22 +521,28 @@ class Client {
459
521
  out.push(m);
460
522
  return out;
461
523
  }
462
- getImage(userId, memoryId) {
463
- if (!memoryId || typeof memoryId !== "string") {
524
+ async exportImages(userId, opts = {}) {
525
+ const out = [];
526
+ for await (const m of this.iterImages(userId, opts))
527
+ out.push(m);
528
+ return out;
529
+ }
530
+ async getImage(userId, memoryId) {
531
+ if (typeof memoryId !== "string" || !memoryId.trim()) {
464
532
  throw new Error("memoryId is required — the id that add/store or listImages returned.");
465
533
  }
466
- return this.requestBytes("/api/v1/memory/image", { user_id: this.uid(userId), memory_id: memoryId });
534
+ return this.requestBytes("/api/v1/memory/image", { user_id: this.uid(userId), memory_id: memoryId.trim() });
467
535
  }
468
- forgetImage(userId, memoryId, opts = {}) {
469
- if (!memoryId || typeof memoryId !== "string") {
536
+ async forgetImage(userId, memoryId, opts = {}) {
537
+ if (typeof memoryId !== "string" || !memoryId.trim()) {
470
538
  throw new Error("memoryId is required — the id of the memory whose image you want removed.");
471
539
  }
472
- const body = { user_id: this.uid(userId), memory_id: memoryId };
540
+ const body = { user_id: this.uid(userId), memory_id: memoryId.trim() };
473
541
  if (opts.preview)
474
542
  body.preview = true;
475
543
  return this.request("DELETE", "/api/v1/memory/image", body);
476
544
  }
477
- listImages(userId, opts = {}) {
545
+ async listImages(userId, opts = {}) {
478
546
  const body = { user_id: this.uid(userId) };
479
547
  if (opts.limit !== undefined)
480
548
  body.limit = opts.limit;
@@ -504,7 +572,7 @@ class Client {
504
572
  throw new Error(`stopped after ${MAX_PAGES} pages — the store did not end. This is a truncated ` +
505
573
  `answer, not the whole store.`);
506
574
  }
507
- revisions(userId, opts = {}) {
575
+ async revisions(userId, opts = {}) {
508
576
  const body = {};
509
577
  if (opts.include !== undefined)
510
578
  body.include = opts.include;
@@ -517,13 +585,13 @@ class Client {
517
585
  body.user_id = this.uid(userId);
518
586
  return this.post("/api/v1/won/revisions", body);
519
587
  }
520
- lineage(userId, memoryId) {
521
- if (!memoryId || typeof memoryId !== "string") {
588
+ async lineage(userId, memoryId) {
589
+ if (typeof memoryId !== "string" || !memoryId.trim()) {
522
590
  throw new Error("memoryId is required — the memory whose history you want.");
523
591
  }
524
- return this.post("/api/v1/memory/lineage", { user_id: this.uid(userId), memory_id: memoryId });
592
+ return this.post("/api/v1/memory/lineage", { user_id: this.uid(userId), memory_id: memoryId.trim() });
525
593
  }
526
- bySpeaker(speaker, userId, opts = {}) {
594
+ async bySpeaker(speaker, userId, opts = {}) {
527
595
  if (!speaker || typeof speaker !== "string" || speaker.trim() === "") {
528
596
  throw new Error('speaker is required — "me" for the assistant, or a person\'s name.');
529
597
  }
@@ -547,41 +615,42 @@ class Client {
547
615
  async listEngrams() {
548
616
  const data = await this.request("GET", "/api/v1/engram");
549
617
  return {
618
+ ...data,
550
619
  engrams: asRecords(data.engrams),
551
620
  forms: asRecords(data.forms),
552
621
  note: typeof data.note === "string" ? data.note : undefined,
553
622
  };
554
623
  }
555
- createStore(userId) {
624
+ async createStore(userId) {
556
625
  return this.post("/api/v1/memory/collection", { user_id: this.uid(userId) });
557
626
  }
558
627
  async listStores() {
559
628
  const r = await this.request("GET", "/api/v1/memory/collections");
560
629
  return asRecords(r.collections);
561
630
  }
562
- deleteStore(userId) {
631
+ async deleteStore(userId) {
563
632
  if (typeof userId !== "string" || !userId.trim())
564
633
  throw new Error("userId is required (a non-blank string) — deleteStore never falls back to the default store.");
565
634
  warnIfStoreIdCollapses(userId);
566
635
  return this.request("DELETE", "/api/v1/memory/collection", { user_id: userId });
567
636
  }
568
- addSpeaker(speaker, userId) {
637
+ async addSpeaker(speaker, userId) {
569
638
  return this.post("/api/v1/memory/speakers", { user_id: this.uid(userId), speaker });
570
639
  }
571
640
  async listSpeakers(userId) {
572
641
  const r = await this.request("GET", `/api/v1/memory/speakers?user_id=${encodeURIComponent(this.uid(userId))}`);
573
642
  return { ...r, speakers: asRecords(r?.speakers) };
574
643
  }
575
- removeSpeaker(speaker, userId) {
644
+ async removeSpeaker(speaker, userId) {
576
645
  return this.request("DELETE", "/api/v1/memory/speakers", { user_id: this.uid(userId), speaker });
577
646
  }
578
- delete(userId, memoryId) {
647
+ async delete(userId, memoryId) {
579
648
  if (typeof memoryId !== "string" || !memoryId.trim()) {
580
649
  throw new Error("memoryId is required (non-blank). To delete every memory in a store, call deleteAll(userId) explicitly.");
581
650
  }
582
651
  return this.post("/api/v1/memory/forget", { user_id: this.uid(userId), memory_id: memoryId.trim() });
583
652
  }
584
- deleteAll(userId) {
653
+ async deleteAll(userId) {
585
654
  if (typeof userId !== "string" || !userId.trim())
586
655
  throw new Error("userId is required (a non-blank string) for deleteAll — anything else would wipe the default store.");
587
656
  warnIfStoreIdCollapses(userId);
@@ -716,7 +785,9 @@ class Client {
716
785
  if (e?.name === "AbortError" || att.signal.aborted) {
717
786
  throw new APIConnectionError(0, this.abortMessage(att.why()));
718
787
  }
719
- throw e;
788
+ if (e instanceof WosError)
789
+ throw e;
790
+ throw new APIConnectionError(0, `network error: ${e?.message ?? e}`);
720
791
  }
721
792
  if (buf.byteLength === 0) {
722
793
  throw new WosError(res.status, "empty image body — the service returned no bytes");
@@ -789,6 +860,14 @@ class Client {
789
860
  const attempts = this.maxRetries + 1;
790
861
  const logPath = path.split("?")[0];
791
862
  const deadlineAt = this.deadlineAt();
863
+ let payload;
864
+ try {
865
+ payload = body === undefined ? undefined : JSON.stringify(body);
866
+ }
867
+ catch (e) {
868
+ throw new Error(`request body could not be serialized: ${e instanceof Error ? e.message : String(e)}. ` +
869
+ "Pass plain JSON values — an ORM entity, a BigInt or a circular reference cannot be sent.");
870
+ }
792
871
  for (let attempt = 0; attempt < attempts; attempt++) {
793
872
  const start = Date.now();
794
873
  const att = this.beginAttempt(deadlineAt);
@@ -797,7 +876,7 @@ class Client {
797
876
  res = await this.fetchImpl(`${this.base}${path}`, {
798
877
  method,
799
878
  headers: extraHeaders ? { ...this.headers, ...extraHeaders } : this.headers,
800
- body: body === undefined ? undefined : JSON.stringify(body),
879
+ body: payload,
801
880
  signal: att.signal,
802
881
  redirect: "manual",
803
882
  });
@@ -841,7 +920,14 @@ class Client {
841
920
  }
842
921
  if (e instanceof WosError)
843
922
  throw e;
844
- throw new APIConnectionError(0, `network error: ${e?.message ?? e}`);
923
+ if (!(IDEMPOTENT_METHODS.has(method.toUpperCase()) && attempt + 1 < attempts)) {
924
+ throw new APIConnectionError(0, `network error: ${e?.message ?? e}`);
925
+ }
926
+ att.clear();
927
+ const delay = this.backoffMs(attempt);
928
+ logDebug(`${method} ${logPath} — body dropped mid-stream, retrying in ${delay}ms (attempt ${attempt + 1}/${attempts})`);
929
+ await this.backoffSleep(delay, deadlineAt);
930
+ continue;
845
931
  }
846
932
  finally {
847
933
  att.clear();
@@ -26,8 +26,8 @@
26
26
  * and searching call no LLM.
27
27
  *
28
28
  * Reliability: every call retries transient failures with exponential backoff +
29
- * jitter, honoring `Retry-After` — 429 always; 502/503 and connection errors only
30
- * when a retry can never double-process a write (idempotent calls, or a failure
29
+ * jitter, honoring `Retry-After` — 429 always; 408/502/503/504 and connection errors
30
+ * only when a retry can never double-process a write (idempotent calls, or a failure
31
31
  * at connect time). Tune with `maxRetries` (0 disables) and `timeoutMs`, or per
32
32
  * call site via the `withTimeout()` / `withRetries()` clones.
33
33
  *
@@ -86,8 +86,8 @@ export declare class RateLimitError extends WosError {
86
86
  /**
87
87
  * 5xx — the service failed.
88
88
  *
89
- * `502` / `503` are transient: the client already retries them for calls where a
90
- * retry cannot double-process a write, and retrying yourself is reasonable.
89
+ * `502` / `503` / `504` are transient: the client already retries them for calls
90
+ * where a retry cannot double-process a write, and retrying yourself is reasonable.
91
91
  *
92
92
  * `501` is NOT transient. It means the engine behind the model you selected does
93
93
  * not implement that endpoint at all (the error carries `model` and `endpoint`).
@@ -428,6 +428,9 @@ export interface EngramCatalog {
428
428
  engrams: EngramInfo[];
429
429
  forms: EngramInfo[];
430
430
  note?: string;
431
+ /** Anything else the reply carried. Responses widen; the runtime keeps those fields
432
+ * and this is what lets a typed caller read them. */
433
+ [key: string]: unknown;
431
434
  }
432
435
  /** `createStore` / `deleteStore` → `{ user_id, status }`. */
433
436
  export interface StoreOpResult {
@@ -490,7 +493,10 @@ export interface SearchFilters {
490
493
  }
491
494
  /** Test hook — the warn-once set is process-global by design. */
492
495
  export declare function _resetFilterWarnings(): void;
493
- /** Known `search` options; extra fields pass through to the API untouched. */
496
+ /** Known `search` options. An unknown key is refused before anything is sent — the
497
+ * service drops keys it does not recognise and answers normally, so a misspelling
498
+ * could not be told from an option that worked. Put a genuinely new one under
499
+ * `extra`. */
494
500
  export interface SearchOptions {
495
501
  /** Recall caching. A hit inside the TTL bills at 0.1× — but the FIRST call
496
502
  * writes the cache and bills the query tokens at 2× (`5m`) or 3× (`1h`), so
@@ -519,7 +525,9 @@ export interface SearchOptions {
519
525
  */
520
526
  verify?: number;
521
527
  /**
522
- * How many image memories the answer may carry, 0–5. Default 1; 0 asks for none.
528
+ * How many image memories the answer may carry, 0–5. Omit it and the service uses
529
+ * 1; `0` asks for none. The MCP server defaults its own tool to 0 instead, so most
530
+ * searches through it carry no image rows.
523
531
  * Out of range is refused, not clamped — silently cutting 5 to 1 would leave you
524
532
  * believing you got five.
525
533
  *
@@ -527,7 +535,13 @@ export interface SearchOptions {
527
535
  * than answering with no images.
528
536
  */
529
537
  max_images?: number;
530
- [key: string]: unknown;
538
+ /**
539
+ * Fields the service accepts that this version does not know about, merged into the
540
+ * request as written. Every other key is refused, so a typo cannot reach the wire —
541
+ * `verfy: 3` used to be sent, dropped by the service, and answered normally, leaving
542
+ * a caller billed for one pass believing they had bought three.
543
+ */
544
+ extra?: Record<string, unknown>;
531
545
  }
532
546
  /**
533
547
  * Per-call options for a write.
@@ -537,9 +551,9 @@ export interface SearchOptions {
537
551
  * arrives with a different body. Use it when a retry is your own (a job that died and
538
552
  * was re-run, a queue that redelivers). The SDK retries a write on exactly one
539
553
  * status: 429, which the service answers before it processes anything, so nothing
540
- * was stored. It never retries a write on 502 / 503 or a dropped body, where the
541
- * request may already have been stored and billed — without a key it cannot know
542
- * whether that first attempt landed.
554
+ * was stored. It never retries a write on 408 / 502 / 503 / 504 or a dropped body,
555
+ * where the first attempt may already have landed — without a key the client cannot
556
+ * know whether it did.
543
557
  *
544
558
  * The key must be UNIQUE PER LOGICAL WRITE — derive it from the thing being stored
545
559
  * (`` `import:${row.id}` ``), never a constant, or the second write replays the first
@@ -571,8 +585,8 @@ export interface ClientOptions {
571
585
  * to the account's built-in `default` store. */
572
586
  userId?: string;
573
587
  /** How many times to retry transient failures before throwing — 429 always;
574
- * 502/503 and connection errors only when a retry can never double-process a
575
- * write. 0 disables retries (default 2). */
588
+ * 408/502/503/504 and connection errors only when a retry can never
589
+ * double-process a write. 0 disables retries (default 2). */
576
590
  maxRetries?: number;
577
591
  /** Alias of `maxRetries`. The Python SDK calls this option `retries`, and these
578
592
  * SDKs are published in lockstep as "the same surface" — so code ported between
@@ -600,10 +614,8 @@ export interface ClientOptions {
600
614
  *
601
615
  * This is the seam for anything the runtime cannot express through options.
602
616
  * The one that matters in practice is a corporate egress proxy: Node's global
603
- * fetch (undici) IGNORES `HTTP_PROXY` / `HTTPS_PROXY`, so behind such a proxy
604
- * this SDK could not connect at all — while the Python and Rust SDKs went
605
- * through, because `requests` and `reqwest` both read those variables. Pass a
606
- * proxy-aware fetch and the three behave the same again:
617
+ * fetch (undici) IGNORES `HTTP_PROXY` / `HTTPS_PROXY`, so behind such a proxy no
618
+ * request leaves at all. Pass a proxy-aware fetch and they go through:
607
619
  *
608
620
  * import { ProxyAgent } from "undici";
609
621
  * const agent = new ProxyAgent(process.env.HTTPS_PROXY!);
@@ -685,11 +697,16 @@ export declare class Client {
685
697
  addTurn(userMsg: string, assistantMsg: string, userId?: string, opts?: WriteOptions): Promise<StatusResult>;
686
698
  /** Bulk-ingest a large blob of text in one call. For backfilling.
687
699
  * The call most worth an `opts.idempotencyKey`: a backfill that dies halfway and is
688
- * re-run would otherwise re-ingest and re-bill the whole blob. */
700
+ * re-run would otherwise ingest the whole blob a second time. */
689
701
  addBulk(content: string, userId?: string, category?: string, timestamp?: string, opts?: WriteOptions): Promise<StatusResult>;
690
702
  /** Supersede an old memory with new content. Payload first, userId last — same shape as add/search. */
691
703
  update(oldMemoryId: string, newContent: string, userId?: string, opts?: WriteOptions): Promise<UpdateResult>;
692
704
  /** Search a store's memories. Returns them most relevant first.
705
+ *
706
+ * `limit` is 5-20, and out of range is refused rather than clamped: asking for 50
707
+ * and silently receiving 20 reads as "that is all there is". The default is 10, so
708
+ * a call that passes no count is unaffected. Before 2.2.35 the count was sent on
709
+ * unchecked.
693
710
  *
694
711
  * `limit` bounds `memories`, not the returned array. On a model that keeps the
695
712
  * assistant's own words separate (Scroll 1.2+) those come back as well, so the
@@ -796,6 +813,11 @@ export declare class Client {
796
813
  }): AsyncGenerator<Memory>;
797
814
  /** Collect ALL of a store's memories into an array (the text you stored, and its metadata). */
798
815
  exportMemories(userId?: string): Promise<Memory[]>;
816
+ /** Collect ALL of a store's image memories into an array — the image-side pair of
817
+ * `exportMemories`. */
818
+ exportImages(userId?: string, opts?: {
819
+ pageSize?: number;
820
+ }): Promise<Memory[]>;
799
821
  /**
800
822
  * Fetch the bytes of an image memory — the picture the SERVICE holds.
801
823
  *