wontopos 2.2.40 → 2.2.41

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.
@@ -15,10 +15,11 @@
15
15
  * so with no `userId` anywhere the zero-setup path just works.
16
16
  *
17
17
  * The API key picks *which memory* (your account); `model` picks *which engine*
18
- * reads it. Models on the shared pool read the same memory, so you can store with
19
- * one and recall with another; `listModels()` reports each model's `memory` as
20
- * `"shared"` or `"isolated"`, and an isolated one starts empty. Set a default in
21
- * the constructor, or override a single call with
18
+ * reads it. `listModels()` reports each model's `memory` as `"shared"` or
19
+ * `"isolated"`, and an isolated one starts empty. Every model on the shared pool
20
+ * lists, fetches and deletes the same memories, but a search may not find memories
21
+ * stored through a different model; store and search with the same one. Set a
22
+ * default in the constructor, or override a single call with
22
23
  * `mem.withModel("tablet-1").recall(...)`.
23
24
  *
24
25
  * Recall quality does not depend on which language a memory was written in: a
@@ -26,10 +27,11 @@
26
27
  * and searching call no LLM.
27
28
  *
28
29
  * Reliability: every call retries transient failures with exponential backoff +
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
- * at connect time). Tune with `maxRetries` (0 disables) and `timeoutMs`, or per
32
- * call site via the `withTimeout()` / `withRetries()` clones.
30
+ * jitter, honoring `Retry-After` up to 30s — 429 and a 409 that says another write
31
+ * to the store was in flight, always; 408/502/503/504 and connection errors only when
32
+ * a retry can never double-process a write (idempotent calls, or a failure at connect
33
+ * time). Tune with `maxRetries` (0 disables) and `timeoutMs`, or per call site via
34
+ * the `withTimeout()` / `withRetries()` clones.
33
35
  *
34
36
  * Debugging: set `WONTOPOS_LOG=debug` to log method/path/status/timing/retries
35
37
  * to stderr — never memory content, request bodies, or the API key.
@@ -40,7 +42,7 @@
40
42
  * runtime's (never disabled by this SDK; Node 18+ floors TLS at 1.2). Prefer
41
43
  * `Client.fromEnv()` over keys in source code.
42
44
  */
43
- /** Test hook — the warn-once set is process-global by design. */
45
+ /** @deprecated Test hook. The warn-once set is process-global by design. */
44
46
  export declare function _resetStoreIdWarnings(): void;
45
47
  export declare const SEARCH_LIMIT_MIN = 5;
46
48
  export declare const SEARCH_LIMIT_MAX = 20;
@@ -53,16 +55,36 @@ export interface RateLimit {
53
55
  remaining: number | null;
54
56
  reset: number | null;
55
57
  }
58
+ /** What a `WosError` carries beyond its status, message and request id. */
59
+ export interface WosErrorInit {
60
+ /** `error.type` from the service's answer. */
61
+ type?: string;
62
+ /** The rest of the service's error object. */
63
+ details?: Record<string, unknown>;
64
+ /** `Retry-After`, in seconds (read by `RateLimitError`). */
65
+ retryAfter?: number;
66
+ /** The underlying error, kept as the standard `cause`. */
67
+ cause?: unknown;
68
+ }
56
69
  export declare class WosError extends Error {
57
70
  readonly status: number;
58
71
  /** The server's id for the request when it sent one — include it when contacting support. */
59
72
  readonly requestId?: string;
60
- constructor(status: number, message: string, requestId?: string);
73
+ /** `error.type` from the service's answer (e.g. `"conflict_error"`), when it sent one.
74
+ * Control characters are removed and it is capped, like the message. */
75
+ readonly type?: string;
76
+ /** Every other field of the service's error object — all but `message`, `type` and
77
+ * `request_id` — when there is any. Control characters are removed from its keys
78
+ * and strings, each string is capped, and when its JSON is over 8192 characters the
79
+ * biggest fields are left out. A 501 carries `model` and `endpoint` here. */
80
+ readonly details?: Record<string, unknown>;
81
+ constructor(status: number, message: string, requestId?: string, init?: WosErrorInit);
61
82
  }
62
83
  /** The request never got a response (DNS/TLS/timeout/connection). `status` is 0. */
63
84
  export declare class APIConnectionError extends WosError {
64
85
  }
65
- /** 400 — the request was malformed (bad arguments). */
86
+ /** 400 — the request was malformed (bad arguments). 413 (body too large) and 422
87
+ * (an idempotency key reused with a different body) arrive as this class too. */
66
88
  export declare class BadRequestError extends WosError {
67
89
  }
68
90
  /** 401 — the API key is missing, wrong, or revoked. */
@@ -77,12 +99,20 @@ export declare class PermissionDeniedError extends WosError {
77
99
  /** 404 — the store or resource doesn't exist. */
78
100
  export declare class NotFoundError extends WosError {
79
101
  }
80
- /** 409 — another write to this store was in flight (nothing was stored; retry), or the
81
- * store id collides with an existing store's (permanent). */
102
+ /** 409: another write to this store was in flight (retried automatically; nothing was
103
+ * stored), or the store id collides with an existing one (not retried). */
82
104
  export declare class ConflictError extends WosError {
105
+ /** The existing store id this one collides with. Set only on a collision, which
106
+ * retrying cannot fix. */
107
+ readonly conflictsWith?: string;
108
+ constructor(status: number, message: string, requestId?: string, init?: WosErrorInit);
83
109
  }
84
- /** 429 — too many requests. Back off and retry (the client already retries these). */
110
+ /** 429 — too many requests. Back off and retry (the client already retries these,
111
+ * unless `Retry-After` asks for more than 30 seconds). */
85
112
  export declare class RateLimitError extends WosError {
113
+ /** Seconds the service asked to wait (`Retry-After`), when it said. */
114
+ readonly retryAfter?: number;
115
+ constructor(status: number, message: string, requestId?: string, init?: WosErrorInit);
86
116
  }
87
117
  /**
88
118
  * 5xx — the service failed.
@@ -90,11 +120,9 @@ export declare class RateLimitError extends WosError {
90
120
  * `502` / `503` / `504` are transient: the client already retries them for calls
91
121
  * where a retry cannot double-process a write, and retrying yourself is reasonable.
92
122
  *
93
- * `501` is NOT transient. It means the engine behind the model you selected does
94
- * not implement that endpoint at all (the error carries `model` and `endpoint`).
95
- * Retrying can never succeed — pick a model that supports it (`listModels`) or
96
- * drop the call. Treating the whole 5xx range as retryable sends a caller into a
97
- * loop that cannot end.
123
+ * `501` is NOT transient. It means the model you selected does not implement that
124
+ * endpoint at all (`details` carries `model` and `endpoint`). Retrying can never
125
+ * succeed — pick a model that supports it (`listModels`) or drop the call.
98
126
  */
99
127
  export declare class ServerError extends WosError {
100
128
  }
@@ -140,8 +168,7 @@ export interface StoreResult {
140
168
  * A genuinely new fact that only varies a detail of one already stored ("no meetings
141
169
  * before 10am" next to "no meetings on Fridays") can land here too, so a duplicate is
142
170
  * not always a harmless no-op: read this id, and either store one sentence that states
143
- * both or `update()` the existing memory with the combined statement. Without it a
144
- * dropped write gave the caller nothing to act on.
171
+ * both or `update()` the existing memory with the combined statement.
145
172
  */
146
173
  duplicate_of?: string;
147
174
  /** Present when the store was a duplicate and something (e.g. a speaker tag) was dropped. */
@@ -160,7 +187,8 @@ export interface StatusResult {
160
187
  /**
161
188
  * An image attached to a memory, passed to `add`/`store` via `opts.image`.
162
189
  *
163
- * Both edges must be 700px or more; a smaller image is refused (400).
190
+ * Both edges must be 700px or more; a smaller image is refused (400). A request
191
+ * body is capped at 10MB.
164
192
  *
165
193
  * What the service keeps is NOT your original. An image whose long edge is over
166
194
  * 1568px is downscaled to 1568 before anything else happens, and that smaller
@@ -176,18 +204,23 @@ export interface StatusResult {
176
204
  * already fits within 1568px, and a re-encode that would make the file BIGGER is
177
205
  * thrown away and your bytes kept as they were.
178
206
  *
179
- * Keep your own copy if you need the full-resolution file. Either archive it
180
- * yourself, or put its URL in `reference` — we store that string and never open it.
207
+ * Keep your own copy if you need the full-resolution file.
181
208
  *
182
209
  * You are billed for the picture we keep, so downscaling never costs you more.
183
210
  */
184
211
  export interface ImageInput {
185
212
  /** base64 of the image. A `data:image/...;base64,` prefix is accepted and stripped. */
186
213
  data: string;
187
- /** Where YOUR copy of the original lives. Stored as-is; the service never fetches it. */
214
+ /**
215
+ * Where YOUR copy of the image lives. Stored as-is; the service never fetches it.
216
+ *
217
+ * When you send one, the service keeps no image bytes: `getImage` answers 404 for
218
+ * this memory, and you fetch the picture from your own reference.
219
+ */
188
220
  reference?: string;
189
221
  /**
190
- * When the image was TAKEN (RFC3339), if you know it — usually from EXIF.
222
+ * When the image was TAKEN, if you know it — usually from EXIF. RFC3339 or a plain
223
+ * date (`YYYY-MM-DD`); anything else is refused (400) naming the field.
191
224
  *
192
225
  * Worth passing: an image knows a moment that its caption does not. This fills
193
226
  * `metadata.event_date` when that is empty, so "the day we moved" sorts by when it
@@ -214,12 +247,8 @@ export interface ImageDeleteResult {
214
247
  status?: string;
215
248
  memory_id?: string;
216
249
  /**
217
- * Whether the MEMORY survives losing its image.
218
- *
219
- * A captioned image keeps its text and only loses the image. An image stored with no
220
- * caption IS the memory, so deleting the image deletes the memory — and the service
221
- * says so here rather than silently taking more than you asked for. Call with
222
- * `preview: true` first if that distinction matters to you.
250
+ * Whether the MEMORY survives losing its image. `false` means the delete removed
251
+ * the whole memory; call with `preview: true` first to find out before it happens.
223
252
  */
224
253
  memory_kept?: boolean;
225
254
  /** Plain-language note about the above, when there is something to say. */
@@ -245,7 +274,7 @@ export interface RevisionsResult {
245
274
  revised?: number;
246
275
  /** Memories nothing has touched since they were written. Always `total - revised`. */
247
276
  unrevised?: number;
248
- /** Every memory in the store. Internal records nobody stored directly are not counted. */
277
+ /** The memories you stored. */
249
278
  total?: number;
250
279
  /** What `revised` counts, spelled out by the service. */
251
280
  counts?: string;
@@ -398,7 +427,9 @@ export interface StatsResult {
398
427
  export interface MemoryPage {
399
428
  memories: Memory[];
400
429
  count: number;
401
- /** Pass back as `cursor` for the next page; `null` on the last page. */
430
+ /** Pass back as `cursor` for the next page; `null` means there is none. It can be
431
+ * non-null on the last page, and the next call then returns an empty page. Pass
432
+ * back only a cursor the service returned. */
402
433
  next_cursor: string | null;
403
434
  [key: string]: unknown;
404
435
  }
@@ -409,12 +440,33 @@ export interface SelfSearchResult {
409
440
  /** The assistant's own words (stored with speaker "me"); `[]` on non-self models. */
410
441
  self_memories: Memory[];
411
442
  }
443
+ /** What a model can do, as `listModels()` reports it. A model without a feature
444
+ * refuses the call that needs it (usually 403) or answers without that part. */
445
+ export interface ModelCapabilities {
446
+ /** Image memories: `opts.image` on `add`, `max_images`, `getImage`, `listImages`. */
447
+ images?: boolean;
448
+ /** `engram()`; `listEngrams()` names the ones this model runs. */
449
+ engrams?: boolean;
450
+ /** Delivery forms (`form` / `tz`) on search, recall and engram. */
451
+ forms?: boolean;
452
+ /** Re-ask passes (`verify`) on search. */
453
+ re_ask?: boolean;
454
+ /** The assistant's own words apart, in `self_memories`. */
455
+ self_memories?: boolean;
456
+ /** Speaker names, as the service reports them. */
457
+ speaker_names?: boolean;
458
+ [key: string]: boolean | undefined;
459
+ }
412
460
  export interface ModelInfo {
413
461
  id: string;
414
462
  name: string;
415
463
  available: boolean;
416
- /** "shared" — reads the common memory pool; "isolated" — its own dedicated store. */
464
+ /** "shared" — lists, fetches and deletes the common memory pool, though a search
465
+ * may not find memories stored through a different model; "isolated" — its own
466
+ * dedicated store. */
417
467
  memory: "shared" | "isolated";
468
+ /** What this model can do. Check it before relying on a feature. */
469
+ capabilities?: ModelCapabilities;
418
470
  }
419
471
  /** One entry of the engram / delivery-form catalogue (`listEngrams`). */
420
472
  export interface EngramInfo {
@@ -480,21 +532,29 @@ export interface SpeakersList {
480
532
  * Retrieval behaves the same in every language, so these behave
481
533
  * identically in every language. Unlisted keys are dropped by the API rather than
482
534
  * rejected, so a typo silently widens the search — spell them exactly.
535
+ *
536
+ * Filters apply to `memories`. The assistant's own words (`self_memories`) are not
537
+ * filtered, and `search` merges them into its answer, so read `searchFull` when a
538
+ * filtered answer must hold only what matched.
539
+ *
540
+ * Every date takes RFC3339 or a plain date (`YYYY-MM-DD`). A plain end date
541
+ * (`time_to`, `event_to`) covers that whole day in UTC. A value that is not a date
542
+ * is refused (400) naming the field.
483
543
  */
484
544
  export interface SearchFilters {
485
545
  /** Only these categories (the `category` you see on `listMemories` results). */
486
546
  categories?: string[];
487
- /** Ingestion-time window, as the API stores it. Plain strings, matched as given. */
547
+ /** When the memory was stored. */
488
548
  time_from?: string;
489
549
  time_to?: string;
490
550
  /** WHEN THE CONTENT HAPPENED (`metadata.event_date`), not when it was written —
491
- * this is the one you usually want. RFC3339, or a plain `YYYY-MM-DD`. */
551
+ * this is the one you usually want. */
492
552
  event_from?: string;
493
553
  event_to?: string;
494
554
  /** Drop matches the engine scored below this importance (0–1). */
495
555
  min_importance?: number;
496
556
  }
497
- /** Test hook — the warn-once set is process-global by design. */
557
+ /** @deprecated Test hook. The warn-once set is process-global by design. */
498
558
  export declare function _resetFilterWarnings(): void;
499
559
  /** Known `search` options. An unknown key is refused before anything is sent — the
500
560
  * service drops keys it does not recognise and answers normally, so a misspelling
@@ -523,23 +583,22 @@ export interface SearchOptions {
523
583
  * that need several distinct memories from far apart in the history; it does little
524
584
  * on a single-fact lookup.
525
585
  *
526
- * Requires a re-ask-capable model. Older ones REFUSE the call (403) rather than
527
- * quietly charging you for passes that never happened.
586
+ * Requires a model that lists `re_ask` in its `listModels()` capabilities; any
587
+ * other refuses the call (403) rather than charging for passes that never happened.
528
588
  */
529
589
  verify?: number;
530
590
  /**
531
591
  * How many image memories the answer may carry, 0–5. Omit it and the service uses
532
- * 1; `0` asks for none. The MCP server defaults its own tool to 0 instead, so most
533
- * searches through it carry no image rows.
534
- * Out of range is refused, not clamped — silently cutting 5 to 1 would leave you
535
- * believing you got five.
592
+ * 1; `0` asks for none. Out of range is refused before the request, not clamped —
593
+ * silently cutting 6 to 5 would leave you believing you got six.
536
594
  *
537
- * Requires an image-capable model, and is refused (403) on one without it rather
538
- * than answering with no images.
595
+ * Requires a model that lists `images` in its capabilities, and is refused (403)
596
+ * on one without it rather than answering with no images.
539
597
  */
540
598
  max_images?: number;
541
- /** Delivery form, "memoir" or "archive" (Scroll 1.2+): how each memory's time is
542
- * rendered. The service refuses an unknown one (400). */
599
+ /** Delivery form, "memoir" or "archive": how each memory's time is rendered. Needs a
600
+ * model that lists `forms` in its capabilities. The service refuses an unknown
601
+ * form (400). */
543
602
  form?: string;
544
603
  /** Your UTC offset in hours, for rendering times in `form`. */
545
604
  tz?: number;
@@ -554,69 +613,128 @@ export interface SearchOptions {
554
613
  * Per-call options for a write.
555
614
  *
556
615
  * `idempotencyKey` makes repeating THIS EXACT write safe: the API replays the first
557
- * response instead of storing again, for 10 minutes, and answers 422 if the same key
558
- * arrives with a different body. Use it when a retry is your own (a job that died and
559
- * was re-run, a queue that redelivers). The SDK retries a write on exactly one
560
- * status: 429, which the service answers before it processes anything, so nothing
561
- * was stored. It never retries a write on 408 / 502 / 503 / 504 or a dropped body,
562
- * where the first attempt may already have landed — without a key the client cannot
563
- * know whether it did.
616
+ * response instead of storing again, for up to 10 minutes, and answers 422 if the
617
+ * same key arrives with a different body. Use it when a retry is your own (a job that
618
+ * died and was re-run, a queue that redelivers). The SDK retries a write on 429, and
619
+ * on a 409 that says another write to the store was in flight: the service answers
620
+ * both before it processes anything, so nothing was stored. It never retries a write
621
+ * on 408 / 502 / 503 / 504 or a dropped body, where the write may already have been
622
+ * applied — without a key the client cannot know whether it was.
623
+ *
624
+ * The key covers a retry sent after the first attempt finished. A retry that overlaps
625
+ * a first attempt still running can run twice, so wait for the first to fail before
626
+ * re-sending.
564
627
  *
565
628
  * The key must be UNIQUE PER LOGICAL WRITE — derive it from the thing being stored
566
629
  * (`` `import:${row.id}` ``), never a constant, or the second write replays the first
567
630
  * and is silently lost. Format: 1-128 chars of `[A-Za-z0-9._:-]`, checked locally.
568
631
  *
569
- * The window is in-memory on the API, so a deploy or restart clears it early. It is a
570
- * guard against a retry storm, not a durable ledger.
632
+ * The window is best-effort and can be shorter than 10 minutes; it is not a durable
633
+ * de-duplication record.
571
634
  */
572
635
  export interface WriteOptions {
573
636
  idempotencyKey?: string;
574
637
  /**
575
- * An image to store alongside the text (Tablet 2 and newer).
638
+ * An image to store alongside the text. Needs a model that lists `images` in its
639
+ * `listModels()` capabilities; any other refuses the write rather than store the
640
+ * caption and quietly drop the image.
576
641
  *
577
- * `content` may be empty when you pass one — then the image IS the memory and is
578
- * searchable on its own. Older models have no image channel and will reject the
579
- * write rather than store the caption and quietly drop the image.
642
+ * A caption is required: the service refuses empty `content` (400) even with an
643
+ * image attached.
580
644
  */
581
645
  image?: ImageInput;
582
646
  }
647
+ /** The part of an `AbortSignal` the client uses, for a runtime that declares none. */
648
+ export interface AbortSignalShape {
649
+ readonly aborted: boolean;
650
+ addEventListener(type: "abort", listener: () => void): void;
651
+ removeEventListener(type: "abort", listener: () => void): void;
652
+ }
653
+ /** The runtime's own `AbortSignal` type when it declares one (the DOM lib,
654
+ * `@types/node`), otherwise {@link AbortSignalShape}. `AbortController#signal` fits. */
655
+ export type AbortSignalLike = typeof globalThis extends {
656
+ AbortSignal: {
657
+ prototype: infer S;
658
+ };
659
+ } ? S : AbortSignalShape;
660
+ /** The `init` the client passes to `fetch`. */
661
+ export interface FetchInitLike {
662
+ method: string;
663
+ headers: Record<string, string>;
664
+ body?: string;
665
+ signal: AbortSignalLike;
666
+ redirect: "manual";
667
+ }
668
+ /** The part of a `fetch` response the client reads. */
669
+ export interface FetchResponseLike {
670
+ readonly status: number;
671
+ readonly ok: boolean;
672
+ readonly type?: string;
673
+ readonly url?: string;
674
+ readonly redirected?: boolean;
675
+ readonly headers: {
676
+ get(name: string): string | null;
677
+ };
678
+ readonly body?: {
679
+ getReader(): {
680
+ read(): Promise<{
681
+ done: boolean;
682
+ value?: Uint8Array;
683
+ }>;
684
+ cancel(reason?: unknown): Promise<void>;
685
+ };
686
+ cancel(reason?: unknown): Promise<void>;
687
+ } | null;
688
+ arrayBuffer(): Promise<ArrayBuffer>;
689
+ }
690
+ /** The runtime's own `fetch` type when it declares one (the DOM lib, `@types/node`),
691
+ * otherwise a structural signature, so these types need neither. */
692
+ export type FetchLike = typeof globalThis extends {
693
+ fetch: infer F;
694
+ } ? F : (url: string, init: FetchInitLike) => Promise<FetchResponseLike>;
583
695
  export interface ClientOptions {
584
696
  apiKey: string;
585
- /** API base URL (defaults to the hosted service). */
697
+ /** API base URL (defaults to the hosted service): an http or https URL or, where
698
+ * the runtime has a page, a URL relative to it. Whitespace at either end is
699
+ * trimmed; whitespace, a control character or a backslash left in it after that is
700
+ * refused.
701
+ * A relative URL where the runtime has no page (server-side rendering) fails the
702
+ * first call that uses it, or goes to `fetch` as given when you pass one. */
586
703
  baseUrl?: string;
587
- /** Per-request timeout in ms, applied to each retry attempt (default 30000). */
704
+ /** Per-request timeout in ms, applied to each retry attempt (default 30000). A
705
+ * value that is not a positive number means the default; values above 2147483647
706
+ * are clamped to it. */
588
707
  timeoutMs?: number;
589
708
  /** Default model for every call (sent as `X-WOS-Model`). See `listModels()`. */
590
709
  model?: string;
591
- /** Default store for every call. Override per call by passing `userId`. Defaults
592
- * to the account's built-in `default` store. */
710
+ /** Default store for every call. Override per call by passing `userId`. Leave it
711
+ * out to use the account's built-in `default` store; passing it as `undefined` is
712
+ * refused, because that is usually a lookup that found nothing. */
593
713
  userId?: string;
594
- /** How many times to retry transient failures before throwing — 429 always;
595
- * 408/502/503/504 and connection errors only when a retry can never
596
- * double-process a write. 0 disables retries (default 2). */
714
+ /** How many times to retry transient failures before throwing — 429 and a 409
715
+ * that says another write was in flight, always; 408/502/503/504 and connection
716
+ * errors only when a retry can never double-process a write. 0 disables retries
717
+ * (default 2). */
597
718
  maxRetries?: number;
598
- /** Alias of `maxRetries`. The Python SDK calls this option `retries`, and these
599
- * SDKs are published in lockstep as "the same surface" — so code ported between
600
- * them silently lost its retry setting instead of failing loudly (an unknown
601
- * key on an options object is not an error in TS at runtime). Both names work;
602
- * `maxRetries` wins if somehow both are given. */
719
+ /** Alias of `maxRetries`. `maxRetries` wins when both are given. */
603
720
  retries?: number;
604
721
  /** A total budget for one call, in ms, across every attempt.
605
722
  *
606
723
  * `timeoutMs` bounds ONE attempt. At the defaults — 30s, two retries — a single
607
724
  * call can hold a connection for 30s + backoff + 30s + backoff + 30s, over a
608
- * minute, and a server handler awaiting it has no way to say "I only have five
609
- * seconds". This is that way. Unset means no overall budget. */
725
+ * minute. Unset means no overall budget. A retry whose wait does not fit in what
726
+ * is left is not made: the call fails with the error of the last response. A
727
+ * value that is not a positive number means no budget; values above 2147483647
728
+ * are clamped to it. */
610
729
  deadlineMs?: number;
611
730
  /** The caller's `AbortSignal` — cancel work already in flight.
612
731
  *
613
732
  * The SDK aborts on its own timeout; this is the seam for the caller's reason.
614
- * When someone closes the chat window, the recall that window asked for should
615
- * end, and the backoff sleep waiting to retry it should end too, instead of
616
- * running to completion and billing for an answer nobody will read.
733
+ * When someone closes the chat window, the recall that window asked for ends, and
734
+ * so does a backoff sleep waiting to retry it.
617
735
  *
618
736
  * Per call, clone: `mem.withSignal(ctrl.signal).recall(...)`. */
619
- signal?: AbortSignal;
737
+ signal?: AbortSignalLike;
620
738
  /** The `fetch` used for every request (default: the global `fetch`).
621
739
  *
622
740
  * This is the seam for anything the runtime cannot express through options.
@@ -633,8 +751,12 @@ export interface ClientOptions {
633
751
  *
634
752
  * It is also how you add instrumentation or drive the client in a test without
635
753
  * a network. Retries, timeouts, redirect refusal and the size cap all still
636
- * apply — this replaces the transport, not the client's rules. */
637
- fetch?: typeof fetch;
754
+ * apply — this replaces the transport, not the client's rules. Pass
755
+ * `init.redirect` through unchanged: a fetch that follows a redirect sends the
756
+ * key to wherever it points, and an answer it reports as redirected
757
+ * (`res.redirected`) is refused. A fetch that sends the request to another URL
758
+ * on purpose, such as a proxy that rewrites it, is fine. */
759
+ fetch?: FetchLike;
638
760
  }
639
761
  /** Wontopos memory client. Store and recall memories, isolated per `userId`. */
640
762
  export declare class Client {
@@ -649,6 +771,13 @@ export declare class Client {
649
771
  /** Transport. Held as a field (not read off `globalThis` per call) so a caller
650
772
  * who passes one gets it for every request, including from cloned clients. */
651
773
  private readonly fetchImpl;
774
+ /** Whether `baseUrl` carries userinfo, which network errors must not show. */
775
+ private readonly userinfo;
776
+ /** Whether `baseUrl` did not resolve to a URL at construction: a relative one where
777
+ * the runtime had no page. */
778
+ private readonly pageRelative;
779
+ /** Whether the caller supplied `fetch`. */
780
+ private readonly customFetch;
652
781
  /** The store every call uses unless one passes `userId`. */
653
782
  private readonly defaultUser;
654
783
  private _rateLimit;
@@ -660,7 +789,7 @@ export declare class Client {
660
789
  /** A client whose key comes from `WONTOPOS_API_KEY` (or `WOS_API_KEY`) — keeps
661
790
  * keys out of source code. Node/Bun/Deno-with-env only. */
662
791
  static fromEnv(opts?: Omit<ClientOptions, "apiKey">): Client;
663
- /** Never expose the key: `JSON.stringify(client)` gets the redacted view. */
792
+ /** Never expose the key or a URL password: `JSON.stringify(client)` gets the redacted view. */
664
793
  toJSON(): Record<string, string>;
665
794
  /** Resolve a call's store: the explicit userId, else the client default. */
666
795
  private uid;
@@ -680,7 +809,7 @@ export declare class Client {
680
809
  * const r = await mem.withSignal(ctrl.signal).recall(q, user);
681
810
  *
682
811
  * Aborting ends the request in flight AND any backoff sleep waiting to retry it. */
683
- withSignal(signal: AbortSignal): Client;
812
+ withSignal(signal: AbortSignalLike): Client;
684
813
  /** A client with a total budget per call, across every retry (everything else
685
814
  * kept). `mem.withDeadline(5_000).search(q)` inside a handler that has five
686
815
  * seconds. */
@@ -690,19 +819,23 @@ export declare class Client {
690
819
  withTimeout(timeoutMs: number): Client;
691
820
  /** A client with a different retry budget (everything else kept). 0 disables retries. */
692
821
  withRetries(maxRetries: number): Client;
693
- /** Store one memory. `metadata` optional — e.g. `{ event_date: "2026-03-01" }` for when the
694
- * content actually happened. A plain date works (read as UTC midnight); a full RFC3339
695
- * timestamp also works; older examples here show the long form.
696
- * `metadata.speaker`: "me" = the assistant's own words, or a person's name (up to 50 per store).
822
+ /** Store one memory. `metadata` optional — the service keeps only these keys and
823
+ * drops any other (this client warns once per unknown key):
824
+ * - `event_date`: when the content actually happened, RFC3339 or a plain date
825
+ * (`YYYY-MM-DD`, read as UTC midnight); anything else is refused (400) naming the field.
826
+ * - `speaker`: "me" = the assistant's own words, or a person's name (up to 50 per store).
827
+ * - `category`, `conversation_id`.
697
828
  *
698
829
  * `opts.idempotencyKey`: see {@link WriteOptions} — pass one to make YOUR retry
699
830
  * of this exact write safe to repeat. */
700
831
  add(content: string, userId?: string, metadata?: Record<string, unknown>, opts?: WriteOptions): Promise<StoreResult>;
701
- /** Alias of `add` — store one memory. Same surface as the Python SDK's `store`. */
832
+ /** Alias of `add` — store one memory. */
702
833
  store(content: string, userId?: string, metadata?: Record<string, unknown>, opts?: WriteOptions): Promise<StoreResult>;
703
834
  /** Store a conversation turn (user + assistant). Payload first, userId last — same shape as add/search. */
704
835
  addTurn(userMsg: string, assistantMsg: string, userId?: string, opts?: WriteOptions): Promise<StatusResult>;
705
836
  /** Bulk-ingest a large blob of text in one call. For backfilling.
837
+ * `timestamp` must be RFC3339 (`2026-05-02T09:00:00Z`): a plain date or any other
838
+ * string is ignored, and the memory is filed at upload time.
706
839
  * The call most worth an `opts.idempotencyKey`: a backfill that dies halfway and is
707
840
  * re-run would otherwise ingest the whole blob a second time. */
708
841
  addBulk(content: string, userId?: string, category?: string, timestamp?: string, opts?: WriteOptions): Promise<StatusResult>;
@@ -711,22 +844,25 @@ export declare class Client {
711
844
  /** Search a store's memories. Returns them most relevant first.
712
845
  *
713
846
  * `limit` is 5-20, and out of range is refused rather than clamped: asking for 50
714
- * and silently receiving 20 reads as "that is all there is". The default is 10, so
715
- * a call that passes no count is unaffected. Before 2.2.35 the count was sent on
716
- * unchecked.
847
+ * and silently receiving 20 reads as "that is all there is". The default is 10.
848
+ *
849
+ * `limit` bounds `memories`, not the returned array. The assistant's own words (on
850
+ * a model that lists `self_memories` in its capabilities) and image memories (one
851
+ * unless `max_images` says otherwise, on a model that lists `images`) come back in
852
+ * it as well, so it can hold more than `limit`. They are billed either way. Size a
853
+ * prompt window on the array you get back, not on `limit`. `searchFull()` hands the
854
+ * fields back apart.
717
855
  *
718
- * `limit` bounds `memories`, not the returned array. The assistant's own words
719
- * (Scroll 1.2+) and image memories (Tablet 2+, one unless `max_images` says
720
- * otherwise) come back in it as well, so it can hold more than `limit`. They are
721
- * billed either way. Size a prompt window on the array you get back, not on
722
- * `limit`. `searchFull()` hands the fields back apart. */
856
+ * `filters` apply to `memories` only; the assistant's own words merged in here are
857
+ * not filtered. */
723
858
  search(query: string, userId?: string, limit?: number, opts?: SearchOptions): Promise<Memory[]>;
724
- /** Search a self-memory model (Scroll 1.2+): both fields from ONE call. Returns
725
- * `{ memories, self_memories }` — `memories` is what others said and general
859
+ /** Search, with the assistant's own words kept apart: both fields from ONE call.
860
+ * Returns `{ memories, self_memories }` — `memories` is what others said and general
726
861
  * memories, `self_memories` is the assistant's OWN words (stored with speaker
727
862
  * "me"), kept apart so whoever reads them never confuses who said what. On a model
728
- * that does not keep them apart, `self_memories` is `[]`. Image memories are not
729
- * included; `search` and `searchFull` carry them. `userId` may be omitted. */
863
+ * that does not list `self_memories` in its capabilities, `self_memories` is `[]`.
864
+ * Image memories are not included; `search` and `searchFull` carry them. `filters`
865
+ * apply to `memories` only. `userId` may be omitted. */
730
866
  searchSelf(query: string, userId?: string, limit?: number, opts?: SearchOptions): Promise<SelfSearchResult>;
731
867
  /**
732
868
  * Search, and keep every field the answer came with.
@@ -739,13 +875,15 @@ export declare class Client {
739
875
  * r.images.length; // the photos, apart from the text memories
740
876
  * r.verify_used; // how many re-ask passes actually ran (you are billed per pass)
741
877
  *
742
- * Both options need Tablet 2 or newer and are refused (403) on an older engine
743
- * rather than accepted and ignored.
878
+ * Each option needs a model that lists it in its `listModels()` capabilities
879
+ * (`images`, `re_ask`) and is refused (403) on any other rather than accepted and
880
+ * ignored. `filters` apply to `memories`; `self_memories` are not filtered.
744
881
  */
745
882
  searchFull(query: string, userId?: string, limit?: number, opts?: SearchOptions): Promise<SearchResult>;
746
883
  /** One-call LLM context: short-term turns + long-term matches + surrounding context.
747
- * `form` ("memoir"/"archive", Scroll 1.2+) renders each long-term memory's time in
748
- * that form; `tz` is your UTC-offset hours for that rendering. */
884
+ * `form` ("memoir"/"archive", on a model that lists `forms` in its capabilities)
885
+ * renders each long-term memory's time in that form; `tz` is your UTC-offset hours
886
+ * for that rendering. */
749
887
  recall(query: string, userId?: string, opts?: {
750
888
  form?: string;
751
889
  tz?: number;
@@ -753,19 +891,19 @@ export declare class Client {
753
891
  * Long-term memories to recall, 5–20. Default 10. Out of range is refused, not
754
892
  * clamped — asking for 20 and silently getting 10 reads as "that is all there is".
755
893
  *
756
- * Needs a limit-aware model. An older one recalls a fixed ten no matter what you
757
- * send, so the API refuses the call (403) rather than answering with a number you
758
- * did not ask for.
894
+ * A model that does not take a recall count refuses the call (403) rather than
895
+ * answering with a number you did not ask for.
759
896
  */
760
897
  limit?: number;
761
898
  /** How much surrounding context is attached around the best match, 0–20. Default 10;
762
- * 0 attaches none. Same model floor as `limit`. */
899
+ * 0 attaches none. Refused (403) by the same models as `limit`. */
763
900
  context_limit?: number;
764
901
  }): Promise<RecallResult>;
765
902
  /** Run a built-in engram ("deep_recall" | "timeline" | "gather" | "equilibrium" |
766
903
  * "tone_stabilizer"; the service is the authority — an unknown name comes back with
767
904
  * the list it accepts). Returns the merged result.
768
- * `form`/`tz` render memory times (memoir/archive) on Scroll 1.2+, same as search/recall. */
905
+ * `form`/`tz` render memory times (memoir/archive) on a model that lists `forms` in
906
+ * its capabilities, same as search/recall. */
769
907
  engram(name: string, query: string, userId?: string, opts?: {
770
908
  form?: string;
771
909
  tz?: number;
@@ -790,13 +928,17 @@ export declare class Client {
790
928
  usage(days?: number): Promise<UsageResult>;
791
929
  /** Fetch ONE memory by id — the text you stored, and its metadata.
792
930
  * The id is what `add`/`store` or `listMemories` returned. Same visibility as
793
- * `listMemories`: an id from another store, an internal record id, or an
794
- * invalidated memory rejects with `NotFoundError`. Pass `undefined` as `userId` for
795
- * the default store — it is positional here, not omittable as it is in Python. */
931
+ * `listMemories`: an id from another store, an id `listMemories` does not return,
932
+ * or an invalidated memory rejects with `NotFoundError`. Pass `undefined` as
933
+ * `userId` for the default store — it is positional here. */
796
934
  get(userId: string | undefined, memoryId: string): Promise<Memory>;
797
935
  /** List a store's stored memories — the text you stored, plus its metadata.
798
936
  * Paginated: pass the returned `next_cursor` back as `cursor` for the next
799
- * page; a `null` cursor means the last page. Use it to browse or export a store.
937
+ * page, and only a cursor the service returned. A `null` cursor means there is no
938
+ * next page; a non-null one can still be followed by an empty page. Use it to
939
+ * browse or export a store.
940
+ *
941
+ * `limit` is 1-500 (default 100); anything else is refused before the request.
800
942
  *
801
943
  * let cursor: string | null = null;
802
944
  * const all: Memory[] = [];
@@ -811,7 +953,11 @@ export declare class Client {
811
953
  cursor?: string;
812
954
  }): Promise<MemoryPage>;
813
955
  /** Async-iterate every stored memory in a store, paging under the hood — no cursor
814
- * bookkeeping. The text you stored and its metadata only.
956
+ * bookkeeping. The text you stored and its metadata only. `pageSize` is 1-500.
957
+ *
958
+ * A walk that cannot reach the end of the store (the service hands back a cursor it
959
+ * already gave after a non-empty page, or the page ceiling is reached) throws rather
960
+ * than end with part of the store.
815
961
  *
816
962
  * for await (const m of mem.iterMemories()) console.log(m.id, m.content);
817
963
  */
@@ -821,7 +967,7 @@ export declare class Client {
821
967
  /** Collect ALL of a store's memories into an array (the text you stored, and its metadata). */
822
968
  exportMemories(userId?: string): Promise<Memory[]>;
823
969
  /** Collect ALL of a store's image memories into an array — the image-side pair of
824
- * `exportMemories`. */
970
+ * `exportMemories`. `pageSize` is 5-20. */
825
971
  exportImages(userId?: string, opts?: {
826
972
  pageSize?: number;
827
973
  }): Promise<Memory[]>;
@@ -837,9 +983,9 @@ export declare class Client {
837
983
  *
838
984
  * The type is sniffed from the bytes themselves rather than from whatever the file
839
985
  * was called, so name the file from `contentType` rather than from what you sent.
840
- * When the format changed, the response also carries `x-wos-image-converted-from`
841
- * naming what you uploaded. Rejects with `NotFoundError` when this memory has no
842
- * image, or when the service keeps no image bytes at all — it says "no" instead of
986
+ * Rejects with `NotFoundError` when this memory has no image, or when the service
987
+ * keeps no image bytes for it — which is always the case for an image stored with
988
+ * `reference`: fetch that one from your own reference. It says "no" instead of
843
989
  * handing back something empty, so "a memory with no image" never looks the same as
844
990
  * "an image we lost".
845
991
  *
@@ -851,10 +997,9 @@ export declare class Client {
851
997
  /**
852
998
  * Remove the PHOTO from a memory, keeping its text.
853
999
  *
854
- * Except when there is no text: an image stored without a caption *is* the memory, so
855
- * deleting the image deletes it. That is the one case worth checking before you
856
- * commit, which is what `preview` is for — it reports `memory_kept` and changes
857
- * nothing.
1000
+ * `preview: true` reports what would happen, including `memory_kept`, and changes
1001
+ * nothing. Retried like `deleteStore`; without `preview`, a 404 after an ambiguous
1002
+ * failure carries the same note.
858
1003
  *
859
1004
  * const p = await mem.forgetImage(undefined, id, { preview: true });
860
1005
  * if (p.memory_kept === false) { /* this would delete the whole memory *\/ }
@@ -870,13 +1015,16 @@ export declare class Client {
870
1015
  * `next_before` and `next_skip_ids` back as `before` / `skipIds`. The pair exists
871
1016
  * because several images can share a timestamp, and a timestamp alone would either
872
1017
  * repeat them or skip them.
1018
+ *
1019
+ * `limit` is 5-20; anything else is refused before the request.
873
1020
  */
874
1021
  listImages(userId?: string, opts?: {
875
1022
  limit?: number;
876
1023
  before?: string;
877
1024
  skipIds?: string[];
878
1025
  }): Promise<ImagePage>;
879
- /** Async-iterate every image memory, paging under the hood. */
1026
+ /** Async-iterate every image memory, paging under the hood. `pageSize` is 5-20.
1027
+ * Like `iterMemories`, a walk that cannot reach the end throws. */
880
1028
  iterImages(userId?: string, opts?: {
881
1029
  pageSize?: number;
882
1030
  }): AsyncGenerator<Memory>;
@@ -889,9 +1037,8 @@ export declare class Client {
889
1037
  * rewritten. Free, and it never reaches the retrieval path.
890
1038
  *
891
1039
  * Counts memories a transform touched (supersede, update, retract, image removed).
892
- * Deletions are NOT counted: a deleted memory leaves nothing to count. Neither are
893
- * the internal records derived from what you stored — nobody stored those directly,
894
- * so they do not belong in a ratio that answers "how much of MY memory changed".
1040
+ * Deletions are NOT counted: a deleted memory leaves nothing to count. `total`
1041
+ * counts the memories you stored.
895
1042
  *
896
1043
  * By default this returns COUNTS ONLY, and the answer is the same size for a store
897
1044
  * of a hundred memories and a store of a hundred million. That is deliberate: a model
@@ -917,10 +1064,8 @@ export declare class Client {
917
1064
  * Pages are ordered by when each memory was STORED, not by when it was edited.
918
1065
  * The response says so in `ordered_by`.
919
1066
  *
920
- * Served from `/api/v1/won/*`, not `/api/v1/memory/*`. Won is the surface for calls
921
- * a model makes ABOUT its memory rather than calls an application makes WITH it, and
922
- * the address says so. The old path still answers, for clients published before
923
- * 2026-08-18, and both share one rate-limit budget.
1067
+ * Served from `/api/v1/won/revisions`: `/api/v1/won/*` holds the calls a model makes
1068
+ * ABOUT its memory rather than calls an application makes WITH it.
924
1069
  */
925
1070
  revisions(userId?: string, opts?: {
926
1071
  /**
@@ -928,7 +1073,8 @@ export declare class Client {
928
1073
  * number. One side per call — there is no way to ask for both lists at once.
929
1074
  */
930
1075
  include?: "revised" | "unrevised";
931
- /** Memories per page. 20 is both the default and the ceiling; more is rejected. */
1076
+ /** Memories per page, 5-20; 20 is the default. Anything else is refused before
1077
+ * the request. */
932
1078
  limit?: number;
933
1079
  /** Cursor: `next_before` from the previous page. */
934
1080
  before?: string;
@@ -948,7 +1094,7 @@ export declare class Client {
948
1094
  *
949
1095
  * `speaker` is the tag written at store time (`metadata.speaker`) — `"me"` for the
950
1096
  * assistant's own words, otherwise a person's name. Same cursor paging as
951
- * `listImages`.
1097
+ * `listImages`; `limit` is 5-20.
952
1098
  *
953
1099
  * `points_to_delete` is the count to show before anyone confirms a delete of this
954
1100
  * speaker's memories.
@@ -969,9 +1115,9 @@ export declare class Client {
969
1115
  *
970
1116
  * Ask rather than hard-code: a name copied from the docs freezes a caller to the
971
1117
  * catalogue as it was that day, and anything added later stays invisible. The
972
- * service is the authority. What
973
- * comes back depends on the model (delivery forms need Scroll 1.2+), so pass
974
- * `withModel()` if you want another model's catalogue.
1118
+ * service is the authority. What comes back depends on the model (`forms` is empty
1119
+ * on one that does not list `forms` in its capabilities), so pass `withModel()` if
1120
+ * you want another model's catalogue.
975
1121
  *
976
1122
  * const { engrams, forms } = await mem.listEngrams();
977
1123
  */
@@ -984,7 +1130,10 @@ export declare class Client {
984
1130
  /** List your stores: `[{ user_id, created_at, canonical_id? }, ...]` (`default`
985
1131
  * first). Each `user_id` is the id the store was created with. */
986
1132
  listStores(): Promise<StoreInfo[]>;
987
- /** Delete a store and ALL its memories. Returns `{ user_id, status }`. */
1133
+ /** Delete a store and ALL its memories. Returns `{ user_id, status }`.
1134
+ * A delete retried after an ambiguous failure (408/502/503/504 or a dropped
1135
+ * connection) that then answers 404 rejects with a `NotFoundError` saying an
1136
+ * earlier attempt may already have deleted it. */
988
1137
  deleteStore(userId: string): Promise<StoreOpResult>;
989
1138
  /** Register a person for this store. Speakers are explicit: register once, then
990
1139
  * store with `{ speaker }`. `"me"` (the assistant itself) never needs
@@ -992,67 +1141,82 @@ export declare class Client {
992
1141
  addSpeaker(speaker: string, userId?: string): Promise<SpeakerOpResult>;
993
1142
  /** The store's registered people, each with its memory count. */
994
1143
  listSpeakers(userId?: string): Promise<SpeakersList>;
995
- /** Unregister a person. Their memories stay; the name tag goes. */
1144
+ /** Unregister a person. Their memories stay; the name tag goes. Retried like
1145
+ * `deleteStore`, with the same note on a 404 after an ambiguous failure. */
996
1146
  removeSpeaker(speaker: string, userId?: string): Promise<SpeakerOpResult>;
997
1147
  /** Delete a single memory by id. Pass `undefined` as `userId` for the default
998
- * store — it is positional here, not omittable as it is in Python. */
1148
+ * store — it is positional here. */
999
1149
  delete(userId: string | undefined, memoryId: string): Promise<StatusResult>;
1000
1150
  /** Delete ALL memories for a store (GDPR erase). `userId` is required on purpose -
1001
1151
  * this is destructive, so it never falls back to the default store. */
1002
1152
  deleteAll(userId: string): Promise<StatusResult>;
1003
1153
  private post;
1004
- /**
1005
- * One request that answers with BYTES rather than JSON.
1006
- *
1007
- * Only `/memory/image` does this, and it is why this cannot go through `request()`:
1008
- * that path reads the body as text and insists the result parses to a JSON object.
1009
- * Feeding it a JPEG would throw "invalid JSON in response" — a confusing error for a
1010
- * call that actually succeeded.
1011
- *
1012
- * Errors still arrive as JSON, so those are handed back to the normal machinery: on
1013
- * a non-2xx we re-read the body as text and reuse `errorFor`, which keeps 404
1014
- * (`NotFoundError`) and 401 behaving the same as everywhere else.
1015
- */
1154
+ /** A DELETE that removes something: a 404 after an ambiguous failure says an earlier
1155
+ * attempt may already have removed it. */
1156
+ private remove;
1016
1157
  /** Where this call's budget runs out, or `undefined` when it has none. Computed
1017
1158
  * once per call, not per attempt — a budget recomputed each attempt is not a
1018
1159
  * budget. */
1019
1160
  private deadlineAt;
1020
1161
  /** The abort wiring for ONE attempt: this SDK's per-attempt timeout, the caller's
1021
1162
  * signal, and whatever is left of the overall deadline — whichever fires first.
1022
- *
1023
- * One function, two callers (`request` and `requestBytes`), because a guard living
1024
- * in one of two request paths is exactly how `getImage` ended up with no retry loop
1025
- * while the module doc promised every call had one. */
1163
+ * With no time left it throws `answer` (the refusal that led here), or the
1164
+ * deadline error when there is none. */
1026
1165
  private beginAttempt;
1027
1166
  /** What to say when an attempt was cut short. The three reasons need different
1028
1167
  * words: a timeout may have landed server-side, a cancellation certainly did not
1029
1168
  * matter to the caller, and an exhausted budget is the caller's own setting. */
1030
1169
  private abortMessage;
1031
- /** Sleep between attempts — but wake the moment the caller aborts, and give up
1032
- * rather than wait out a backoff the budget cannot cover. A backoff that runs to
1033
- * completion after the caller gave up wastes exactly as long as the request it was
1034
- * waiting to repeat.
1170
+ /** Whether a wait of `ms` still leaves time before the deadline. */
1171
+ private fits;
1172
+ /** Sleep between attempts, waking the moment the caller aborts. */
1173
+ private pause;
1174
+ /** The error for a request that got no answer. The message names the cause code
1175
+ * (ECONNREFUSED, ENOTFOUND, CERT_HAS_EXPIRED, …) and never carries the query
1176
+ * string, which can hold a store id, or the base URL's userinfo. With either in
1177
+ * the URL the transport error is not kept as the cause, since its own message can
1178
+ * quote the URL. */
1179
+ private networkError;
1180
+ /** A redirect: a 3xx, or one a custom fetch followed. Where a custom fetch sent the
1181
+ * request (`res.url`) is its own business. */
1182
+ private isRedirected;
1183
+ /** The error an answer carries: the service's message (control characters removed,
1184
+ * capped), request id, `type` and the rest of its error object. `unread` says why
1185
+ * the body is missing, when it could not be read. */
1186
+ private errorFromAnswer;
1187
+ /**
1188
+ * Send one call, retrying what can be retried, and return the answer with its body
1189
+ * read (under the same timeout, so a body that trickles in cannot stall the call).
1190
+ * The error body of an answer about to be retried gets at most a second.
1191
+ *
1192
+ * Retried: 429, and a 409 whose error carries `retry_after_ms` (another write to the
1193
+ * store was in flight; nothing was stored), on every method; 408/502/503/504 and
1194
+ * dropped connections on idempotent methods; connect failures on every method. A
1195
+ * retry whose wait is over the cap, or does not fit the deadline, is not made: the
1196
+ * call fails with the error of the response it has. So does a retry the deadline
1197
+ * leaves no time to send, and the retry of an idempotent method or `getImage` that
1198
+ * the deadline cuts short. Any other retry cut short stays a status-0 deadline error:
1199
+ * a write may have been applied.
1200
+ */
1201
+ private send;
1202
+ /**
1203
+ * One request that answers with BYTES rather than JSON.
1035
1204
  *
1036
- * Clamping the wait to what is left and retrying anyway is the same answer as
1037
- * having no deadline at all: the call still spends the whole allowance, and the one
1038
- * attempt it buys has nothing left to finish in. If the wait does not fit, the
1039
- * budget is already decided. */
1040
- private backoffSleep;
1205
+ * Only `/memory/image` does this: `request()` insists the body parses to a JSON
1206
+ * object, and a JPEG would come back as "invalid JSON in response" for a call that
1207
+ * succeeded. Retries, timeouts and errors are the same as every other call's.
1208
+ */
1041
1209
  private requestBytes;
1042
1210
  /** Validate an idempotency key BEFORE the network. The API answers a malformed
1043
1211
  * key with a 400, which on a retry path reads as "my write failed" when in fact
1044
1212
  * it was never attempted — cheaper and clearer to reject it here. */
1045
1213
  private idemHeader;
1046
- /** Seconds→ms to sleep before retry `attempt` (0-based). Honors Retry-After. */
1214
+ /** Milliseconds to sleep before retry `attempt` (0-based): exponential with jitter,
1215
+ * or what a valid `Retry-After` asks for. */
1047
1216
  private backoffMs;
1048
1217
  /** Read the body with a hard size cap, so a broken/hostile endpoint can't
1049
- * make the process buffer gigabytes. */
1050
- private readCapped;
1051
- /** The same cap, for a body that is not text.
1052
- *
1053
- * `arrayBuffer()` would buffer whatever arrives. The cap exists for a hostile or
1054
- * broken `baseUrl`, and an endpoint that returns megabytes is where that costs the
1055
- * most. One implementation, two callers, so they cannot drift. */
1218
+ * make the process buffer gigabytes. With `withinMs`, a body still arriving after
1219
+ * that long is dropped and the read fails. */
1056
1220
  private readCappedBytes;
1057
1221
  private request;
1058
1222
  }