wontopos 2.2.11 → 2.2.12

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.
@@ -32,12 +32,47 @@
32
32
  * runtime's (never disabled by this SDK; Node 18+ floors TLS at 1.2). Prefer
33
33
  * `Client.fromEnv()` over keys in source code.
34
34
  */
35
+ /** Quota from a response's `X-RateLimit-*` headers, or `null` if absent. */
36
+ export interface RateLimit {
37
+ limit: number | null;
38
+ remaining: number | null;
39
+ reset: number | null;
40
+ }
35
41
  export declare class WosError extends Error {
36
42
  readonly status: number;
37
43
  /** The server's id for the request when it sent one — include it when contacting support. */
38
44
  readonly requestId?: string;
39
45
  constructor(status: number, message: string, requestId?: string);
40
46
  }
47
+ /** The request never got a response (DNS/TLS/timeout/connection). `status` is 0. */
48
+ export declare class APIConnectionError extends WosError {
49
+ }
50
+ /** 400 — the request was malformed (bad arguments). */
51
+ export declare class BadRequestError extends WosError {
52
+ }
53
+ /** 401 — the API key is missing, wrong, or revoked. */
54
+ export declare class AuthenticationError extends WosError {
55
+ }
56
+ /** 402 — no card on file or the balance is depleted. Top up to continue. */
57
+ export declare class PaymentRequiredError extends WosError {
58
+ }
59
+ /** 403 — the key/model isn't allowed to do this. */
60
+ export declare class PermissionDeniedError extends WosError {
61
+ }
62
+ /** 404 — the store or resource doesn't exist (create the store first). */
63
+ export declare class NotFoundError extends WosError {
64
+ }
65
+ /** 409 — a concurrent write to the same store. Retry. */
66
+ export declare class ConflictError extends WosError {
67
+ }
68
+ /** 429 — too many requests. Back off and retry (the client already retries these). */
69
+ export declare class RateLimitError extends WosError {
70
+ }
71
+ /** 5xx — the service failed. Safe to retry. */
72
+ export declare class ServerError extends WosError {
73
+ }
74
+ /** Build the most specific WosError subclass for an HTTP status. */
75
+ export declare function errorFor(status: number, message: string, requestId?: string): WosError;
41
76
  export interface Usage {
42
77
  input_tokens?: number;
43
78
  output_tokens?: number;
@@ -205,6 +240,11 @@ export declare class Client {
205
240
  private readonly maxRetries;
206
241
  /** The store every call uses unless one passes `userId`. */
207
242
  private readonly defaultUser;
243
+ private _rateLimit;
244
+ /** Quota from the MOST RECENT call: `{ limit, remaining, reset }` (or `null` before
245
+ * the first call). Read it to self-throttle — the client already retries 429s, but
246
+ * this lets you slow down before hitting the wall. */
247
+ get rateLimit(): RateLimit | null;
208
248
  constructor(opts: ClientOptions);
209
249
  /** A client whose key comes from `WONTOPOS_API_KEY` (or `WOS_API_KEY`) — keeps
210
250
  * keys out of source code. Node/Bun/Deno-with-env only. */
@@ -259,6 +299,20 @@ export declare class Client {
259
299
  limit?: number;
260
300
  cursor?: string;
261
301
  }): Promise<MemoryPage>;
302
+ /** Async-iterate every stored memory in a store, paging under the hood — no cursor
303
+ * bookkeeping. Original text + metadata only, never vectors.
304
+ *
305
+ * for await (const m of mem.iterMemories()) console.log(m.id, m.content);
306
+ */
307
+ iterMemories(userId?: string, opts?: {
308
+ pageSize?: number;
309
+ }): AsyncGenerator<Memory>;
310
+ /** Collect ALL of a store's memories into an array (original text + metadata, no vectors). */
311
+ exportMemories(userId?: string): Promise<Memory[]>;
312
+ /** Check connectivity AND that the API key works. Resolves `true`, or rejects — an
313
+ * `AuthenticationError` (bad key), `PaymentRequiredError` (valid key but no card /
314
+ * depleted balance), or `APIConnectionError` (unreachable). Makes one metered request. */
315
+ ping(): Promise<boolean>;
262
316
  /** Available models: `[{ id, name, available, memory }, ...]`. Needs no API key. */
263
317
  listModels(): Promise<ModelInfo[]>;
264
318
  /** Create a store — the `userId` you read and write under. Stores are explicit:
package/dist/wontopos.js CHANGED
@@ -32,7 +32,7 @@
32
32
  * runtime's (never disabled by this SDK; Node 18+ floors TLS at 1.2). Prefer
33
33
  * `Client.fromEnv()` over keys in source code.
34
34
  */
35
- const VERSION = "2.2.11";
35
+ const VERSION = "2.2.12";
36
36
  const USER_AGENT = `wontopos-node/${VERSION}`;
37
37
  const DEFAULT_BASE_URL = "https://api.wontopos.com";
38
38
  const DEFAULT_MODEL = "tablet-1";
@@ -48,17 +48,77 @@ const MODEL_RE = /^[A-Za-z0-9._-]+$/;
48
48
  function maskKey(key) {
49
49
  return key.length > 12 ? `${key.slice(0, 4)}...${key.slice(-4)}` : "***";
50
50
  }
51
+ function parseRateLimit(h) {
52
+ const num = (name) => {
53
+ const v = h.get(name);
54
+ if (v == null || v.trim() === "")
55
+ return null; // Number("") is 0 — treat blank as absent
56
+ const n = Number(v);
57
+ return Number.isFinite(n) ? n : null;
58
+ };
59
+ const limit = num("X-RateLimit-Limit");
60
+ const remaining = num("X-RateLimit-Remaining");
61
+ const reset = num("X-RateLimit-Reset");
62
+ if (limit === null && remaining === null && reset === null)
63
+ return null;
64
+ return { limit, remaining, reset };
65
+ }
51
66
  export class WosError extends Error {
52
67
  status;
53
68
  /** The server's id for the request when it sent one — include it when contacting support. */
54
69
  requestId;
55
70
  constructor(status, message, requestId) {
56
71
  super(`[${status}] ${message}${requestId ? ` (request_id: ${requestId})` : ""}`);
57
- this.name = "WosError";
72
+ this.name = new.target.name; // the concrete subclass name (RateLimitError, …)
58
73
  this.status = status;
59
74
  this.requestId = requestId;
60
75
  }
61
76
  }
77
+ // Typed subclasses so callers can branch on the failure — `catch (e) { if (e instanceof
78
+ // RateLimitError) … }`. Each is a WosError, so a broad `instanceof WosError` still works.
79
+ /** The request never got a response (DNS/TLS/timeout/connection). `status` is 0. */
80
+ export class APIConnectionError extends WosError {
81
+ }
82
+ /** 400 — the request was malformed (bad arguments). */
83
+ export class BadRequestError extends WosError {
84
+ }
85
+ /** 401 — the API key is missing, wrong, or revoked. */
86
+ export class AuthenticationError extends WosError {
87
+ }
88
+ /** 402 — no card on file or the balance is depleted. Top up to continue. */
89
+ export class PaymentRequiredError extends WosError {
90
+ }
91
+ /** 403 — the key/model isn't allowed to do this. */
92
+ export class PermissionDeniedError extends WosError {
93
+ }
94
+ /** 404 — the store or resource doesn't exist (create the store first). */
95
+ export class NotFoundError extends WosError {
96
+ }
97
+ /** 409 — a concurrent write to the same store. Retry. */
98
+ export class ConflictError extends WosError {
99
+ }
100
+ /** 429 — too many requests. Back off and retry (the client already retries these). */
101
+ export class RateLimitError extends WosError {
102
+ }
103
+ /** 5xx — the service failed. Safe to retry. */
104
+ export class ServerError extends WosError {
105
+ }
106
+ const STATUS_ERRORS = {
107
+ 400: BadRequestError,
108
+ 401: AuthenticationError,
109
+ 402: PaymentRequiredError,
110
+ 403: PermissionDeniedError,
111
+ 404: NotFoundError,
112
+ 409: ConflictError,
113
+ 429: RateLimitError,
114
+ };
115
+ /** Build the most specific WosError subclass for an HTTP status. */
116
+ export function errorFor(status, message, requestId) {
117
+ const Cls = status === 0
118
+ ? APIConnectionError
119
+ : STATUS_ERRORS[status] ?? (status >= 500 && status < 600 ? ServerError : WosError);
120
+ return new Cls(status, message, requestId);
121
+ }
62
122
  const DEFAULT_USER = "default";
63
123
  /** Wontopos memory client. Store and recall memories, isolated per `userId`. */
64
124
  export class Client {
@@ -70,6 +130,13 @@ export class Client {
70
130
  maxRetries;
71
131
  /** The store every call uses unless one passes `userId`. */
72
132
  defaultUser;
133
+ _rateLimit = null;
134
+ /** Quota from the MOST RECENT call: `{ limit, remaining, reset }` (or `null` before
135
+ * the first call). Read it to self-throttle — the client already retries 429s, but
136
+ * this lets you slow down before hitting the wall. */
137
+ get rateLimit() {
138
+ return this._rateLimit;
139
+ }
73
140
  constructor(opts) {
74
141
  // Trim the key and reject inner whitespace — a stray newline from a file or
75
142
  // env var otherwise turns into a mystery 401 (or a mangled header).
@@ -215,6 +282,36 @@ export class Client {
215
282
  body.cursor = opts.cursor;
216
283
  return this.post("/api/v1/memory/list", body);
217
284
  }
285
+ /** Async-iterate every stored memory in a store, paging under the hood — no cursor
286
+ * bookkeeping. Original text + metadata only, never vectors.
287
+ *
288
+ * for await (const m of mem.iterMemories()) console.log(m.id, m.content);
289
+ */
290
+ async *iterMemories(userId, opts = {}) {
291
+ let cursor;
292
+ for (;;) {
293
+ const page = await this.listMemories(userId, { limit: opts.pageSize ?? 100, cursor });
294
+ for (const m of page.memories ?? [])
295
+ yield m;
296
+ cursor = page.next_cursor ?? undefined;
297
+ if (!cursor)
298
+ break;
299
+ }
300
+ }
301
+ /** Collect ALL of a store's memories into an array (original text + metadata, no vectors). */
302
+ async exportMemories(userId) {
303
+ const out = [];
304
+ for await (const m of this.iterMemories(userId))
305
+ out.push(m);
306
+ return out;
307
+ }
308
+ /** Check connectivity AND that the API key works. Resolves `true`, or rejects — an
309
+ * `AuthenticationError` (bad key), `PaymentRequiredError` (valid key but no card /
310
+ * depleted balance), or `APIConnectionError` (unreachable). Makes one metered request. */
311
+ async ping() {
312
+ await this.request("GET", "/api/v1/memory/collections");
313
+ return true;
314
+ }
218
315
  // ----- models -----
219
316
  /** Available models: `[{ id, name, available, memory }, ...]`. Needs no API key. */
220
317
  async listModels() {
@@ -342,7 +439,7 @@ export class Client {
342
439
  await new Promise((r) => setTimeout(r, this.backoffMs(attempt)));
343
440
  continue;
344
441
  }
345
- throw new WosError(0, timedOut ? `request timed out after ${this.timeoutMs}ms` : `network error: ${e?.message ?? e}`);
442
+ throw new APIConnectionError(0, timedOut ? `request timed out after ${this.timeoutMs}ms` : `network error: ${e?.message ?? e}`);
346
443
  }
347
444
  clearTimeout(timer);
348
445
  if (RETRY_STATUSES.has(res.status) && attempt + 1 < attempts) {
@@ -354,6 +451,7 @@ export class Client {
354
451
  if ((res.status >= 300 && res.status < 400) || res.type === "opaqueredirect") {
355
452
  throw new WosError(res.status, "unexpected redirect — refused (the API key never follows a redirect). Check baseUrl: exact host, https://.");
356
453
  }
454
+ this._rateLimit = parseRateLimit(res.headers) ?? this._rateLimit;
357
455
  const text = await this.readCapped(res);
358
456
  if (!res.ok) {
359
457
  // Server returns either Anthropic-style envelope
@@ -379,7 +477,7 @@ export class Client {
379
477
  catch {
380
478
  /* keep raw text */
381
479
  }
382
- throw new WosError(res.status, msg, requestId);
480
+ throw errorFor(res.status, msg, requestId);
383
481
  }
384
482
  return text ? JSON.parse(text) : null;
385
483
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wontopos",
3
- "version": "2.2.11",
3
+ "version": "2.2.12",
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",