wontopos 2.2.44 → 2.2.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@ npm install wontopos
5
5
  ```
6
6
 
7
7
  Get an API key in the [console](https://wontopos.com). Keys look like `wos-live-...`;
8
- the client also reads `WONTOPOS_API_KEY` from the environment.
8
+ `Client.fromEnv()` reads `WONTOPOS_API_KEY` from the environment instead.
9
9
 
10
10
  ```ts
11
11
  import { Client } from "wontopos";
@@ -24,8 +24,8 @@ const ctx = await mem.recall("what does alice drink?", "alice");
24
24
  ## Why
25
25
 
26
26
  - **The same in every language** — identical recall whichever language a memory was written in (Korean · Japanese · Chinese · English).
27
- - **No LLM in the loop** — `store` / `search` / `recall` never call a language model. You pay retrieval, not generation.
28
- - **Bounded retrieval** — `recall()` returns a small, fixed-size slice regardless of how much you've stored (~1,000 tokens on `tablet-2`, the default engine).
27
+ - **No LLM reads your memories** — no language model reads your stored memories, on any model. You pay retrieval, not generation.
28
+ - **Bounded retrieval** — `recall()` returns the recent turns, at most `limit` long-term memories (default 10, up to 20) and at most `context_limit` rows of the surrounding conversation (default 10, up to 20), however much you've stored.
29
29
 
30
30
  ## Methods
31
31
 
@@ -35,10 +35,10 @@ Where the store id goes depends on whether the method takes a payload. A method
35
35
  writes or searches puts the payload first and the store second — `mem.add("...", "alice")`,
36
36
  `mem.search("...", "alice")`. A method that only addresses a store takes it first —
37
37
  `mem.listMemories("alice")`, `mem.stats("alice")`, `mem.delete("alice", memoryId)`.
38
- Most of them let you leave it out for the client's default store. Four take it
39
- positionally and cannot — `get`, `delete`, `lineage` and `forgetImage` want an explicit
40
- `undefined` there, because a second required argument follows it. Python omits it in
41
- all four; this is the one place the three clients differ in shape rather than meaning.
38
+ Most of them let you leave it out for the client's default store. Five take it
39
+ positionally and cannot — `get`, `getImage`, `delete`, `lineage` and `forgetImage` want
40
+ an explicit `undefined` there, because a second required argument follows it. Python
41
+ omits it in all five, and Rust takes it positionally (`None` for the default store, where there is one).
42
42
  Let your editor confirm the order: every signature is typed. A store is one isolated memory space per end-user, agent, or topic. WHO said each memory inside a store is the `speaker` tag below — storing the assistant's own words never needs a separate id.
43
43
 
44
44
  ## Who said it (speakers)
@@ -56,19 +56,22 @@ await mem.search("what did Bob say about deadlines?", "alice", 10, { speaker: "B
56
56
  ```
57
57
 
58
58
  `listSpeakers()` shows the registered people with per-person memory counts;
59
- `removeSpeaker()` unregisters (memories stay, the tag goes). A store registers
59
+ `removeSpeaker()` unregisters (memories stay; the tag goes, except on `tablet-2`, which keeps it on memories already stored). A store registers
60
60
  up to 50 people to start (a limit we plan to raise); `"me"` never needs
61
61
  registration and never counts against it.
62
62
 
63
63
  ## Recall caching
64
64
 
65
- Opt in per search and repeated or extended queries reuse the previous result
66
- at 10% of the normal rate (Tablet and Scroll models).
65
+ Opt in per search and a repeated query reuses the previous result at 10% of the
66
+ normal rate; an extended one reuses part of it (Tablet and Scroll models).
67
67
 
68
68
  It is not free to turn on: the FIRST call writes the cache and bills the query
69
- tokens at 2x for a `5m` TTL, 3x for `1h`. Only hits inside the TTL bill at 0.1x.
70
- So it pays for a query you repeat or extend, and costs more for one you issue
71
- once — do not switch it on globally. Any write to the store invalidates its cache
69
+ tokens at 2x for a `5m` TTL, 3x for `1h`. The same query again inside the TTL bills
70
+ its query and its results at 0.1x. A query that extends the cached one (its text plus
71
+ more) bills the cached part at 0.1x, the new part at the write rate and its results
72
+ at the normal rate. A store keeps one cached query per model and set of options, so a
73
+ different query replaces it. So it pays for a query you repeat or extend, and costs
74
+ more for one you issue once — do not switch it on globally. Any write to the store invalidates its cache
72
75
  at once, so a hit can never predate a new memory.
73
76
 
74
77
  ```ts
@@ -82,11 +85,11 @@ Built in, no configuration needed:
82
85
 
83
86
  - **Automatic retries** — 429 and a 409 for a write already in flight always, and 408 /
84
87
  502 / 503 / 504 or a connection error only when a retry cannot double-process a write.
85
- The writes and the searches are POSTs, and any of those four may have been returned
86
- *after* the write already landed, so those get 429, that 409 and connect-level
87
- failures only. The GET and DELETE calls — `ping`, `listStores`, `listSpeakers`,
88
- `listModels`, `listEngrams`, `usage`, `deleteStore`, `removeSpeaker`, `forgetImage` —
89
- retry all four. Twice, with exponential backoff + jitter, honoring the server's
88
+ A write may already have landed when any of those four comes back, so a POST write gets
89
+ 429, that 409 and connect-level failures only. The calls that are safe to send again
90
+ retry all four: the reads sent as POST (`search`, `recall`, `get`, `listMemories`,
91
+ `stats`, ...) and the GET and DELETE calls (`ping`, `listStores`, `listSpeakers`,
92
+ `listModels`, `listEngrams`, `usage`, `deleteStore`, `removeSpeaker`, `forgetImage`). Twice, with exponential backoff + jitter, honoring the server's
90
93
  `Retry-After` up to 30s. Tune with `new Client({ maxRetries })`; `0` disables.
91
94
  - **Redirects refused** — the API key never follows a 3xx to another host.
92
95
  - **Timeouts** — 30s per attempt by default (`timeoutMs`), and a total budget for
@@ -112,6 +115,7 @@ This one needs `npm i undici` — Node's global `fetch` is built on undici, but
112
115
  package itself is not installed for you.
113
116
 
114
117
  ```ts
118
+ import { Client } from "wontopos";
115
119
  import { ProxyAgent } from "undici";
116
120
 
117
121
  const agent = new ProxyAgent(process.env.HTTPS_PROXY!);
@@ -146,8 +150,9 @@ Built in, none of it configurable off:
146
150
  Every method returns a documented shape — `StoreResult`, `UpdateResult`,
147
151
  `RecallResult`, `EngramResult`, `StatsResult`, `SpeakersList`, `Memory[]`, ... —
148
152
  each with an index signature, so new server fields flow through without an SDK
149
- update. `search` options are typed too (`SearchOptions`: `cache_control`,
150
- `speaker`, plus pass-through).
153
+ update. `searchSelf`'s result and `getImage`'s bytes are built by the client and
154
+ carry only their own fields. `search` options are typed too (`SearchOptions`: `cache_control`,
155
+ `speaker`, plus `extra` for pass-through).
151
156
 
152
157
  ```ts
153
158
  import { type Memory } from "wontopos";
@@ -156,7 +161,8 @@ const hits: Memory[] = await mem.search("what did Bob say?", "alice", 10, { spea
156
161
  console.log(hits[0].speaker, hits[0].time_bucket); // typed: string | undefined
157
162
  ```
158
163
 
159
- `hits` arrives best-first — take it in the order given. `similarity` is a raw
164
+ `hits` holds memories best-first, then the assistant's own words and image memories in
165
+ their own order. Take it in the order given. `similarity` is a raw
160
166
  closeness score, not the ranking key: what produces the order is internal and is
161
167
  not returned, so sorting by `similarity` overrides the ranking and makes results
162
168
  worse. There is no `score` field.
@@ -206,14 +212,14 @@ Found something wrong, or something that looks unsafe? Tell us — every report
206
212
  - Security: <https://wontopos.com/contact?topic=security> (also published at
207
213
  [`/.well-known/security.txt`](https://wontopos.com/.well-known/security.txt))
208
214
 
209
- Include the SDK version (`the version in package.json`) and the language. If it involves a store id or a
215
+ Include the SDK version (the one in package.json) and the language. If it involves a store id or a
210
216
  memory, describe the shape rather than pasting the contents — we do not need your
211
217
  data to fix it.
212
218
 
213
219
  ## Changelog
214
220
 
215
221
  The three clients release in lockstep — same version, same surface, same day. Patch
216
- releases are additive. Eight inside 2.2 were not, deliberately and each with its
222
+ releases are additive. Nine inside 2.2 were not, deliberately and each with its
217
223
  reason; the changelog lists them.
218
224
 
219
225
  See [CHANGELOG.md](https://github.com/wontopos/wontopos-node/blob/main/CHANGELOG.md).
@@ -14,7 +14,7 @@
14
14
  * (`createStore`) or calls return 404. Every account starts with a `default` store,
15
15
  * so with no `userId` anywhere the zero-setup path just works.
16
16
  *
17
- * The API key picks *which memory* (your account); `model` picks *which engine*
17
+ * The API key picks *which memory* (your workspace); `model` picks *which engine*
18
18
  * reads it. Models on the shared pool read the same memory, so you can store with
19
19
  * one and recall with another; `listModels()` reports each model's `memory` as
20
20
  * `"shared"` or `"isolated"`, and an isolated one starts empty. Set a default in
@@ -22,14 +22,14 @@
22
22
  * `mem.withModel("tablet-1").recall(...)`.
23
23
  *
24
24
  * Recall quality does not depend on which language a memory was written in: a
25
- * memory stored in one language is found by a question asked in another. Storing
26
- * and searching call no LLM.
25
+ * memory stored in one language is found by a question asked in another. No language
26
+ * model reads your stored memories.
27
27
  *
28
28
  * Reliability: every call retries transient failures with exponential backoff +
29
29
  * jitter, honoring `Retry-After` up to 30s — 429 and a 409 that says another write
30
30
  * to the store was in flight, always; 408/502/503/504 and connection errors only when
31
- * a retry can never double-process a write (idempotent calls, or a failure at connect
32
- * time). Tune with `maxRetries` (0 disables) and `timeoutMs`, or per call site via
31
+ * a retry can never double-process a write (reads, the DELETE calls, or a failure at
32
+ * connect time). Tune with `maxRetries` (0 disables) and `timeoutMs`, or per call site via
33
33
  * the `withTimeout()` / `withRetries()` clones.
34
34
  *
35
35
  * Debugging: set `WONTOPOS_LOG=debug` to log method/path/status/timing/retries
@@ -89,7 +89,9 @@ export declare class BadRequestError extends WosError {
89
89
  /** 401 — the API key is missing, wrong, or revoked. */
90
90
  export declare class AuthenticationError extends WosError {
91
91
  }
92
- /** 402 — no card on file or the balance is depleted. Top up to continue. */
92
+ /** 402 — no card on file, the balance is depleted, or the month's spend cap for the
93
+ * account's tier is reached (`details.reason` is `tier_cap_reached`). Top up to
94
+ * continue; a top-up also raises the tier. */
93
95
  export declare class PaymentRequiredError extends WosError {
94
96
  }
95
97
  /** 403 — the key/model isn't allowed to do this. */
@@ -194,8 +196,9 @@ export interface StatusResult {
194
196
  /**
195
197
  * An image attached to a memory, passed to `add`/`store` via `opts.image`.
196
198
  *
197
- * Both edges must be 700px or more; a smaller image is refused (400). A request
198
- * body is capped at 10MB.
199
+ * Both edges must be 700px or more and the long edge at most 2500px, and the image
200
+ * file at most 7MB; anything else is refused (400) with the reason. A request body is
201
+ * capped at 10MB.
199
202
  *
200
203
  * What the service keeps is NOT your original. An image whose long edge is over
201
204
  * 1568px is downscaled to 1568 before anything else happens, and that smaller
@@ -241,8 +244,9 @@ export interface ImageBytes {
241
244
  * The image as stored. Write it to a file, or wrap it in a Blob to show it.
242
245
  *
243
246
  * This is the service's copy, not necessarily your upload: anything over 1568px
244
- * on its long edge was downscaled on the way in, and re-encoded to WebP unless it
245
- * was a JPEG (see `ImageInput`). Take the file extension from `contentType`.
247
+ * on its long edge (2500 at most) was downscaled on the way in, and re-encoded to
248
+ * WebP unless it was a JPEG (see `ImageInput`). Take the file extension from
249
+ * `contentType`.
246
250
  */
247
251
  bytes: Uint8Array;
248
252
  /** Sniffed from the BYTES, not from whatever the upload was named — e.g. `image/jpeg`. */
@@ -317,7 +321,8 @@ export interface LineageStep {
317
321
  is_current?: boolean;
318
322
  [key: string]: unknown;
319
323
  }
320
- /** `lineage` → the full chain of edits behind one memory, oldest first. */
324
+ /** `lineage` → the edits that followed one memory, from the id passed forward to the
325
+ * version in force, oldest first. */
321
326
  export interface LineageResult {
322
327
  memory_id?: string;
323
328
  chain: LineageStep[];
@@ -360,8 +365,8 @@ export interface SearchResult {
360
365
  /** Image memories the answer carried (one by default on an image-capable model,
361
366
  * up to `max_images`); `[]` when there were none. */
362
367
  images: Memory[];
363
- /** Re-ask passes actually performed. Present only when `verify` was sent; lower
364
- * than you asked for means the store had nothing further to add. */
368
+ /** Re-ask passes that added memories. Present only when `verify` above 0 was sent;
369
+ * lower than you asked for means a pass found nothing new, or could not run. */
365
370
  verify_used?: number;
366
371
  [key: string]: unknown;
367
372
  }
@@ -414,7 +419,7 @@ export interface UsageResult {
414
419
  output_tokens?: number;
415
420
  since?: string;
416
421
  };
417
- /** The workspace this key belongs to, over the window. */
422
+ /** The workspace this key belongs to, over the window: every key in it, not only this one. */
418
423
  workspace?: {
419
424
  workspace_id?: string | null;
420
425
  requests?: number;
@@ -542,9 +547,8 @@ export interface SpeakersList {
542
547
  [key: string]: unknown;
543
548
  }
544
549
  /**
545
- * Narrow a search to part of a store. The filter chooses what is searched, not what
546
- * is kept afterwards — so a narrow filter still returns your full `limit` when that
547
- * many matches sit inside it.
550
+ * Narrow a search to part of a store. A narrow filter can return fewer than `limit`
551
+ * even when the store holds more inside it.
548
552
  *
549
553
  * Retrieval behaves the same in every language, so these behave
550
554
  * identically in every language. Unlisted keys are dropped by the API rather than
@@ -565,7 +569,8 @@ export interface SearchFilters {
565
569
  time_from?: string;
566
570
  time_to?: string;
567
571
  /** WHEN THE CONTENT HAPPENED (`metadata.event_date`), not when it was written —
568
- * this is the one you usually want. */
572
+ * this is the one you usually want. A memory stored without an `event_date` is
573
+ * matched by when it was stored. */
569
574
  event_from?: string;
570
575
  event_to?: string;
571
576
  /** Drop matches the engine scored below this importance (0–1). */
@@ -578,9 +583,11 @@ export declare function _resetFilterWarnings(): void;
578
583
  * could not be told from an option that worked. Put a genuinely new one under
579
584
  * `extra`. */
580
585
  export interface SearchOptions {
581
- /** Recall caching. A hit inside the TTL bills at 0.1× — but the FIRST call
582
- * writes the cache and bills the query tokens at 2× (`5m`) or 3× (`1h`), so
583
- * this only pays for a query you repeat or extend. Do not enable it globally. */
586
+ /** Recall caching. The same query again inside the TTL bills at 0.1×; one that
587
+ * extends the cached query bills only its cached part at 0.1×, the new part at the
588
+ * write rate and its results at the normal rate. The FIRST call writes the cache
589
+ * and bills the query tokens at 2× (`5m`) or 3× (`1h`), so this only pays for a
590
+ * query you repeat or extend. Do not enable it globally. */
584
591
  cache_control?: {
585
592
  ttl: "5m" | "1h";
586
593
  };
@@ -591,9 +598,9 @@ export interface SearchOptions {
591
598
  /**
592
599
  * Re-ask passes, 0–3. After the first retrieval the service asks the engine again up
593
600
  * to this many times, each time telling it what was already returned so it skips
594
- * those and reaches further back. No LLM runs at any
601
+ * those and reaches further back. Raising it adds no language model call at any
595
602
  * value. Stops early when a pass finds nothing new; `verify_used` in the response
596
- * says how many actually ran.
603
+ * says how many passes added memories.
597
604
  *
598
605
  * Each pass is another engine call and can add up to `limit` more memories, so it
599
606
  * costs more — you are billed for what is delivered. Default 0. Worth it on questions
@@ -652,9 +659,10 @@ export interface SearchOptions {
652
659
  export interface WriteOptions {
653
660
  idempotencyKey?: string;
654
661
  /**
655
- * An image to store alongside the text. Needs a model that lists `images` in its
656
- * `listModels()` capabilities; any other refuses the write rather than store the
657
- * caption and quietly drop the image.
662
+ * An image to store alongside the text, read by `add` (and `store`) only: `addTurn`,
663
+ * `addBulk` and `update` warn about it and do not send it. Needs a model that lists
664
+ * `images` in its `listModels()` capabilities; any other refuses the write rather than
665
+ * store the caption and quietly drop the image.
658
666
  *
659
667
  * A caption is required: the service refuses empty `content` (400) even with an
660
668
  * image attached.
@@ -858,15 +866,16 @@ export declare class Client {
858
866
  addBulk(content: string, userId?: string, category?: string, timestamp?: string, opts?: WriteOptions): Promise<StatusResult>;
859
867
  /** Supersede an old memory with new content. Payload first, userId last — same shape as add/search. */
860
868
  update(oldMemoryId: string, newContent: string, userId?: string, opts?: WriteOptions): Promise<UpdateResult>;
861
- /** Search a store's memories. Returns them most relevant first.
869
+ /** Search a store's memories. They come most relevant first; the assistant's own
870
+ * words and image memories, when there are any, follow them in their own order.
862
871
  *
863
872
  * `limit` is 5-20, and out of range is refused rather than clamped: asking for 50
864
873
  * and silently receiving 20 reads as "that is all there is". The default is 10.
865
874
  *
866
- * `limit` bounds `memories`, not the returned array. The assistant's own words (on
867
- * a model that lists `self_memories` in its capabilities) and image memories (one
868
- * unless `max_images` says otherwise, on a model that lists `images`) come back in
869
- * it as well, so it can hold more than `limit`. They are billed either way. Size a
875
+ * `limit` bounds `memories`, not the returned array, and each `verify` pass can add
876
+ * up to `limit` more. The assistant's own words (on a model that lists
877
+ * `self_memories` in its capabilities) and image memories (one unless `max_images`
878
+ * says otherwise, on a model that lists `images`) come back after them in it as well, so it can hold more than `limit`. They are billed either way. Size a
870
879
  * prompt window on the array you get back, not on `limit`. `searchFull()` hands the
871
880
  * fields back apart.
872
881
  *
@@ -890,7 +899,7 @@ export declare class Client {
890
899
  * const r = await mem.searchFull("the day we moved", "alice", 10,
891
900
  * { max_images: 3, verify: 2 });
892
901
  * r.images.length; // the photos, apart from the text memories
893
- * r.verify_used; // how many re-ask passes actually ran (you are billed per pass)
902
+ * r.verify_used; // re-ask passes that added memories (each billed for what it added)
894
903
  *
895
904
  * Each option needs a model that lists it in its `listModels()` capabilities
896
905
  * (`images`, `re_ask`) and is refused (403) on any other rather than accepted and
@@ -927,7 +936,8 @@ export declare class Client {
927
936
  }): Promise<EngramResult>;
928
937
  /** Recent conversation turns (short-term memory). */
929
938
  history(userId?: string): Promise<HistoryTurn[]>;
930
- /** Memory counts for a store: { total_memories, short_term_turns }. */
939
+ /** Memory counts for a store: { total_memories, short_term_turns }. `total_memories`
940
+ * can be larger than the number `listMemories` lists. */
931
941
  stats(userId?: string): Promise<StatsResult>;
932
942
  /**
933
943
  * What this key has spent, and what is left — the numbers behind "can I keep going?".
@@ -935,18 +945,17 @@ export declare class Client {
935
945
  * Free: it carries no charge and skips the balance gate, because an account at zero
936
946
  * still has to be able to find out why. Rate-limited instead.
937
947
  *
938
- * Scoped to THIS key: its own lifetime spend, its workspace and stores over the
939
- * window. It never returns another key's spend. `balance_cents` is account-wide,
940
- * because that is what gates the next call whichever key makes it.
948
+ * `key` is THIS key's own lifetime spend. `workspace` and `stores` cover the window
949
+ * for the workspace this key belongs to, every key in it included. `balance_cents` is
950
+ * account-wide, because that is what gates the next call whichever key makes it.
941
951
  *
942
952
  * const u = await mem.usage(7);
943
953
  * if (u.balance_cents! < 100) stop();
944
954
  */
945
955
  usage(days?: number): Promise<UsageResult>;
946
956
  /** Fetch ONE memory by id — the text you stored, and its metadata.
947
- * The id is what `add`/`store` or `listMemories` returned. Same visibility as
948
- * `listMemories`: an id from another store, an id `listMemories` does not return,
949
- * or an invalidated memory rejects with `NotFoundError`. Pass `undefined` as
957
+ * The id is what `add`/`store` or `listMemories` returned. An unknown id or one
958
+ * from another store rejects with `NotFoundError`. Pass `undefined` as
950
959
  * `userId` for the default store — it is positional here. */
951
960
  get(userId: string | undefined, memoryId: string): Promise<Memory>;
952
961
  /** List a store's stored memories — the text you stored, plus its metadata.
@@ -992,9 +1001,10 @@ export declare class Client {
992
1001
  * Fetch the bytes of an image memory — the picture the SERVICE holds.
993
1002
  *
994
1003
  * Not necessarily your upload, in size OR in format: an image whose long edge was
995
- * over 1568px was downscaled to 1568 on the way in and re-encoded — lossless
996
- * formats as WebP, so a PNG comes back as `image/webp`; JPEG stays JPEG — and that
997
- * smaller picture is what is stored and comes back here. Nothing on our side ever
1004
+ * over 1568px (2500 at most) was downscaled to 1568 on the way in and re-encoded —
1005
+ * lossless formats as WebP, so a PNG comes back as `image/webp`; JPEG stays JPEG;
1006
+ * kept as sent when the re-encode came out bigger — and that smaller picture is what
1007
+ * is stored and comes back here. Nothing on our side ever
998
1008
  * uses more than 1568, so the extra pixels would be bytes nobody reads.
999
1009
  * Keep your own copy for the full-resolution file.
1000
1010
  *
@@ -1099,11 +1109,13 @@ export declare class Client {
1099
1109
  skipIds?: string[];
1100
1110
  }): Promise<RevisionsResult>;
1101
1111
  /**
1102
- * The full chain of edits behind one memory, oldest first.
1112
+ * The edits that followed one memory: from the id you pass forward to the version in
1113
+ * force, oldest first.
1103
1114
  *
1104
- * `is_current` marks the version in force. Use it to answer "what did this used to
1105
- * say, and when did it change" — `revisions()` tells you HOW MUCH a store moved,
1106
- * this tells you what happened to one fact.
1115
+ * `is_current` marks the version in force. The chain runs forward only, so to answer
1116
+ * "what did this used to say, and when did it change" pass the first version's id;
1117
+ * the current version's id (what `search` returns) comes back alone. `revisions()`
1118
+ * tells you HOW MUCH a store moved, this tells you what happened to one fact.
1107
1119
  */
1108
1120
  lineage(userId: string | undefined, memoryId: string): Promise<LineageResult>;
1109
1121
  /**
@@ -1125,8 +1137,7 @@ export declare class Client {
1125
1137
  * `AuthenticationError` (bad key), `PaymentRequiredError` (valid key but no card /
1126
1138
  * depleted balance), or `APIConnectionError` (unreachable). Makes one metered request. */
1127
1139
  ping(): Promise<boolean>;
1128
- /** Available models: `[{ id, name, available, memory, capabilities, retires_at? }, ...]`.
1129
- * Needs no API key. */
1140
+ /** Available models: `[{ id, name, available, memory, capabilities, retires_at? }, ...]`. */
1130
1141
  listModels(): Promise<ModelInfo[]>;
1131
1142
  /**
1132
1143
  * The engrams (and delivery forms) the selected model can actually run.
@@ -1159,7 +1170,9 @@ export declare class Client {
1159
1170
  addSpeaker(speaker: string, userId?: string): Promise<SpeakerOpResult>;
1160
1171
  /** The store's registered people, each with its memory count. */
1161
1172
  listSpeakers(userId?: string): Promise<SpeakersList>;
1162
- /** Unregister a person. Their memories stay; the name tag goes. Retried like
1173
+ /** Unregister a person. Their memories stay, and the name can no longer tag a new
1174
+ * memory or filter a search. The tag goes from stored memories too, except on
1175
+ * `tablet-2`, which keeps it on memories already stored with it. Retried like
1163
1176
  * `deleteStore`, with the same note on a 404 after an ambiguous failure. */
1164
1177
  removeSpeaker(speaker: string, userId?: string): Promise<SpeakerOpResult>;
1165
1178
  /** Delete a single memory by id. Pass `undefined` as `userId` for the default
@@ -1209,12 +1222,12 @@ export declare class Client {
1209
1222
  *
1210
1223
  * Retried: 429, and a 409 whose error carries `retry_after_ms` (another write to the
1211
1224
  * store was in flight; nothing was stored), on every method; 408/502/503/504 and
1212
- * dropped connections on idempotent methods; connect failures on every method. A
1213
- * retry whose wait is over the cap, or does not fit the deadline, is not made: the
1214
- * call fails with the error of the response it has. So does a retry the deadline
1215
- * leaves no time to send, and the retry of an idempotent method or of a POST that
1216
- * only reads that the deadline cuts short. Any other retry cut short stays a status-0
1217
- * deadline error: a write may have been applied.
1225
+ * dropped connections on a call that only reads (an idempotent method, or a POST
1226
+ * that only reads); connect failures on every method. A retry whose wait is over the
1227
+ * cap, or does not fit the deadline, is not made: the call fails with the error of
1228
+ * the response it has. So does a retry the deadline leaves no time to send, and the
1229
+ * retry of a call that only reads that the deadline cuts short. Any other retry cut
1230
+ * short stays a status-0 deadline error: a write may have been applied.
1218
1231
  */
1219
1232
  private send;
1220
1233
  /**
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WME = exports.Client = exports.ServerError = exports.RateLimitError = exports.GoneError = 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.44";
6
+ const VERSION = "2.2.46";
7
7
  const RUNTIME = (() => {
8
8
  const p = globalThis?.process;
9
9
  return p?.version ? ` (node/${p.version}; ${p.platform}-${p.arch})` : "";
@@ -508,7 +508,7 @@ const KNOWN_ENGRAM_KEYS = new Set(["form", "tz"]);
508
508
  function checkSearchOpts(opts) {
509
509
  checkOpts(opts, KNOWN_SEARCH_KEYS, "search");
510
510
  const o = opts;
511
- optRange(o.max_images !== undefined ? o.max_images : o.extra?.max_images, "max_images", 0, MAX_IMAGES_MAX);
511
+ optRange(o.max_images != null ? o.max_images : o.extra?.max_images, "max_images", 0, MAX_IMAGES_MAX);
512
512
  }
513
513
  function checkOpts(opts, known, what) {
514
514
  for (const k of Object.keys(opts)) {
@@ -558,6 +558,17 @@ function warnOnceUnknownKeys(obj, known, warned, message) {
558
558
  console.warn(message(k));
559
559
  }
560
560
  }
561
+ function givenOpts(known) {
562
+ return Object.fromEntries(Object.entries(known).filter(([, v]) => v !== null));
563
+ }
564
+ const ADD_KEYS = new Set(["idempotencyKey", "image"]);
565
+ const WRITE_KEYS = new Set(["idempotencyKey"]);
566
+ const warnedWriteKeys = new Set();
567
+ function writeKey(opts, takesImage = false) {
568
+ warnOnceUnknownKeys(opts, takesImage ? ADD_KEYS : WRITE_KEYS, warnedWriteKeys, (k) => `wontopos: write option ${JSON.stringify(k)} is not sent. ` +
569
+ (takesImage ? "add takes idempotencyKey and image." : "This call takes idempotencyKey only."));
570
+ return opts.idempotencyKey;
571
+ }
561
572
  function warnOnUnknownFilters(filters) {
562
573
  warnOnceUnknownKeys(filters, KNOWN_FILTER_KEYS, warnedFilterKeys, (k) => `wontopos: unknown search filter ${JSON.stringify(k)} — the API drops keys it does not know, ` +
563
574
  `so this filter has NO effect and the search is wider than you think. ` +
@@ -718,22 +729,22 @@ class Client {
718
729
  if (opts.image !== undefined)
719
730
  body.image = normalizeImage(opts.image);
720
731
  warnOnUnknownMetadata(metadata);
721
- return this.post("/api/v1/memory/store", body, opts.idempotencyKey);
732
+ return this.post("/api/v1/memory/store", body, writeKey(opts, true));
722
733
  }
723
734
  async store(content, userId, metadata = {}, opts = {}) {
724
735
  return this.add(content, userId, metadata, opts);
725
736
  }
726
737
  async addTurn(userMsg, assistantMsg, userId, opts = {}) {
727
- return this.post("/api/v1/memory/store-turn", { user_id: this.uid(userId), user_msg: userMsg, assistant_msg: assistantMsg }, opts.idempotencyKey);
738
+ return this.post("/api/v1/memory/store-turn", { user_id: this.uid(userId), user_msg: userMsg, assistant_msg: assistantMsg }, writeKey(opts));
728
739
  }
729
740
  async addBulk(content, userId, category = "general", timestamp, opts = {}) {
730
- const body = { user_id: this.uid(userId), content, category };
741
+ const body = { user_id: this.uid(userId), content, category: category ?? "general" };
731
742
  if (timestamp)
732
743
  body.timestamp = timestamp;
733
- return this.post("/api/v1/memory/bulk-store", body, opts.idempotencyKey);
744
+ return this.post("/api/v1/memory/bulk-store", body, writeKey(opts));
734
745
  }
735
746
  async update(oldMemoryId, newContent, userId, opts = {}) {
736
- return this.post("/api/v1/memory/supersede", { user_id: this.uid(userId), old_memory_id: oldMemoryId, new_content: newContent }, opts.idempotencyKey);
747
+ return this.post("/api/v1/memory/supersede", { user_id: this.uid(userId), old_memory_id: oldMemoryId, new_content: newContent }, writeKey(opts));
737
748
  }
738
749
  async search(query, userId, limit = 10, opts = {}) {
739
750
  checkSearchOpts(opts);
@@ -741,7 +752,7 @@ class Client {
741
752
  limit ??= 10;
742
753
  checkCount(limit, "limit");
743
754
  const { extra, ...known } = opts;
744
- const r = await this.post("/api/v1/memory/search", { ...extra, ...known, user_id: this.uid(userId), query, max_results: limit });
755
+ const r = await this.post("/api/v1/memory/search", { ...extra, ...givenOpts(known), user_id: this.uid(userId), query, max_results: limit });
745
756
  return mergeResults(r);
746
757
  }
747
758
  async searchSelf(query, userId, limit = 10, opts = {}) {
@@ -750,7 +761,7 @@ class Client {
750
761
  limit ??= 10;
751
762
  checkCount(limit, "limit");
752
763
  const { extra, ...known } = opts;
753
- const r = await this.post("/api/v1/memory/search", { ...extra, ...known, user_id: this.uid(userId), query, max_results: limit });
764
+ const r = await this.post("/api/v1/memory/search", { ...extra, ...givenOpts(known), user_id: this.uid(userId), query, max_results: limit });
754
765
  return { memories: asRecords(r.memories), self_memories: asRecords(r.self_memories) };
755
766
  }
756
767
  async searchFull(query, userId, limit = 10, opts = {}) {
@@ -759,7 +770,7 @@ class Client {
759
770
  limit ??= 10;
760
771
  checkCount(limit, "limit");
761
772
  const { extra, ...known } = opts;
762
- const r = await this.post("/api/v1/memory/search", { ...extra, ...known, user_id: this.uid(userId), query, max_results: limit });
773
+ const r = await this.post("/api/v1/memory/search", { ...extra, ...givenOpts(known), user_id: this.uid(userId), query, max_results: limit });
763
774
  return {
764
775
  ...r,
765
776
  memories: asRecords(r.memories),
@@ -894,9 +905,13 @@ class Client {
894
905
  throw truncatedWalk(`stopped after ${MAX_PAGES} pages — the store did not end.`);
895
906
  }
896
907
  async revisions(userId, opts = {}) {
908
+ const inc = opts.include;
909
+ if (inc !== undefined && inc !== null && inc !== "revised" && inc !== "unrevised") {
910
+ throw new Error(`include must be "revised" or "unrevised", got ${JSON.stringify(inc)}.`);
911
+ }
897
912
  const limit = optRange(opts.limit, "limit", PAGE_LIMIT_MIN, PAGE_LIMIT_MAX);
898
913
  const body = {};
899
- if (opts.include !== undefined)
914
+ if (opts.include != null)
900
915
  body.include = opts.include;
901
916
  if (limit !== undefined)
902
917
  body.limit = limit;
@@ -1098,8 +1113,8 @@ class Client {
1098
1113
  }
1099
1114
  const attempts = this.maxRetries + 1;
1100
1115
  const upper = method.toUpperCase();
1101
- const idempotent = IDEMPOTENT_METHODS.has(upper);
1102
1116
  const logPath = path.split("?")[0];
1117
+ const idempotent = IDEMPOTENT_METHODS.has(upper) || reads || (upper === "POST" && READ_POSTS.has(logPath));
1103
1118
  const deadlineAt = this.deadlineAt();
1104
1119
  const headers = extraHeaders ? { ...this.headers, ...extraHeaders } : this.headers;
1105
1120
  let ambiguous = false;
@@ -1120,7 +1135,7 @@ class Client {
1120
1135
  };
1121
1136
  const cutShort = () => {
1122
1137
  const why = att.why();
1123
- if (why === "deadline" && (idempotent || reads || (upper === "POST" && READ_POSTS.has(logPath))) && previous) {
1138
+ if (why === "deadline" && idempotent && previous) {
1124
1139
  return previous;
1125
1140
  }
1126
1141
  return new APIConnectionError(0, this.abortMessage(why));
@@ -14,7 +14,7 @@
14
14
  * (`createStore`) or calls return 404. Every account starts with a `default` store,
15
15
  * so with no `userId` anywhere the zero-setup path just works.
16
16
  *
17
- * The API key picks *which memory* (your account); `model` picks *which engine*
17
+ * The API key picks *which memory* (your workspace); `model` picks *which engine*
18
18
  * reads it. Models on the shared pool read the same memory, so you can store with
19
19
  * one and recall with another; `listModels()` reports each model's `memory` as
20
20
  * `"shared"` or `"isolated"`, and an isolated one starts empty. Set a default in
@@ -22,14 +22,14 @@
22
22
  * `mem.withModel("tablet-1").recall(...)`.
23
23
  *
24
24
  * Recall quality does not depend on which language a memory was written in: a
25
- * memory stored in one language is found by a question asked in another. Storing
26
- * and searching call no LLM.
25
+ * memory stored in one language is found by a question asked in another. No language
26
+ * model reads your stored memories.
27
27
  *
28
28
  * Reliability: every call retries transient failures with exponential backoff +
29
29
  * jitter, honoring `Retry-After` up to 30s — 429 and a 409 that says another write
30
30
  * to the store was in flight, always; 408/502/503/504 and connection errors only when
31
- * a retry can never double-process a write (idempotent calls, or a failure at connect
32
- * time). Tune with `maxRetries` (0 disables) and `timeoutMs`, or per call site via
31
+ * a retry can never double-process a write (reads, the DELETE calls, or a failure at
32
+ * connect time). Tune with `maxRetries` (0 disables) and `timeoutMs`, or per call site via
33
33
  * the `withTimeout()` / `withRetries()` clones.
34
34
  *
35
35
  * Debugging: set `WONTOPOS_LOG=debug` to log method/path/status/timing/retries
@@ -89,7 +89,9 @@ export declare class BadRequestError extends WosError {
89
89
  /** 401 — the API key is missing, wrong, or revoked. */
90
90
  export declare class AuthenticationError extends WosError {
91
91
  }
92
- /** 402 — no card on file or the balance is depleted. Top up to continue. */
92
+ /** 402 — no card on file, the balance is depleted, or the month's spend cap for the
93
+ * account's tier is reached (`details.reason` is `tier_cap_reached`). Top up to
94
+ * continue; a top-up also raises the tier. */
93
95
  export declare class PaymentRequiredError extends WosError {
94
96
  }
95
97
  /** 403 — the key/model isn't allowed to do this. */
@@ -194,8 +196,9 @@ export interface StatusResult {
194
196
  /**
195
197
  * An image attached to a memory, passed to `add`/`store` via `opts.image`.
196
198
  *
197
- * Both edges must be 700px or more; a smaller image is refused (400). A request
198
- * body is capped at 10MB.
199
+ * Both edges must be 700px or more and the long edge at most 2500px, and the image
200
+ * file at most 7MB; anything else is refused (400) with the reason. A request body is
201
+ * capped at 10MB.
199
202
  *
200
203
  * What the service keeps is NOT your original. An image whose long edge is over
201
204
  * 1568px is downscaled to 1568 before anything else happens, and that smaller
@@ -241,8 +244,9 @@ export interface ImageBytes {
241
244
  * The image as stored. Write it to a file, or wrap it in a Blob to show it.
242
245
  *
243
246
  * This is the service's copy, not necessarily your upload: anything over 1568px
244
- * on its long edge was downscaled on the way in, and re-encoded to WebP unless it
245
- * was a JPEG (see `ImageInput`). Take the file extension from `contentType`.
247
+ * on its long edge (2500 at most) was downscaled on the way in, and re-encoded to
248
+ * WebP unless it was a JPEG (see `ImageInput`). Take the file extension from
249
+ * `contentType`.
246
250
  */
247
251
  bytes: Uint8Array;
248
252
  /** Sniffed from the BYTES, not from whatever the upload was named — e.g. `image/jpeg`. */
@@ -317,7 +321,8 @@ export interface LineageStep {
317
321
  is_current?: boolean;
318
322
  [key: string]: unknown;
319
323
  }
320
- /** `lineage` → the full chain of edits behind one memory, oldest first. */
324
+ /** `lineage` → the edits that followed one memory, from the id passed forward to the
325
+ * version in force, oldest first. */
321
326
  export interface LineageResult {
322
327
  memory_id?: string;
323
328
  chain: LineageStep[];
@@ -360,8 +365,8 @@ export interface SearchResult {
360
365
  /** Image memories the answer carried (one by default on an image-capable model,
361
366
  * up to `max_images`); `[]` when there were none. */
362
367
  images: Memory[];
363
- /** Re-ask passes actually performed. Present only when `verify` was sent; lower
364
- * than you asked for means the store had nothing further to add. */
368
+ /** Re-ask passes that added memories. Present only when `verify` above 0 was sent;
369
+ * lower than you asked for means a pass found nothing new, or could not run. */
365
370
  verify_used?: number;
366
371
  [key: string]: unknown;
367
372
  }
@@ -414,7 +419,7 @@ export interface UsageResult {
414
419
  output_tokens?: number;
415
420
  since?: string;
416
421
  };
417
- /** The workspace this key belongs to, over the window. */
422
+ /** The workspace this key belongs to, over the window: every key in it, not only this one. */
418
423
  workspace?: {
419
424
  workspace_id?: string | null;
420
425
  requests?: number;
@@ -542,9 +547,8 @@ export interface SpeakersList {
542
547
  [key: string]: unknown;
543
548
  }
544
549
  /**
545
- * Narrow a search to part of a store. The filter chooses what is searched, not what
546
- * is kept afterwards — so a narrow filter still returns your full `limit` when that
547
- * many matches sit inside it.
550
+ * Narrow a search to part of a store. A narrow filter can return fewer than `limit`
551
+ * even when the store holds more inside it.
548
552
  *
549
553
  * Retrieval behaves the same in every language, so these behave
550
554
  * identically in every language. Unlisted keys are dropped by the API rather than
@@ -565,7 +569,8 @@ export interface SearchFilters {
565
569
  time_from?: string;
566
570
  time_to?: string;
567
571
  /** WHEN THE CONTENT HAPPENED (`metadata.event_date`), not when it was written —
568
- * this is the one you usually want. */
572
+ * this is the one you usually want. A memory stored without an `event_date` is
573
+ * matched by when it was stored. */
569
574
  event_from?: string;
570
575
  event_to?: string;
571
576
  /** Drop matches the engine scored below this importance (0–1). */
@@ -578,9 +583,11 @@ export declare function _resetFilterWarnings(): void;
578
583
  * could not be told from an option that worked. Put a genuinely new one under
579
584
  * `extra`. */
580
585
  export interface SearchOptions {
581
- /** Recall caching. A hit inside the TTL bills at 0.1× — but the FIRST call
582
- * writes the cache and bills the query tokens at 2× (`5m`) or 3× (`1h`), so
583
- * this only pays for a query you repeat or extend. Do not enable it globally. */
586
+ /** Recall caching. The same query again inside the TTL bills at 0.1×; one that
587
+ * extends the cached query bills only its cached part at 0.1×, the new part at the
588
+ * write rate and its results at the normal rate. The FIRST call writes the cache
589
+ * and bills the query tokens at 2× (`5m`) or 3× (`1h`), so this only pays for a
590
+ * query you repeat or extend. Do not enable it globally. */
584
591
  cache_control?: {
585
592
  ttl: "5m" | "1h";
586
593
  };
@@ -591,9 +598,9 @@ export interface SearchOptions {
591
598
  /**
592
599
  * Re-ask passes, 0–3. After the first retrieval the service asks the engine again up
593
600
  * to this many times, each time telling it what was already returned so it skips
594
- * those and reaches further back. No LLM runs at any
601
+ * those and reaches further back. Raising it adds no language model call at any
595
602
  * value. Stops early when a pass finds nothing new; `verify_used` in the response
596
- * says how many actually ran.
603
+ * says how many passes added memories.
597
604
  *
598
605
  * Each pass is another engine call and can add up to `limit` more memories, so it
599
606
  * costs more — you are billed for what is delivered. Default 0. Worth it on questions
@@ -652,9 +659,10 @@ export interface SearchOptions {
652
659
  export interface WriteOptions {
653
660
  idempotencyKey?: string;
654
661
  /**
655
- * An image to store alongside the text. Needs a model that lists `images` in its
656
- * `listModels()` capabilities; any other refuses the write rather than store the
657
- * caption and quietly drop the image.
662
+ * An image to store alongside the text, read by `add` (and `store`) only: `addTurn`,
663
+ * `addBulk` and `update` warn about it and do not send it. Needs a model that lists
664
+ * `images` in its `listModels()` capabilities; any other refuses the write rather than
665
+ * store the caption and quietly drop the image.
658
666
  *
659
667
  * A caption is required: the service refuses empty `content` (400) even with an
660
668
  * image attached.
@@ -858,15 +866,16 @@ export declare class Client {
858
866
  addBulk(content: string, userId?: string, category?: string, timestamp?: string, opts?: WriteOptions): Promise<StatusResult>;
859
867
  /** Supersede an old memory with new content. Payload first, userId last — same shape as add/search. */
860
868
  update(oldMemoryId: string, newContent: string, userId?: string, opts?: WriteOptions): Promise<UpdateResult>;
861
- /** Search a store's memories. Returns them most relevant first.
869
+ /** Search a store's memories. They come most relevant first; the assistant's own
870
+ * words and image memories, when there are any, follow them in their own order.
862
871
  *
863
872
  * `limit` is 5-20, and out of range is refused rather than clamped: asking for 50
864
873
  * and silently receiving 20 reads as "that is all there is". The default is 10.
865
874
  *
866
- * `limit` bounds `memories`, not the returned array. The assistant's own words (on
867
- * a model that lists `self_memories` in its capabilities) and image memories (one
868
- * unless `max_images` says otherwise, on a model that lists `images`) come back in
869
- * it as well, so it can hold more than `limit`. They are billed either way. Size a
875
+ * `limit` bounds `memories`, not the returned array, and each `verify` pass can add
876
+ * up to `limit` more. The assistant's own words (on a model that lists
877
+ * `self_memories` in its capabilities) and image memories (one unless `max_images`
878
+ * says otherwise, on a model that lists `images`) come back after them in it as well, so it can hold more than `limit`. They are billed either way. Size a
870
879
  * prompt window on the array you get back, not on `limit`. `searchFull()` hands the
871
880
  * fields back apart.
872
881
  *
@@ -890,7 +899,7 @@ export declare class Client {
890
899
  * const r = await mem.searchFull("the day we moved", "alice", 10,
891
900
  * { max_images: 3, verify: 2 });
892
901
  * r.images.length; // the photos, apart from the text memories
893
- * r.verify_used; // how many re-ask passes actually ran (you are billed per pass)
902
+ * r.verify_used; // re-ask passes that added memories (each billed for what it added)
894
903
  *
895
904
  * Each option needs a model that lists it in its `listModels()` capabilities
896
905
  * (`images`, `re_ask`) and is refused (403) on any other rather than accepted and
@@ -927,7 +936,8 @@ export declare class Client {
927
936
  }): Promise<EngramResult>;
928
937
  /** Recent conversation turns (short-term memory). */
929
938
  history(userId?: string): Promise<HistoryTurn[]>;
930
- /** Memory counts for a store: { total_memories, short_term_turns }. */
939
+ /** Memory counts for a store: { total_memories, short_term_turns }. `total_memories`
940
+ * can be larger than the number `listMemories` lists. */
931
941
  stats(userId?: string): Promise<StatsResult>;
932
942
  /**
933
943
  * What this key has spent, and what is left — the numbers behind "can I keep going?".
@@ -935,18 +945,17 @@ export declare class Client {
935
945
  * Free: it carries no charge and skips the balance gate, because an account at zero
936
946
  * still has to be able to find out why. Rate-limited instead.
937
947
  *
938
- * Scoped to THIS key: its own lifetime spend, its workspace and stores over the
939
- * window. It never returns another key's spend. `balance_cents` is account-wide,
940
- * because that is what gates the next call whichever key makes it.
948
+ * `key` is THIS key's own lifetime spend. `workspace` and `stores` cover the window
949
+ * for the workspace this key belongs to, every key in it included. `balance_cents` is
950
+ * account-wide, because that is what gates the next call whichever key makes it.
941
951
  *
942
952
  * const u = await mem.usage(7);
943
953
  * if (u.balance_cents! < 100) stop();
944
954
  */
945
955
  usage(days?: number): Promise<UsageResult>;
946
956
  /** Fetch ONE memory by id — the text you stored, and its metadata.
947
- * The id is what `add`/`store` or `listMemories` returned. Same visibility as
948
- * `listMemories`: an id from another store, an id `listMemories` does not return,
949
- * or an invalidated memory rejects with `NotFoundError`. Pass `undefined` as
957
+ * The id is what `add`/`store` or `listMemories` returned. An unknown id or one
958
+ * from another store rejects with `NotFoundError`. Pass `undefined` as
950
959
  * `userId` for the default store — it is positional here. */
951
960
  get(userId: string | undefined, memoryId: string): Promise<Memory>;
952
961
  /** List a store's stored memories — the text you stored, plus its metadata.
@@ -992,9 +1001,10 @@ export declare class Client {
992
1001
  * Fetch the bytes of an image memory — the picture the SERVICE holds.
993
1002
  *
994
1003
  * Not necessarily your upload, in size OR in format: an image whose long edge was
995
- * over 1568px was downscaled to 1568 on the way in and re-encoded — lossless
996
- * formats as WebP, so a PNG comes back as `image/webp`; JPEG stays JPEG — and that
997
- * smaller picture is what is stored and comes back here. Nothing on our side ever
1004
+ * over 1568px (2500 at most) was downscaled to 1568 on the way in and re-encoded —
1005
+ * lossless formats as WebP, so a PNG comes back as `image/webp`; JPEG stays JPEG;
1006
+ * kept as sent when the re-encode came out bigger — and that smaller picture is what
1007
+ * is stored and comes back here. Nothing on our side ever
998
1008
  * uses more than 1568, so the extra pixels would be bytes nobody reads.
999
1009
  * Keep your own copy for the full-resolution file.
1000
1010
  *
@@ -1099,11 +1109,13 @@ export declare class Client {
1099
1109
  skipIds?: string[];
1100
1110
  }): Promise<RevisionsResult>;
1101
1111
  /**
1102
- * The full chain of edits behind one memory, oldest first.
1112
+ * The edits that followed one memory: from the id you pass forward to the version in
1113
+ * force, oldest first.
1103
1114
  *
1104
- * `is_current` marks the version in force. Use it to answer "what did this used to
1105
- * say, and when did it change" — `revisions()` tells you HOW MUCH a store moved,
1106
- * this tells you what happened to one fact.
1115
+ * `is_current` marks the version in force. The chain runs forward only, so to answer
1116
+ * "what did this used to say, and when did it change" pass the first version's id;
1117
+ * the current version's id (what `search` returns) comes back alone. `revisions()`
1118
+ * tells you HOW MUCH a store moved, this tells you what happened to one fact.
1107
1119
  */
1108
1120
  lineage(userId: string | undefined, memoryId: string): Promise<LineageResult>;
1109
1121
  /**
@@ -1125,8 +1137,7 @@ export declare class Client {
1125
1137
  * `AuthenticationError` (bad key), `PaymentRequiredError` (valid key but no card /
1126
1138
  * depleted balance), or `APIConnectionError` (unreachable). Makes one metered request. */
1127
1139
  ping(): Promise<boolean>;
1128
- /** Available models: `[{ id, name, available, memory, capabilities, retires_at? }, ...]`.
1129
- * Needs no API key. */
1140
+ /** Available models: `[{ id, name, available, memory, capabilities, retires_at? }, ...]`. */
1130
1141
  listModels(): Promise<ModelInfo[]>;
1131
1142
  /**
1132
1143
  * The engrams (and delivery forms) the selected model can actually run.
@@ -1159,7 +1170,9 @@ export declare class Client {
1159
1170
  addSpeaker(speaker: string, userId?: string): Promise<SpeakerOpResult>;
1160
1171
  /** The store's registered people, each with its memory count. */
1161
1172
  listSpeakers(userId?: string): Promise<SpeakersList>;
1162
- /** Unregister a person. Their memories stay; the name tag goes. Retried like
1173
+ /** Unregister a person. Their memories stay, and the name can no longer tag a new
1174
+ * memory or filter a search. The tag goes from stored memories too, except on
1175
+ * `tablet-2`, which keeps it on memories already stored with it. Retried like
1163
1176
  * `deleteStore`, with the same note on a 404 after an ambiguous failure. */
1164
1177
  removeSpeaker(speaker: string, userId?: string): Promise<SpeakerOpResult>;
1165
1178
  /** Delete a single memory by id. Pass `undefined` as `userId` for the default
@@ -1209,12 +1222,12 @@ export declare class Client {
1209
1222
  *
1210
1223
  * Retried: 429, and a 409 whose error carries `retry_after_ms` (another write to the
1211
1224
  * store was in flight; nothing was stored), on every method; 408/502/503/504 and
1212
- * dropped connections on idempotent methods; connect failures on every method. A
1213
- * retry whose wait is over the cap, or does not fit the deadline, is not made: the
1214
- * call fails with the error of the response it has. So does a retry the deadline
1215
- * leaves no time to send, and the retry of an idempotent method or of a POST that
1216
- * only reads that the deadline cuts short. Any other retry cut short stays a status-0
1217
- * deadline error: a write may have been applied.
1225
+ * dropped connections on a call that only reads (an idempotent method, or a POST
1226
+ * that only reads); connect failures on every method. A retry whose wait is over the
1227
+ * cap, or does not fit the deadline, is not made: the call fails with the error of
1228
+ * the response it has. So does a retry the deadline leaves no time to send, and the
1229
+ * retry of a call that only reads that the deadline cuts short. Any other retry cut
1230
+ * short stays a status-0 deadline error: a write may have been applied.
1218
1231
  */
1219
1232
  private send;
1220
1233
  /**
package/dist/wontopos.js CHANGED
@@ -1,4 +1,4 @@
1
- const VERSION = "2.2.44";
1
+ const VERSION = "2.2.46";
2
2
  const RUNTIME = (() => {
3
3
  const p = globalThis?.process;
4
4
  return p?.version ? ` (node/${p.version}; ${p.platform}-${p.arch})` : "";
@@ -492,7 +492,7 @@ const KNOWN_ENGRAM_KEYS = new Set(["form", "tz"]);
492
492
  function checkSearchOpts(opts) {
493
493
  checkOpts(opts, KNOWN_SEARCH_KEYS, "search");
494
494
  const o = opts;
495
- optRange(o.max_images !== undefined ? o.max_images : o.extra?.max_images, "max_images", 0, MAX_IMAGES_MAX);
495
+ optRange(o.max_images != null ? o.max_images : o.extra?.max_images, "max_images", 0, MAX_IMAGES_MAX);
496
496
  }
497
497
  function checkOpts(opts, known, what) {
498
498
  for (const k of Object.keys(opts)) {
@@ -542,6 +542,17 @@ function warnOnceUnknownKeys(obj, known, warned, message) {
542
542
  console.warn(message(k));
543
543
  }
544
544
  }
545
+ function givenOpts(known) {
546
+ return Object.fromEntries(Object.entries(known).filter(([, v]) => v !== null));
547
+ }
548
+ const ADD_KEYS = new Set(["idempotencyKey", "image"]);
549
+ const WRITE_KEYS = new Set(["idempotencyKey"]);
550
+ const warnedWriteKeys = new Set();
551
+ function writeKey(opts, takesImage = false) {
552
+ warnOnceUnknownKeys(opts, takesImage ? ADD_KEYS : WRITE_KEYS, warnedWriteKeys, (k) => `wontopos: write option ${JSON.stringify(k)} is not sent. ` +
553
+ (takesImage ? "add takes idempotencyKey and image." : "This call takes idempotencyKey only."));
554
+ return opts.idempotencyKey;
555
+ }
545
556
  function warnOnUnknownFilters(filters) {
546
557
  warnOnceUnknownKeys(filters, KNOWN_FILTER_KEYS, warnedFilterKeys, (k) => `wontopos: unknown search filter ${JSON.stringify(k)} — the API drops keys it does not know, ` +
547
558
  `so this filter has NO effect and the search is wider than you think. ` +
@@ -702,22 +713,22 @@ export class Client {
702
713
  if (opts.image !== undefined)
703
714
  body.image = normalizeImage(opts.image);
704
715
  warnOnUnknownMetadata(metadata);
705
- return this.post("/api/v1/memory/store", body, opts.idempotencyKey);
716
+ return this.post("/api/v1/memory/store", body, writeKey(opts, true));
706
717
  }
707
718
  async store(content, userId, metadata = {}, opts = {}) {
708
719
  return this.add(content, userId, metadata, opts);
709
720
  }
710
721
  async addTurn(userMsg, assistantMsg, userId, opts = {}) {
711
- return this.post("/api/v1/memory/store-turn", { user_id: this.uid(userId), user_msg: userMsg, assistant_msg: assistantMsg }, opts.idempotencyKey);
722
+ return this.post("/api/v1/memory/store-turn", { user_id: this.uid(userId), user_msg: userMsg, assistant_msg: assistantMsg }, writeKey(opts));
712
723
  }
713
724
  async addBulk(content, userId, category = "general", timestamp, opts = {}) {
714
- const body = { user_id: this.uid(userId), content, category };
725
+ const body = { user_id: this.uid(userId), content, category: category ?? "general" };
715
726
  if (timestamp)
716
727
  body.timestamp = timestamp;
717
- return this.post("/api/v1/memory/bulk-store", body, opts.idempotencyKey);
728
+ return this.post("/api/v1/memory/bulk-store", body, writeKey(opts));
718
729
  }
719
730
  async update(oldMemoryId, newContent, userId, opts = {}) {
720
- return this.post("/api/v1/memory/supersede", { user_id: this.uid(userId), old_memory_id: oldMemoryId, new_content: newContent }, opts.idempotencyKey);
731
+ return this.post("/api/v1/memory/supersede", { user_id: this.uid(userId), old_memory_id: oldMemoryId, new_content: newContent }, writeKey(opts));
721
732
  }
722
733
  async search(query, userId, limit = 10, opts = {}) {
723
734
  checkSearchOpts(opts);
@@ -725,7 +736,7 @@ export class Client {
725
736
  limit ??= 10;
726
737
  checkCount(limit, "limit");
727
738
  const { extra, ...known } = opts;
728
- const r = await this.post("/api/v1/memory/search", { ...extra, ...known, user_id: this.uid(userId), query, max_results: limit });
739
+ const r = await this.post("/api/v1/memory/search", { ...extra, ...givenOpts(known), user_id: this.uid(userId), query, max_results: limit });
729
740
  return mergeResults(r);
730
741
  }
731
742
  async searchSelf(query, userId, limit = 10, opts = {}) {
@@ -734,7 +745,7 @@ export class Client {
734
745
  limit ??= 10;
735
746
  checkCount(limit, "limit");
736
747
  const { extra, ...known } = opts;
737
- const r = await this.post("/api/v1/memory/search", { ...extra, ...known, user_id: this.uid(userId), query, max_results: limit });
748
+ const r = await this.post("/api/v1/memory/search", { ...extra, ...givenOpts(known), user_id: this.uid(userId), query, max_results: limit });
738
749
  return { memories: asRecords(r.memories), self_memories: asRecords(r.self_memories) };
739
750
  }
740
751
  async searchFull(query, userId, limit = 10, opts = {}) {
@@ -743,7 +754,7 @@ export class Client {
743
754
  limit ??= 10;
744
755
  checkCount(limit, "limit");
745
756
  const { extra, ...known } = opts;
746
- const r = await this.post("/api/v1/memory/search", { ...extra, ...known, user_id: this.uid(userId), query, max_results: limit });
757
+ const r = await this.post("/api/v1/memory/search", { ...extra, ...givenOpts(known), user_id: this.uid(userId), query, max_results: limit });
747
758
  return {
748
759
  ...r,
749
760
  memories: asRecords(r.memories),
@@ -878,9 +889,13 @@ export class Client {
878
889
  throw truncatedWalk(`stopped after ${MAX_PAGES} pages — the store did not end.`);
879
890
  }
880
891
  async revisions(userId, opts = {}) {
892
+ const inc = opts.include;
893
+ if (inc !== undefined && inc !== null && inc !== "revised" && inc !== "unrevised") {
894
+ throw new Error(`include must be "revised" or "unrevised", got ${JSON.stringify(inc)}.`);
895
+ }
881
896
  const limit = optRange(opts.limit, "limit", PAGE_LIMIT_MIN, PAGE_LIMIT_MAX);
882
897
  const body = {};
883
- if (opts.include !== undefined)
898
+ if (opts.include != null)
884
899
  body.include = opts.include;
885
900
  if (limit !== undefined)
886
901
  body.limit = limit;
@@ -1082,8 +1097,8 @@ export class Client {
1082
1097
  }
1083
1098
  const attempts = this.maxRetries + 1;
1084
1099
  const upper = method.toUpperCase();
1085
- const idempotent = IDEMPOTENT_METHODS.has(upper);
1086
1100
  const logPath = path.split("?")[0];
1101
+ const idempotent = IDEMPOTENT_METHODS.has(upper) || reads || (upper === "POST" && READ_POSTS.has(logPath));
1087
1102
  const deadlineAt = this.deadlineAt();
1088
1103
  const headers = extraHeaders ? { ...this.headers, ...extraHeaders } : this.headers;
1089
1104
  let ambiguous = false;
@@ -1104,7 +1119,7 @@ export class Client {
1104
1119
  };
1105
1120
  const cutShort = () => {
1106
1121
  const why = att.why();
1107
- if (why === "deadline" && (idempotent || reads || (upper === "POST" && READ_POSTS.has(logPath))) && previous) {
1122
+ if (why === "deadline" && idempotent && previous) {
1108
1123
  return previous;
1109
1124
  }
1110
1125
  return new APIConnectionError(0, this.abortMessage(why));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wontopos",
3
- "version": "2.2.44",
3
+ "version": "2.2.46",
4
4
  "description": "Wontopos \u2014 long-term memory for AI agents. Identical recall in every language, ~100\u00d7 lower LLM bill.",
5
5
  "keywords": [
6
6
  "memory",