wontopos 2.2.15 → 2.2.16

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.
Files changed (2) hide show
  1. package/dist/wontopos.js +16 -6
  2. package/package.json +1 -1
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.15";
40
+ const VERSION = "2.2.16";
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). */
@@ -69,6 +69,10 @@ const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "0.0.0.0"]);
69
69
  const MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
70
70
  /** Model names travel in a header — only header-safe characters. */
71
71
  const MODEL_RE = /^[A-Za-z0-9._-]+$/;
72
+ /** Cap a server-controlled error message so a hostile body can't blow up a log. */
73
+ const MAX_ERR_MSG = 4096;
74
+ /** Paging backstop: bound the walk so a fresh-cursor-forever server can't loop us. */
75
+ const MAX_PAGES = 1_000_000;
72
76
  /** Error codes that only occur while ESTABLISHING a connection — the request
73
77
  * never reached the server, so a retry can't double-process a write. Mid-stream
74
78
  * codes (ECONNRESET, EPIPE, UND_ERR_SOCKET, ETIMEDOUT) are ambiguous: the server
@@ -381,12 +385,13 @@ export class Client {
381
385
  async *iterMemories(userId, opts = {}) {
382
386
  let cursor;
383
387
  const seen = new Set();
384
- for (;;) {
385
- const page = await this.listMemories(userId, { limit: opts.pageSize ?? 100, cursor });
386
- for (const m of page.memories ?? [])
388
+ // Backstop: the cursor-repeat guard catches a repeated cursor, but not a
389
+ // server that mints a FRESH cursor every page forever — bound the walk.
390
+ for (let page = 0; page < MAX_PAGES; page++) {
391
+ const p = await this.listMemories(userId, { limit: opts.pageSize ?? 100, cursor });
392
+ for (const m of p.memories ?? [])
387
393
  yield m;
388
- const next = page.next_cursor ?? undefined;
389
- // Stop on last page OR a server that repeats a cursor (would loop forever).
394
+ const next = p.next_cursor ?? undefined;
390
395
  if (!next || seen.has(next))
391
396
  break;
392
397
  seen.add(next);
@@ -564,6 +569,7 @@ export class Client {
564
569
  }
565
570
  if ((res.status >= 300 && res.status < 400) || res.type === "opaqueredirect") {
566
571
  clearTimeout(timer);
572
+ void res.body?.cancel(); // release the stream — otherwise the connection leaks
567
573
  throw new WosError(res.status, "unexpected redirect — refused (the API key never follows a redirect). Check baseUrl: exact host, https://.");
568
574
  }
569
575
  this._rateLimit = parseRateLimit(res.headers) ?? this._rateLimit;
@@ -612,6 +618,10 @@ export class Client {
612
618
  catch {
613
619
  /* keep raw text */
614
620
  }
621
+ // Cap the FINAL message (a string `error` field can be as huge as the body),
622
+ // so a hostile server can't turn our exception/log into a giant string.
623
+ if (typeof msg === "string" && msg.length > MAX_ERR_MSG)
624
+ msg = msg.slice(0, MAX_ERR_MSG) + "…(truncated)";
615
625
  throw errorFor(res.status, msg, requestId);
616
626
  }
617
627
  if (!text)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wontopos",
3
- "version": "2.2.15",
3
+ "version": "2.2.16",
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",