wontopos 2.2.25 → 2.2.27
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 +28 -1
- package/dist/wontopos.d.ts +34 -2
- package/dist/wontopos.js +45 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,7 +52,13 @@ registration and never counts against it.
|
|
|
52
52
|
## Recall caching
|
|
53
53
|
|
|
54
54
|
Opt in per search and repeated or extended queries reuse the previous result
|
|
55
|
-
at 10% of the normal rate (Tablet and Scroll models)
|
|
55
|
+
at 10% of the normal rate (Tablet and Scroll models).
|
|
56
|
+
|
|
57
|
+
It is not free to turn on: the FIRST call writes the cache and bills the query
|
|
58
|
+
tokens at 2x for a `5m` TTL, 3x for `1h`. Only hits inside the TTL bill at 0.1x.
|
|
59
|
+
So it pays for a query you repeat or extend, and costs more for one you issue
|
|
60
|
+
once — do not switch it on globally. Any write to the store invalidates its cache
|
|
61
|
+
at once, so a hit can never predate a new memory.
|
|
56
62
|
|
|
57
63
|
```ts
|
|
58
64
|
const hits = await mem.search("...the conversation so far...", "alice", 10,
|
|
@@ -74,6 +80,27 @@ Built in, no configuration needed:
|
|
|
74
80
|
meaning "delete everything"; wiping a store is only ever the explicit
|
|
75
81
|
`deleteAll(userId)` / `deleteStore(userId)`.
|
|
76
82
|
|
|
83
|
+
### Behind a corporate proxy
|
|
84
|
+
|
|
85
|
+
Node's global `fetch` (undici) ignores `HTTP_PROXY` / `HTTPS_PROXY`, so behind an
|
|
86
|
+
egress proxy this SDK could not connect at all — while the Python and Rust SDKs went
|
|
87
|
+
through, because `requests` and `reqwest` both read those variables. Pass a
|
|
88
|
+
proxy-aware `fetch` and the three behave the same:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { ProxyAgent } from "undici";
|
|
92
|
+
|
|
93
|
+
const agent = new ProxyAgent(process.env.HTTPS_PROXY!);
|
|
94
|
+
const mem = new Client({
|
|
95
|
+
apiKey: process.env.WONTOPOS_API_KEY!,
|
|
96
|
+
fetch: (url, init) => fetch(url, { ...init, dispatcher: agent } as any),
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The same option is how you add instrumentation or drive the client without a
|
|
101
|
+
network in tests. It replaces the transport, not the client's rules — retries,
|
|
102
|
+
timeouts, redirect refusal and the response cap all still apply.
|
|
103
|
+
|
|
77
104
|
## Security
|
|
78
105
|
|
|
79
106
|
Built in, none of it configurable off:
|
package/dist/wontopos.d.ts
CHANGED
|
@@ -286,7 +286,9 @@ export interface SearchFilters {
|
|
|
286
286
|
export declare function _resetFilterWarnings(): void;
|
|
287
287
|
/** Known `search` options; extra fields pass through to the API untouched. */
|
|
288
288
|
export interface SearchOptions {
|
|
289
|
-
/** Recall caching
|
|
289
|
+
/** Recall caching. A hit inside the TTL bills at 0.1× — but the FIRST call
|
|
290
|
+
* writes the cache and bills the query tokens at 2× (`5m`) or 3× (`1h`), so
|
|
291
|
+
* this only pays for a query you repeat or extend. Do not enable it globally. */
|
|
290
292
|
cache_control?: {
|
|
291
293
|
ttl: "5m" | "1h";
|
|
292
294
|
};
|
|
@@ -336,6 +338,26 @@ export interface ClientOptions {
|
|
|
336
338
|
* key on an options object is not an error in TS at runtime). Both names work;
|
|
337
339
|
* `maxRetries` wins if somehow both are given. */
|
|
338
340
|
retries?: number;
|
|
341
|
+
/** The `fetch` used for every request (default: the global `fetch`).
|
|
342
|
+
*
|
|
343
|
+
* This is the seam for anything the runtime cannot express through options.
|
|
344
|
+
* The one that matters in practice is a corporate egress proxy: Node's global
|
|
345
|
+
* fetch (undici) IGNORES `HTTP_PROXY` / `HTTPS_PROXY`, so behind such a proxy
|
|
346
|
+
* this SDK could not connect at all — while the Python and Rust SDKs went
|
|
347
|
+
* through, because `requests` and `reqwest` both read those variables. Pass a
|
|
348
|
+
* proxy-aware fetch and the three behave the same again:
|
|
349
|
+
*
|
|
350
|
+
* import { ProxyAgent } from "undici";
|
|
351
|
+
* const agent = new ProxyAgent(process.env.HTTPS_PROXY!);
|
|
352
|
+
* const mem = new Client({
|
|
353
|
+
* apiKey,
|
|
354
|
+
* fetch: (url, init) => fetch(url, { ...init, dispatcher: agent } as any),
|
|
355
|
+
* });
|
|
356
|
+
*
|
|
357
|
+
* It is also how you add instrumentation or drive the client in a test without
|
|
358
|
+
* a network. Retries, timeouts, redirect refusal and the size cap all still
|
|
359
|
+
* apply — this replaces the transport, not the client's rules. */
|
|
360
|
+
fetch?: typeof fetch;
|
|
339
361
|
}
|
|
340
362
|
/** Wontopos memory client. Store and recall memories, isolated per `userId`. */
|
|
341
363
|
export declare class Client {
|
|
@@ -345,6 +367,9 @@ export declare class Client {
|
|
|
345
367
|
private readonly apiKey;
|
|
346
368
|
private readonly model;
|
|
347
369
|
private readonly maxRetries;
|
|
370
|
+
/** Transport. Held as a field (not read off `globalThis` per call) so a caller
|
|
371
|
+
* who passes one gets it for every request, including from cloned clients. */
|
|
372
|
+
private readonly fetchImpl;
|
|
348
373
|
/** The store every call uses unless one passes `userId`. */
|
|
349
374
|
private readonly defaultUser;
|
|
350
375
|
private _rateLimit;
|
|
@@ -393,7 +418,14 @@ export declare class Client {
|
|
|
393
418
|
addBulk(content: string, userId?: string, category?: string, timestamp?: string, opts?: WriteOptions): Promise<StatusResult>;
|
|
394
419
|
/** Supersede an old memory with new content. Payload first, userId last — same shape as add/search. */
|
|
395
420
|
update(oldMemoryId: string, newContent: string, userId?: string, opts?: WriteOptions): Promise<UpdateResult>;
|
|
396
|
-
/** Semantic search over a store's memories. Returns memories, most relevant first.
|
|
421
|
+
/** Semantic search over a store's memories. Returns memories, most relevant first.
|
|
422
|
+
*
|
|
423
|
+
* `limit` bounds the MAIN lane, not the returned array. On a self-lane model
|
|
424
|
+
* (Scroll 1.2+) the service answers in two disjoint lanes — the assistant's own
|
|
425
|
+
* words come back separately — and both are returned here, so the array can hold
|
|
426
|
+
* more than `limit`. They were retrieved and billed either way; dropping them would
|
|
427
|
+
* only hide what you already paid for. Size a prompt window on the array you get
|
|
428
|
+
* back, not on `limit`. `searchSelf()` hands the lanes back apart. */
|
|
397
429
|
search(query: string, userId?: string, limit?: number, opts?: SearchOptions): Promise<Memory[]>;
|
|
398
430
|
/** Search a self-memory model (Scroll 1.2+): both lanes from ONE call. Returns
|
|
399
431
|
* `{ memories, self_memories }` — `memories` is the main lane (what others said /
|
package/dist/wontopos.js
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
* runtime's (never disabled by this SDK; Node 18+ floors TLS at 1.2). Prefer
|
|
38
38
|
* `Client.fromEnv()` over keys in source code.
|
|
39
39
|
*/
|
|
40
|
-
const VERSION = "2.2.
|
|
40
|
+
const VERSION = "2.2.27";
|
|
41
41
|
/** Runtime info helps support debug a report ("node 18 on Windows...") —
|
|
42
42
|
* platform only, never anything identifying. Browsers have no `process` (and
|
|
43
43
|
* silently drop the UA header anyway). */
|
|
@@ -91,6 +91,17 @@ const IDEMPOTENCY_KEY_RE = /^[A-Za-z0-9._:\-]{1,128}$/;
|
|
|
91
91
|
function normalizeStoreId(id) {
|
|
92
92
|
return id.toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
93
93
|
}
|
|
94
|
+
/** Ids already warned about, so each is reported once.
|
|
95
|
+
*
|
|
96
|
+
* Bounded on purpose. The warning fires on ids that fold, and the shape it exists
|
|
97
|
+
* to catch is an email — which this SDK's own documented pattern ("one store per
|
|
98
|
+
* end user") turns into one entry PER USER, held for the life of the process and
|
|
99
|
+
* never released, even when the client is. A server passing 50,000 user ids grew
|
|
100
|
+
* this to 50,000 entries, a leak in exactly the case being warned about. A `Set`
|
|
101
|
+
* keeps insertion order, so the oldest entry is the one evicted; dropping the
|
|
102
|
+
* oldest rather than refusing new ones means a collision that first shows up late
|
|
103
|
+
* still gets its one warning. */
|
|
104
|
+
const WARNED_STORE_IDS_MAX = 1024;
|
|
94
105
|
const warnedStoreIds = new Set();
|
|
95
106
|
function warnIfStoreIdCollapses(id) {
|
|
96
107
|
if (!id || warnedStoreIds.has(id))
|
|
@@ -99,6 +110,12 @@ function warnIfStoreIdCollapses(id) {
|
|
|
99
110
|
if (normalized === id)
|
|
100
111
|
return;
|
|
101
112
|
warnedStoreIds.add(id);
|
|
113
|
+
while (warnedStoreIds.size > WARNED_STORE_IDS_MAX) {
|
|
114
|
+
const oldest = warnedStoreIds.values().next().value;
|
|
115
|
+
if (oldest === undefined)
|
|
116
|
+
break;
|
|
117
|
+
warnedStoreIds.delete(oldest);
|
|
118
|
+
}
|
|
102
119
|
console.warn(`wontopos: store id ${JSON.stringify(id)} is stored as ${JSON.stringify(normalized)} ` +
|
|
103
120
|
`(lowercased, and anything outside [a-z0-9_] becomes "_"). Ids that differ only by case or ` +
|
|
104
121
|
`punctuation share ONE store and therefore one set of memories — if these ids come from your ` +
|
|
@@ -310,6 +327,9 @@ export class Client {
|
|
|
310
327
|
apiKey;
|
|
311
328
|
model;
|
|
312
329
|
maxRetries;
|
|
330
|
+
/** Transport. Held as a field (not read off `globalThis` per call) so a caller
|
|
331
|
+
* who passes one gets it for every request, including from cloned clients. */
|
|
332
|
+
fetchImpl;
|
|
313
333
|
/** The store every call uses unless one passes `userId`. */
|
|
314
334
|
defaultUser;
|
|
315
335
|
_rateLimit = null;
|
|
@@ -344,6 +364,17 @@ export class Client {
|
|
|
344
364
|
// "retries exhausted"; Infinity would retry forever.
|
|
345
365
|
const mr = opts.maxRetries ?? opts.retries;
|
|
346
366
|
this.maxRetries = typeof mr === "number" && Number.isFinite(mr) ? Math.max(0, Math.floor(mr)) : 2;
|
|
367
|
+
// Bind the global so `fetch` is not called as a method of `globalThis`, which
|
|
368
|
+
// throws "Illegal invocation" on some runtimes. A caller-supplied fetch is
|
|
369
|
+
// taken as given — it is already whatever they meant to hand us.
|
|
370
|
+
const f = opts.fetch;
|
|
371
|
+
if (f !== undefined && typeof f !== "function") {
|
|
372
|
+
throw new Error("fetch must be a function (a fetch-compatible transport)");
|
|
373
|
+
}
|
|
374
|
+
this.fetchImpl = f ?? (globalThis.fetch ? globalThis.fetch.bind(globalThis) : undefined);
|
|
375
|
+
if (typeof this.fetchImpl !== "function") {
|
|
376
|
+
throw new Error("no fetch available in this runtime — pass one as `fetch` (Node 18+ has it built in; older Node needs undici)");
|
|
377
|
+
}
|
|
347
378
|
this.headers = {
|
|
348
379
|
"X-API-Key": this.apiKey,
|
|
349
380
|
"Content-Type": "application/json",
|
|
@@ -395,6 +426,10 @@ export class Client {
|
|
|
395
426
|
model: this.model,
|
|
396
427
|
userId: this.defaultUser,
|
|
397
428
|
maxRetries: this.maxRetries,
|
|
429
|
+
// Carry the transport across clones. Without this, `withModel(...)` and
|
|
430
|
+
// friends would silently fall back to the global fetch and a proxy-bound
|
|
431
|
+
// client would stop connecting the moment it was cloned.
|
|
432
|
+
fetch: this.fetchImpl,
|
|
398
433
|
...overrides,
|
|
399
434
|
});
|
|
400
435
|
}
|
|
@@ -455,7 +490,14 @@ export class Client {
|
|
|
455
490
|
return this.post("/api/v1/memory/supersede", { user_id: this.uid(userId), old_memory_id: oldMemoryId, new_content: newContent }, opts.idempotencyKey);
|
|
456
491
|
}
|
|
457
492
|
// ----- read -----
|
|
458
|
-
/** Semantic search over a store's memories. Returns memories, most relevant first.
|
|
493
|
+
/** Semantic search over a store's memories. Returns memories, most relevant first.
|
|
494
|
+
*
|
|
495
|
+
* `limit` bounds the MAIN lane, not the returned array. On a self-lane model
|
|
496
|
+
* (Scroll 1.2+) the service answers in two disjoint lanes — the assistant's own
|
|
497
|
+
* words come back separately — and both are returned here, so the array can hold
|
|
498
|
+
* more than `limit`. They were retrieved and billed either way; dropping them would
|
|
499
|
+
* only hide what you already paid for. Size a prompt window on the array you get
|
|
500
|
+
* back, not on `limit`. `searchSelf()` hands the lanes back apart. */
|
|
459
501
|
async search(query, userId, limit = 10, opts = {}) {
|
|
460
502
|
// Reserved fields win over ...opts: an app that forwards untrusted input as
|
|
461
503
|
// opts must not be able to override the store (user_id), query, or limit.
|
|
@@ -710,7 +752,7 @@ export class Client {
|
|
|
710
752
|
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
711
753
|
let res;
|
|
712
754
|
try {
|
|
713
|
-
res = await
|
|
755
|
+
res = await this.fetchImpl(`${this.base}${path}`, {
|
|
714
756
|
method,
|
|
715
757
|
headers: extraHeaders ? { ...this.headers, ...extraHeaders } : this.headers,
|
|
716
758
|
body: body === undefined ? undefined : JSON.stringify(body),
|
package/package.json
CHANGED