wontopos 2.2.14 → 2.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,7 @@ const ctx = await mem.recall("what does alice drink?", "alice");
22
22
 
23
23
  ## Methods
24
24
 
25
- `add` · `addTurn` · `addBulk` · `update` · `search` · `recall` · `history` · `stats` · `delete` · `deleteAll`
25
+ `add` · `addTurn` · `addBulk` · `update` · `search` · `recall` · `history` · `stats` · `get` · `listMemories` · `delete` · `deleteAll`
26
26
 
27
27
  All methods take `{ userId }` — it names the **store**: one isolated memory space per end-user, agent, or topic, then per account. WHO said each memory inside a store is the `speaker` tag below — storing the assistant's own words never needs a separate id.
28
28
 
@@ -25,7 +25,11 @@
25
25
  * Reliability: every call retries transient failures with exponential backoff +
26
26
  * jitter, honoring `Retry-After` — 429 always; 502/503 and connection errors only
27
27
  * when a retry can never double-process a write (idempotent calls, or a failure
28
- * at connect time). Tune with `maxRetries` (0 disables) and `timeoutMs`.
28
+ * at connect time). Tune with `maxRetries` (0 disables) and `timeoutMs`, or per
29
+ * call site via the `withTimeout()` / `withRetries()` clones.
30
+ *
31
+ * Debugging: set `WONTOPOS_LOG=debug` to log method/path/status/timing/retries
32
+ * to stderr — never memory content, request bodies, or the API key.
29
33
  *
30
34
  * Security posture (not configurable off): redirects are refused so the API key
31
35
  * can never follow one to another host; responses over 64MB are refused; the key
@@ -264,6 +268,11 @@ export declare class Client {
264
268
  withModel(model: string): Client;
265
269
  /** A client bound to `userId` as its default store (everything else kept). */
266
270
  withUser(userId: string): Client;
271
+ /** A client with a different per-request timeout in ms (everything else kept).
272
+ * `mem.withTimeout(120_000).addBulk(bigBlob)` — this slow call only. */
273
+ withTimeout(timeoutMs: number): Client;
274
+ /** A client with a different retry budget (everything else kept). 0 disables retries. */
275
+ withRetries(maxRetries: number): Client;
267
276
  /** Store one memory. `metadata` optional (e.g. { event_date: "2026-03-01" }).
268
277
  * `metadata.speaker`: "me" = the assistant's own words, or a person's name (up to 50 per store). */
269
278
  add(content: string, userId?: string, metadata?: Record<string, unknown>): Promise<StoreResult>;
@@ -285,6 +294,11 @@ export declare class Client {
285
294
  history(userId?: string): Promise<HistoryTurn[]>;
286
295
  /** Memory counts for a store: { total_memories, short_term_turns }. */
287
296
  stats(userId?: string): Promise<StatsResult>;
297
+ /** Fetch ONE memory by id — the original text + metadata, never the vector.
298
+ * The id is what `add`/`store` or `listMemories` returned. Same visibility as
299
+ * `listMemories`: an id from another store, a decomposed atom sentence, or an
300
+ * invalidated memory rejects with `NotFoundError`. `userId` optional (default store). */
301
+ get(userId: string | undefined, memoryId: string): Promise<Memory>;
288
302
  /** List a store's stored memories — the original text plus metadata, never the
289
303
  * vector. Paginated: pass the returned `next_cursor` back as `cursor` for the next
290
304
  * page; a `null` cursor means the last page. Use it to browse or export a store.
package/dist/wontopos.js CHANGED
@@ -25,7 +25,11 @@
25
25
  * Reliability: every call retries transient failures with exponential backoff +
26
26
  * jitter, honoring `Retry-After` — 429 always; 502/503 and connection errors only
27
27
  * when a retry can never double-process a write (idempotent calls, or a failure
28
- * at connect time). Tune with `maxRetries` (0 disables) and `timeoutMs`.
28
+ * at connect time). Tune with `maxRetries` (0 disables) and `timeoutMs`, or per
29
+ * call site via the `withTimeout()` / `withRetries()` clones.
30
+ *
31
+ * Debugging: set `WONTOPOS_LOG=debug` to log method/path/status/timing/retries
32
+ * to stderr — never memory content, request bodies, or the API key.
29
33
  *
30
34
  * Security posture (not configurable off): redirects are refused so the API key
31
35
  * can never follow one to another host; responses over 64MB are refused; the key
@@ -33,8 +37,23 @@
33
37
  * runtime's (never disabled by this SDK; Node 18+ floors TLS at 1.2). Prefer
34
38
  * `Client.fromEnv()` over keys in source code.
35
39
  */
36
- const VERSION = "2.2.14";
37
- const USER_AGENT = `wontopos-node/${VERSION}`;
40
+ const VERSION = "2.2.15";
41
+ /** Runtime info helps support debug a report ("node 18 on Windows...") —
42
+ * platform only, never anything identifying. Browsers have no `process` (and
43
+ * silently drop the UA header anyway). */
44
+ const RUNTIME = (() => {
45
+ const p = globalThis?.process;
46
+ return p?.version ? ` (node/${p.version}; ${p.platform}-${p.arch})` : "";
47
+ })();
48
+ const USER_AGENT = `wontopos-node/${VERSION}${RUNTIME}`;
49
+ /** Wire-level debug logging: set WONTOPOS_LOG=debug. Logs method/path/status/
50
+ * timing/retries to stderr — NEVER memory content, request bodies, or the key. */
51
+ function logDebug(msg) {
52
+ const env = globalThis?.process?.env;
53
+ if (typeof env?.WONTOPOS_LOG === "string" && env.WONTOPOS_LOG.toLowerCase() === "debug") {
54
+ console.error(`wontopos: ${msg}`);
55
+ }
56
+ }
38
57
  const DEFAULT_BASE_URL = "https://api.wontopos.com";
39
58
  const DEFAULT_MODEL = "tablet-1";
40
59
  /** 429 = rate-limited BEFORE processing → always safe to retry (no write, no bill). */
@@ -263,6 +282,15 @@ export class Client {
263
282
  withUser(userId) {
264
283
  return this.clone({ userId });
265
284
  }
285
+ /** A client with a different per-request timeout in ms (everything else kept).
286
+ * `mem.withTimeout(120_000).addBulk(bigBlob)` — this slow call only. */
287
+ withTimeout(timeoutMs) {
288
+ return this.clone({ timeoutMs });
289
+ }
290
+ /** A client with a different retry budget (everything else kept). 0 disables retries. */
291
+ withRetries(maxRetries) {
292
+ return this.clone({ maxRetries });
293
+ }
266
294
  // ----- write -----
267
295
  // `userId` is optional in every call below - omit it to use the client's default
268
296
  // store (set via `new Client({ apiKey, userId })` or `withUser`).
@@ -315,6 +343,18 @@ export class Client {
315
343
  stats(userId) {
316
344
  return this.post("/api/v1/memory/stats", { user_id: this.uid(userId) });
317
345
  }
346
+ /** Fetch ONE memory by id — the original text + metadata, never the vector.
347
+ * The id is what `add`/`store` or `listMemories` returned. Same visibility as
348
+ * `listMemories`: an id from another store, a decomposed atom sentence, or an
349
+ * invalidated memory rejects with `NotFoundError`. `userId` optional (default store). */
350
+ get(userId, memoryId) {
351
+ // Sync throw (same shape as `delete`), so a missing id fails loudly even
352
+ // when the caller forgets to await.
353
+ if (!memoryId || typeof memoryId !== "string") {
354
+ throw new Error("memoryId is required — the id that add/store or listMemories returned.");
355
+ }
356
+ return this.post("/api/v1/memory/get", { user_id: this.uid(userId), memory_id: memoryId }).then((r) => (r.memory ?? {}));
357
+ }
318
358
  /** List a store's stored memories — the original text plus metadata, never the
319
359
  * vector. Paginated: pass the returned `next_cursor` back as `cursor` for the next
320
360
  * page; a `null` cursor means the last page. Use it to browse or export a store.
@@ -475,7 +515,11 @@ export class Client {
475
515
  }
476
516
  async request(method, path, body) {
477
517
  const attempts = this.maxRetries + 1;
518
+ // Debug logging shows the path WITHOUT its query string — a query can carry
519
+ // a store id (listSpeakers), and logs must never carry data.
520
+ const logPath = path.split("?")[0];
478
521
  for (let attempt = 0; attempt < attempts; attempt++) {
522
+ const start = Date.now();
479
523
  const ctrl = new AbortController();
480
524
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
481
525
  let res;
@@ -500,7 +544,9 @@ export class Client {
500
544
  // a POST may already have stored + billed server-side.
501
545
  const safe = IDEMPOTENT_METHODS.has(method.toUpperCase()) || isConnectFailure(e);
502
546
  if (!timedOut && safe && attempt + 1 < attempts) {
503
- await new Promise((r) => setTimeout(r, this.backoffMs(attempt)));
547
+ const delay = this.backoffMs(attempt);
548
+ logDebug(`${method} ${logPath}: ${e?.code ?? e?.name ?? "network error"} — retrying in ${delay}ms (attempt ${attempt + 1}/${attempts})`);
549
+ await new Promise((r) => setTimeout(r, delay));
504
550
  continue;
505
551
  }
506
552
  throw new APIConnectionError(0, timedOut ? `request timed out after ${this.timeoutMs}ms` : `network error: ${e?.message ?? e}`);
@@ -511,7 +557,9 @@ export class Client {
511
557
  clearTimeout(timer);
512
558
  const retryAfter = res.headers.get("Retry-After");
513
559
  void res.body?.cancel();
514
- await new Promise((r) => setTimeout(r, this.backoffMs(attempt, retryAfter)));
560
+ const delay = this.backoffMs(attempt, retryAfter);
561
+ logDebug(`${method} ${logPath} -> ${res.status} — retrying in ${delay}ms (attempt ${attempt + 1}/${attempts})`);
562
+ await new Promise((r) => setTimeout(r, delay));
515
563
  continue;
516
564
  }
517
565
  if ((res.status >= 300 && res.status < 400) || res.type === "opaqueredirect") {
@@ -539,6 +587,7 @@ export class Client {
539
587
  finally {
540
588
  clearTimeout(timer);
541
589
  }
590
+ logDebug(`${method} ${logPath} -> ${res.status} in ${Date.now() - start}ms (attempt ${attempt + 1}/${attempts})`);
542
591
  if (!res.ok) {
543
592
  // Server returns either Anthropic-style envelope
544
593
  // {"type":"error","error":{"type":...,"message":...,"request_id":...}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wontopos",
3
- "version": "2.2.14",
3
+ "version": "2.2.15",
4
4
  "description": "Wontopos — long-term memory for AI agents. Pure semantic retrieval, identical recall in every language, ~100× lower LLM bill.",
5
5
  "keywords": [
6
6
  "memory",