wontopos 2.2.37 → 2.2.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -108
- package/dist/cjs/wontopos.d.cts +1054 -0
- package/dist/cjs/wontopos.js +129 -43
- package/dist/wontopos.d.ts +39 -17
- package/dist/wontopos.js +129 -43
- package/package.json +17 -7
|
@@ -0,0 +1,1054 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wontopos — long-term memory for AI, in three lines.
|
|
3
|
+
*
|
|
4
|
+
* npm install wontopos
|
|
5
|
+
*
|
|
6
|
+
* import { Client } from "wontopos";
|
|
7
|
+
* const mem = new Client({ apiKey: "wos-...", userId: "alice" }); // set the store once
|
|
8
|
+
* await mem.createStore(); // create it (uses the client's userId)
|
|
9
|
+
* await mem.add("she prefers tea over coffee"); // no userId needed
|
|
10
|
+
* const hits = await mem.search("what does alice drink?");
|
|
11
|
+
*
|
|
12
|
+
* Set `userId` once on the client and every call uses it; override any single call
|
|
13
|
+
* by passing `userId` to it. Stores are explicit: the store must exist first
|
|
14
|
+
* (`createStore`) or calls return 404. Every account starts with a `default` store,
|
|
15
|
+
* so with no `userId` anywhere the zero-setup path just works.
|
|
16
|
+
*
|
|
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
|
|
22
|
+
* `mem.withModel("tablet-1").recall(...)`.
|
|
23
|
+
*
|
|
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.
|
|
27
|
+
*
|
|
28
|
+
* 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.
|
|
33
|
+
*
|
|
34
|
+
* Debugging: set `WONTOPOS_LOG=debug` to log method/path/status/timing/retries
|
|
35
|
+
* to stderr — never memory content, request bodies, or the API key.
|
|
36
|
+
*
|
|
37
|
+
* Security posture (not configurable off): redirects are refused so the API key
|
|
38
|
+
* can never follow one to another host; responses over 64MB are refused; the key
|
|
39
|
+
* is masked in `toJSON`/inspect output. TLS certificate verification is the
|
|
40
|
+
* runtime's (never disabled by this SDK; Node 18+ floors TLS at 1.2). Prefer
|
|
41
|
+
* `Client.fromEnv()` over keys in source code.
|
|
42
|
+
*/
|
|
43
|
+
/** Test hook — the warn-once set is process-global by design. */
|
|
44
|
+
export declare function _resetStoreIdWarnings(): void;
|
|
45
|
+
export declare const SEARCH_LIMIT_MIN = 5;
|
|
46
|
+
export declare const SEARCH_LIMIT_MAX = 20;
|
|
47
|
+
/** `recall`'s surrounding-context count. 0 to 20 inclusive; 0 attaches none. */
|
|
48
|
+
export declare const CONTEXT_LIMIT_MIN = 0;
|
|
49
|
+
export declare const CONTEXT_LIMIT_MAX = 20;
|
|
50
|
+
/** Quota from a response's `X-RateLimit-*` headers, or `null` if absent. */
|
|
51
|
+
export interface RateLimit {
|
|
52
|
+
limit: number | null;
|
|
53
|
+
remaining: number | null;
|
|
54
|
+
reset: number | null;
|
|
55
|
+
}
|
|
56
|
+
export declare class WosError extends Error {
|
|
57
|
+
readonly status: number;
|
|
58
|
+
/** The server's id for the request when it sent one — include it when contacting support. */
|
|
59
|
+
readonly requestId?: string;
|
|
60
|
+
constructor(status: number, message: string, requestId?: string);
|
|
61
|
+
}
|
|
62
|
+
/** The request never got a response (DNS/TLS/timeout/connection). `status` is 0. */
|
|
63
|
+
export declare class APIConnectionError extends WosError {
|
|
64
|
+
}
|
|
65
|
+
/** 400 — the request was malformed (bad arguments). */
|
|
66
|
+
export declare class BadRequestError extends WosError {
|
|
67
|
+
}
|
|
68
|
+
/** 401 — the API key is missing, wrong, or revoked. */
|
|
69
|
+
export declare class AuthenticationError extends WosError {
|
|
70
|
+
}
|
|
71
|
+
/** 402 — no card on file or the balance is depleted. Top up to continue. */
|
|
72
|
+
export declare class PaymentRequiredError extends WosError {
|
|
73
|
+
}
|
|
74
|
+
/** 403 — the key/model isn't allowed to do this. */
|
|
75
|
+
export declare class PermissionDeniedError extends WosError {
|
|
76
|
+
}
|
|
77
|
+
/** 404 — the store or resource doesn't exist (create the store first). */
|
|
78
|
+
export declare class NotFoundError extends WosError {
|
|
79
|
+
}
|
|
80
|
+
/** 409 — a concurrent write to the same store. Retry. */
|
|
81
|
+
export declare class ConflictError extends WosError {
|
|
82
|
+
}
|
|
83
|
+
/** 429 — too many requests. Back off and retry (the client already retries these). */
|
|
84
|
+
export declare class RateLimitError extends WosError {
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 5xx — the service failed.
|
|
88
|
+
*
|
|
89
|
+
* `502` / `503` / `504` are transient: the client already retries them for calls
|
|
90
|
+
* where a retry cannot double-process a write, and retrying yourself is reasonable.
|
|
91
|
+
*
|
|
92
|
+
* `501` is NOT transient. It means the engine behind the model you selected does
|
|
93
|
+
* not implement that endpoint at all (the error carries `model` and `endpoint`).
|
|
94
|
+
* Retrying can never succeed — pick a model that supports it (`listModels`) or
|
|
95
|
+
* drop the call. Treating the whole 5xx range as retryable sends a caller into a
|
|
96
|
+
* loop that cannot end.
|
|
97
|
+
*/
|
|
98
|
+
export declare class ServerError extends WosError {
|
|
99
|
+
}
|
|
100
|
+
export interface Usage {
|
|
101
|
+
input_tokens?: number;
|
|
102
|
+
output_tokens?: number;
|
|
103
|
+
[key: string]: unknown;
|
|
104
|
+
}
|
|
105
|
+
export interface Memory {
|
|
106
|
+
id?: string;
|
|
107
|
+
content?: string;
|
|
108
|
+
/** Cognitive category, e.g. "general". */
|
|
109
|
+
category?: string;
|
|
110
|
+
/** Raw closeness to the query — higher is closer. NOT the ranking key: results already
|
|
111
|
+
* arrive best-first, and what produces that order is internal and not returned, so
|
|
112
|
+
* re-sorting by `similarity` overrides the ranking and makes results worse. Take the
|
|
113
|
+
* list in the order given. There is no `score` field. */
|
|
114
|
+
similarity?: number;
|
|
115
|
+
/** Importance weight the service assigns to the memory. */
|
|
116
|
+
importance?: number;
|
|
117
|
+
/** Month bucket the memory belongs to (e.g. "2026-07"). Omitted when temporal fields are stripped. */
|
|
118
|
+
time_bucket?: string;
|
|
119
|
+
/** True if a later memory has superseded this one. */
|
|
120
|
+
is_superseded?: boolean;
|
|
121
|
+
/** Id of the memory that superseded this one, or null. */
|
|
122
|
+
superseded_by?: string | null;
|
|
123
|
+
/** When the memory was stored (RFC3339). Omitted when temporal fields are stripped. */
|
|
124
|
+
created_at?: string;
|
|
125
|
+
/** When the content actually happened (RFC3339), if known. */
|
|
126
|
+
event_date?: string;
|
|
127
|
+
/** WHO said it: "me" (the assistant) or a registered person's name. Absent = untagged. */
|
|
128
|
+
speaker?: string;
|
|
129
|
+
[key: string]: unknown;
|
|
130
|
+
}
|
|
131
|
+
/** `add` / `store` → `{ id, status }` (`status: "stored" | "stored (async)" | "duplicate"`). */
|
|
132
|
+
export interface StoreResult {
|
|
133
|
+
id?: string;
|
|
134
|
+
/** `"stored"`, or `"duplicate"` when nothing was saved. */
|
|
135
|
+
status?: string;
|
|
136
|
+
/**
|
|
137
|
+
* Present when `status` is `"duplicate"`: the id of the memory this write collided with.
|
|
138
|
+
*
|
|
139
|
+
* A genuinely new fact that only varies a detail of one already stored ("no meetings
|
|
140
|
+
* before 10am" next to "no meetings on Fridays") can land here too, so a duplicate is
|
|
141
|
+
* not always a harmless no-op: read this id, and either store one sentence that states
|
|
142
|
+
* both or `update()` the existing memory with the combined statement. Without it a
|
|
143
|
+
* dropped write gave the caller nothing to act on.
|
|
144
|
+
*/
|
|
145
|
+
duplicate_of?: string;
|
|
146
|
+
/** Present when the store was a duplicate and something (e.g. a speaker tag) was dropped. */
|
|
147
|
+
note?: string;
|
|
148
|
+
/** `true` when this response was REPLAYED from a previous request with the same
|
|
149
|
+
* `idempotencyKey` — nothing new was stored. Absent on a fresh write. Read it to
|
|
150
|
+
* tell "my retry landed" from "my retry was a no-op". */
|
|
151
|
+
replayed?: boolean;
|
|
152
|
+
[key: string]: unknown;
|
|
153
|
+
}
|
|
154
|
+
/** `addTurn` / `addBulk` / `delete` / `deleteAll` → `{ status }`. */
|
|
155
|
+
export interface StatusResult {
|
|
156
|
+
status?: string;
|
|
157
|
+
[key: string]: unknown;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* An image attached to a memory, passed to `add`/`store` via `opts.image`.
|
|
161
|
+
*
|
|
162
|
+
* What the service keeps is NOT your original. An image whose long edge is over
|
|
163
|
+
* 1568px is downscaled to 1568 before anything else happens, and that smaller
|
|
164
|
+
* picture is what gets stored, and handed back by `getImage`. Nothing on our side
|
|
165
|
+
* ever uses more than 1568, so the extra pixels would be bytes nobody reads. This
|
|
166
|
+
* is a memory
|
|
167
|
+
* service, not a photo host: the picture it holds has the resolution of a memory.
|
|
168
|
+
*
|
|
169
|
+
* It can also come back in a different FORMAT. Downscaling means re-encoding, and
|
|
170
|
+
* we write lossless formats as WebP — send a PNG or a GIF over 1568px and `getImage`
|
|
171
|
+
* hands back `image/webp`; JPEG stays JPEG. So do not name the file from the
|
|
172
|
+
* extension you uploaded: read `contentType`. Nothing is re-encoded when the image
|
|
173
|
+
* already fits within 1568px, and a re-encode that would make the file BIGGER is
|
|
174
|
+
* thrown away and your bytes kept as they were.
|
|
175
|
+
*
|
|
176
|
+
* Keep your own copy if you need the full-resolution file. Either archive it
|
|
177
|
+
* yourself, or put its URL in `reference` — we store that string and never open it.
|
|
178
|
+
*
|
|
179
|
+
* You are billed for the picture we keep, so downscaling never costs you more.
|
|
180
|
+
*/
|
|
181
|
+
export interface ImageInput {
|
|
182
|
+
/** base64 of the image. A `data:image/...;base64,` prefix is accepted and stripped. */
|
|
183
|
+
data: string;
|
|
184
|
+
/** Where YOUR copy of the original lives. Stored as-is; the service never fetches it. */
|
|
185
|
+
reference?: string;
|
|
186
|
+
/**
|
|
187
|
+
* When the image was TAKEN (RFC3339), if you know it — usually from EXIF.
|
|
188
|
+
*
|
|
189
|
+
* Worth passing: an image knows a moment that its caption does not. This fills
|
|
190
|
+
* `metadata.event_date` when that is empty, so "the day we moved" sorts by when it
|
|
191
|
+
* happened rather than by when it was uploaded.
|
|
192
|
+
*/
|
|
193
|
+
taken_at?: string;
|
|
194
|
+
}
|
|
195
|
+
/** `getImage` → the picture the service holds, plus what it actually is. */
|
|
196
|
+
export interface ImageBytes {
|
|
197
|
+
/**
|
|
198
|
+
* The image as stored. Write it to a file, or wrap it in a Blob to show it.
|
|
199
|
+
*
|
|
200
|
+
* This is the service's copy, not necessarily your upload: anything over 1568px
|
|
201
|
+
* on its long edge was downscaled on the way in, and re-encoded to WebP unless it
|
|
202
|
+
* was a JPEG (see `ImageInput`). Take the file extension from `contentType`.
|
|
203
|
+
*/
|
|
204
|
+
bytes: Uint8Array;
|
|
205
|
+
/** Sniffed from the BYTES, not from whatever the upload was named — e.g. `image/jpeg`. */
|
|
206
|
+
contentType: string;
|
|
207
|
+
}
|
|
208
|
+
/** `forgetImage` → what happened, or (with `preview`) what would happen. */
|
|
209
|
+
export interface ImageDeleteResult {
|
|
210
|
+
/** `"image_deleted"`, or `"preview"` when nothing was touched. */
|
|
211
|
+
status?: string;
|
|
212
|
+
memory_id?: string;
|
|
213
|
+
/**
|
|
214
|
+
* Whether the MEMORY survives losing its image.
|
|
215
|
+
*
|
|
216
|
+
* A captioned image keeps its text and only loses the image. An image stored with no
|
|
217
|
+
* caption IS the memory, so deleting the image deletes the memory — and the service
|
|
218
|
+
* says so here rather than silently taking more than you asked for. Call with
|
|
219
|
+
* `preview: true` first if that distinction matters to you.
|
|
220
|
+
*/
|
|
221
|
+
memory_kept?: boolean;
|
|
222
|
+
/** Plain-language note about the above, when there is something to say. */
|
|
223
|
+
note?: string | null;
|
|
224
|
+
/** Only on a preview. */
|
|
225
|
+
would_delete_image?: boolean;
|
|
226
|
+
[key: string]: unknown;
|
|
227
|
+
}
|
|
228
|
+
/** `listImages` → one page of image memories, newest first. */
|
|
229
|
+
export interface ImagePage {
|
|
230
|
+
images: Memory[];
|
|
231
|
+
/** TOTAL images in the store, not the size of this page. */
|
|
232
|
+
count?: number;
|
|
233
|
+
has_more?: boolean;
|
|
234
|
+
/** Hand these two back as `before` / `skipIds` to get the next page. */
|
|
235
|
+
next_before?: string | null;
|
|
236
|
+
next_skip_ids?: string[];
|
|
237
|
+
[key: string]: unknown;
|
|
238
|
+
}
|
|
239
|
+
/** `revisions` → how much of this store has been edited since it was written. */
|
|
240
|
+
export interface RevisionsResult {
|
|
241
|
+
/** Memories a transform has touched (supersede, update, retract, image removed). */
|
|
242
|
+
revised?: number;
|
|
243
|
+
/** Memories nothing has touched since they were written. Always `total - revised`. */
|
|
244
|
+
unrevised?: number;
|
|
245
|
+
/** Every memory in the store. Internal records nobody stored directly are not counted. */
|
|
246
|
+
total?: number;
|
|
247
|
+
/** What `revised` counts, spelled out by the service. */
|
|
248
|
+
counts?: string;
|
|
249
|
+
/** What it does NOT count — deletions leave nothing to count. */
|
|
250
|
+
excludes?: string;
|
|
251
|
+
/** Which side was paged, echoed back. Only present when you asked for a page. */
|
|
252
|
+
include?: "revised" | "unrevised";
|
|
253
|
+
/** TOTAL memories behind this page, not the size of the page. Only with `include`. */
|
|
254
|
+
matched?: number;
|
|
255
|
+
/** One page, at most 20. **Absent unless you asked for it** — see `revisions()`. */
|
|
256
|
+
memories?: Memory[];
|
|
257
|
+
has_more?: boolean;
|
|
258
|
+
/** Hand these two back as `before` / `skipIds` to get the next page. */
|
|
259
|
+
next_before?: string | null;
|
|
260
|
+
next_skip_ids?: string[];
|
|
261
|
+
/** Which order the page is in — by when each memory was STORED, not when it was edited. */
|
|
262
|
+
ordered_by?: string;
|
|
263
|
+
[key: string]: unknown;
|
|
264
|
+
}
|
|
265
|
+
/** One link in a memory's supersede chain. */
|
|
266
|
+
export interface LineageStep {
|
|
267
|
+
memory_id?: string;
|
|
268
|
+
content?: string;
|
|
269
|
+
speaker?: string | null;
|
|
270
|
+
created_at?: string | null;
|
|
271
|
+
/** When this link was superseded (RFC3339), or null while it is still current. */
|
|
272
|
+
changed_at?: string | null;
|
|
273
|
+
/** What happened here — e.g. how the replacement related to this version. */
|
|
274
|
+
action?: string | null;
|
|
275
|
+
confidence?: number | null;
|
|
276
|
+
superseded_by?: string | null;
|
|
277
|
+
/** True for the one version that is still in force. */
|
|
278
|
+
is_current?: boolean;
|
|
279
|
+
[key: string]: unknown;
|
|
280
|
+
}
|
|
281
|
+
/** `lineage` → the full chain of edits behind one memory, oldest first. */
|
|
282
|
+
export interface LineageResult {
|
|
283
|
+
memory_id?: string;
|
|
284
|
+
chain: LineageStep[];
|
|
285
|
+
count?: number;
|
|
286
|
+
/** True when the chain was longer than the service would walk. */
|
|
287
|
+
truncated?: boolean;
|
|
288
|
+
[key: string]: unknown;
|
|
289
|
+
}
|
|
290
|
+
/** `bySpeaker` → one page of what a given person said. */
|
|
291
|
+
export interface SpeakerPage {
|
|
292
|
+
speaker?: string;
|
|
293
|
+
memories: Memory[];
|
|
294
|
+
/** How many underlying records a delete would actually remove, beyond what was returned. */
|
|
295
|
+
chunks?: number;
|
|
296
|
+
points_to_delete?: number;
|
|
297
|
+
returned?: number;
|
|
298
|
+
has_more?: boolean;
|
|
299
|
+
next_before?: string | null;
|
|
300
|
+
next_skip_ids?: string[];
|
|
301
|
+
[key: string]: unknown;
|
|
302
|
+
}
|
|
303
|
+
/** `update` (supersede) → old and new memory ids. */
|
|
304
|
+
export interface UpdateResult {
|
|
305
|
+
old_memory_id?: string;
|
|
306
|
+
new_memory_id?: string;
|
|
307
|
+
status?: string;
|
|
308
|
+
[key: string]: unknown;
|
|
309
|
+
}
|
|
310
|
+
/** `searchFull` → everything one search answered with, not just the merged memories.
|
|
311
|
+
*
|
|
312
|
+
* `search` returns the memories and nothing else, which is the right answer for
|
|
313
|
+
* almost every call. Two options make it the wrong one: `max_images` asks for
|
|
314
|
+
* photos, which arrive in their own field, and `verify` is reported on by
|
|
315
|
+
* `verify_used`. Merging away both means paying for a
|
|
316
|
+
* search you shaped and never seeing what came of it. */
|
|
317
|
+
export interface SearchResult {
|
|
318
|
+
/** What others said, and general memories. */
|
|
319
|
+
memories: Memory[];
|
|
320
|
+
/** The assistant's own words (speaker "me"); `[]` on a model that does not keep
|
|
321
|
+
* them apart. */
|
|
322
|
+
self_memories: Memory[];
|
|
323
|
+
/** Image memories, when `max_images` asked for any; `[]` otherwise. */
|
|
324
|
+
images: Memory[];
|
|
325
|
+
/** Re-ask passes actually performed. Present only when `verify` was sent; lower
|
|
326
|
+
* than you asked for means the store had nothing further to add. */
|
|
327
|
+
verify_used?: number;
|
|
328
|
+
[key: string]: unknown;
|
|
329
|
+
}
|
|
330
|
+
export interface RecallResult {
|
|
331
|
+
short_term: {
|
|
332
|
+
turns: unknown[];
|
|
333
|
+
count: number;
|
|
334
|
+
};
|
|
335
|
+
long_term: {
|
|
336
|
+
memories: Memory[];
|
|
337
|
+
count: number;
|
|
338
|
+
};
|
|
339
|
+
context: {
|
|
340
|
+
around_top_memory: unknown[];
|
|
341
|
+
count: number;
|
|
342
|
+
};
|
|
343
|
+
[key: string]: unknown;
|
|
344
|
+
}
|
|
345
|
+
/** `engram` → the merged multi-hop result. */
|
|
346
|
+
export interface EngramResult {
|
|
347
|
+
engram?: string;
|
|
348
|
+
form?: string;
|
|
349
|
+
user_id?: string;
|
|
350
|
+
hops?: number;
|
|
351
|
+
count?: number;
|
|
352
|
+
memories?: Memory[];
|
|
353
|
+
usage?: Usage;
|
|
354
|
+
[key: string]: unknown;
|
|
355
|
+
}
|
|
356
|
+
export interface HistoryTurn {
|
|
357
|
+
user_msg?: string;
|
|
358
|
+
assistant_msg?: string;
|
|
359
|
+
timestamp?: string;
|
|
360
|
+
[key: string]: unknown;
|
|
361
|
+
}
|
|
362
|
+
/** `usage` → what this key has spent, and what is left to spend. */
|
|
363
|
+
export interface UsageResult {
|
|
364
|
+
window_days?: number;
|
|
365
|
+
/** This API key's LIFETIME spend. Never another key's. */
|
|
366
|
+
key?: {
|
|
367
|
+
requests?: number;
|
|
368
|
+
cost_cents?: number;
|
|
369
|
+
input_tokens?: number;
|
|
370
|
+
output_tokens?: number;
|
|
371
|
+
since?: string;
|
|
372
|
+
};
|
|
373
|
+
/** The workspace this key belongs to, over the window. */
|
|
374
|
+
workspace?: {
|
|
375
|
+
workspace_id?: string | null;
|
|
376
|
+
requests?: number;
|
|
377
|
+
cost_cents?: number;
|
|
378
|
+
};
|
|
379
|
+
/** Per-store spend over the window, busiest first, and at most 50 rows — a longer
|
|
380
|
+
* list is cut, so these need not sum to `workspace`. `other`, when present, is an
|
|
381
|
+
* overflow bucket rather than a store. */
|
|
382
|
+
stores?: {
|
|
383
|
+
store?: string;
|
|
384
|
+
requests?: number;
|
|
385
|
+
cost_cents?: number;
|
|
386
|
+
}[];
|
|
387
|
+
/** Prepaid balance left on the ACCOUNT. Negative means overdrawn. */
|
|
388
|
+
balance_cents?: number;
|
|
389
|
+
expiring_soon_cents?: number;
|
|
390
|
+
[key: string]: unknown;
|
|
391
|
+
}
|
|
392
|
+
export interface StatsResult {
|
|
393
|
+
total_memories?: number;
|
|
394
|
+
short_term_turns?: number;
|
|
395
|
+
[key: string]: unknown;
|
|
396
|
+
}
|
|
397
|
+
/** `listMemories` → one page of a store's raw memories (the text you stored, and its metadata). */
|
|
398
|
+
export interface MemoryPage {
|
|
399
|
+
memories: Memory[];
|
|
400
|
+
count: number;
|
|
401
|
+
/** Pass back as `cursor` for the next page; `null` on the last page. */
|
|
402
|
+
next_cursor: string | null;
|
|
403
|
+
[key: string]: unknown;
|
|
404
|
+
}
|
|
405
|
+
/** `searchSelf` result: general memories and the assistant's own, kept apart. */
|
|
406
|
+
export interface SelfSearchResult {
|
|
407
|
+
/** What others said, and general memories. */
|
|
408
|
+
memories: Memory[];
|
|
409
|
+
/** The assistant's own words (stored with speaker "me"); `[]` on non-self models. */
|
|
410
|
+
self_memories: Memory[];
|
|
411
|
+
}
|
|
412
|
+
export interface ModelInfo {
|
|
413
|
+
id: string;
|
|
414
|
+
name: string;
|
|
415
|
+
available: boolean;
|
|
416
|
+
/** "shared" — reads the common memory pool; "isolated" — its own dedicated store. */
|
|
417
|
+
memory: "shared" | "isolated";
|
|
418
|
+
}
|
|
419
|
+
/** One entry of the engram / delivery-form catalogue (`listEngrams`). */
|
|
420
|
+
export interface EngramInfo {
|
|
421
|
+
name: string;
|
|
422
|
+
description: string;
|
|
423
|
+
[key: string]: unknown;
|
|
424
|
+
}
|
|
425
|
+
/** `listEngrams` → what the SELECTED model can run. `forms` is empty on models
|
|
426
|
+
* without delivery forms; `note` explains an empty `engrams`. */
|
|
427
|
+
export interface EngramCatalog {
|
|
428
|
+
engrams: EngramInfo[];
|
|
429
|
+
forms: EngramInfo[];
|
|
430
|
+
note?: string;
|
|
431
|
+
/** Anything else the reply carried. Responses widen; the runtime keeps those fields
|
|
432
|
+
* and this is what lets a typed caller read them. */
|
|
433
|
+
[key: string]: unknown;
|
|
434
|
+
}
|
|
435
|
+
/** `createStore` / `deleteStore` → `{ user_id, status }`. */
|
|
436
|
+
export interface StoreOpResult {
|
|
437
|
+
/** The store id the API actually used — NOT necessarily the one you sent.
|
|
438
|
+
* Ids are normalized (see the `note`), so compare this against your input. */
|
|
439
|
+
user_id?: string;
|
|
440
|
+
status?: string;
|
|
441
|
+
/** Present when the API changed your id, explaining how. Read it: two of your
|
|
442
|
+
* end users can land in one store if their ids differ only by punctuation. */
|
|
443
|
+
note?: string;
|
|
444
|
+
[key: string]: unknown;
|
|
445
|
+
}
|
|
446
|
+
export interface StoreInfo {
|
|
447
|
+
user_id: string;
|
|
448
|
+
created_at: string;
|
|
449
|
+
[key: string]: unknown;
|
|
450
|
+
}
|
|
451
|
+
/** `addSpeaker` / `removeSpeaker` → `{ user_id, speaker, status }`. */
|
|
452
|
+
export interface SpeakerOpResult {
|
|
453
|
+
user_id?: string;
|
|
454
|
+
speaker?: string;
|
|
455
|
+
status?: string;
|
|
456
|
+
[key: string]: unknown;
|
|
457
|
+
}
|
|
458
|
+
export interface SpeakerInfo {
|
|
459
|
+
speaker: string;
|
|
460
|
+
memories: number;
|
|
461
|
+
created_at?: string;
|
|
462
|
+
[key: string]: unknown;
|
|
463
|
+
}
|
|
464
|
+
/** `listSpeakers` → registered people with per-person memory counts. */
|
|
465
|
+
export interface SpeakersList {
|
|
466
|
+
speakers: SpeakerInfo[];
|
|
467
|
+
count?: number;
|
|
468
|
+
/** The registration cap for this store (50 to start). */
|
|
469
|
+
limit?: number;
|
|
470
|
+
[key: string]: unknown;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Narrow a search to part of a store. The filter chooses what is searched, not what
|
|
474
|
+
* is kept afterwards — so a narrow filter still returns your full `limit` when that
|
|
475
|
+
* many matches sit inside it.
|
|
476
|
+
*
|
|
477
|
+
* Retrieval behaves the same in every language, so these behave
|
|
478
|
+
* identically in every language. Unlisted keys are dropped by the API rather than
|
|
479
|
+
* rejected, so a typo silently widens the search — spell them exactly.
|
|
480
|
+
*/
|
|
481
|
+
export interface SearchFilters {
|
|
482
|
+
/** Only these categories (the `category` you see on `listMemories` results). */
|
|
483
|
+
categories?: string[];
|
|
484
|
+
/** Ingestion-time window, as the API stores it. Plain strings, matched as given. */
|
|
485
|
+
time_from?: string;
|
|
486
|
+
time_to?: string;
|
|
487
|
+
/** WHEN THE CONTENT HAPPENED (`metadata.event_date`), not when it was written —
|
|
488
|
+
* this is the one you usually want. RFC3339, or a plain `YYYY-MM-DD`. */
|
|
489
|
+
event_from?: string;
|
|
490
|
+
event_to?: string;
|
|
491
|
+
/** Drop matches the engine scored below this importance (0–1). */
|
|
492
|
+
min_importance?: number;
|
|
493
|
+
}
|
|
494
|
+
/** Test hook — the warn-once set is process-global by design. */
|
|
495
|
+
export declare function _resetFilterWarnings(): void;
|
|
496
|
+
/** Known `search` options. An unknown key is refused before anything is sent — the
|
|
497
|
+
* service drops keys it does not recognise and answers normally, so a misspelling
|
|
498
|
+
* could not be told from an option that worked. Put a genuinely new one under
|
|
499
|
+
* `extra`. */
|
|
500
|
+
export interface SearchOptions {
|
|
501
|
+
/** Recall caching. A hit inside the TTL bills at 0.1× — but the FIRST call
|
|
502
|
+
* writes the cache and bills the query tokens at 2× (`5m`) or 3× (`1h`), so
|
|
503
|
+
* this only pays for a query you repeat or extend. Do not enable it globally. */
|
|
504
|
+
cache_control?: {
|
|
505
|
+
ttl: "5m" | "1h";
|
|
506
|
+
};
|
|
507
|
+
/** Recall only this person's words ("me" or a registered name). */
|
|
508
|
+
speaker?: string;
|
|
509
|
+
/** Restrict the search to part of the store — see {@link SearchFilters}. */
|
|
510
|
+
filters?: SearchFilters;
|
|
511
|
+
/**
|
|
512
|
+
* Re-ask passes, 0–3. After the first retrieval the service asks the engine again up
|
|
513
|
+
* to this many times, each time telling it what was already returned so it skips
|
|
514
|
+
* those and reaches further back. No LLM runs at any
|
|
515
|
+
* value. Stops early when a pass finds nothing new; `verify_used` in the response
|
|
516
|
+
* says how many actually ran.
|
|
517
|
+
*
|
|
518
|
+
* Each pass is another engine call and can add up to `limit` more memories, so it
|
|
519
|
+
* costs more — you are billed for what is delivered. Default 0. Worth it on questions
|
|
520
|
+
* that need several distinct memories from far apart in the history; it does little
|
|
521
|
+
* on a single-fact lookup.
|
|
522
|
+
*
|
|
523
|
+
* Requires a re-ask-capable model. Older ones REFUSE the call (403) rather than
|
|
524
|
+
* quietly charging you for passes that never happened.
|
|
525
|
+
*/
|
|
526
|
+
verify?: number;
|
|
527
|
+
/**
|
|
528
|
+
* How many image memories the answer may carry, 0–5. Omit it and the service uses
|
|
529
|
+
* 1; `0` asks for none. The MCP server defaults its own tool to 0 instead, so most
|
|
530
|
+
* searches through it carry no image rows.
|
|
531
|
+
* Out of range is refused, not clamped — silently cutting 5 to 1 would leave you
|
|
532
|
+
* believing you got five.
|
|
533
|
+
*
|
|
534
|
+
* Requires an image-capable model, and is refused (403) on one without it rather
|
|
535
|
+
* than answering with no images.
|
|
536
|
+
*/
|
|
537
|
+
max_images?: number;
|
|
538
|
+
/**
|
|
539
|
+
* Fields the service accepts that this version does not know about, merged into the
|
|
540
|
+
* request as written. Every other key is refused, so a typo cannot reach the wire —
|
|
541
|
+
* `verfy: 3` used to be sent, dropped by the service, and answered normally, leaving
|
|
542
|
+
* a caller billed for one pass believing they had bought three.
|
|
543
|
+
*/
|
|
544
|
+
extra?: Record<string, unknown>;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Per-call options for a write.
|
|
548
|
+
*
|
|
549
|
+
* `idempotencyKey` makes repeating THIS EXACT write safe: the API replays the first
|
|
550
|
+
* response instead of storing again, for 10 minutes, and answers 422 if the same key
|
|
551
|
+
* arrives with a different body. Use it when a retry is your own (a job that died and
|
|
552
|
+
* was re-run, a queue that redelivers). The SDK retries a write on exactly one
|
|
553
|
+
* status: 429, which the service answers before it processes anything, so nothing
|
|
554
|
+
* was stored. It never retries a write on 408 / 502 / 503 / 504 or a dropped body,
|
|
555
|
+
* where the first attempt may already have landed — without a key the client cannot
|
|
556
|
+
* know whether it did.
|
|
557
|
+
*
|
|
558
|
+
* The key must be UNIQUE PER LOGICAL WRITE — derive it from the thing being stored
|
|
559
|
+
* (`` `import:${row.id}` ``), never a constant, or the second write replays the first
|
|
560
|
+
* and is silently lost. Format: 1-128 chars of `[A-Za-z0-9._:-]`, checked locally.
|
|
561
|
+
*
|
|
562
|
+
* The window is in-memory on the API, so a deploy or restart clears it early. It is a
|
|
563
|
+
* guard against a retry storm, not a durable ledger.
|
|
564
|
+
*/
|
|
565
|
+
export interface WriteOptions {
|
|
566
|
+
idempotencyKey?: string;
|
|
567
|
+
/**
|
|
568
|
+
* An image to store alongside the text (Tablet 2 and newer).
|
|
569
|
+
*
|
|
570
|
+
* `content` may be empty when you pass one — then the image IS the memory and is
|
|
571
|
+
* searchable on its own. Older models have no image channel and will reject the
|
|
572
|
+
* write rather than store the caption and quietly drop the image.
|
|
573
|
+
*/
|
|
574
|
+
image?: ImageInput;
|
|
575
|
+
}
|
|
576
|
+
export interface ClientOptions {
|
|
577
|
+
apiKey: string;
|
|
578
|
+
/** API base URL (defaults to the hosted service). */
|
|
579
|
+
baseUrl?: string;
|
|
580
|
+
/** Per-request timeout in ms, applied to each retry attempt (default 30000). */
|
|
581
|
+
timeoutMs?: number;
|
|
582
|
+
/** Default model for every call (sent as `X-WOS-Model`). See `listModels()`. */
|
|
583
|
+
model?: string;
|
|
584
|
+
/** Default store for every call. Override per call by passing `userId`. Defaults
|
|
585
|
+
* to the account's built-in `default` store. */
|
|
586
|
+
userId?: string;
|
|
587
|
+
/** How many times to retry transient failures before throwing — 429 always;
|
|
588
|
+
* 408/502/503/504 and connection errors only when a retry can never
|
|
589
|
+
* double-process a write. 0 disables retries (default 2). */
|
|
590
|
+
maxRetries?: number;
|
|
591
|
+
/** Alias of `maxRetries`. The Python SDK calls this option `retries`, and these
|
|
592
|
+
* SDKs are published in lockstep as "the same surface" — so code ported between
|
|
593
|
+
* them silently lost its retry setting instead of failing loudly (an unknown
|
|
594
|
+
* key on an options object is not an error in TS at runtime). Both names work;
|
|
595
|
+
* `maxRetries` wins if somehow both are given. */
|
|
596
|
+
retries?: number;
|
|
597
|
+
/** A total budget for one call, in ms, across every attempt.
|
|
598
|
+
*
|
|
599
|
+
* `timeoutMs` bounds ONE attempt. At the defaults — 30s, two retries — a single
|
|
600
|
+
* call can hold a connection for 30s + backoff + 30s + backoff + 30s, over a
|
|
601
|
+
* minute, and a server handler awaiting it has no way to say "I only have five
|
|
602
|
+
* seconds". This is that way. Unset means no overall budget. */
|
|
603
|
+
deadlineMs?: number;
|
|
604
|
+
/** The caller's `AbortSignal` — cancel work already in flight.
|
|
605
|
+
*
|
|
606
|
+
* The SDK aborts on its own timeout; this is the seam for the caller's reason.
|
|
607
|
+
* When someone closes the chat window, the recall that window asked for should
|
|
608
|
+
* end, and the backoff sleep waiting to retry it should end too, instead of
|
|
609
|
+
* running to completion and billing for an answer nobody will read.
|
|
610
|
+
*
|
|
611
|
+
* Per call, clone: `mem.withSignal(ctrl.signal).recall(...)`. */
|
|
612
|
+
signal?: AbortSignal;
|
|
613
|
+
/** The `fetch` used for every request (default: the global `fetch`).
|
|
614
|
+
*
|
|
615
|
+
* This is the seam for anything the runtime cannot express through options.
|
|
616
|
+
* The one that matters in practice is a corporate egress proxy: Node's global
|
|
617
|
+
* fetch (undici) IGNORES `HTTP_PROXY` / `HTTPS_PROXY`, so behind such a proxy no
|
|
618
|
+
* request leaves at all. Pass a proxy-aware fetch and they go through:
|
|
619
|
+
*
|
|
620
|
+
* import { ProxyAgent } from "undici";
|
|
621
|
+
* const agent = new ProxyAgent(process.env.HTTPS_PROXY!);
|
|
622
|
+
* const mem = new Client({
|
|
623
|
+
* apiKey,
|
|
624
|
+
* fetch: (url, init) => fetch(url, { ...init, dispatcher: agent } as any),
|
|
625
|
+
* });
|
|
626
|
+
*
|
|
627
|
+
* It is also how you add instrumentation or drive the client in a test without
|
|
628
|
+
* a network. Retries, timeouts, redirect refusal and the size cap all still
|
|
629
|
+
* apply — this replaces the transport, not the client's rules. */
|
|
630
|
+
fetch?: typeof fetch;
|
|
631
|
+
}
|
|
632
|
+
/** Wontopos memory client. Store and recall memories, isolated per `userId`. */
|
|
633
|
+
export declare class Client {
|
|
634
|
+
private readonly base;
|
|
635
|
+
private readonly headers;
|
|
636
|
+
private readonly timeoutMs;
|
|
637
|
+
private readonly apiKey;
|
|
638
|
+
private readonly model;
|
|
639
|
+
private readonly maxRetries;
|
|
640
|
+
private readonly deadlineMs?;
|
|
641
|
+
private readonly signal?;
|
|
642
|
+
/** Transport. Held as a field (not read off `globalThis` per call) so a caller
|
|
643
|
+
* who passes one gets it for every request, including from cloned clients. */
|
|
644
|
+
private readonly fetchImpl;
|
|
645
|
+
/** The store every call uses unless one passes `userId`. */
|
|
646
|
+
private readonly defaultUser;
|
|
647
|
+
private _rateLimit;
|
|
648
|
+
/** Quota from the MOST RECENT call: `{ limit, remaining, reset }` (or `null` before
|
|
649
|
+
* the first call). Read it to self-throttle — the client already retries 429s, but
|
|
650
|
+
* this lets you slow down before hitting the wall. */
|
|
651
|
+
get rateLimit(): RateLimit | null;
|
|
652
|
+
constructor(opts: ClientOptions);
|
|
653
|
+
/** A client whose key comes from `WONTOPOS_API_KEY` (or `WOS_API_KEY`) — keeps
|
|
654
|
+
* keys out of source code. Node/Bun/Deno-with-env only. */
|
|
655
|
+
static fromEnv(opts?: Omit<ClientOptions, "apiKey">): Client;
|
|
656
|
+
/** Never expose the key: `JSON.stringify(client)` gets the redacted view. */
|
|
657
|
+
toJSON(): Record<string, string>;
|
|
658
|
+
/** Resolve a call's store: the explicit userId, else the client default. */
|
|
659
|
+
private uid;
|
|
660
|
+
private clone;
|
|
661
|
+
/**
|
|
662
|
+
* A client that uses `model` (sent as `X-WOS-Model`). Use it to set the default
|
|
663
|
+
* (`new Client({ apiKey, model })`) or to override one call
|
|
664
|
+
* (`client.withModel("tablet-1").recall(...)`).
|
|
665
|
+
*/
|
|
666
|
+
withModel(model: string): Client;
|
|
667
|
+
/** A client bound to `userId` as its default store (everything else kept). */
|
|
668
|
+
withUser(userId: string): Client;
|
|
669
|
+
/** A client bound to a caller's `AbortSignal` (everything else kept).
|
|
670
|
+
*
|
|
671
|
+
* const ctrl = new AbortController();
|
|
672
|
+
* req.on("close", () => ctrl.abort());
|
|
673
|
+
* const r = await mem.withSignal(ctrl.signal).recall(q, user);
|
|
674
|
+
*
|
|
675
|
+
* Aborting ends the request in flight AND any backoff sleep waiting to retry it. */
|
|
676
|
+
withSignal(signal: AbortSignal): Client;
|
|
677
|
+
/** A client with a total budget per call, across every retry (everything else
|
|
678
|
+
* kept). `mem.withDeadline(5_000).search(q)` inside a handler that has five
|
|
679
|
+
* seconds. */
|
|
680
|
+
withDeadline(deadlineMs: number): Client;
|
|
681
|
+
/** A client with a different per-request timeout in ms (everything else kept).
|
|
682
|
+
* `mem.withTimeout(120_000).addBulk(bigBlob)` — this slow call only. */
|
|
683
|
+
withTimeout(timeoutMs: number): Client;
|
|
684
|
+
/** A client with a different retry budget (everything else kept). 0 disables retries. */
|
|
685
|
+
withRetries(maxRetries: number): Client;
|
|
686
|
+
/** Store one memory. `metadata` optional — e.g. `{ event_date: "2026-03-01" }` for when the
|
|
687
|
+
* content actually happened. A plain date works (read as UTC midnight); a full RFC3339
|
|
688
|
+
* timestamp also works; older examples here show the long form.
|
|
689
|
+
* `metadata.speaker`: "me" = the assistant's own words, or a person's name (up to 50 per store).
|
|
690
|
+
*
|
|
691
|
+
* `opts.idempotencyKey`: see {@link WriteOptions} — pass one to make YOUR retry
|
|
692
|
+
* of this exact write safe to repeat. */
|
|
693
|
+
add(content: string, userId?: string, metadata?: Record<string, unknown>, opts?: WriteOptions): Promise<StoreResult>;
|
|
694
|
+
/** Alias of `add` — store one memory. Same surface as the Python SDK's `store`. */
|
|
695
|
+
store(content: string, userId?: string, metadata?: Record<string, unknown>, opts?: WriteOptions): Promise<StoreResult>;
|
|
696
|
+
/** Store a conversation turn (user + assistant). Payload first, userId last — same shape as add/search. */
|
|
697
|
+
addTurn(userMsg: string, assistantMsg: string, userId?: string, opts?: WriteOptions): Promise<StatusResult>;
|
|
698
|
+
/** Bulk-ingest a large blob of text in one call. For backfilling.
|
|
699
|
+
* The call most worth an `opts.idempotencyKey`: a backfill that dies halfway and is
|
|
700
|
+
* re-run would otherwise ingest the whole blob a second time. */
|
|
701
|
+
addBulk(content: string, userId?: string, category?: string, timestamp?: string, opts?: WriteOptions): Promise<StatusResult>;
|
|
702
|
+
/** Supersede an old memory with new content. Payload first, userId last — same shape as add/search. */
|
|
703
|
+
update(oldMemoryId: string, newContent: string, userId?: string, opts?: WriteOptions): Promise<UpdateResult>;
|
|
704
|
+
/** Search a store's memories. Returns them most relevant first.
|
|
705
|
+
*
|
|
706
|
+
* `limit` is 5-20, and out of range is refused rather than clamped: asking for 50
|
|
707
|
+
* and silently receiving 20 reads as "that is all there is". The default is 10, so
|
|
708
|
+
* a call that passes no count is unaffected. Before 2.2.35 the count was sent on
|
|
709
|
+
* unchecked.
|
|
710
|
+
*
|
|
711
|
+
* `limit` bounds `memories`, not the returned array. On a model that keeps the
|
|
712
|
+
* assistant's own words separate (Scroll 1.2+) those come back as well, so the
|
|
713
|
+
* array can hold more than `limit`. They were retrieved and billed either way;
|
|
714
|
+
* dropping them would only hide what you already paid for. Size a prompt window
|
|
715
|
+
* on the array you get back, not on `limit`. `searchSelf()` hands the two back
|
|
716
|
+
* apart. */
|
|
717
|
+
search(query: string, userId?: string, limit?: number, opts?: SearchOptions): Promise<Memory[]>;
|
|
718
|
+
/** Search a self-memory model (Scroll 1.2+): both fields from ONE call. Returns
|
|
719
|
+
* `{ memories, self_memories }` — `memories` is what others said and general
|
|
720
|
+
* memories, `self_memories` is the assistant's OWN words (stored with speaker
|
|
721
|
+
* "me"), kept apart so whoever reads them never confuses who said what. On a model
|
|
722
|
+
* that does not keep them apart, `self_memories` is `[]`. `userId` may be omitted. */
|
|
723
|
+
searchSelf(query: string, userId?: string, limit?: number, opts?: SearchOptions): Promise<SelfSearchResult>;
|
|
724
|
+
/**
|
|
725
|
+
* Search, and keep every field the answer came with.
|
|
726
|
+
*
|
|
727
|
+
* Same request as {@link Client.search} — reach for this one when the options make
|
|
728
|
+
* the merged array an incomplete answer:
|
|
729
|
+
*
|
|
730
|
+
* const r = await mem.searchFull("the day we moved", "alice", 10,
|
|
731
|
+
* { max_images: 3, verify: 2 });
|
|
732
|
+
* r.images.length; // the photos, which `search` drops
|
|
733
|
+
* r.verify_used; // how many re-ask passes actually ran (you are billed per pass)
|
|
734
|
+
*
|
|
735
|
+
* Both options need Tablet 2 or newer and are refused (403) on an older engine
|
|
736
|
+
* rather than accepted and ignored.
|
|
737
|
+
*/
|
|
738
|
+
searchFull(query: string, userId?: string, limit?: number, opts?: SearchOptions): Promise<SearchResult>;
|
|
739
|
+
/** One-call LLM context: short-term turns + long-term matches + surrounding context.
|
|
740
|
+
* `form` ("memoir"/"archive", Scroll 1.2+) renders each long-term memory's time in
|
|
741
|
+
* that form; `tz` is your UTC-offset hours for that rendering. */
|
|
742
|
+
recall(query: string, userId?: string, opts?: {
|
|
743
|
+
form?: string;
|
|
744
|
+
tz?: number;
|
|
745
|
+
/**
|
|
746
|
+
* Long-term memories to recall, 5–20. Default 10. Out of range is refused, not
|
|
747
|
+
* clamped — asking for 20 and silently getting 10 reads as "that is all there is".
|
|
748
|
+
*
|
|
749
|
+
* Needs a limit-aware model. An older one recalls a fixed ten no matter what you
|
|
750
|
+
* send, so the API refuses the call (403) rather than answering with a number you
|
|
751
|
+
* did not ask for.
|
|
752
|
+
*/
|
|
753
|
+
limit?: number;
|
|
754
|
+
/** How much surrounding context is attached around the best match, 0–20. Default 10;
|
|
755
|
+
* 0 attaches none. Same model floor as `limit`. */
|
|
756
|
+
context_limit?: number;
|
|
757
|
+
}): Promise<RecallResult>;
|
|
758
|
+
/** Run a built-in engram ("deep_recall" | "timeline" | "gather" | "equilibrium" |
|
|
759
|
+
* "tone_stabilizer"; the service is the authority — an unknown name comes back with
|
|
760
|
+
* the list it accepts). Returns the merged result.
|
|
761
|
+
* `form`/`tz` render memory times (memoir/archive) on Scroll 1.2+, same as search/recall. */
|
|
762
|
+
engram(name: string, query: string, userId?: string, opts?: {
|
|
763
|
+
form?: string;
|
|
764
|
+
tz?: number;
|
|
765
|
+
}): Promise<EngramResult>;
|
|
766
|
+
/** Recent conversation turns (short-term memory). */
|
|
767
|
+
history(userId?: string): Promise<HistoryTurn[]>;
|
|
768
|
+
/** Memory counts for a store: { total_memories, short_term_turns }. */
|
|
769
|
+
stats(userId?: string): Promise<StatsResult>;
|
|
770
|
+
/**
|
|
771
|
+
* What this key has spent, and what is left — the numbers behind "can I keep going?".
|
|
772
|
+
*
|
|
773
|
+
* Free: it carries no charge and skips the balance gate, because an account at zero
|
|
774
|
+
* still has to be able to find out why. Rate-limited instead.
|
|
775
|
+
*
|
|
776
|
+
* Scoped to THIS key: its own lifetime spend, its workspace and stores over the
|
|
777
|
+
* window. It never returns another key's spend. `balance_cents` is account-wide,
|
|
778
|
+
* because that is what gates the next call whichever key makes it.
|
|
779
|
+
*
|
|
780
|
+
* const u = await mem.usage(7);
|
|
781
|
+
* if (u.balance_cents! < 100) stop();
|
|
782
|
+
*/
|
|
783
|
+
usage(days?: number): Promise<UsageResult>;
|
|
784
|
+
/** Fetch ONE memory by id — the text you stored, and its metadata.
|
|
785
|
+
* The id is what `add`/`store` or `listMemories` returned. Same visibility as
|
|
786
|
+
* `listMemories`: an id from another store, an internal record id, or an
|
|
787
|
+
* invalidated memory rejects with `NotFoundError`. Pass `undefined` as `userId` for
|
|
788
|
+
* the default store — it is positional here, not omittable as it is in Python. */
|
|
789
|
+
get(userId: string | undefined, memoryId: string): Promise<Memory>;
|
|
790
|
+
/** List a store's stored memories — the text you stored, plus its metadata.
|
|
791
|
+
* Paginated: pass the returned `next_cursor` back as `cursor` for the next
|
|
792
|
+
* page; a `null` cursor means the last page. Use it to browse or export a store.
|
|
793
|
+
*
|
|
794
|
+
* let cursor: string | null = null;
|
|
795
|
+
* const all: Memory[] = [];
|
|
796
|
+
* do {
|
|
797
|
+
* const page = await mem.listMemories(undefined, { cursor: cursor ?? undefined });
|
|
798
|
+
* all.push(...page.memories);
|
|
799
|
+
* cursor = page.next_cursor;
|
|
800
|
+
* } while (cursor);
|
|
801
|
+
*/
|
|
802
|
+
listMemories(userId?: string, opts?: {
|
|
803
|
+
limit?: number;
|
|
804
|
+
cursor?: string;
|
|
805
|
+
}): Promise<MemoryPage>;
|
|
806
|
+
/** Async-iterate every stored memory in a store, paging under the hood — no cursor
|
|
807
|
+
* bookkeeping. The text you stored and its metadata only.
|
|
808
|
+
*
|
|
809
|
+
* for await (const m of mem.iterMemories()) console.log(m.id, m.content);
|
|
810
|
+
*/
|
|
811
|
+
iterMemories(userId?: string, opts?: {
|
|
812
|
+
pageSize?: number;
|
|
813
|
+
}): AsyncGenerator<Memory>;
|
|
814
|
+
/** Collect ALL of a store's memories into an array (the text you stored, and its metadata). */
|
|
815
|
+
exportMemories(userId?: string): Promise<Memory[]>;
|
|
816
|
+
/** Collect ALL of a store's image memories into an array — the image-side pair of
|
|
817
|
+
* `exportMemories`. */
|
|
818
|
+
exportImages(userId?: string, opts?: {
|
|
819
|
+
pageSize?: number;
|
|
820
|
+
}): Promise<Memory[]>;
|
|
821
|
+
/**
|
|
822
|
+
* Fetch the bytes of an image memory — the picture the SERVICE holds.
|
|
823
|
+
*
|
|
824
|
+
* Not necessarily your upload, in size OR in format: an image whose long edge was
|
|
825
|
+
* over 1568px was downscaled to 1568 on the way in and re-encoded — lossless
|
|
826
|
+
* formats as WebP, so a PNG comes back as `image/webp`; JPEG stays JPEG — and that
|
|
827
|
+
* smaller picture is what is stored and comes back here. Nothing on our side ever
|
|
828
|
+
* uses more than 1568, so the extra pixels would be bytes nobody reads.
|
|
829
|
+
* Keep your own copy for the full-resolution file.
|
|
830
|
+
*
|
|
831
|
+
* The type is sniffed from the bytes themselves rather than from whatever the file
|
|
832
|
+
* was called, so name the file from `contentType` rather than from what you sent.
|
|
833
|
+
* When the format changed, the response also carries `x-wos-image-converted-from`
|
|
834
|
+
* naming what you uploaded. Rejects with `NotFoundError` when this memory has no
|
|
835
|
+
* image, or when the service keeps no image bytes at all — it says "no" instead of
|
|
836
|
+
* handing back something empty, so "a memory with no image" never looks the same as
|
|
837
|
+
* "an image we lost".
|
|
838
|
+
*
|
|
839
|
+
* const { bytes, contentType } = await mem.getImage(undefined, id);
|
|
840
|
+
* const ext = contentType.split("/")[1]; // "webp" for a downscaled PNG
|
|
841
|
+
* await fs.writeFile(`image.${ext}`, bytes);
|
|
842
|
+
*/
|
|
843
|
+
getImage(userId: string | undefined, memoryId: string): Promise<ImageBytes>;
|
|
844
|
+
/**
|
|
845
|
+
* Remove the PHOTO from a memory, keeping its text.
|
|
846
|
+
*
|
|
847
|
+
* Except when there is no text: an image stored without a caption *is* the memory, so
|
|
848
|
+
* deleting the image deletes it. That is the one case worth checking before you
|
|
849
|
+
* commit, which is what `preview` is for — it reports `memory_kept` and changes
|
|
850
|
+
* nothing.
|
|
851
|
+
*
|
|
852
|
+
* const p = await mem.forgetImage(undefined, id, { preview: true });
|
|
853
|
+
* if (p.memory_kept === false) { /* this would delete the whole memory *\/ }
|
|
854
|
+
*/
|
|
855
|
+
forgetImage(userId: string | undefined, memoryId: string, opts?: {
|
|
856
|
+
preview?: boolean;
|
|
857
|
+
}): Promise<ImageDeleteResult>;
|
|
858
|
+
/**
|
|
859
|
+
* List a store's image memories, newest first, and count them.
|
|
860
|
+
*
|
|
861
|
+
* `count` is the TOTAL in the store, not the size of the page — so you can show
|
|
862
|
+
* "142 images" without walking every page. Paging is by cursor, not offset: hand
|
|
863
|
+
* `next_before` and `next_skip_ids` back as `before` / `skipIds`. The pair exists
|
|
864
|
+
* because several images can share a timestamp, and a timestamp alone would either
|
|
865
|
+
* repeat them or skip them.
|
|
866
|
+
*/
|
|
867
|
+
listImages(userId?: string, opts?: {
|
|
868
|
+
limit?: number;
|
|
869
|
+
before?: string;
|
|
870
|
+
skipIds?: string[];
|
|
871
|
+
}): Promise<ImagePage>;
|
|
872
|
+
/** Async-iterate every image memory, paging under the hood. */
|
|
873
|
+
iterImages(userId?: string, opts?: {
|
|
874
|
+
pageSize?: number;
|
|
875
|
+
}): AsyncGenerator<Memory>;
|
|
876
|
+
/**
|
|
877
|
+
* How much of this store has been altered since it was written.
|
|
878
|
+
*
|
|
879
|
+
* This one is aimed at the MODEL, not at you. An assistant leaning on its own memory
|
|
880
|
+
* should be able to ask how far that memory has been edited underneath it — a store
|
|
881
|
+
* nobody has touched reads differently from one where a third of the facts were
|
|
882
|
+
* rewritten. Free, and it never reaches the retrieval path.
|
|
883
|
+
*
|
|
884
|
+
* Counts memories a transform touched (supersede, update, retract, image removed).
|
|
885
|
+
* Deletions are NOT counted: a deleted memory leaves nothing to count. Neither are
|
|
886
|
+
* the internal records derived from what you stored — nobody stored those directly,
|
|
887
|
+
* so they do not belong in a ratio that answers "how much of MY memory changed".
|
|
888
|
+
*
|
|
889
|
+
* By default this returns COUNTS ONLY, and the answer is the same size for a store
|
|
890
|
+
* of a hundred memories and a store of a hundred million. That is deliberate: a model
|
|
891
|
+
* asks this mid-conversation, and an answer that grew with the store would be
|
|
892
|
+
* unusable for exactly the customers who most need to ask.
|
|
893
|
+
*
|
|
894
|
+
* To see WHICH memories, ask for one side and page through it — at most 20 per call,
|
|
895
|
+
* and never both sides in one response:
|
|
896
|
+
*
|
|
897
|
+
* ```ts
|
|
898
|
+
* const { revised, unrevised, total } = await mem.revisions(); // numbers only
|
|
899
|
+
*
|
|
900
|
+
* let before: string | undefined, skipIds: string[] | undefined;
|
|
901
|
+
* for (;;) {
|
|
902
|
+
* const p = await mem.revisions(undefined, { include: "revised", before, skipIds });
|
|
903
|
+
* for (const m of p.memories ?? []) console.log(m.content);
|
|
904
|
+
* if (!p.has_more || !p.next_before) break;
|
|
905
|
+
* before = p.next_before;
|
|
906
|
+
* skipIds = p.next_skip_ids;
|
|
907
|
+
* }
|
|
908
|
+
* ```
|
|
909
|
+
*
|
|
910
|
+
* Pages are ordered by when each memory was STORED, not by when it was edited.
|
|
911
|
+
* The response says so in `ordered_by`.
|
|
912
|
+
*
|
|
913
|
+
* Served from `/api/v1/won/*`, not `/api/v1/memory/*`. Won is the surface for calls
|
|
914
|
+
* a model makes ABOUT its memory rather than calls an application makes WITH it, and
|
|
915
|
+
* the address says so. The old path still answers, for clients published before
|
|
916
|
+
* 2026-08-18, and both share one rate-limit budget.
|
|
917
|
+
*/
|
|
918
|
+
revisions(userId?: string, opts?: {
|
|
919
|
+
/**
|
|
920
|
+
* Omit for counts only. Set it to ALSO get one page of the memories behind that
|
|
921
|
+
* number. One side per call — there is no way to ask for both lists at once.
|
|
922
|
+
*/
|
|
923
|
+
include?: "revised" | "unrevised";
|
|
924
|
+
/** Memories per page. 20 is both the default and the ceiling; more is rejected. */
|
|
925
|
+
limit?: number;
|
|
926
|
+
/** Cursor: `next_before` from the previous page. */
|
|
927
|
+
before?: string;
|
|
928
|
+
/** Cursor: `next_skip_ids` from the previous page. Hand back what you were given. */
|
|
929
|
+
skipIds?: string[];
|
|
930
|
+
}): Promise<RevisionsResult>;
|
|
931
|
+
/**
|
|
932
|
+
* The full chain of edits behind one memory, oldest first.
|
|
933
|
+
*
|
|
934
|
+
* `is_current` marks the version in force. Use it to answer "what did this used to
|
|
935
|
+
* say, and when did it change" — `revisions()` tells you HOW MUCH a store moved,
|
|
936
|
+
* this tells you what happened to one fact.
|
|
937
|
+
*/
|
|
938
|
+
lineage(userId: string | undefined, memoryId: string): Promise<LineageResult>;
|
|
939
|
+
/**
|
|
940
|
+
* What one person said, newest first.
|
|
941
|
+
*
|
|
942
|
+
* `speaker` is the tag written at store time (`metadata.speaker`) — `"me"` for the
|
|
943
|
+
* assistant's own words, otherwise a person's name. Same cursor paging as
|
|
944
|
+
* `listImages`.
|
|
945
|
+
*
|
|
946
|
+
* `chunks` / `points_to_delete` report how many internal records a delete would
|
|
947
|
+
* actually remove — usually more than `returned`, and worth showing to whoever is
|
|
948
|
+
* about to confirm one.
|
|
949
|
+
*/
|
|
950
|
+
bySpeaker(speaker: string, userId?: string, opts?: {
|
|
951
|
+
limit?: number;
|
|
952
|
+
before?: string;
|
|
953
|
+
skipIds?: string[];
|
|
954
|
+
}): Promise<SpeakerPage>;
|
|
955
|
+
/** Check connectivity AND that the API key works. Resolves `true`, or rejects — an
|
|
956
|
+
* `AuthenticationError` (bad key), `PaymentRequiredError` (valid key but no card /
|
|
957
|
+
* depleted balance), or `APIConnectionError` (unreachable). Makes one metered request. */
|
|
958
|
+
ping(): Promise<boolean>;
|
|
959
|
+
/** Available models: `[{ id, name, available, memory }, ...]`. Needs no API key. */
|
|
960
|
+
listModels(): Promise<ModelInfo[]>;
|
|
961
|
+
/**
|
|
962
|
+
* The engrams (and delivery forms) the selected model can actually run.
|
|
963
|
+
*
|
|
964
|
+
* Ask rather than hard-code: a name copied from the docs freezes a caller to the
|
|
965
|
+
* catalogue as it was that day, and anything added later stays invisible. The
|
|
966
|
+
* service is the authority. What
|
|
967
|
+
* comes back depends on the model (delivery forms need Scroll 1.2+), so pass
|
|
968
|
+
* `withModel()` if you want another model's catalogue.
|
|
969
|
+
*
|
|
970
|
+
* const { engrams, forms } = await mem.listEngrams();
|
|
971
|
+
*/
|
|
972
|
+
listEngrams(): Promise<EngramCatalog>;
|
|
973
|
+
/** Create a store — the `userId` you read and write under. Stores are explicit:
|
|
974
|
+
* a store must exist before you `add` to or `search` it, otherwise those calls
|
|
975
|
+
* return 404. Idempotent. Every account starts with a `default` store.
|
|
976
|
+
* Returns `{ user_id, status }` (`status` is `"created"` or `"exists"`). */
|
|
977
|
+
createStore(userId?: string): Promise<StoreOpResult>;
|
|
978
|
+
/** List your stores: `[{ user_id, created_at }, ...]` (`default` first). */
|
|
979
|
+
listStores(): Promise<StoreInfo[]>;
|
|
980
|
+
/** Delete a store and ALL its memories. Returns `{ user_id, status }`. */
|
|
981
|
+
deleteStore(userId: string): Promise<StoreOpResult>;
|
|
982
|
+
/** Register a person for this store. Speakers are explicit: register once, then
|
|
983
|
+
* store with `{ speaker }`. `"me"` (the assistant itself) never needs
|
|
984
|
+
* registration. A store registers up to 50 people to start. */
|
|
985
|
+
addSpeaker(speaker: string, userId?: string): Promise<SpeakerOpResult>;
|
|
986
|
+
/** The store's registered people, each with its memory count. */
|
|
987
|
+
listSpeakers(userId?: string): Promise<SpeakersList>;
|
|
988
|
+
/** Unregister a person. Their memories stay; the name tag goes. */
|
|
989
|
+
removeSpeaker(speaker: string, userId?: string): Promise<SpeakerOpResult>;
|
|
990
|
+
/** Delete a single memory by id. Pass `undefined` as `userId` for the default
|
|
991
|
+
* store — it is positional here, not omittable as it is in Python. */
|
|
992
|
+
delete(userId: string | undefined, memoryId: string): Promise<StatusResult>;
|
|
993
|
+
/** Delete ALL memories for a store (GDPR erase). `userId` is required on purpose -
|
|
994
|
+
* this is destructive, so it never falls back to the default store. */
|
|
995
|
+
deleteAll(userId: string): Promise<StatusResult>;
|
|
996
|
+
private post;
|
|
997
|
+
/**
|
|
998
|
+
* One request that answers with BYTES rather than JSON.
|
|
999
|
+
*
|
|
1000
|
+
* Only `/memory/image` does this, and it is why this cannot go through `request()`:
|
|
1001
|
+
* that path reads the body as text and insists the result parses to a JSON object.
|
|
1002
|
+
* Feeding it a JPEG would throw "invalid JSON in response" — a confusing error for a
|
|
1003
|
+
* call that actually succeeded.
|
|
1004
|
+
*
|
|
1005
|
+
* Errors still arrive as JSON, so those are handed back to the normal machinery: on
|
|
1006
|
+
* a non-2xx we re-read the body as text and reuse `errorFor`, which keeps 404
|
|
1007
|
+
* (`NotFoundError`) and 401 behaving the same as everywhere else.
|
|
1008
|
+
*/
|
|
1009
|
+
/** Where this call's budget runs out, or `undefined` when it has none. Computed
|
|
1010
|
+
* once per call, not per attempt — a budget recomputed each attempt is not a
|
|
1011
|
+
* budget. */
|
|
1012
|
+
private deadlineAt;
|
|
1013
|
+
/** The abort wiring for ONE attempt: this SDK's per-attempt timeout, the caller's
|
|
1014
|
+
* signal, and whatever is left of the overall deadline — whichever fires first.
|
|
1015
|
+
*
|
|
1016
|
+
* One function, two callers (`request` and `requestBytes`), because a guard living
|
|
1017
|
+
* in one of two request paths is exactly how `getImage` ended up with no retry loop
|
|
1018
|
+
* while the module doc promised every call had one. */
|
|
1019
|
+
private beginAttempt;
|
|
1020
|
+
/** What to say when an attempt was cut short. The three reasons need different
|
|
1021
|
+
* words: a timeout may have landed server-side, a cancellation certainly did not
|
|
1022
|
+
* matter to the caller, and an exhausted budget is the caller's own setting. */
|
|
1023
|
+
private abortMessage;
|
|
1024
|
+
/** Sleep between attempts — but wake the moment the caller aborts, and give up
|
|
1025
|
+
* rather than wait out a backoff the budget cannot cover. A backoff that runs to
|
|
1026
|
+
* completion after the caller gave up wastes exactly as long as the request it was
|
|
1027
|
+
* waiting to repeat.
|
|
1028
|
+
*
|
|
1029
|
+
* Clamping the wait to what is left and retrying anyway is the same answer as
|
|
1030
|
+
* having no deadline at all: the call still spends the whole allowance, and the one
|
|
1031
|
+
* attempt it buys has nothing left to finish in. If the wait does not fit, the
|
|
1032
|
+
* budget is already decided. */
|
|
1033
|
+
private backoffSleep;
|
|
1034
|
+
private requestBytes;
|
|
1035
|
+
/** Validate an idempotency key BEFORE the network. The API answers a malformed
|
|
1036
|
+
* key with a 400, which on a retry path reads as "my write failed" when in fact
|
|
1037
|
+
* it was never attempted — cheaper and clearer to reject it here. */
|
|
1038
|
+
private idemHeader;
|
|
1039
|
+
/** Seconds→ms to sleep before retry `attempt` (0-based). Honors Retry-After. */
|
|
1040
|
+
private backoffMs;
|
|
1041
|
+
/** Read the body with a hard size cap, so a broken/hostile endpoint can't
|
|
1042
|
+
* make the process buffer gigabytes. */
|
|
1043
|
+
private readCapped;
|
|
1044
|
+
/** The same cap, for a body that is not text.
|
|
1045
|
+
*
|
|
1046
|
+
* `arrayBuffer()` would buffer whatever arrives. The cap exists for a hostile or
|
|
1047
|
+
* broken `baseUrl`, and an endpoint that returns megabytes is where that costs the
|
|
1048
|
+
* most. One implementation, two callers, so they cannot drift. */
|
|
1049
|
+
private readCappedBytes;
|
|
1050
|
+
private request;
|
|
1051
|
+
}
|
|
1052
|
+
/** Back-compat alias: older code used `WME`. */
|
|
1053
|
+
export declare const WME: typeof Client;
|
|
1054
|
+
export default Client;
|