wontopos 2.2.37 → 2.2.38
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 +5 -100
- package/dist/cjs/wontopos.d.cts +1048 -0
- package/dist/cjs/wontopos.js +88 -19
- package/dist/wontopos.d.ts +19 -3
- package/dist/wontopos.js +88 -19
- package/package.json +17 -7
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.38";
|
|
7
7
|
const RUNTIME = (() => {
|
|
8
8
|
const p = globalThis?.process;
|
|
9
9
|
return p?.version ? ` (node/${p.version}; ${p.platform}-${p.arch})` : "";
|
|
@@ -29,6 +29,12 @@ function normalizeStoreId(id) {
|
|
|
29
29
|
return id.toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
30
30
|
}
|
|
31
31
|
const WARNED_STORE_IDS_MAX = 1024;
|
|
32
|
+
function assertUsableStoreId(userId) {
|
|
33
|
+
if (userId !== undefined && (typeof userId !== "string" || !userId.trim())) {
|
|
34
|
+
throw new Error(`userId must be a non-blank string; got ${JSON.stringify(userId)}. Omit it to use the client's ` +
|
|
35
|
+
"default store, or pass a real store id — anything else would silently write into the default store.");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
32
38
|
const WARNED_FILTER_KEYS_MAX = 1024;
|
|
33
39
|
const warnedStoreIds = new Set();
|
|
34
40
|
function warnIfStoreIdCollapses(id) {
|
|
@@ -212,6 +218,47 @@ const KNOWN_FILTER_KEYS = new Set([
|
|
|
212
218
|
"min_importance",
|
|
213
219
|
]);
|
|
214
220
|
const warnedFilterKeys = new Set();
|
|
221
|
+
const KNOWN_SEARCH_KEYS = new Set([
|
|
222
|
+
"cache_control", "speaker", "filters", "verify", "max_images", "extra",
|
|
223
|
+
]);
|
|
224
|
+
const RESERVED_SEARCH_KEYS = new Set(["user_id", "query", "max_results"]);
|
|
225
|
+
const KNOWN_RECALL_KEYS = new Set(["form", "tz", "limit", "context_limit"]);
|
|
226
|
+
function checkSearchOpts(opts) {
|
|
227
|
+
checkOpts(opts, KNOWN_SEARCH_KEYS, "search");
|
|
228
|
+
}
|
|
229
|
+
function checkOpts(opts, known, what) {
|
|
230
|
+
for (const k of Object.keys(opts)) {
|
|
231
|
+
if (RESERVED_SEARCH_KEYS.has(k))
|
|
232
|
+
throw new Error(`${JSON.stringify(k)} is set by the call, not by options — pass it as an argument. ` +
|
|
233
|
+
`An app forwarding untrusted input as options cannot steer the store, the query ` +
|
|
234
|
+
`or the count, and this says so rather than dropping it silently.`);
|
|
235
|
+
if (known.has(k))
|
|
236
|
+
continue;
|
|
237
|
+
const near = [...known].find((n) => n !== "extra" && editWithin(k, n, 2));
|
|
238
|
+
throw new Error(`unknown ${what} option ${JSON.stringify(k)}` +
|
|
239
|
+
(near ? ` — did you mean ${JSON.stringify(near)}?` : "") +
|
|
240
|
+
`. The service drops keys it does not know and answers anyway, so this would ` +
|
|
241
|
+
`have looked like it worked.` +
|
|
242
|
+
(known.has("extra")
|
|
243
|
+
? ` Pass it under \`extra\` if the service accepts it and this client does not know it yet.`
|
|
244
|
+
: ""));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function editWithin(a, b, max) {
|
|
248
|
+
if (Math.abs(a.length - b.length) > max)
|
|
249
|
+
return false;
|
|
250
|
+
const prev = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
251
|
+
for (let i = 1; i <= a.length; i++) {
|
|
252
|
+
let diag = prev[0];
|
|
253
|
+
prev[0] = i;
|
|
254
|
+
for (let j = 1; j <= b.length; j++) {
|
|
255
|
+
const tmp = prev[j];
|
|
256
|
+
prev[j] = Math.min(prev[j] + 1, prev[j - 1] + 1, diag + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
257
|
+
diag = tmp;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return prev[b.length] <= max;
|
|
261
|
+
}
|
|
215
262
|
function warnOnUnknownFilters(filters) {
|
|
216
263
|
if (!filters || typeof filters !== "object" || Array.isArray(filters))
|
|
217
264
|
return;
|
|
@@ -257,6 +304,11 @@ class Client {
|
|
|
257
304
|
throw new Error("apiKey contains whitespace - check for a stray newline or paste error");
|
|
258
305
|
if (/[\x00-\x1f\x7f]/.test(key))
|
|
259
306
|
throw new Error("apiKey contains a control character - check for a stray byte or paste error");
|
|
307
|
+
if (/[^\x00-\x7f]/.test(key)) {
|
|
308
|
+
const bad = key.match(/[^\x00-\x7f]/)?.[0];
|
|
309
|
+
throw new Error(`apiKey contains a non-ASCII character (${JSON.stringify(bad)}) - rich text turns '-' into an ` +
|
|
310
|
+
"en dash; copy the key from a plain-text field");
|
|
311
|
+
}
|
|
260
312
|
this.apiKey = key;
|
|
261
313
|
this.base = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
262
314
|
const tm = opts.timeoutMs;
|
|
@@ -265,7 +317,8 @@ class Client {
|
|
|
265
317
|
if (this.model && !MODEL_RE.test(this.model)) {
|
|
266
318
|
throw new Error(`invalid model name: ${JSON.stringify(this.model)} (letters, digits, '.', '_', '-' only)`);
|
|
267
319
|
}
|
|
268
|
-
|
|
320
|
+
assertUsableStoreId(opts.userId);
|
|
321
|
+
this.defaultUser = opts.userId ?? DEFAULT_USER;
|
|
269
322
|
const mr = opts.maxRetries ?? opts.retries;
|
|
270
323
|
this.maxRetries = typeof mr === "number" && Number.isFinite(mr) ? Math.max(0, Math.floor(mr)) : 2;
|
|
271
324
|
const dl = opts.deadlineMs;
|
|
@@ -313,11 +366,8 @@ class Client {
|
|
|
313
366
|
return `Client(${this.base}, model=${this.model}, userId=${this.defaultUser}, apiKey=${maskKey(this.apiKey)})`;
|
|
314
367
|
}
|
|
315
368
|
uid(userId) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
"default store, or pass a real store id — anything else would silently write into the default store.");
|
|
319
|
-
}
|
|
320
|
-
const id = userId || this.defaultUser;
|
|
369
|
+
assertUsableStoreId(userId);
|
|
370
|
+
const id = userId ?? this.defaultUser;
|
|
321
371
|
warnIfStoreIdCollapses(id);
|
|
322
372
|
return id;
|
|
323
373
|
}
|
|
@@ -339,6 +389,10 @@ class Client {
|
|
|
339
389
|
return this.clone({ model });
|
|
340
390
|
}
|
|
341
391
|
withUser(userId) {
|
|
392
|
+
if (userId === undefined) {
|
|
393
|
+
throw new Error("withUser() needs a store id. It was called with undefined — usually a lookup that " +
|
|
394
|
+
"found nothing. Use the client's own default instead of calling withUser at all.");
|
|
395
|
+
}
|
|
342
396
|
return this.clone({ userId });
|
|
343
397
|
}
|
|
344
398
|
withSignal(signal) {
|
|
@@ -375,21 +429,27 @@ class Client {
|
|
|
375
429
|
return this.post("/api/v1/memory/supersede", { user_id: this.uid(userId), old_memory_id: oldMemoryId, new_content: newContent }, opts.idempotencyKey);
|
|
376
430
|
}
|
|
377
431
|
async search(query, userId, limit = 10, opts = {}) {
|
|
432
|
+
checkSearchOpts(opts);
|
|
378
433
|
warnOnUnknownFilters(opts.filters);
|
|
379
434
|
checkCount(limit, "limit");
|
|
380
|
-
const
|
|
435
|
+
const { extra, ...known } = opts;
|
|
436
|
+
const r = await this.post("/api/v1/memory/search", { ...extra, ...known, user_id: this.uid(userId), query, max_results: limit });
|
|
381
437
|
return mergeResults(r);
|
|
382
438
|
}
|
|
383
439
|
async searchSelf(query, userId, limit = 10, opts = {}) {
|
|
440
|
+
checkSearchOpts(opts);
|
|
384
441
|
warnOnUnknownFilters(opts.filters);
|
|
385
442
|
checkCount(limit, "limit");
|
|
386
|
-
const
|
|
443
|
+
const { extra, ...known } = opts;
|
|
444
|
+
const r = await this.post("/api/v1/memory/search", { ...extra, ...known, user_id: this.uid(userId), query, max_results: limit });
|
|
387
445
|
return { memories: asRecords(r.memories), self_memories: asRecords(r.self_memories) };
|
|
388
446
|
}
|
|
389
447
|
async searchFull(query, userId, limit = 10, opts = {}) {
|
|
448
|
+
checkSearchOpts(opts);
|
|
390
449
|
warnOnUnknownFilters(opts.filters);
|
|
391
450
|
checkCount(limit, "limit");
|
|
392
|
-
const
|
|
451
|
+
const { extra, ...known } = opts;
|
|
452
|
+
const r = await this.post("/api/v1/memory/search", { ...extra, ...known, user_id: this.uid(userId), query, max_results: limit });
|
|
393
453
|
return {
|
|
394
454
|
...r,
|
|
395
455
|
memories: asRecords(r.memories),
|
|
@@ -398,6 +458,7 @@ class Client {
|
|
|
398
458
|
};
|
|
399
459
|
}
|
|
400
460
|
async recall(query, userId, opts = {}) {
|
|
461
|
+
checkOpts(opts, KNOWN_RECALL_KEYS, "recall");
|
|
401
462
|
const body = { user_id: this.uid(userId), query };
|
|
402
463
|
if (opts.limit !== undefined) {
|
|
403
464
|
checkCount(opts.limit, "limit");
|
|
@@ -426,10 +487,10 @@ class Client {
|
|
|
426
487
|
return this.request("GET", `/api/v1/won/usage?days=${days}`);
|
|
427
488
|
}
|
|
428
489
|
get(userId, memoryId) {
|
|
429
|
-
if (
|
|
490
|
+
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
430
491
|
throw new Error("memoryId is required — the id that add/store or listMemories returned.");
|
|
431
492
|
}
|
|
432
|
-
return this.post("/api/v1/memory/get", { user_id: this.uid(userId), memory_id: memoryId }).then((r) => asObj(r.memory));
|
|
493
|
+
return this.post("/api/v1/memory/get", { user_id: this.uid(userId), memory_id: memoryId.trim() }).then((r) => asObj(r.memory));
|
|
433
494
|
}
|
|
434
495
|
listMemories(userId, opts = {}) {
|
|
435
496
|
const body = { user_id: this.uid(userId), limit: opts.limit ?? 100 };
|
|
@@ -460,16 +521,16 @@ class Client {
|
|
|
460
521
|
return out;
|
|
461
522
|
}
|
|
462
523
|
getImage(userId, memoryId) {
|
|
463
|
-
if (
|
|
524
|
+
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
464
525
|
throw new Error("memoryId is required — the id that add/store or listImages returned.");
|
|
465
526
|
}
|
|
466
|
-
return this.requestBytes("/api/v1/memory/image", { user_id: this.uid(userId), memory_id: memoryId });
|
|
527
|
+
return this.requestBytes("/api/v1/memory/image", { user_id: this.uid(userId), memory_id: memoryId.trim() });
|
|
467
528
|
}
|
|
468
529
|
forgetImage(userId, memoryId, opts = {}) {
|
|
469
|
-
if (
|
|
530
|
+
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
470
531
|
throw new Error("memoryId is required — the id of the memory whose image you want removed.");
|
|
471
532
|
}
|
|
472
|
-
const body = { user_id: this.uid(userId), memory_id: memoryId };
|
|
533
|
+
const body = { user_id: this.uid(userId), memory_id: memoryId.trim() };
|
|
473
534
|
if (opts.preview)
|
|
474
535
|
body.preview = true;
|
|
475
536
|
return this.request("DELETE", "/api/v1/memory/image", body);
|
|
@@ -518,10 +579,10 @@ class Client {
|
|
|
518
579
|
return this.post("/api/v1/won/revisions", body);
|
|
519
580
|
}
|
|
520
581
|
lineage(userId, memoryId) {
|
|
521
|
-
if (
|
|
582
|
+
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
522
583
|
throw new Error("memoryId is required — the memory whose history you want.");
|
|
523
584
|
}
|
|
524
|
-
return this.post("/api/v1/memory/lineage", { user_id: this.uid(userId), memory_id: memoryId });
|
|
585
|
+
return this.post("/api/v1/memory/lineage", { user_id: this.uid(userId), memory_id: memoryId.trim() });
|
|
525
586
|
}
|
|
526
587
|
bySpeaker(speaker, userId, opts = {}) {
|
|
527
588
|
if (!speaker || typeof speaker !== "string" || speaker.trim() === "") {
|
|
@@ -789,6 +850,14 @@ class Client {
|
|
|
789
850
|
const attempts = this.maxRetries + 1;
|
|
790
851
|
const logPath = path.split("?")[0];
|
|
791
852
|
const deadlineAt = this.deadlineAt();
|
|
853
|
+
let payload;
|
|
854
|
+
try {
|
|
855
|
+
payload = body === undefined ? undefined : JSON.stringify(body);
|
|
856
|
+
}
|
|
857
|
+
catch (e) {
|
|
858
|
+
throw new Error(`request body could not be serialized: ${e instanceof Error ? e.message : String(e)}. ` +
|
|
859
|
+
"Pass plain JSON values — an ORM entity, a BigInt or a circular reference cannot be sent.");
|
|
860
|
+
}
|
|
792
861
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
793
862
|
const start = Date.now();
|
|
794
863
|
const att = this.beginAttempt(deadlineAt);
|
|
@@ -797,7 +866,7 @@ class Client {
|
|
|
797
866
|
res = await this.fetchImpl(`${this.base}${path}`, {
|
|
798
867
|
method,
|
|
799
868
|
headers: extraHeaders ? { ...this.headers, ...extraHeaders } : this.headers,
|
|
800
|
-
body:
|
|
869
|
+
body: payload,
|
|
801
870
|
signal: att.signal,
|
|
802
871
|
redirect: "manual",
|
|
803
872
|
});
|
package/dist/wontopos.d.ts
CHANGED
|
@@ -490,7 +490,10 @@ export interface SearchFilters {
|
|
|
490
490
|
}
|
|
491
491
|
/** Test hook — the warn-once set is process-global by design. */
|
|
492
492
|
export declare function _resetFilterWarnings(): void;
|
|
493
|
-
/** Known `search` options
|
|
493
|
+
/** Known `search` options. An unknown key is refused before anything is sent — the
|
|
494
|
+
* service drops keys it does not recognise and answers normally, so a misspelling
|
|
495
|
+
* could not be told from an option that worked. Put a genuinely new one under
|
|
496
|
+
* `extra`. */
|
|
494
497
|
export interface SearchOptions {
|
|
495
498
|
/** Recall caching. A hit inside the TTL bills at 0.1× — but the FIRST call
|
|
496
499
|
* writes the cache and bills the query tokens at 2× (`5m`) or 3× (`1h`), so
|
|
@@ -519,7 +522,9 @@ export interface SearchOptions {
|
|
|
519
522
|
*/
|
|
520
523
|
verify?: number;
|
|
521
524
|
/**
|
|
522
|
-
* How many image memories the answer may carry, 0–5.
|
|
525
|
+
* How many image memories the answer may carry, 0–5. Omit it and the service uses
|
|
526
|
+
* 1; `0` asks for none. The MCP server defaults its own tool to 0 instead, so most
|
|
527
|
+
* searches through it carry no image rows.
|
|
523
528
|
* Out of range is refused, not clamped — silently cutting 5 to 1 would leave you
|
|
524
529
|
* believing you got five.
|
|
525
530
|
*
|
|
@@ -527,7 +532,13 @@ export interface SearchOptions {
|
|
|
527
532
|
* than answering with no images.
|
|
528
533
|
*/
|
|
529
534
|
max_images?: number;
|
|
530
|
-
|
|
535
|
+
/**
|
|
536
|
+
* 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
|
+
* `verfy: 3` used to be sent, dropped by the service, and answered normally, leaving
|
|
539
|
+
* a caller billed for one pass believing they had bought three.
|
|
540
|
+
*/
|
|
541
|
+
extra?: Record<string, unknown>;
|
|
531
542
|
}
|
|
532
543
|
/**
|
|
533
544
|
* Per-call options for a write.
|
|
@@ -690,6 +701,11 @@ export declare class Client {
|
|
|
690
701
|
/** Supersede an old memory with new content. Payload first, userId last — same shape as add/search. */
|
|
691
702
|
update(oldMemoryId: string, newContent: string, userId?: string, opts?: WriteOptions): Promise<UpdateResult>;
|
|
692
703
|
/** Search a store's memories. Returns them most relevant first.
|
|
704
|
+
*
|
|
705
|
+
* `limit` is 5-20, and out of range is refused rather than clamped: asking for 50
|
|
706
|
+
* and silently receiving 20 reads as "that is all there is". The default is 10, so
|
|
707
|
+
* a call that passes no count is unaffected. Before 2.2.35 the count was sent on
|
|
708
|
+
* unchecked.
|
|
693
709
|
*
|
|
694
710
|
* `limit` bounds `memories`, not the returned array. On a model that keeps the
|
|
695
711
|
* assistant's own words separate (Scroll 1.2+) those come back as well, so the
|
package/dist/wontopos.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const VERSION = "2.2.
|
|
1
|
+
const VERSION = "2.2.38";
|
|
2
2
|
const RUNTIME = (() => {
|
|
3
3
|
const p = globalThis?.process;
|
|
4
4
|
return p?.version ? ` (node/${p.version}; ${p.platform}-${p.arch})` : "";
|
|
@@ -24,6 +24,12 @@ function normalizeStoreId(id) {
|
|
|
24
24
|
return id.toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
25
25
|
}
|
|
26
26
|
const WARNED_STORE_IDS_MAX = 1024;
|
|
27
|
+
function assertUsableStoreId(userId) {
|
|
28
|
+
if (userId !== undefined && (typeof userId !== "string" || !userId.trim())) {
|
|
29
|
+
throw new Error(`userId must be a non-blank string; got ${JSON.stringify(userId)}. Omit it to use the client's ` +
|
|
30
|
+
"default store, or pass a real store id — anything else would silently write into the default store.");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
27
33
|
const WARNED_FILTER_KEYS_MAX = 1024;
|
|
28
34
|
const warnedStoreIds = new Set();
|
|
29
35
|
function warnIfStoreIdCollapses(id) {
|
|
@@ -197,6 +203,47 @@ const KNOWN_FILTER_KEYS = new Set([
|
|
|
197
203
|
"min_importance",
|
|
198
204
|
]);
|
|
199
205
|
const warnedFilterKeys = new Set();
|
|
206
|
+
const KNOWN_SEARCH_KEYS = new Set([
|
|
207
|
+
"cache_control", "speaker", "filters", "verify", "max_images", "extra",
|
|
208
|
+
]);
|
|
209
|
+
const RESERVED_SEARCH_KEYS = new Set(["user_id", "query", "max_results"]);
|
|
210
|
+
const KNOWN_RECALL_KEYS = new Set(["form", "tz", "limit", "context_limit"]);
|
|
211
|
+
function checkSearchOpts(opts) {
|
|
212
|
+
checkOpts(opts, KNOWN_SEARCH_KEYS, "search");
|
|
213
|
+
}
|
|
214
|
+
function checkOpts(opts, known, what) {
|
|
215
|
+
for (const k of Object.keys(opts)) {
|
|
216
|
+
if (RESERVED_SEARCH_KEYS.has(k))
|
|
217
|
+
throw new Error(`${JSON.stringify(k)} is set by the call, not by options — pass it as an argument. ` +
|
|
218
|
+
`An app forwarding untrusted input as options cannot steer the store, the query ` +
|
|
219
|
+
`or the count, and this says so rather than dropping it silently.`);
|
|
220
|
+
if (known.has(k))
|
|
221
|
+
continue;
|
|
222
|
+
const near = [...known].find((n) => n !== "extra" && editWithin(k, n, 2));
|
|
223
|
+
throw new Error(`unknown ${what} option ${JSON.stringify(k)}` +
|
|
224
|
+
(near ? ` — did you mean ${JSON.stringify(near)}?` : "") +
|
|
225
|
+
`. The service drops keys it does not know and answers anyway, so this would ` +
|
|
226
|
+
`have looked like it worked.` +
|
|
227
|
+
(known.has("extra")
|
|
228
|
+
? ` Pass it under \`extra\` if the service accepts it and this client does not know it yet.`
|
|
229
|
+
: ""));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function editWithin(a, b, max) {
|
|
233
|
+
if (Math.abs(a.length - b.length) > max)
|
|
234
|
+
return false;
|
|
235
|
+
const prev = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
236
|
+
for (let i = 1; i <= a.length; i++) {
|
|
237
|
+
let diag = prev[0];
|
|
238
|
+
prev[0] = i;
|
|
239
|
+
for (let j = 1; j <= b.length; j++) {
|
|
240
|
+
const tmp = prev[j];
|
|
241
|
+
prev[j] = Math.min(prev[j] + 1, prev[j - 1] + 1, diag + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
242
|
+
diag = tmp;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return prev[b.length] <= max;
|
|
246
|
+
}
|
|
200
247
|
function warnOnUnknownFilters(filters) {
|
|
201
248
|
if (!filters || typeof filters !== "object" || Array.isArray(filters))
|
|
202
249
|
return;
|
|
@@ -242,6 +289,11 @@ export class Client {
|
|
|
242
289
|
throw new Error("apiKey contains whitespace - check for a stray newline or paste error");
|
|
243
290
|
if (/[\x00-\x1f\x7f]/.test(key))
|
|
244
291
|
throw new Error("apiKey contains a control character - check for a stray byte or paste error");
|
|
292
|
+
if (/[^\x00-\x7f]/.test(key)) {
|
|
293
|
+
const bad = key.match(/[^\x00-\x7f]/)?.[0];
|
|
294
|
+
throw new Error(`apiKey contains a non-ASCII character (${JSON.stringify(bad)}) - rich text turns '-' into an ` +
|
|
295
|
+
"en dash; copy the key from a plain-text field");
|
|
296
|
+
}
|
|
245
297
|
this.apiKey = key;
|
|
246
298
|
this.base = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
247
299
|
const tm = opts.timeoutMs;
|
|
@@ -250,7 +302,8 @@ export class Client {
|
|
|
250
302
|
if (this.model && !MODEL_RE.test(this.model)) {
|
|
251
303
|
throw new Error(`invalid model name: ${JSON.stringify(this.model)} (letters, digits, '.', '_', '-' only)`);
|
|
252
304
|
}
|
|
253
|
-
|
|
305
|
+
assertUsableStoreId(opts.userId);
|
|
306
|
+
this.defaultUser = opts.userId ?? DEFAULT_USER;
|
|
254
307
|
const mr = opts.maxRetries ?? opts.retries;
|
|
255
308
|
this.maxRetries = typeof mr === "number" && Number.isFinite(mr) ? Math.max(0, Math.floor(mr)) : 2;
|
|
256
309
|
const dl = opts.deadlineMs;
|
|
@@ -298,11 +351,8 @@ export class Client {
|
|
|
298
351
|
return `Client(${this.base}, model=${this.model}, userId=${this.defaultUser}, apiKey=${maskKey(this.apiKey)})`;
|
|
299
352
|
}
|
|
300
353
|
uid(userId) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
"default store, or pass a real store id — anything else would silently write into the default store.");
|
|
304
|
-
}
|
|
305
|
-
const id = userId || this.defaultUser;
|
|
354
|
+
assertUsableStoreId(userId);
|
|
355
|
+
const id = userId ?? this.defaultUser;
|
|
306
356
|
warnIfStoreIdCollapses(id);
|
|
307
357
|
return id;
|
|
308
358
|
}
|
|
@@ -324,6 +374,10 @@ export class Client {
|
|
|
324
374
|
return this.clone({ model });
|
|
325
375
|
}
|
|
326
376
|
withUser(userId) {
|
|
377
|
+
if (userId === undefined) {
|
|
378
|
+
throw new Error("withUser() needs a store id. It was called with undefined — usually a lookup that " +
|
|
379
|
+
"found nothing. Use the client's own default instead of calling withUser at all.");
|
|
380
|
+
}
|
|
327
381
|
return this.clone({ userId });
|
|
328
382
|
}
|
|
329
383
|
withSignal(signal) {
|
|
@@ -360,21 +414,27 @@ export class Client {
|
|
|
360
414
|
return this.post("/api/v1/memory/supersede", { user_id: this.uid(userId), old_memory_id: oldMemoryId, new_content: newContent }, opts.idempotencyKey);
|
|
361
415
|
}
|
|
362
416
|
async search(query, userId, limit = 10, opts = {}) {
|
|
417
|
+
checkSearchOpts(opts);
|
|
363
418
|
warnOnUnknownFilters(opts.filters);
|
|
364
419
|
checkCount(limit, "limit");
|
|
365
|
-
const
|
|
420
|
+
const { extra, ...known } = opts;
|
|
421
|
+
const r = await this.post("/api/v1/memory/search", { ...extra, ...known, user_id: this.uid(userId), query, max_results: limit });
|
|
366
422
|
return mergeResults(r);
|
|
367
423
|
}
|
|
368
424
|
async searchSelf(query, userId, limit = 10, opts = {}) {
|
|
425
|
+
checkSearchOpts(opts);
|
|
369
426
|
warnOnUnknownFilters(opts.filters);
|
|
370
427
|
checkCount(limit, "limit");
|
|
371
|
-
const
|
|
428
|
+
const { extra, ...known } = opts;
|
|
429
|
+
const r = await this.post("/api/v1/memory/search", { ...extra, ...known, user_id: this.uid(userId), query, max_results: limit });
|
|
372
430
|
return { memories: asRecords(r.memories), self_memories: asRecords(r.self_memories) };
|
|
373
431
|
}
|
|
374
432
|
async searchFull(query, userId, limit = 10, opts = {}) {
|
|
433
|
+
checkSearchOpts(opts);
|
|
375
434
|
warnOnUnknownFilters(opts.filters);
|
|
376
435
|
checkCount(limit, "limit");
|
|
377
|
-
const
|
|
436
|
+
const { extra, ...known } = opts;
|
|
437
|
+
const r = await this.post("/api/v1/memory/search", { ...extra, ...known, user_id: this.uid(userId), query, max_results: limit });
|
|
378
438
|
return {
|
|
379
439
|
...r,
|
|
380
440
|
memories: asRecords(r.memories),
|
|
@@ -383,6 +443,7 @@ export class Client {
|
|
|
383
443
|
};
|
|
384
444
|
}
|
|
385
445
|
async recall(query, userId, opts = {}) {
|
|
446
|
+
checkOpts(opts, KNOWN_RECALL_KEYS, "recall");
|
|
386
447
|
const body = { user_id: this.uid(userId), query };
|
|
387
448
|
if (opts.limit !== undefined) {
|
|
388
449
|
checkCount(opts.limit, "limit");
|
|
@@ -411,10 +472,10 @@ export class Client {
|
|
|
411
472
|
return this.request("GET", `/api/v1/won/usage?days=${days}`);
|
|
412
473
|
}
|
|
413
474
|
get(userId, memoryId) {
|
|
414
|
-
if (
|
|
475
|
+
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
415
476
|
throw new Error("memoryId is required — the id that add/store or listMemories returned.");
|
|
416
477
|
}
|
|
417
|
-
return this.post("/api/v1/memory/get", { user_id: this.uid(userId), memory_id: memoryId }).then((r) => asObj(r.memory));
|
|
478
|
+
return this.post("/api/v1/memory/get", { user_id: this.uid(userId), memory_id: memoryId.trim() }).then((r) => asObj(r.memory));
|
|
418
479
|
}
|
|
419
480
|
listMemories(userId, opts = {}) {
|
|
420
481
|
const body = { user_id: this.uid(userId), limit: opts.limit ?? 100 };
|
|
@@ -445,16 +506,16 @@ export class Client {
|
|
|
445
506
|
return out;
|
|
446
507
|
}
|
|
447
508
|
getImage(userId, memoryId) {
|
|
448
|
-
if (
|
|
509
|
+
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
449
510
|
throw new Error("memoryId is required — the id that add/store or listImages returned.");
|
|
450
511
|
}
|
|
451
|
-
return this.requestBytes("/api/v1/memory/image", { user_id: this.uid(userId), memory_id: memoryId });
|
|
512
|
+
return this.requestBytes("/api/v1/memory/image", { user_id: this.uid(userId), memory_id: memoryId.trim() });
|
|
452
513
|
}
|
|
453
514
|
forgetImage(userId, memoryId, opts = {}) {
|
|
454
|
-
if (
|
|
515
|
+
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
455
516
|
throw new Error("memoryId is required — the id of the memory whose image you want removed.");
|
|
456
517
|
}
|
|
457
|
-
const body = { user_id: this.uid(userId), memory_id: memoryId };
|
|
518
|
+
const body = { user_id: this.uid(userId), memory_id: memoryId.trim() };
|
|
458
519
|
if (opts.preview)
|
|
459
520
|
body.preview = true;
|
|
460
521
|
return this.request("DELETE", "/api/v1/memory/image", body);
|
|
@@ -503,10 +564,10 @@ export class Client {
|
|
|
503
564
|
return this.post("/api/v1/won/revisions", body);
|
|
504
565
|
}
|
|
505
566
|
lineage(userId, memoryId) {
|
|
506
|
-
if (
|
|
567
|
+
if (typeof memoryId !== "string" || !memoryId.trim()) {
|
|
507
568
|
throw new Error("memoryId is required — the memory whose history you want.");
|
|
508
569
|
}
|
|
509
|
-
return this.post("/api/v1/memory/lineage", { user_id: this.uid(userId), memory_id: memoryId });
|
|
570
|
+
return this.post("/api/v1/memory/lineage", { user_id: this.uid(userId), memory_id: memoryId.trim() });
|
|
510
571
|
}
|
|
511
572
|
bySpeaker(speaker, userId, opts = {}) {
|
|
512
573
|
if (!speaker || typeof speaker !== "string" || speaker.trim() === "") {
|
|
@@ -774,6 +835,14 @@ export class Client {
|
|
|
774
835
|
const attempts = this.maxRetries + 1;
|
|
775
836
|
const logPath = path.split("?")[0];
|
|
776
837
|
const deadlineAt = this.deadlineAt();
|
|
838
|
+
let payload;
|
|
839
|
+
try {
|
|
840
|
+
payload = body === undefined ? undefined : JSON.stringify(body);
|
|
841
|
+
}
|
|
842
|
+
catch (e) {
|
|
843
|
+
throw new Error(`request body could not be serialized: ${e instanceof Error ? e.message : String(e)}. ` +
|
|
844
|
+
"Pass plain JSON values — an ORM entity, a BigInt or a circular reference cannot be sent.");
|
|
845
|
+
}
|
|
777
846
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
778
847
|
const start = Date.now();
|
|
779
848
|
const att = this.beginAttempt(deadlineAt);
|
|
@@ -782,7 +851,7 @@ export class Client {
|
|
|
782
851
|
res = await this.fetchImpl(`${this.base}${path}`, {
|
|
783
852
|
method,
|
|
784
853
|
headers: extraHeaders ? { ...this.headers, ...extraHeaders } : this.headers,
|
|
785
|
-
body:
|
|
854
|
+
body: payload,
|
|
786
855
|
signal: att.signal,
|
|
787
856
|
redirect: "manual",
|
|
788
857
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wontopos",
|
|
3
|
-
"version": "2.2.
|
|
4
|
-
"description": "Wontopos
|
|
3
|
+
"version": "2.2.38",
|
|
4
|
+
"description": "Wontopos \u2014 long-term memory for AI agents. Identical recall in every language, ~100\u00d7 lower LLM bill.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"memory",
|
|
7
7
|
"ai",
|
|
@@ -20,11 +20,17 @@
|
|
|
20
20
|
"types": "./dist/wontopos.d.ts",
|
|
21
21
|
"exports": {
|
|
22
22
|
".": {
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
"import": {
|
|
24
|
+
"types": "./dist/wontopos.d.ts",
|
|
25
|
+
"default": "./dist/wontopos.js"
|
|
26
|
+
},
|
|
27
|
+
"require": {
|
|
28
|
+
"types": "./dist/cjs/wontopos.d.cts",
|
|
29
|
+
"default": "./dist/cjs/wontopos.js"
|
|
30
|
+
},
|
|
26
31
|
"default": "./dist/wontopos.js"
|
|
27
|
-
}
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
28
34
|
},
|
|
29
35
|
"files": [
|
|
30
36
|
"dist",
|
|
@@ -35,7 +41,7 @@
|
|
|
35
41
|
"node": ">=18"
|
|
36
42
|
},
|
|
37
43
|
"scripts": {
|
|
38
|
-
"build": "tsc && tsc -p tsconfig.types.json && tsc -p tsconfig.cjs.json && node -e \"require('fs').writeFileSync('dist/cjs/package.json','{\\\"type\\\":\\\"commonjs\\\"}\\n')\"",
|
|
44
|
+
"build": "tsc && tsc -p tsconfig.types.json && tsc -p tsconfig.cjs.json && node -e \"const fs=require('fs');fs.writeFileSync('dist/cjs/package.json','{\\\"type\\\":\\\"commonjs\\\"}\\n');fs.copyFileSync('dist/wontopos.d.ts','dist/cjs/wontopos.d.cts')\"",
|
|
39
45
|
"test": "npm run build && node --test tests/client.test.mjs tests/cjs.test.cjs",
|
|
40
46
|
"prepublishOnly": "npm run build && node scripts/pre-publish.mjs"
|
|
41
47
|
},
|
|
@@ -44,5 +50,9 @@
|
|
|
44
50
|
},
|
|
45
51
|
"bugs": {
|
|
46
52
|
"url": "https://wontopos.com/contact?topic=bug"
|
|
53
|
+
},
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+https://github.com/wontopos/wontopos-node.git"
|
|
47
57
|
}
|
|
48
58
|
}
|