wontopos 2.2.38 → 2.2.40
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 +7 -9
- package/dist/cjs/wontopos.d.cts +57 -44
- package/dist/cjs/wontopos.js +87 -40
- package/dist/wontopos.d.ts +57 -44
- package/dist/wontopos.js +87 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,12 +80,12 @@ const hits = await mem.search("...the conversation so far...", "alice", 10,
|
|
|
80
80
|
|
|
81
81
|
Built in, no configuration needed:
|
|
82
82
|
|
|
83
|
-
- **Automatic retries** — 429 always, and 502 / 503 or a connection error
|
|
84
|
-
retry cannot double-process a write. The writes and the searches are
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
- **Automatic retries** — 429 always, and 408 / 502 / 503 / 504 or a connection error
|
|
84
|
+
only when a retry cannot double-process a write. The writes and the searches are
|
|
85
|
+
POSTs, and any of those four may have been returned *after* the write already
|
|
86
|
+
landed, so those get 429 and connect-level failures only. The reads and the
|
|
87
87
|
deletes that address a whole store — `listStores`, `listSpeakers`, `deleteStore`,
|
|
88
|
-
`removeSpeaker`, `forgetImage` — are GET or DELETE and do retry
|
|
88
|
+
`removeSpeaker`, `forgetImage` — are GET or DELETE and do retry all four. Twice, with
|
|
89
89
|
exponential backoff + jitter, honoring the server's `Retry-After`. Tune with
|
|
90
90
|
`new Client({ maxRetries })`; `0` disables.
|
|
91
91
|
- **Redirects refused** — the API key never follows a 3xx to another host.
|
|
@@ -106,9 +106,7 @@ Built in, no configuration needed:
|
|
|
106
106
|
### Behind a corporate proxy
|
|
107
107
|
|
|
108
108
|
Node's global `fetch` (undici) ignores `HTTP_PROXY` / `HTTPS_PROXY`, so behind an
|
|
109
|
-
egress proxy
|
|
110
|
-
through, because `requests` and `reqwest` both read those variables. Pass a
|
|
111
|
-
proxy-aware `fetch` and the three behave the same:
|
|
109
|
+
egress proxy no request leaves at all. Pass a proxy-aware `fetch` and they go through:
|
|
112
110
|
|
|
113
111
|
This one needs `npm i undici` — Node's global `fetch` is built on undici, but the
|
|
114
112
|
package itself is not installed for you.
|
|
@@ -211,7 +209,7 @@ data to fix it.
|
|
|
211
209
|
## Changelog
|
|
212
210
|
|
|
213
211
|
The three clients release in lockstep — same version, same surface, same day. Patch
|
|
214
|
-
releases are additive.
|
|
212
|
+
releases are additive. Six inside 2.2 were not, deliberately and each with its
|
|
215
213
|
reason; the changelog lists them.
|
|
216
214
|
|
|
217
215
|
See [CHANGELOG.md](https://github.com/wontopos/wontopos-node/blob/main/CHANGELOG.md).
|
package/dist/cjs/wontopos.d.cts
CHANGED
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
* and searching call no LLM.
|
|
27
27
|
*
|
|
28
28
|
* Reliability: every call retries transient failures with exponential backoff +
|
|
29
|
-
* jitter, honoring `Retry-After` — 429 always; 502/503 and connection errors
|
|
30
|
-
* when a retry can never double-process a write (idempotent calls, or a failure
|
|
29
|
+
* jitter, honoring `Retry-After` — 429 always; 408/502/503/504 and connection errors
|
|
30
|
+
* only when a retry can never double-process a write (idempotent calls, or a failure
|
|
31
31
|
* at connect time). Tune with `maxRetries` (0 disables) and `timeoutMs`, or per
|
|
32
32
|
* call site via the `withTimeout()` / `withRetries()` clones.
|
|
33
33
|
*
|
|
@@ -74,10 +74,11 @@ export declare class PaymentRequiredError extends WosError {
|
|
|
74
74
|
/** 403 — the key/model isn't allowed to do this. */
|
|
75
75
|
export declare class PermissionDeniedError extends WosError {
|
|
76
76
|
}
|
|
77
|
-
/** 404 — the store or resource doesn't exist
|
|
77
|
+
/** 404 — the store or resource doesn't exist. */
|
|
78
78
|
export declare class NotFoundError extends WosError {
|
|
79
79
|
}
|
|
80
|
-
/** 409 —
|
|
80
|
+
/** 409 — another write to this store was in flight (nothing was stored; retry), or the
|
|
81
|
+
* store id collides with an existing store's (permanent). */
|
|
81
82
|
export declare class ConflictError extends WosError {
|
|
82
83
|
}
|
|
83
84
|
/** 429 — too many requests. Back off and retry (the client already retries these). */
|
|
@@ -86,8 +87,8 @@ export declare class RateLimitError extends WosError {
|
|
|
86
87
|
/**
|
|
87
88
|
* 5xx — the service failed.
|
|
88
89
|
*
|
|
89
|
-
* `502` / `503` are transient: the client already retries them for calls
|
|
90
|
-
* retry cannot double-process a write, and retrying yourself is reasonable.
|
|
90
|
+
* `502` / `503` / `504` are transient: the client already retries them for calls
|
|
91
|
+
* where a retry cannot double-process a write, and retrying yourself is reasonable.
|
|
91
92
|
*
|
|
92
93
|
* `501` is NOT transient. It means the engine behind the model you selected does
|
|
93
94
|
* not implement that endpoint at all (the error carries `model` and `endpoint`).
|
|
@@ -159,6 +160,8 @@ export interface StatusResult {
|
|
|
159
160
|
/**
|
|
160
161
|
* An image attached to a memory, passed to `add`/`store` via `opts.image`.
|
|
161
162
|
*
|
|
163
|
+
* Both edges must be 700px or more; a smaller image is refused (400).
|
|
164
|
+
*
|
|
162
165
|
* What the service keeps is NOT your original. An image whose long edge is over
|
|
163
166
|
* 1568px is downscaled to 1568 before anything else happens, and that smaller
|
|
164
167
|
* picture is what gets stored, and handed back by `getImage`. Nothing on our side
|
|
@@ -291,8 +294,8 @@ export interface LineageResult {
|
|
|
291
294
|
export interface SpeakerPage {
|
|
292
295
|
speaker?: string;
|
|
293
296
|
memories: Memory[];
|
|
294
|
-
/** How many underlying records a delete would actually remove, beyond what was returned. */
|
|
295
297
|
chunks?: number;
|
|
298
|
+
/** The count to show before anyone confirms a delete of this speaker's memories. */
|
|
296
299
|
points_to_delete?: number;
|
|
297
300
|
returned?: number;
|
|
298
301
|
has_more?: boolean;
|
|
@@ -307,20 +310,17 @@ export interface UpdateResult {
|
|
|
307
310
|
status?: string;
|
|
308
311
|
[key: string]: unknown;
|
|
309
312
|
}
|
|
310
|
-
/** `searchFull` → everything one search answered with
|
|
311
|
-
*
|
|
312
|
-
* `
|
|
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. */
|
|
313
|
+
/** `searchFull` → everything one search answered with. `search` returns every memory
|
|
314
|
+
* as one array; this keeps the fields apart and adds `verify_used`, the report on
|
|
315
|
+
* the `verify` option. */
|
|
317
316
|
export interface SearchResult {
|
|
318
317
|
/** What others said, and general memories. */
|
|
319
318
|
memories: Memory[];
|
|
320
319
|
/** The assistant's own words (speaker "me"); `[]` on a model that does not keep
|
|
321
320
|
* them apart. */
|
|
322
321
|
self_memories: Memory[];
|
|
323
|
-
/** Image memories
|
|
322
|
+
/** Image memories the answer carried (one by default on an image-capable model,
|
|
323
|
+
* up to `max_images`); `[]` when there were none. */
|
|
324
324
|
images: Memory[];
|
|
325
325
|
/** Re-ask passes actually performed. Present only when `verify` was sent; lower
|
|
326
326
|
* than you asked for means the store had nothing further to add. */
|
|
@@ -428,21 +428,27 @@ export interface EngramCatalog {
|
|
|
428
428
|
engrams: EngramInfo[];
|
|
429
429
|
forms: EngramInfo[];
|
|
430
430
|
note?: string;
|
|
431
|
+
/** Anything else the reply carried. Responses widen; the runtime keeps those fields
|
|
432
|
+
* and this is what lets a typed caller read them. */
|
|
433
|
+
[key: string]: unknown;
|
|
431
434
|
}
|
|
432
435
|
/** `createStore` / `deleteStore` → `{ user_id, status }`. */
|
|
433
436
|
export interface StoreOpResult {
|
|
434
|
-
/** The store id
|
|
435
|
-
* Ids are normalized (see the `note`), so compare this against your input. */
|
|
437
|
+
/** The store id. `createStore` answers with the id as you sent it. */
|
|
436
438
|
user_id?: string;
|
|
439
|
+
/** The normalized form the store is filed under, when it differs from `user_id`. */
|
|
440
|
+
canonical_id?: string;
|
|
437
441
|
status?: string;
|
|
438
|
-
/** Present when the API
|
|
439
|
-
* end users can land in one store if their ids differ only by punctuation. */
|
|
442
|
+
/** Present when the API filed your id under a normalized form, explaining how. */
|
|
440
443
|
note?: string;
|
|
441
444
|
[key: string]: unknown;
|
|
442
445
|
}
|
|
443
446
|
export interface StoreInfo {
|
|
447
|
+
/** The id the store was created with — pass it back as `userId`. */
|
|
444
448
|
user_id: string;
|
|
445
449
|
created_at: string;
|
|
450
|
+
/** The normalized form the store is filed under, when it differs from `user_id`. */
|
|
451
|
+
canonical_id?: string;
|
|
446
452
|
[key: string]: unknown;
|
|
447
453
|
}
|
|
448
454
|
/** `addSpeaker` / `removeSpeaker` → `{ user_id, speaker, status }`. */
|
|
@@ -532,11 +538,15 @@ export interface SearchOptions {
|
|
|
532
538
|
* than answering with no images.
|
|
533
539
|
*/
|
|
534
540
|
max_images?: number;
|
|
541
|
+
/** Delivery form, "memoir" or "archive" (Scroll 1.2+): how each memory's time is
|
|
542
|
+
* rendered. The service refuses an unknown one (400). */
|
|
543
|
+
form?: string;
|
|
544
|
+
/** Your UTC offset in hours, for rendering times in `form`. */
|
|
545
|
+
tz?: number;
|
|
535
546
|
/**
|
|
536
547
|
* Fields the service accepts that this version does not know about, merged into the
|
|
537
|
-
* request as written. Every other key is refused, so a typo cannot reach the wire
|
|
538
|
-
*
|
|
539
|
-
* a caller billed for one pass believing they had bought three.
|
|
548
|
+
* request as written. Every other key is refused, so a typo cannot reach the wire.
|
|
549
|
+
* The store, the query and the count always win over a copy in here.
|
|
540
550
|
*/
|
|
541
551
|
extra?: Record<string, unknown>;
|
|
542
552
|
}
|
|
@@ -548,9 +558,9 @@ export interface SearchOptions {
|
|
|
548
558
|
* arrives with a different body. Use it when a retry is your own (a job that died and
|
|
549
559
|
* was re-run, a queue that redelivers). The SDK retries a write on exactly one
|
|
550
560
|
* status: 429, which the service answers before it processes anything, so nothing
|
|
551
|
-
* was stored. It never retries a write on 502 / 503 or a dropped body,
|
|
552
|
-
*
|
|
553
|
-
* whether
|
|
561
|
+
* was stored. It never retries a write on 408 / 502 / 503 / 504 or a dropped body,
|
|
562
|
+
* where the first attempt may already have landed — without a key the client cannot
|
|
563
|
+
* know whether it did.
|
|
554
564
|
*
|
|
555
565
|
* The key must be UNIQUE PER LOGICAL WRITE — derive it from the thing being stored
|
|
556
566
|
* (`` `import:${row.id}` ``), never a constant, or the second write replays the first
|
|
@@ -582,8 +592,8 @@ export interface ClientOptions {
|
|
|
582
592
|
* to the account's built-in `default` store. */
|
|
583
593
|
userId?: string;
|
|
584
594
|
/** How many times to retry transient failures before throwing — 429 always;
|
|
585
|
-
* 502/503 and connection errors only when a retry can never
|
|
586
|
-
* write. 0 disables retries (default 2). */
|
|
595
|
+
* 408/502/503/504 and connection errors only when a retry can never
|
|
596
|
+
* double-process a write. 0 disables retries (default 2). */
|
|
587
597
|
maxRetries?: number;
|
|
588
598
|
/** Alias of `maxRetries`. The Python SDK calls this option `retries`, and these
|
|
589
599
|
* SDKs are published in lockstep as "the same surface" — so code ported between
|
|
@@ -611,10 +621,8 @@ export interface ClientOptions {
|
|
|
611
621
|
*
|
|
612
622
|
* This is the seam for anything the runtime cannot express through options.
|
|
613
623
|
* The one that matters in practice is a corporate egress proxy: Node's global
|
|
614
|
-
* fetch (undici) IGNORES `HTTP_PROXY` / `HTTPS_PROXY`, so behind such a proxy
|
|
615
|
-
*
|
|
616
|
-
* through, because `requests` and `reqwest` both read those variables. Pass a
|
|
617
|
-
* proxy-aware fetch and the three behave the same again:
|
|
624
|
+
* fetch (undici) IGNORES `HTTP_PROXY` / `HTTPS_PROXY`, so behind such a proxy no
|
|
625
|
+
* request leaves at all. Pass a proxy-aware fetch and they go through:
|
|
618
626
|
*
|
|
619
627
|
* import { ProxyAgent } from "undici";
|
|
620
628
|
* const agent = new ProxyAgent(process.env.HTTPS_PROXY!);
|
|
@@ -696,7 +704,7 @@ export declare class Client {
|
|
|
696
704
|
addTurn(userMsg: string, assistantMsg: string, userId?: string, opts?: WriteOptions): Promise<StatusResult>;
|
|
697
705
|
/** Bulk-ingest a large blob of text in one call. For backfilling.
|
|
698
706
|
* The call most worth an `opts.idempotencyKey`: a backfill that dies halfway and is
|
|
699
|
-
* re-run would otherwise
|
|
707
|
+
* re-run would otherwise ingest the whole blob a second time. */
|
|
700
708
|
addBulk(content: string, userId?: string, category?: string, timestamp?: string, opts?: WriteOptions): Promise<StatusResult>;
|
|
701
709
|
/** Supersede an old memory with new content. Payload first, userId last — same shape as add/search. */
|
|
702
710
|
update(oldMemoryId: string, newContent: string, userId?: string, opts?: WriteOptions): Promise<UpdateResult>;
|
|
@@ -707,18 +715,18 @@ export declare class Client {
|
|
|
707
715
|
* a call that passes no count is unaffected. Before 2.2.35 the count was sent on
|
|
708
716
|
* unchecked.
|
|
709
717
|
*
|
|
710
|
-
* `limit` bounds `memories`, not the returned array.
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
*
|
|
715
|
-
* apart. */
|
|
718
|
+
* `limit` bounds `memories`, not the returned array. The assistant's own words
|
|
719
|
+
* (Scroll 1.2+) and image memories (Tablet 2+, one unless `max_images` says
|
|
720
|
+
* otherwise) come back in it as well, so it can hold more than `limit`. They are
|
|
721
|
+
* billed either way. Size a prompt window on the array you get back, not on
|
|
722
|
+
* `limit`. `searchFull()` hands the fields back apart. */
|
|
716
723
|
search(query: string, userId?: string, limit?: number, opts?: SearchOptions): Promise<Memory[]>;
|
|
717
724
|
/** Search a self-memory model (Scroll 1.2+): both fields from ONE call. Returns
|
|
718
725
|
* `{ memories, self_memories }` — `memories` is what others said and general
|
|
719
726
|
* memories, `self_memories` is the assistant's OWN words (stored with speaker
|
|
720
727
|
* "me"), kept apart so whoever reads them never confuses who said what. On a model
|
|
721
|
-
* that does not keep them apart, `self_memories` is `[]`.
|
|
728
|
+
* that does not keep them apart, `self_memories` is `[]`. Image memories are not
|
|
729
|
+
* included; `search` and `searchFull` carry them. `userId` may be omitted. */
|
|
722
730
|
searchSelf(query: string, userId?: string, limit?: number, opts?: SearchOptions): Promise<SelfSearchResult>;
|
|
723
731
|
/**
|
|
724
732
|
* Search, and keep every field the answer came with.
|
|
@@ -728,7 +736,7 @@ export declare class Client {
|
|
|
728
736
|
*
|
|
729
737
|
* const r = await mem.searchFull("the day we moved", "alice", 10,
|
|
730
738
|
* { max_images: 3, verify: 2 });
|
|
731
|
-
* r.images.length; // the photos,
|
|
739
|
+
* r.images.length; // the photos, apart from the text memories
|
|
732
740
|
* r.verify_used; // how many re-ask passes actually ran (you are billed per pass)
|
|
733
741
|
*
|
|
734
742
|
* Both options need Tablet 2 or newer and are refused (403) on an older engine
|
|
@@ -812,6 +820,11 @@ export declare class Client {
|
|
|
812
820
|
}): AsyncGenerator<Memory>;
|
|
813
821
|
/** Collect ALL of a store's memories into an array (the text you stored, and its metadata). */
|
|
814
822
|
exportMemories(userId?: string): Promise<Memory[]>;
|
|
823
|
+
/** Collect ALL of a store's image memories into an array — the image-side pair of
|
|
824
|
+
* `exportMemories`. */
|
|
825
|
+
exportImages(userId?: string, opts?: {
|
|
826
|
+
pageSize?: number;
|
|
827
|
+
}): Promise<Memory[]>;
|
|
815
828
|
/**
|
|
816
829
|
* Fetch the bytes of an image memory — the picture the SERVICE holds.
|
|
817
830
|
*
|
|
@@ -937,9 +950,8 @@ export declare class Client {
|
|
|
937
950
|
* assistant's own words, otherwise a person's name. Same cursor paging as
|
|
938
951
|
* `listImages`.
|
|
939
952
|
*
|
|
940
|
-
* `
|
|
941
|
-
*
|
|
942
|
-
* about to confirm one.
|
|
953
|
+
* `points_to_delete` is the count to show before anyone confirms a delete of this
|
|
954
|
+
* speaker's memories.
|
|
943
955
|
*/
|
|
944
956
|
bySpeaker(speaker: string, userId?: string, opts?: {
|
|
945
957
|
limit?: number;
|
|
@@ -969,7 +981,8 @@ export declare class Client {
|
|
|
969
981
|
* return 404. Idempotent. Every account starts with a `default` store.
|
|
970
982
|
* Returns `{ user_id, status }` (`status` is `"created"` or `"exists"`). */
|
|
971
983
|
createStore(userId?: string): Promise<StoreOpResult>;
|
|
972
|
-
/** List your stores: `[{ user_id, created_at }, ...]` (`default`
|
|
984
|
+
/** List your stores: `[{ user_id, created_at, canonical_id? }, ...]` (`default`
|
|
985
|
+
* first). Each `user_id` is the id the store was created with. */
|
|
973
986
|
listStores(): Promise<StoreInfo[]>;
|
|
974
987
|
/** Delete a store and ALL its memories. Returns `{ user_id, status }`. */
|
|
975
988
|
deleteStore(userId: string): Promise<StoreOpResult>;
|
package/dist/cjs/wontopos.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.WME = exports.Client = exports.ServerError = exports.RateLimitError = exports.ConflictError = exports.NotFoundError = exports.PermissionDeniedError = exports.PaymentRequiredError = exports.AuthenticationError = exports.BadRequestError = exports.APIConnectionError = exports.WosError = exports.CONTEXT_LIMIT_MAX = exports.CONTEXT_LIMIT_MIN = exports.SEARCH_LIMIT_MAX = exports.SEARCH_LIMIT_MIN = void 0;
|
|
4
4
|
exports._resetStoreIdWarnings = _resetStoreIdWarnings;
|
|
5
5
|
exports._resetFilterWarnings = _resetFilterWarnings;
|
|
6
|
-
const VERSION = "2.2.
|
|
6
|
+
const VERSION = "2.2.40";
|
|
7
7
|
const RUNTIME = (() => {
|
|
8
8
|
const p = globalThis?.process;
|
|
9
9
|
return p?.version ? ` (node/${p.version}; ${p.platform}-${p.arch})` : "";
|
|
@@ -18,7 +18,7 @@ function logDebug(msg) {
|
|
|
18
18
|
const DEFAULT_BASE_URL = "https://api.wontopos.com";
|
|
19
19
|
const DEFAULT_MODEL = "tablet-2";
|
|
20
20
|
const RETRY_ALWAYS = new Set([429]);
|
|
21
|
-
const RETRY_IF_IDEMPOTENT = new Set([502, 503]);
|
|
21
|
+
const RETRY_IF_IDEMPOTENT = new Set([408, 502, 503, 504]);
|
|
22
22
|
const IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
23
23
|
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "0.0.0.0"]);
|
|
24
24
|
const MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
|
|
@@ -40,8 +40,9 @@ const warnedStoreIds = new Set();
|
|
|
40
40
|
function warnIfStoreIdCollapses(id) {
|
|
41
41
|
if (!id || warnedStoreIds.has(id))
|
|
42
42
|
return;
|
|
43
|
+
const valid = STORE_ID_FORMAT.test(id);
|
|
43
44
|
const normalized = normalizeStoreId(id);
|
|
44
|
-
if (normalized === id)
|
|
45
|
+
if (valid && normalized === id)
|
|
45
46
|
return;
|
|
46
47
|
warnedStoreIds.add(id);
|
|
47
48
|
while (warnedStoreIds.size > WARNED_STORE_IDS_MAX) {
|
|
@@ -50,10 +51,27 @@ function warnIfStoreIdCollapses(id) {
|
|
|
50
51
|
break;
|
|
51
52
|
warnedStoreIds.delete(oldest);
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
-
`
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
if (!valid) {
|
|
55
|
+
console.warn(`wontopos: store id ${JSON.stringify(id)} is not a valid store id: use 1-64 ASCII letters, ` +
|
|
56
|
+
`digits, ".", "_" and "-", starting with a letter or digit. Creating it is refused (400).`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
console.warn(`wontopos: store id ${JSON.stringify(id)} normalizes to ${JSON.stringify(normalized)}. ` +
|
|
60
|
+
`Ids that differ only by case name this same store; one that differs only by punctuation ` +
|
|
61
|
+
`cannot be created beside it (409) and is not found when used (404). If these ids come from ` +
|
|
62
|
+
`your end users, normalize them yourself first so two people never compete for one name.`);
|
|
63
|
+
}
|
|
64
|
+
const STORE_ID_FORMAT = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
65
|
+
const NOT_METADATA = new Set(["userid", "storeid", "idempotencykey"]);
|
|
66
|
+
function checkMetadataKeys(metadata) {
|
|
67
|
+
if (metadata == null || typeof metadata !== "object")
|
|
68
|
+
return;
|
|
69
|
+
for (const k of Object.keys(metadata)) {
|
|
70
|
+
if (NOT_METADATA.has(k.toLowerCase().replace(/[\s_.-]/g, ""))) {
|
|
71
|
+
throw new Error(`${JSON.stringify(k)} is not a metadata field: pass the store as userId and an ` +
|
|
72
|
+
`idempotency key as opts.idempotencyKey. Nothing was sent.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
57
75
|
}
|
|
58
76
|
function _resetStoreIdWarnings() {
|
|
59
77
|
warnedStoreIds.clear();
|
|
@@ -141,13 +159,24 @@ function parseRateLimit(h) {
|
|
|
141
159
|
const asList = (x) => (Array.isArray(x) ? x : []);
|
|
142
160
|
const asObj = (x) => (x != null && typeof x === "object" && !Array.isArray(x) ? x : {});
|
|
143
161
|
const asRecords = (x) => asList(x).filter((m) => m != null && typeof m === "object" && !Array.isArray(m));
|
|
162
|
+
const memoryFromGet = (r) => {
|
|
163
|
+
const o = asObj(r);
|
|
164
|
+
if (o.memory != null && typeof o.memory === "object" && !Array.isArray(o.memory))
|
|
165
|
+
return o.memory;
|
|
166
|
+
return typeof o.id === "string" ? o : {};
|
|
167
|
+
};
|
|
144
168
|
function mergeResults(r) {
|
|
145
|
-
const
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
169
|
+
const out = asRecords(r?.memories);
|
|
170
|
+
const seen = new Set(out.map((m) => m.id).filter((id) => typeof id === "string"));
|
|
171
|
+
for (const m of [...asRecords(r?.self_memories), ...asRecords(r?.images)]) {
|
|
172
|
+
if (typeof m.id === "string") {
|
|
173
|
+
if (seen.has(m.id))
|
|
174
|
+
continue;
|
|
175
|
+
seen.add(m.id);
|
|
176
|
+
}
|
|
177
|
+
out.push(m);
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
151
180
|
}
|
|
152
181
|
function withForm(body, form, tz) {
|
|
153
182
|
if (form !== undefined)
|
|
@@ -219,10 +248,11 @@ const KNOWN_FILTER_KEYS = new Set([
|
|
|
219
248
|
]);
|
|
220
249
|
const warnedFilterKeys = new Set();
|
|
221
250
|
const KNOWN_SEARCH_KEYS = new Set([
|
|
222
|
-
"cache_control", "speaker", "filters", "verify", "max_images", "extra",
|
|
251
|
+
"cache_control", "speaker", "filters", "verify", "max_images", "form", "tz", "extra",
|
|
223
252
|
]);
|
|
224
253
|
const RESERVED_SEARCH_KEYS = new Set(["user_id", "query", "max_results"]);
|
|
225
254
|
const KNOWN_RECALL_KEYS = new Set(["form", "tz", "limit", "context_limit"]);
|
|
255
|
+
const KNOWN_ENGRAM_KEYS = new Set(["form", "tz"]);
|
|
226
256
|
function checkSearchOpts(opts) {
|
|
227
257
|
checkOpts(opts, KNOWN_SEARCH_KEYS, "search");
|
|
228
258
|
}
|
|
@@ -237,8 +267,7 @@ function checkOpts(opts, known, what) {
|
|
|
237
267
|
const near = [...known].find((n) => n !== "extra" && editWithin(k, n, 2));
|
|
238
268
|
throw new Error(`unknown ${what} option ${JSON.stringify(k)}` +
|
|
239
269
|
(near ? ` — did you mean ${JSON.stringify(near)}?` : "") +
|
|
240
|
-
`.
|
|
241
|
-
`have looked like it worked.` +
|
|
270
|
+
`. It would have had no effect, and the call would have looked like it worked.` +
|
|
242
271
|
(known.has("extra")
|
|
243
272
|
? ` Pass it under \`extra\` if the service accepts it and this client does not know it yet.`
|
|
244
273
|
: ""));
|
|
@@ -407,25 +436,26 @@ class Client {
|
|
|
407
436
|
withRetries(maxRetries) {
|
|
408
437
|
return this.clone({ maxRetries });
|
|
409
438
|
}
|
|
410
|
-
add(content, userId, metadata = {}, opts = {}) {
|
|
439
|
+
async add(content, userId, metadata = {}, opts = {}) {
|
|
440
|
+
checkMetadataKeys(metadata);
|
|
411
441
|
const body = { user_id: this.uid(userId), content, metadata };
|
|
412
442
|
if (opts.image !== undefined)
|
|
413
443
|
body.image = normalizeImage(opts.image);
|
|
414
444
|
return this.post("/api/v1/memory/store", body, opts.idempotencyKey);
|
|
415
445
|
}
|
|
416
|
-
store(content, userId, metadata = {}, opts = {}) {
|
|
446
|
+
async store(content, userId, metadata = {}, opts = {}) {
|
|
417
447
|
return this.add(content, userId, metadata, opts);
|
|
418
448
|
}
|
|
419
|
-
addTurn(userMsg, assistantMsg, userId, opts = {}) {
|
|
449
|
+
async addTurn(userMsg, assistantMsg, userId, opts = {}) {
|
|
420
450
|
return this.post("/api/v1/memory/store-turn", { user_id: this.uid(userId), user_msg: userMsg, assistant_msg: assistantMsg }, opts.idempotencyKey);
|
|
421
451
|
}
|
|
422
|
-
addBulk(content, userId, category = "general", timestamp, opts = {}) {
|
|
452
|
+
async addBulk(content, userId, category = "general", timestamp, opts = {}) {
|
|
423
453
|
const body = { user_id: this.uid(userId), content, category };
|
|
424
454
|
if (timestamp)
|
|
425
455
|
body.timestamp = timestamp;
|
|
426
456
|
return this.post("/api/v1/memory/bulk-store", body, opts.idempotencyKey);
|
|
427
457
|
}
|
|
428
|
-
update(oldMemoryId, newContent, userId, opts = {}) {
|
|
458
|
+
async update(oldMemoryId, newContent, userId, opts = {}) {
|
|
429
459
|
return this.post("/api/v1/memory/supersede", { user_id: this.uid(userId), old_memory_id: oldMemoryId, new_content: newContent }, opts.idempotencyKey);
|
|
430
460
|
}
|
|
431
461
|
async search(query, userId, limit = 10, opts = {}) {
|
|
@@ -470,14 +500,15 @@ class Client {
|
|
|
470
500
|
}
|
|
471
501
|
return this.post("/api/v1/memory/recall", withForm(body, opts.form, opts.tz));
|
|
472
502
|
}
|
|
473
|
-
engram(name, query, userId, opts = {}) {
|
|
503
|
+
async engram(name, query, userId, opts = {}) {
|
|
504
|
+
checkOpts(opts, KNOWN_ENGRAM_KEYS, "engram");
|
|
474
505
|
return this.post("/api/v1/engram/run", withForm({ name, user_id: this.uid(userId), query }, opts.form, opts.tz));
|
|
475
506
|
}
|
|
476
507
|
async history(userId) {
|
|
477
508
|
const r = await this.post("/api/v1/memory/history", { user_id: this.uid(userId) });
|
|
478
509
|
return asRecords(r.turns);
|
|
479
510
|
}
|
|
480
|
-
stats(userId) {
|
|
511
|
+
async stats(userId) {
|
|
481
512
|
return this.post("/api/v1/memory/stats", { user_id: this.uid(userId) });
|
|
482
513
|
}
|
|
483
514
|
async usage(days = 7) {
|
|
@@ -486,13 +517,13 @@ class Client {
|
|
|
486
517
|
}
|
|
487
518
|
return this.request("GET", `/api/v1/won/usage?days=${days}`);
|
|
488
519
|
}
|
|
489
|
-
get(userId, memoryId) {
|
|
520
|
+
async get(userId, memoryId) {
|
|
490
521
|
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
491
522
|
throw new Error("memoryId is required — the id that add/store or listMemories returned.");
|
|
492
523
|
}
|
|
493
|
-
return this.post("/api/v1/memory/get", { user_id: this.uid(userId), memory_id: memoryId.trim() }).then(
|
|
524
|
+
return this.post("/api/v1/memory/get", { user_id: this.uid(userId), memory_id: memoryId.trim() }).then(memoryFromGet);
|
|
494
525
|
}
|
|
495
|
-
listMemories(userId, opts = {}) {
|
|
526
|
+
async listMemories(userId, opts = {}) {
|
|
496
527
|
const body = { user_id: this.uid(userId), limit: opts.limit ?? 100 };
|
|
497
528
|
if (opts.cursor)
|
|
498
529
|
body.cursor = opts.cursor;
|
|
@@ -520,13 +551,19 @@ class Client {
|
|
|
520
551
|
out.push(m);
|
|
521
552
|
return out;
|
|
522
553
|
}
|
|
523
|
-
|
|
554
|
+
async exportImages(userId, opts = {}) {
|
|
555
|
+
const out = [];
|
|
556
|
+
for await (const m of this.iterImages(userId, opts))
|
|
557
|
+
out.push(m);
|
|
558
|
+
return out;
|
|
559
|
+
}
|
|
560
|
+
async getImage(userId, memoryId) {
|
|
524
561
|
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
525
562
|
throw new Error("memoryId is required — the id that add/store or listImages returned.");
|
|
526
563
|
}
|
|
527
564
|
return this.requestBytes("/api/v1/memory/image", { user_id: this.uid(userId), memory_id: memoryId.trim() });
|
|
528
565
|
}
|
|
529
|
-
forgetImage(userId, memoryId, opts = {}) {
|
|
566
|
+
async forgetImage(userId, memoryId, opts = {}) {
|
|
530
567
|
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
531
568
|
throw new Error("memoryId is required — the id of the memory whose image you want removed.");
|
|
532
569
|
}
|
|
@@ -535,7 +572,7 @@ class Client {
|
|
|
535
572
|
body.preview = true;
|
|
536
573
|
return this.request("DELETE", "/api/v1/memory/image", body);
|
|
537
574
|
}
|
|
538
|
-
listImages(userId, opts = {}) {
|
|
575
|
+
async listImages(userId, opts = {}) {
|
|
539
576
|
const body = { user_id: this.uid(userId) };
|
|
540
577
|
if (opts.limit !== undefined)
|
|
541
578
|
body.limit = opts.limit;
|
|
@@ -565,7 +602,7 @@ class Client {
|
|
|
565
602
|
throw new Error(`stopped after ${MAX_PAGES} pages — the store did not end. This is a truncated ` +
|
|
566
603
|
`answer, not the whole store.`);
|
|
567
604
|
}
|
|
568
|
-
revisions(userId, opts = {}) {
|
|
605
|
+
async revisions(userId, opts = {}) {
|
|
569
606
|
const body = {};
|
|
570
607
|
if (opts.include !== undefined)
|
|
571
608
|
body.include = opts.include;
|
|
@@ -578,13 +615,13 @@ class Client {
|
|
|
578
615
|
body.user_id = this.uid(userId);
|
|
579
616
|
return this.post("/api/v1/won/revisions", body);
|
|
580
617
|
}
|
|
581
|
-
lineage(userId, memoryId) {
|
|
618
|
+
async lineage(userId, memoryId) {
|
|
582
619
|
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
583
620
|
throw new Error("memoryId is required — the memory whose history you want.");
|
|
584
621
|
}
|
|
585
622
|
return this.post("/api/v1/memory/lineage", { user_id: this.uid(userId), memory_id: memoryId.trim() });
|
|
586
623
|
}
|
|
587
|
-
bySpeaker(speaker, userId, opts = {}) {
|
|
624
|
+
async bySpeaker(speaker, userId, opts = {}) {
|
|
588
625
|
if (!speaker || typeof speaker !== "string" || speaker.trim() === "") {
|
|
589
626
|
throw new Error('speaker is required — "me" for the assistant, or a person\'s name.');
|
|
590
627
|
}
|
|
@@ -608,41 +645,42 @@ class Client {
|
|
|
608
645
|
async listEngrams() {
|
|
609
646
|
const data = await this.request("GET", "/api/v1/engram");
|
|
610
647
|
return {
|
|
648
|
+
...data,
|
|
611
649
|
engrams: asRecords(data.engrams),
|
|
612
650
|
forms: asRecords(data.forms),
|
|
613
651
|
note: typeof data.note === "string" ? data.note : undefined,
|
|
614
652
|
};
|
|
615
653
|
}
|
|
616
|
-
createStore(userId) {
|
|
654
|
+
async createStore(userId) {
|
|
617
655
|
return this.post("/api/v1/memory/collection", { user_id: this.uid(userId) });
|
|
618
656
|
}
|
|
619
657
|
async listStores() {
|
|
620
658
|
const r = await this.request("GET", "/api/v1/memory/collections");
|
|
621
659
|
return asRecords(r.collections);
|
|
622
660
|
}
|
|
623
|
-
deleteStore(userId) {
|
|
661
|
+
async deleteStore(userId) {
|
|
624
662
|
if (typeof userId !== "string" || !userId.trim())
|
|
625
663
|
throw new Error("userId is required (a non-blank string) — deleteStore never falls back to the default store.");
|
|
626
664
|
warnIfStoreIdCollapses(userId);
|
|
627
665
|
return this.request("DELETE", "/api/v1/memory/collection", { user_id: userId });
|
|
628
666
|
}
|
|
629
|
-
addSpeaker(speaker, userId) {
|
|
667
|
+
async addSpeaker(speaker, userId) {
|
|
630
668
|
return this.post("/api/v1/memory/speakers", { user_id: this.uid(userId), speaker });
|
|
631
669
|
}
|
|
632
670
|
async listSpeakers(userId) {
|
|
633
671
|
const r = await this.request("GET", `/api/v1/memory/speakers?user_id=${encodeURIComponent(this.uid(userId))}`);
|
|
634
672
|
return { ...r, speakers: asRecords(r?.speakers) };
|
|
635
673
|
}
|
|
636
|
-
removeSpeaker(speaker, userId) {
|
|
674
|
+
async removeSpeaker(speaker, userId) {
|
|
637
675
|
return this.request("DELETE", "/api/v1/memory/speakers", { user_id: this.uid(userId), speaker });
|
|
638
676
|
}
|
|
639
|
-
delete(userId, memoryId) {
|
|
677
|
+
async delete(userId, memoryId) {
|
|
640
678
|
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
641
679
|
throw new Error("memoryId is required (non-blank). To delete every memory in a store, call deleteAll(userId) explicitly.");
|
|
642
680
|
}
|
|
643
681
|
return this.post("/api/v1/memory/forget", { user_id: this.uid(userId), memory_id: memoryId.trim() });
|
|
644
682
|
}
|
|
645
|
-
deleteAll(userId) {
|
|
683
|
+
async deleteAll(userId) {
|
|
646
684
|
if (typeof userId !== "string" || !userId.trim())
|
|
647
685
|
throw new Error("userId is required (a non-blank string) for deleteAll — anything else would wipe the default store.");
|
|
648
686
|
warnIfStoreIdCollapses(userId);
|
|
@@ -777,7 +815,9 @@ class Client {
|
|
|
777
815
|
if (e?.name === "AbortError" || att.signal.aborted) {
|
|
778
816
|
throw new APIConnectionError(0, this.abortMessage(att.why()));
|
|
779
817
|
}
|
|
780
|
-
|
|
818
|
+
if (e instanceof WosError)
|
|
819
|
+
throw e;
|
|
820
|
+
throw new APIConnectionError(0, `network error: ${e?.message ?? e}`);
|
|
781
821
|
}
|
|
782
822
|
if (buf.byteLength === 0) {
|
|
783
823
|
throw new WosError(res.status, "empty image body — the service returned no bytes");
|
|
@@ -910,7 +950,14 @@ class Client {
|
|
|
910
950
|
}
|
|
911
951
|
if (e instanceof WosError)
|
|
912
952
|
throw e;
|
|
913
|
-
|
|
953
|
+
if (!(IDEMPOTENT_METHODS.has(method.toUpperCase()) && attempt + 1 < attempts)) {
|
|
954
|
+
throw new APIConnectionError(0, `network error: ${e?.message ?? e}`);
|
|
955
|
+
}
|
|
956
|
+
att.clear();
|
|
957
|
+
const delay = this.backoffMs(attempt);
|
|
958
|
+
logDebug(`${method} ${logPath} — body dropped mid-stream, retrying in ${delay}ms (attempt ${attempt + 1}/${attempts})`);
|
|
959
|
+
await this.backoffSleep(delay, deadlineAt);
|
|
960
|
+
continue;
|
|
914
961
|
}
|
|
915
962
|
finally {
|
|
916
963
|
att.clear();
|
package/dist/wontopos.d.ts
CHANGED
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
* and searching call no LLM.
|
|
27
27
|
*
|
|
28
28
|
* Reliability: every call retries transient failures with exponential backoff +
|
|
29
|
-
* jitter, honoring `Retry-After` — 429 always; 502/503 and connection errors
|
|
30
|
-
* when a retry can never double-process a write (idempotent calls, or a failure
|
|
29
|
+
* jitter, honoring `Retry-After` — 429 always; 408/502/503/504 and connection errors
|
|
30
|
+
* only when a retry can never double-process a write (idempotent calls, or a failure
|
|
31
31
|
* at connect time). Tune with `maxRetries` (0 disables) and `timeoutMs`, or per
|
|
32
32
|
* call site via the `withTimeout()` / `withRetries()` clones.
|
|
33
33
|
*
|
|
@@ -74,10 +74,11 @@ export declare class PaymentRequiredError extends WosError {
|
|
|
74
74
|
/** 403 — the key/model isn't allowed to do this. */
|
|
75
75
|
export declare class PermissionDeniedError extends WosError {
|
|
76
76
|
}
|
|
77
|
-
/** 404 — the store or resource doesn't exist
|
|
77
|
+
/** 404 — the store or resource doesn't exist. */
|
|
78
78
|
export declare class NotFoundError extends WosError {
|
|
79
79
|
}
|
|
80
|
-
/** 409 —
|
|
80
|
+
/** 409 — another write to this store was in flight (nothing was stored; retry), or the
|
|
81
|
+
* store id collides with an existing store's (permanent). */
|
|
81
82
|
export declare class ConflictError extends WosError {
|
|
82
83
|
}
|
|
83
84
|
/** 429 — too many requests. Back off and retry (the client already retries these). */
|
|
@@ -86,8 +87,8 @@ export declare class RateLimitError extends WosError {
|
|
|
86
87
|
/**
|
|
87
88
|
* 5xx — the service failed.
|
|
88
89
|
*
|
|
89
|
-
* `502` / `503` are transient: the client already retries them for calls
|
|
90
|
-
* retry cannot double-process a write, and retrying yourself is reasonable.
|
|
90
|
+
* `502` / `503` / `504` are transient: the client already retries them for calls
|
|
91
|
+
* where a retry cannot double-process a write, and retrying yourself is reasonable.
|
|
91
92
|
*
|
|
92
93
|
* `501` is NOT transient. It means the engine behind the model you selected does
|
|
93
94
|
* not implement that endpoint at all (the error carries `model` and `endpoint`).
|
|
@@ -159,6 +160,8 @@ export interface StatusResult {
|
|
|
159
160
|
/**
|
|
160
161
|
* An image attached to a memory, passed to `add`/`store` via `opts.image`.
|
|
161
162
|
*
|
|
163
|
+
* Both edges must be 700px or more; a smaller image is refused (400).
|
|
164
|
+
*
|
|
162
165
|
* What the service keeps is NOT your original. An image whose long edge is over
|
|
163
166
|
* 1568px is downscaled to 1568 before anything else happens, and that smaller
|
|
164
167
|
* picture is what gets stored, and handed back by `getImage`. Nothing on our side
|
|
@@ -291,8 +294,8 @@ export interface LineageResult {
|
|
|
291
294
|
export interface SpeakerPage {
|
|
292
295
|
speaker?: string;
|
|
293
296
|
memories: Memory[];
|
|
294
|
-
/** How many underlying records a delete would actually remove, beyond what was returned. */
|
|
295
297
|
chunks?: number;
|
|
298
|
+
/** The count to show before anyone confirms a delete of this speaker's memories. */
|
|
296
299
|
points_to_delete?: number;
|
|
297
300
|
returned?: number;
|
|
298
301
|
has_more?: boolean;
|
|
@@ -307,20 +310,17 @@ export interface UpdateResult {
|
|
|
307
310
|
status?: string;
|
|
308
311
|
[key: string]: unknown;
|
|
309
312
|
}
|
|
310
|
-
/** `searchFull` → everything one search answered with
|
|
311
|
-
*
|
|
312
|
-
* `
|
|
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. */
|
|
313
|
+
/** `searchFull` → everything one search answered with. `search` returns every memory
|
|
314
|
+
* as one array; this keeps the fields apart and adds `verify_used`, the report on
|
|
315
|
+
* the `verify` option. */
|
|
317
316
|
export interface SearchResult {
|
|
318
317
|
/** What others said, and general memories. */
|
|
319
318
|
memories: Memory[];
|
|
320
319
|
/** The assistant's own words (speaker "me"); `[]` on a model that does not keep
|
|
321
320
|
* them apart. */
|
|
322
321
|
self_memories: Memory[];
|
|
323
|
-
/** Image memories
|
|
322
|
+
/** Image memories the answer carried (one by default on an image-capable model,
|
|
323
|
+
* up to `max_images`); `[]` when there were none. */
|
|
324
324
|
images: Memory[];
|
|
325
325
|
/** Re-ask passes actually performed. Present only when `verify` was sent; lower
|
|
326
326
|
* than you asked for means the store had nothing further to add. */
|
|
@@ -428,21 +428,27 @@ export interface EngramCatalog {
|
|
|
428
428
|
engrams: EngramInfo[];
|
|
429
429
|
forms: EngramInfo[];
|
|
430
430
|
note?: string;
|
|
431
|
+
/** Anything else the reply carried. Responses widen; the runtime keeps those fields
|
|
432
|
+
* and this is what lets a typed caller read them. */
|
|
433
|
+
[key: string]: unknown;
|
|
431
434
|
}
|
|
432
435
|
/** `createStore` / `deleteStore` → `{ user_id, status }`. */
|
|
433
436
|
export interface StoreOpResult {
|
|
434
|
-
/** The store id
|
|
435
|
-
* Ids are normalized (see the `note`), so compare this against your input. */
|
|
437
|
+
/** The store id. `createStore` answers with the id as you sent it. */
|
|
436
438
|
user_id?: string;
|
|
439
|
+
/** The normalized form the store is filed under, when it differs from `user_id`. */
|
|
440
|
+
canonical_id?: string;
|
|
437
441
|
status?: string;
|
|
438
|
-
/** Present when the API
|
|
439
|
-
* end users can land in one store if their ids differ only by punctuation. */
|
|
442
|
+
/** Present when the API filed your id under a normalized form, explaining how. */
|
|
440
443
|
note?: string;
|
|
441
444
|
[key: string]: unknown;
|
|
442
445
|
}
|
|
443
446
|
export interface StoreInfo {
|
|
447
|
+
/** The id the store was created with — pass it back as `userId`. */
|
|
444
448
|
user_id: string;
|
|
445
449
|
created_at: string;
|
|
450
|
+
/** The normalized form the store is filed under, when it differs from `user_id`. */
|
|
451
|
+
canonical_id?: string;
|
|
446
452
|
[key: string]: unknown;
|
|
447
453
|
}
|
|
448
454
|
/** `addSpeaker` / `removeSpeaker` → `{ user_id, speaker, status }`. */
|
|
@@ -532,11 +538,15 @@ export interface SearchOptions {
|
|
|
532
538
|
* than answering with no images.
|
|
533
539
|
*/
|
|
534
540
|
max_images?: number;
|
|
541
|
+
/** Delivery form, "memoir" or "archive" (Scroll 1.2+): how each memory's time is
|
|
542
|
+
* rendered. The service refuses an unknown one (400). */
|
|
543
|
+
form?: string;
|
|
544
|
+
/** Your UTC offset in hours, for rendering times in `form`. */
|
|
545
|
+
tz?: number;
|
|
535
546
|
/**
|
|
536
547
|
* Fields the service accepts that this version does not know about, merged into the
|
|
537
|
-
* request as written. Every other key is refused, so a typo cannot reach the wire
|
|
538
|
-
*
|
|
539
|
-
* a caller billed for one pass believing they had bought three.
|
|
548
|
+
* request as written. Every other key is refused, so a typo cannot reach the wire.
|
|
549
|
+
* The store, the query and the count always win over a copy in here.
|
|
540
550
|
*/
|
|
541
551
|
extra?: Record<string, unknown>;
|
|
542
552
|
}
|
|
@@ -548,9 +558,9 @@ export interface SearchOptions {
|
|
|
548
558
|
* arrives with a different body. Use it when a retry is your own (a job that died and
|
|
549
559
|
* was re-run, a queue that redelivers). The SDK retries a write on exactly one
|
|
550
560
|
* status: 429, which the service answers before it processes anything, so nothing
|
|
551
|
-
* was stored. It never retries a write on 502 / 503 or a dropped body,
|
|
552
|
-
*
|
|
553
|
-
* whether
|
|
561
|
+
* was stored. It never retries a write on 408 / 502 / 503 / 504 or a dropped body,
|
|
562
|
+
* where the first attempt may already have landed — without a key the client cannot
|
|
563
|
+
* know whether it did.
|
|
554
564
|
*
|
|
555
565
|
* The key must be UNIQUE PER LOGICAL WRITE — derive it from the thing being stored
|
|
556
566
|
* (`` `import:${row.id}` ``), never a constant, or the second write replays the first
|
|
@@ -582,8 +592,8 @@ export interface ClientOptions {
|
|
|
582
592
|
* to the account's built-in `default` store. */
|
|
583
593
|
userId?: string;
|
|
584
594
|
/** How many times to retry transient failures before throwing — 429 always;
|
|
585
|
-
* 502/503 and connection errors only when a retry can never
|
|
586
|
-
* write. 0 disables retries (default 2). */
|
|
595
|
+
* 408/502/503/504 and connection errors only when a retry can never
|
|
596
|
+
* double-process a write. 0 disables retries (default 2). */
|
|
587
597
|
maxRetries?: number;
|
|
588
598
|
/** Alias of `maxRetries`. The Python SDK calls this option `retries`, and these
|
|
589
599
|
* SDKs are published in lockstep as "the same surface" — so code ported between
|
|
@@ -611,10 +621,8 @@ export interface ClientOptions {
|
|
|
611
621
|
*
|
|
612
622
|
* This is the seam for anything the runtime cannot express through options.
|
|
613
623
|
* The one that matters in practice is a corporate egress proxy: Node's global
|
|
614
|
-
* fetch (undici) IGNORES `HTTP_PROXY` / `HTTPS_PROXY`, so behind such a proxy
|
|
615
|
-
*
|
|
616
|
-
* through, because `requests` and `reqwest` both read those variables. Pass a
|
|
617
|
-
* proxy-aware fetch and the three behave the same again:
|
|
624
|
+
* fetch (undici) IGNORES `HTTP_PROXY` / `HTTPS_PROXY`, so behind such a proxy no
|
|
625
|
+
* request leaves at all. Pass a proxy-aware fetch and they go through:
|
|
618
626
|
*
|
|
619
627
|
* import { ProxyAgent } from "undici";
|
|
620
628
|
* const agent = new ProxyAgent(process.env.HTTPS_PROXY!);
|
|
@@ -696,7 +704,7 @@ export declare class Client {
|
|
|
696
704
|
addTurn(userMsg: string, assistantMsg: string, userId?: string, opts?: WriteOptions): Promise<StatusResult>;
|
|
697
705
|
/** Bulk-ingest a large blob of text in one call. For backfilling.
|
|
698
706
|
* The call most worth an `opts.idempotencyKey`: a backfill that dies halfway and is
|
|
699
|
-
* re-run would otherwise
|
|
707
|
+
* re-run would otherwise ingest the whole blob a second time. */
|
|
700
708
|
addBulk(content: string, userId?: string, category?: string, timestamp?: string, opts?: WriteOptions): Promise<StatusResult>;
|
|
701
709
|
/** Supersede an old memory with new content. Payload first, userId last — same shape as add/search. */
|
|
702
710
|
update(oldMemoryId: string, newContent: string, userId?: string, opts?: WriteOptions): Promise<UpdateResult>;
|
|
@@ -707,18 +715,18 @@ export declare class Client {
|
|
|
707
715
|
* a call that passes no count is unaffected. Before 2.2.35 the count was sent on
|
|
708
716
|
* unchecked.
|
|
709
717
|
*
|
|
710
|
-
* `limit` bounds `memories`, not the returned array.
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
*
|
|
715
|
-
* apart. */
|
|
718
|
+
* `limit` bounds `memories`, not the returned array. The assistant's own words
|
|
719
|
+
* (Scroll 1.2+) and image memories (Tablet 2+, one unless `max_images` says
|
|
720
|
+
* otherwise) come back in it as well, so it can hold more than `limit`. They are
|
|
721
|
+
* billed either way. Size a prompt window on the array you get back, not on
|
|
722
|
+
* `limit`. `searchFull()` hands the fields back apart. */
|
|
716
723
|
search(query: string, userId?: string, limit?: number, opts?: SearchOptions): Promise<Memory[]>;
|
|
717
724
|
/** Search a self-memory model (Scroll 1.2+): both fields from ONE call. Returns
|
|
718
725
|
* `{ memories, self_memories }` — `memories` is what others said and general
|
|
719
726
|
* memories, `self_memories` is the assistant's OWN words (stored with speaker
|
|
720
727
|
* "me"), kept apart so whoever reads them never confuses who said what. On a model
|
|
721
|
-
* that does not keep them apart, `self_memories` is `[]`.
|
|
728
|
+
* that does not keep them apart, `self_memories` is `[]`. Image memories are not
|
|
729
|
+
* included; `search` and `searchFull` carry them. `userId` may be omitted. */
|
|
722
730
|
searchSelf(query: string, userId?: string, limit?: number, opts?: SearchOptions): Promise<SelfSearchResult>;
|
|
723
731
|
/**
|
|
724
732
|
* Search, and keep every field the answer came with.
|
|
@@ -728,7 +736,7 @@ export declare class Client {
|
|
|
728
736
|
*
|
|
729
737
|
* const r = await mem.searchFull("the day we moved", "alice", 10,
|
|
730
738
|
* { max_images: 3, verify: 2 });
|
|
731
|
-
* r.images.length; // the photos,
|
|
739
|
+
* r.images.length; // the photos, apart from the text memories
|
|
732
740
|
* r.verify_used; // how many re-ask passes actually ran (you are billed per pass)
|
|
733
741
|
*
|
|
734
742
|
* Both options need Tablet 2 or newer and are refused (403) on an older engine
|
|
@@ -812,6 +820,11 @@ export declare class Client {
|
|
|
812
820
|
}): AsyncGenerator<Memory>;
|
|
813
821
|
/** Collect ALL of a store's memories into an array (the text you stored, and its metadata). */
|
|
814
822
|
exportMemories(userId?: string): Promise<Memory[]>;
|
|
823
|
+
/** Collect ALL of a store's image memories into an array — the image-side pair of
|
|
824
|
+
* `exportMemories`. */
|
|
825
|
+
exportImages(userId?: string, opts?: {
|
|
826
|
+
pageSize?: number;
|
|
827
|
+
}): Promise<Memory[]>;
|
|
815
828
|
/**
|
|
816
829
|
* Fetch the bytes of an image memory — the picture the SERVICE holds.
|
|
817
830
|
*
|
|
@@ -937,9 +950,8 @@ export declare class Client {
|
|
|
937
950
|
* assistant's own words, otherwise a person's name. Same cursor paging as
|
|
938
951
|
* `listImages`.
|
|
939
952
|
*
|
|
940
|
-
* `
|
|
941
|
-
*
|
|
942
|
-
* about to confirm one.
|
|
953
|
+
* `points_to_delete` is the count to show before anyone confirms a delete of this
|
|
954
|
+
* speaker's memories.
|
|
943
955
|
*/
|
|
944
956
|
bySpeaker(speaker: string, userId?: string, opts?: {
|
|
945
957
|
limit?: number;
|
|
@@ -969,7 +981,8 @@ export declare class Client {
|
|
|
969
981
|
* return 404. Idempotent. Every account starts with a `default` store.
|
|
970
982
|
* Returns `{ user_id, status }` (`status` is `"created"` or `"exists"`). */
|
|
971
983
|
createStore(userId?: string): Promise<StoreOpResult>;
|
|
972
|
-
/** List your stores: `[{ user_id, created_at }, ...]` (`default`
|
|
984
|
+
/** List your stores: `[{ user_id, created_at, canonical_id? }, ...]` (`default`
|
|
985
|
+
* first). Each `user_id` is the id the store was created with. */
|
|
973
986
|
listStores(): Promise<StoreInfo[]>;
|
|
974
987
|
/** Delete a store and ALL its memories. Returns `{ user_id, status }`. */
|
|
975
988
|
deleteStore(userId: string): Promise<StoreOpResult>;
|
package/dist/wontopos.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const VERSION = "2.2.
|
|
1
|
+
const VERSION = "2.2.40";
|
|
2
2
|
const RUNTIME = (() => {
|
|
3
3
|
const p = globalThis?.process;
|
|
4
4
|
return p?.version ? ` (node/${p.version}; ${p.platform}-${p.arch})` : "";
|
|
@@ -13,7 +13,7 @@ function logDebug(msg) {
|
|
|
13
13
|
const DEFAULT_BASE_URL = "https://api.wontopos.com";
|
|
14
14
|
const DEFAULT_MODEL = "tablet-2";
|
|
15
15
|
const RETRY_ALWAYS = new Set([429]);
|
|
16
|
-
const RETRY_IF_IDEMPOTENT = new Set([502, 503]);
|
|
16
|
+
const RETRY_IF_IDEMPOTENT = new Set([408, 502, 503, 504]);
|
|
17
17
|
const IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
18
18
|
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "0.0.0.0"]);
|
|
19
19
|
const MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
|
|
@@ -35,8 +35,9 @@ const warnedStoreIds = new Set();
|
|
|
35
35
|
function warnIfStoreIdCollapses(id) {
|
|
36
36
|
if (!id || warnedStoreIds.has(id))
|
|
37
37
|
return;
|
|
38
|
+
const valid = STORE_ID_FORMAT.test(id);
|
|
38
39
|
const normalized = normalizeStoreId(id);
|
|
39
|
-
if (normalized === id)
|
|
40
|
+
if (valid && normalized === id)
|
|
40
41
|
return;
|
|
41
42
|
warnedStoreIds.add(id);
|
|
42
43
|
while (warnedStoreIds.size > WARNED_STORE_IDS_MAX) {
|
|
@@ -45,10 +46,27 @@ function warnIfStoreIdCollapses(id) {
|
|
|
45
46
|
break;
|
|
46
47
|
warnedStoreIds.delete(oldest);
|
|
47
48
|
}
|
|
48
|
-
|
|
49
|
-
`
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
if (!valid) {
|
|
50
|
+
console.warn(`wontopos: store id ${JSON.stringify(id)} is not a valid store id: use 1-64 ASCII letters, ` +
|
|
51
|
+
`digits, ".", "_" and "-", starting with a letter or digit. Creating it is refused (400).`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
console.warn(`wontopos: store id ${JSON.stringify(id)} normalizes to ${JSON.stringify(normalized)}. ` +
|
|
55
|
+
`Ids that differ only by case name this same store; one that differs only by punctuation ` +
|
|
56
|
+
`cannot be created beside it (409) and is not found when used (404). If these ids come from ` +
|
|
57
|
+
`your end users, normalize them yourself first so two people never compete for one name.`);
|
|
58
|
+
}
|
|
59
|
+
const STORE_ID_FORMAT = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
60
|
+
const NOT_METADATA = new Set(["userid", "storeid", "idempotencykey"]);
|
|
61
|
+
function checkMetadataKeys(metadata) {
|
|
62
|
+
if (metadata == null || typeof metadata !== "object")
|
|
63
|
+
return;
|
|
64
|
+
for (const k of Object.keys(metadata)) {
|
|
65
|
+
if (NOT_METADATA.has(k.toLowerCase().replace(/[\s_.-]/g, ""))) {
|
|
66
|
+
throw new Error(`${JSON.stringify(k)} is not a metadata field: pass the store as userId and an ` +
|
|
67
|
+
`idempotency key as opts.idempotencyKey. Nothing was sent.`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
52
70
|
}
|
|
53
71
|
export function _resetStoreIdWarnings() {
|
|
54
72
|
warnedStoreIds.clear();
|
|
@@ -136,13 +154,24 @@ function parseRateLimit(h) {
|
|
|
136
154
|
const asList = (x) => (Array.isArray(x) ? x : []);
|
|
137
155
|
const asObj = (x) => (x != null && typeof x === "object" && !Array.isArray(x) ? x : {});
|
|
138
156
|
const asRecords = (x) => asList(x).filter((m) => m != null && typeof m === "object" && !Array.isArray(m));
|
|
157
|
+
const memoryFromGet = (r) => {
|
|
158
|
+
const o = asObj(r);
|
|
159
|
+
if (o.memory != null && typeof o.memory === "object" && !Array.isArray(o.memory))
|
|
160
|
+
return o.memory;
|
|
161
|
+
return typeof o.id === "string" ? o : {};
|
|
162
|
+
};
|
|
139
163
|
function mergeResults(r) {
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
164
|
+
const out = asRecords(r?.memories);
|
|
165
|
+
const seen = new Set(out.map((m) => m.id).filter((id) => typeof id === "string"));
|
|
166
|
+
for (const m of [...asRecords(r?.self_memories), ...asRecords(r?.images)]) {
|
|
167
|
+
if (typeof m.id === "string") {
|
|
168
|
+
if (seen.has(m.id))
|
|
169
|
+
continue;
|
|
170
|
+
seen.add(m.id);
|
|
171
|
+
}
|
|
172
|
+
out.push(m);
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
146
175
|
}
|
|
147
176
|
function withForm(body, form, tz) {
|
|
148
177
|
if (form !== undefined)
|
|
@@ -204,10 +233,11 @@ const KNOWN_FILTER_KEYS = new Set([
|
|
|
204
233
|
]);
|
|
205
234
|
const warnedFilterKeys = new Set();
|
|
206
235
|
const KNOWN_SEARCH_KEYS = new Set([
|
|
207
|
-
"cache_control", "speaker", "filters", "verify", "max_images", "extra",
|
|
236
|
+
"cache_control", "speaker", "filters", "verify", "max_images", "form", "tz", "extra",
|
|
208
237
|
]);
|
|
209
238
|
const RESERVED_SEARCH_KEYS = new Set(["user_id", "query", "max_results"]);
|
|
210
239
|
const KNOWN_RECALL_KEYS = new Set(["form", "tz", "limit", "context_limit"]);
|
|
240
|
+
const KNOWN_ENGRAM_KEYS = new Set(["form", "tz"]);
|
|
211
241
|
function checkSearchOpts(opts) {
|
|
212
242
|
checkOpts(opts, KNOWN_SEARCH_KEYS, "search");
|
|
213
243
|
}
|
|
@@ -222,8 +252,7 @@ function checkOpts(opts, known, what) {
|
|
|
222
252
|
const near = [...known].find((n) => n !== "extra" && editWithin(k, n, 2));
|
|
223
253
|
throw new Error(`unknown ${what} option ${JSON.stringify(k)}` +
|
|
224
254
|
(near ? ` — did you mean ${JSON.stringify(near)}?` : "") +
|
|
225
|
-
`.
|
|
226
|
-
`have looked like it worked.` +
|
|
255
|
+
`. It would have had no effect, and the call would have looked like it worked.` +
|
|
227
256
|
(known.has("extra")
|
|
228
257
|
? ` Pass it under \`extra\` if the service accepts it and this client does not know it yet.`
|
|
229
258
|
: ""));
|
|
@@ -392,25 +421,26 @@ export class Client {
|
|
|
392
421
|
withRetries(maxRetries) {
|
|
393
422
|
return this.clone({ maxRetries });
|
|
394
423
|
}
|
|
395
|
-
add(content, userId, metadata = {}, opts = {}) {
|
|
424
|
+
async add(content, userId, metadata = {}, opts = {}) {
|
|
425
|
+
checkMetadataKeys(metadata);
|
|
396
426
|
const body = { user_id: this.uid(userId), content, metadata };
|
|
397
427
|
if (opts.image !== undefined)
|
|
398
428
|
body.image = normalizeImage(opts.image);
|
|
399
429
|
return this.post("/api/v1/memory/store", body, opts.idempotencyKey);
|
|
400
430
|
}
|
|
401
|
-
store(content, userId, metadata = {}, opts = {}) {
|
|
431
|
+
async store(content, userId, metadata = {}, opts = {}) {
|
|
402
432
|
return this.add(content, userId, metadata, opts);
|
|
403
433
|
}
|
|
404
|
-
addTurn(userMsg, assistantMsg, userId, opts = {}) {
|
|
434
|
+
async addTurn(userMsg, assistantMsg, userId, opts = {}) {
|
|
405
435
|
return this.post("/api/v1/memory/store-turn", { user_id: this.uid(userId), user_msg: userMsg, assistant_msg: assistantMsg }, opts.idempotencyKey);
|
|
406
436
|
}
|
|
407
|
-
addBulk(content, userId, category = "general", timestamp, opts = {}) {
|
|
437
|
+
async addBulk(content, userId, category = "general", timestamp, opts = {}) {
|
|
408
438
|
const body = { user_id: this.uid(userId), content, category };
|
|
409
439
|
if (timestamp)
|
|
410
440
|
body.timestamp = timestamp;
|
|
411
441
|
return this.post("/api/v1/memory/bulk-store", body, opts.idempotencyKey);
|
|
412
442
|
}
|
|
413
|
-
update(oldMemoryId, newContent, userId, opts = {}) {
|
|
443
|
+
async update(oldMemoryId, newContent, userId, opts = {}) {
|
|
414
444
|
return this.post("/api/v1/memory/supersede", { user_id: this.uid(userId), old_memory_id: oldMemoryId, new_content: newContent }, opts.idempotencyKey);
|
|
415
445
|
}
|
|
416
446
|
async search(query, userId, limit = 10, opts = {}) {
|
|
@@ -455,14 +485,15 @@ export class Client {
|
|
|
455
485
|
}
|
|
456
486
|
return this.post("/api/v1/memory/recall", withForm(body, opts.form, opts.tz));
|
|
457
487
|
}
|
|
458
|
-
engram(name, query, userId, opts = {}) {
|
|
488
|
+
async engram(name, query, userId, opts = {}) {
|
|
489
|
+
checkOpts(opts, KNOWN_ENGRAM_KEYS, "engram");
|
|
459
490
|
return this.post("/api/v1/engram/run", withForm({ name, user_id: this.uid(userId), query }, opts.form, opts.tz));
|
|
460
491
|
}
|
|
461
492
|
async history(userId) {
|
|
462
493
|
const r = await this.post("/api/v1/memory/history", { user_id: this.uid(userId) });
|
|
463
494
|
return asRecords(r.turns);
|
|
464
495
|
}
|
|
465
|
-
stats(userId) {
|
|
496
|
+
async stats(userId) {
|
|
466
497
|
return this.post("/api/v1/memory/stats", { user_id: this.uid(userId) });
|
|
467
498
|
}
|
|
468
499
|
async usage(days = 7) {
|
|
@@ -471,13 +502,13 @@ export class Client {
|
|
|
471
502
|
}
|
|
472
503
|
return this.request("GET", `/api/v1/won/usage?days=${days}`);
|
|
473
504
|
}
|
|
474
|
-
get(userId, memoryId) {
|
|
505
|
+
async get(userId, memoryId) {
|
|
475
506
|
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
476
507
|
throw new Error("memoryId is required — the id that add/store or listMemories returned.");
|
|
477
508
|
}
|
|
478
|
-
return this.post("/api/v1/memory/get", { user_id: this.uid(userId), memory_id: memoryId.trim() }).then(
|
|
509
|
+
return this.post("/api/v1/memory/get", { user_id: this.uid(userId), memory_id: memoryId.trim() }).then(memoryFromGet);
|
|
479
510
|
}
|
|
480
|
-
listMemories(userId, opts = {}) {
|
|
511
|
+
async listMemories(userId, opts = {}) {
|
|
481
512
|
const body = { user_id: this.uid(userId), limit: opts.limit ?? 100 };
|
|
482
513
|
if (opts.cursor)
|
|
483
514
|
body.cursor = opts.cursor;
|
|
@@ -505,13 +536,19 @@ export class Client {
|
|
|
505
536
|
out.push(m);
|
|
506
537
|
return out;
|
|
507
538
|
}
|
|
508
|
-
|
|
539
|
+
async exportImages(userId, opts = {}) {
|
|
540
|
+
const out = [];
|
|
541
|
+
for await (const m of this.iterImages(userId, opts))
|
|
542
|
+
out.push(m);
|
|
543
|
+
return out;
|
|
544
|
+
}
|
|
545
|
+
async getImage(userId, memoryId) {
|
|
509
546
|
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
510
547
|
throw new Error("memoryId is required — the id that add/store or listImages returned.");
|
|
511
548
|
}
|
|
512
549
|
return this.requestBytes("/api/v1/memory/image", { user_id: this.uid(userId), memory_id: memoryId.trim() });
|
|
513
550
|
}
|
|
514
|
-
forgetImage(userId, memoryId, opts = {}) {
|
|
551
|
+
async forgetImage(userId, memoryId, opts = {}) {
|
|
515
552
|
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
516
553
|
throw new Error("memoryId is required — the id of the memory whose image you want removed.");
|
|
517
554
|
}
|
|
@@ -520,7 +557,7 @@ export class Client {
|
|
|
520
557
|
body.preview = true;
|
|
521
558
|
return this.request("DELETE", "/api/v1/memory/image", body);
|
|
522
559
|
}
|
|
523
|
-
listImages(userId, opts = {}) {
|
|
560
|
+
async listImages(userId, opts = {}) {
|
|
524
561
|
const body = { user_id: this.uid(userId) };
|
|
525
562
|
if (opts.limit !== undefined)
|
|
526
563
|
body.limit = opts.limit;
|
|
@@ -550,7 +587,7 @@ export class Client {
|
|
|
550
587
|
throw new Error(`stopped after ${MAX_PAGES} pages — the store did not end. This is a truncated ` +
|
|
551
588
|
`answer, not the whole store.`);
|
|
552
589
|
}
|
|
553
|
-
revisions(userId, opts = {}) {
|
|
590
|
+
async revisions(userId, opts = {}) {
|
|
554
591
|
const body = {};
|
|
555
592
|
if (opts.include !== undefined)
|
|
556
593
|
body.include = opts.include;
|
|
@@ -563,13 +600,13 @@ export class Client {
|
|
|
563
600
|
body.user_id = this.uid(userId);
|
|
564
601
|
return this.post("/api/v1/won/revisions", body);
|
|
565
602
|
}
|
|
566
|
-
lineage(userId, memoryId) {
|
|
603
|
+
async lineage(userId, memoryId) {
|
|
567
604
|
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
568
605
|
throw new Error("memoryId is required — the memory whose history you want.");
|
|
569
606
|
}
|
|
570
607
|
return this.post("/api/v1/memory/lineage", { user_id: this.uid(userId), memory_id: memoryId.trim() });
|
|
571
608
|
}
|
|
572
|
-
bySpeaker(speaker, userId, opts = {}) {
|
|
609
|
+
async bySpeaker(speaker, userId, opts = {}) {
|
|
573
610
|
if (!speaker || typeof speaker !== "string" || speaker.trim() === "") {
|
|
574
611
|
throw new Error('speaker is required — "me" for the assistant, or a person\'s name.');
|
|
575
612
|
}
|
|
@@ -593,41 +630,42 @@ export class Client {
|
|
|
593
630
|
async listEngrams() {
|
|
594
631
|
const data = await this.request("GET", "/api/v1/engram");
|
|
595
632
|
return {
|
|
633
|
+
...data,
|
|
596
634
|
engrams: asRecords(data.engrams),
|
|
597
635
|
forms: asRecords(data.forms),
|
|
598
636
|
note: typeof data.note === "string" ? data.note : undefined,
|
|
599
637
|
};
|
|
600
638
|
}
|
|
601
|
-
createStore(userId) {
|
|
639
|
+
async createStore(userId) {
|
|
602
640
|
return this.post("/api/v1/memory/collection", { user_id: this.uid(userId) });
|
|
603
641
|
}
|
|
604
642
|
async listStores() {
|
|
605
643
|
const r = await this.request("GET", "/api/v1/memory/collections");
|
|
606
644
|
return asRecords(r.collections);
|
|
607
645
|
}
|
|
608
|
-
deleteStore(userId) {
|
|
646
|
+
async deleteStore(userId) {
|
|
609
647
|
if (typeof userId !== "string" || !userId.trim())
|
|
610
648
|
throw new Error("userId is required (a non-blank string) — deleteStore never falls back to the default store.");
|
|
611
649
|
warnIfStoreIdCollapses(userId);
|
|
612
650
|
return this.request("DELETE", "/api/v1/memory/collection", { user_id: userId });
|
|
613
651
|
}
|
|
614
|
-
addSpeaker(speaker, userId) {
|
|
652
|
+
async addSpeaker(speaker, userId) {
|
|
615
653
|
return this.post("/api/v1/memory/speakers", { user_id: this.uid(userId), speaker });
|
|
616
654
|
}
|
|
617
655
|
async listSpeakers(userId) {
|
|
618
656
|
const r = await this.request("GET", `/api/v1/memory/speakers?user_id=${encodeURIComponent(this.uid(userId))}`);
|
|
619
657
|
return { ...r, speakers: asRecords(r?.speakers) };
|
|
620
658
|
}
|
|
621
|
-
removeSpeaker(speaker, userId) {
|
|
659
|
+
async removeSpeaker(speaker, userId) {
|
|
622
660
|
return this.request("DELETE", "/api/v1/memory/speakers", { user_id: this.uid(userId), speaker });
|
|
623
661
|
}
|
|
624
|
-
delete(userId, memoryId) {
|
|
662
|
+
async delete(userId, memoryId) {
|
|
625
663
|
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
626
664
|
throw new Error("memoryId is required (non-blank). To delete every memory in a store, call deleteAll(userId) explicitly.");
|
|
627
665
|
}
|
|
628
666
|
return this.post("/api/v1/memory/forget", { user_id: this.uid(userId), memory_id: memoryId.trim() });
|
|
629
667
|
}
|
|
630
|
-
deleteAll(userId) {
|
|
668
|
+
async deleteAll(userId) {
|
|
631
669
|
if (typeof userId !== "string" || !userId.trim())
|
|
632
670
|
throw new Error("userId is required (a non-blank string) for deleteAll — anything else would wipe the default store.");
|
|
633
671
|
warnIfStoreIdCollapses(userId);
|
|
@@ -762,7 +800,9 @@ export class Client {
|
|
|
762
800
|
if (e?.name === "AbortError" || att.signal.aborted) {
|
|
763
801
|
throw new APIConnectionError(0, this.abortMessage(att.why()));
|
|
764
802
|
}
|
|
765
|
-
|
|
803
|
+
if (e instanceof WosError)
|
|
804
|
+
throw e;
|
|
805
|
+
throw new APIConnectionError(0, `network error: ${e?.message ?? e}`);
|
|
766
806
|
}
|
|
767
807
|
if (buf.byteLength === 0) {
|
|
768
808
|
throw new WosError(res.status, "empty image body — the service returned no bytes");
|
|
@@ -895,7 +935,14 @@ export class Client {
|
|
|
895
935
|
}
|
|
896
936
|
if (e instanceof WosError)
|
|
897
937
|
throw e;
|
|
898
|
-
|
|
938
|
+
if (!(IDEMPOTENT_METHODS.has(method.toUpperCase()) && attempt + 1 < attempts)) {
|
|
939
|
+
throw new APIConnectionError(0, `network error: ${e?.message ?? e}`);
|
|
940
|
+
}
|
|
941
|
+
att.clear();
|
|
942
|
+
const delay = this.backoffMs(attempt);
|
|
943
|
+
logDebug(`${method} ${logPath} — body dropped mid-stream, retrying in ${delay}ms (attempt ${attempt + 1}/${attempts})`);
|
|
944
|
+
await this.backoffSleep(delay, deadlineAt);
|
|
945
|
+
continue;
|
|
899
946
|
}
|
|
900
947
|
finally {
|
|
901
948
|
att.clear();
|