wontopos 2.2.0 → 2.2.2

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.
@@ -4,9 +4,15 @@
4
4
  * npm install wontopos
5
5
  *
6
6
  * import { Client } from "wontopos";
7
- * const mem = new Client({ apiKey: "wos-...", model: "tablet-1" });
8
- * await mem.add("she prefers tea over coffee", "alice");
9
- * const hits = await mem.search("what does alice drink?", "alice");
7
+ * const mem = new Client({ apiKey: "wos-...", userId: "alice" }); // set the store once
8
+ * await mem.createStore(); // create it (uses the client's userId)
9
+ * await mem.add("she prefers tea over coffee"); // no userId needed
10
+ * const hits = await mem.search("what does alice drink?");
11
+ *
12
+ * Set `userId` once on the client and every call uses it; override any single call
13
+ * by passing `userId` to it. Stores are explicit: the store must exist first
14
+ * (`createStore`) or calls return 404. Every account starts with a `default` store,
15
+ * so with no `userId` anywhere the zero-setup path just works.
10
16
  *
11
17
  * The API key picks *which memory* (your account); `model` picks *which engine*
12
18
  * reads it. All models share one memory, so you can store
@@ -56,6 +62,9 @@ export interface ClientOptions {
56
62
  timeoutMs?: number;
57
63
  /** Default model for every call (sent as `X-WOS-Model`). See `listModels()`. */
58
64
  model?: string;
65
+ /** Default store for every call. Override per call by passing `userId`. Defaults
66
+ * to the account's built-in `default` store. */
67
+ userId?: string;
59
68
  }
60
69
  /** Wontopos memory client. Store and recall memories, isolated per `userId`. */
61
70
  export declare class Client {
@@ -63,38 +72,61 @@ export declare class Client {
63
72
  private readonly headers;
64
73
  private readonly timeoutMs;
65
74
  private readonly apiKey;
75
+ private readonly model;
76
+ /** The store every call uses unless one passes `userId`. */
77
+ private readonly defaultUser;
66
78
  constructor(opts: ClientOptions);
79
+ /** Resolve a call's store: the explicit userId, else the client default. */
80
+ private uid;
67
81
  /**
68
82
  * A client that uses `model` (sent as `X-WOS-Model`). Use it to set the default
69
83
  * (`new Client({ apiKey, model })`) or to override one call
70
84
  * (`client.withModel("tablet-1").recall(...)`).
71
85
  */
72
86
  withModel(model: string): Client;
87
+ /** A client bound to `userId` as its default store (everything else kept). */
88
+ withUser(userId: string): Client;
73
89
  /** Store one memory. `metadata` optional (e.g. { event_date: "2026-03-01" }). */
74
- add(content: string, userId: string, metadata?: Record<string, unknown>): Promise<any>;
75
- /** Store a conversation turn (user + assistant) into short-term + long-term memory. */
76
- addTurn(userId: string, userMsg: string, assistantMsg: string): Promise<any>;
90
+ add(content: string, userId?: string, metadata?: Record<string, unknown>): Promise<any>;
91
+ /** Alias of `add` — store one memory. Same surface as the Python SDK's `store`. */
92
+ store(content: string, userId?: string, metadata?: Record<string, unknown>): Promise<any>;
93
+ /** Store a conversation turn (user + assistant). Payload first, userId last — same shape as add/search. */
94
+ addTurn(userMsg: string, assistantMsg: string, userId?: string): Promise<any>;
77
95
  /** Bulk-ingest a large blob of text (chunked + embedded server-side). For backfilling. */
78
- addBulk(content: string, userId: string, category?: string, timestamp?: string): Promise<any>;
79
- /** Supersede an old memory with new content (a fact that changed). */
80
- update(userId: string, oldMemoryId: string, newContent: string): Promise<any>;
81
- /** Semantic search over a user's memories. Returns memories, most relevant first. */
82
- search(query: string, userId: string, limit?: number, opts?: Record<string, unknown>): Promise<Memory[]>;
96
+ addBulk(content: string, userId?: string, category?: string, timestamp?: string): Promise<any>;
97
+ /** Supersede an old memory with new content. Payload first, userId last — same shape as add/search. */
98
+ update(oldMemoryId: string, newContent: string, userId?: string): Promise<any>;
99
+ /** Semantic search over a store's memories. Returns memories, most relevant first. */
100
+ search(query: string, userId?: string, limit?: number, opts?: Record<string, unknown>): Promise<Memory[]>;
83
101
  /** One-call LLM context: short-term turns + long-term matches + surrounding context. */
84
- recall(query: string, userId: string): Promise<RecallResult>;
102
+ recall(query: string, userId?: string): Promise<RecallResult>;
85
103
  /** Run a built-in engram ("deep_recall" | "timeline" | "gather"). Returns the merged result. */
86
- engram(name: string, query: string, userId: string): Promise<unknown>;
104
+ engram(name: string, query: string, userId?: string): Promise<any>;
87
105
  /** Recent conversation turns (short-term memory). */
88
- history(userId: string): Promise<unknown[]>;
89
- /** Memory counts for a user: { total_memories, short_term_turns }. */
90
- stats(userId: string): Promise<any>;
106
+ history(userId?: string): Promise<unknown[]>;
107
+ /** Memory counts for a store: { total_memories, short_term_turns }. */
108
+ stats(userId?: string): Promise<any>;
91
109
  /** Available models: `[{ id, name, available, memory }, ...]`. Needs no API key. */
92
110
  listModels(): Promise<ModelInfo[]>;
93
- /** Delete a single memory by id. */
94
- delete(userId: string, memoryId: string): Promise<any>;
95
- /** Delete ALL memories for a user (GDPR erase). */
111
+ /** Create a store — the `userId` you read and write under. Stores are explicit:
112
+ * a store must exist before you `add` to or `search` it, otherwise those calls
113
+ * return 404. Idempotent. Every account starts with a `default` store.
114
+ * Returns `{ user_id, status }` (`status` is `"created"` or `"exists"`). */
115
+ createStore(userId?: string): Promise<any>;
116
+ /** List your stores: `[{ user_id, created_at }, ...]` (`default` first). */
117
+ listStores(): Promise<Array<{
118
+ user_id: string;
119
+ created_at: string;
120
+ }>>;
121
+ /** Delete a store and ALL its memories. Returns `{ user_id, status }`. */
122
+ deleteStore(userId: string): Promise<any>;
123
+ /** Delete a single memory by id. `userId` optional (uses the default store). */
124
+ delete(userId: string | undefined, memoryId: string): Promise<any>;
125
+ /** Delete ALL memories for a store (GDPR erase). `userId` is required on purpose -
126
+ * this is destructive, so it never falls back to the default store. */
96
127
  deleteAll(userId: string): Promise<any>;
97
128
  private post;
129
+ private request;
98
130
  }
99
131
  /** Back-compat alias: older code used `WME`. */
100
132
  export declare const WME: typeof Client;
package/dist/wontopos.js CHANGED
@@ -4,9 +4,15 @@
4
4
  * npm install wontopos
5
5
  *
6
6
  * import { Client } from "wontopos";
7
- * const mem = new Client({ apiKey: "wos-...", model: "tablet-1" });
8
- * await mem.add("she prefers tea over coffee", "alice");
9
- * const hits = await mem.search("what does alice drink?", "alice");
7
+ * const mem = new Client({ apiKey: "wos-...", userId: "alice" }); // set the store once
8
+ * await mem.createStore(); // create it (uses the client's userId)
9
+ * await mem.add("she prefers tea over coffee"); // no userId needed
10
+ * const hits = await mem.search("what does alice drink?");
11
+ *
12
+ * Set `userId` once on the client and every call uses it; override any single call
13
+ * by passing `userId` to it. Stores are explicit: the store must exist first
14
+ * (`createStore`) or calls return 404. Every account starts with a `default` store,
15
+ * so with no `userId` anywhere the zero-setup path just works.
10
16
  *
11
17
  * The API key picks *which memory* (your account); `model` picks *which engine*
12
18
  * reads it. All models share one memory, so you can store
@@ -26,22 +32,31 @@ export class WosError extends Error {
26
32
  this.status = status;
27
33
  }
28
34
  }
35
+ const DEFAULT_USER = "default";
29
36
  /** Wontopos memory client. Store and recall memories, isolated per `userId`. */
30
37
  export class Client {
31
38
  base;
32
39
  headers;
33
40
  timeoutMs;
34
41
  apiKey;
42
+ model;
43
+ /** The store every call uses unless one passes `userId`. */
44
+ defaultUser;
35
45
  constructor(opts) {
36
46
  if (!opts?.apiKey)
37
47
  throw new Error("apiKey is required");
38
48
  this.apiKey = opts.apiKey;
39
49
  this.base = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
40
50
  this.timeoutMs = opts.timeoutMs ?? 30_000;
41
- const model = opts.model ?? DEFAULT_MODEL;
51
+ this.model = opts.model ?? DEFAULT_MODEL;
52
+ this.defaultUser = opts.userId || DEFAULT_USER;
42
53
  this.headers = { "X-API-Key": opts.apiKey, "Content-Type": "application/json" };
43
- if (model)
44
- this.headers["X-WOS-Model"] = model;
54
+ if (this.model)
55
+ this.headers["X-WOS-Model"] = this.model;
56
+ }
57
+ /** Resolve a call's store: the explicit userId, else the client default. */
58
+ uid(userId) {
59
+ return userId || this.defaultUser;
45
60
  }
46
61
  /**
47
62
  * A client that uses `model` (sent as `X-WOS-Model`). Use it to set the default
@@ -49,50 +64,60 @@ export class Client {
49
64
  * (`client.withModel("tablet-1").recall(...)`).
50
65
  */
51
66
  withModel(model) {
52
- return new Client({ apiKey: this.apiKey, baseUrl: this.base, timeoutMs: this.timeoutMs, model });
67
+ return new Client({ apiKey: this.apiKey, baseUrl: this.base, timeoutMs: this.timeoutMs, model, userId: this.defaultUser });
68
+ }
69
+ /** A client bound to `userId` as its default store (everything else kept). */
70
+ withUser(userId) {
71
+ return new Client({ apiKey: this.apiKey, baseUrl: this.base, timeoutMs: this.timeoutMs, model: this.model, userId });
53
72
  }
54
73
  // ----- write -----
74
+ // `userId` is optional in every call below - omit it to use the client's default
75
+ // store (set via `new Client({ apiKey, userId })` or `withUser`).
55
76
  /** Store one memory. `metadata` optional (e.g. { event_date: "2026-03-01" }). */
56
77
  add(content, userId, metadata = {}) {
57
- return this.post("/api/v1/memory/store", { user_id: userId, content, metadata });
78
+ return this.post("/api/v1/memory/store", { user_id: this.uid(userId), content, metadata });
79
+ }
80
+ /** Alias of `add` — store one memory. Same surface as the Python SDK's `store`. */
81
+ store(content, userId, metadata = {}) {
82
+ return this.add(content, userId, metadata);
58
83
  }
59
- /** Store a conversation turn (user + assistant) into short-term + long-term memory. */
60
- addTurn(userId, userMsg, assistantMsg) {
61
- return this.post("/api/v1/memory/store-turn", { user_id: userId, user_msg: userMsg, assistant_msg: assistantMsg });
84
+ /** Store a conversation turn (user + assistant). Payload first, userId last — same shape as add/search. */
85
+ addTurn(userMsg, assistantMsg, userId) {
86
+ return this.post("/api/v1/memory/store-turn", { user_id: this.uid(userId), user_msg: userMsg, assistant_msg: assistantMsg });
62
87
  }
63
88
  /** Bulk-ingest a large blob of text (chunked + embedded server-side). For backfilling. */
64
89
  addBulk(content, userId, category = "general", timestamp) {
65
- const body = { user_id: userId, content, category };
90
+ const body = { user_id: this.uid(userId), content, category };
66
91
  if (timestamp)
67
92
  body.timestamp = timestamp;
68
93
  return this.post("/api/v1/memory/bulk-store", body);
69
94
  }
70
- /** Supersede an old memory with new content (a fact that changed). */
71
- update(userId, oldMemoryId, newContent) {
72
- return this.post("/api/v1/memory/supersede", { user_id: userId, old_memory_id: oldMemoryId, new_content: newContent });
95
+ /** Supersede an old memory with new content. Payload first, userId last — same shape as add/search. */
96
+ update(oldMemoryId, newContent, userId) {
97
+ return this.post("/api/v1/memory/supersede", { user_id: this.uid(userId), old_memory_id: oldMemoryId, new_content: newContent });
73
98
  }
74
99
  // ----- read -----
75
- /** Semantic search over a user's memories. Returns memories, most relevant first. */
100
+ /** Semantic search over a store's memories. Returns memories, most relevant first. */
76
101
  async search(query, userId, limit = 10, opts = {}) {
77
- const r = await this.post("/api/v1/memory/search", { user_id: userId, query, max_results: limit, ...opts });
102
+ const r = await this.post("/api/v1/memory/search", { user_id: this.uid(userId), query, max_results: limit, ...opts });
78
103
  return (r.memories ?? []);
79
104
  }
80
105
  /** One-call LLM context: short-term turns + long-term matches + surrounding context. */
81
106
  recall(query, userId) {
82
- return this.post("/api/v1/memory/recall", { user_id: userId, query });
107
+ return this.post("/api/v1/memory/recall", { user_id: this.uid(userId), query });
83
108
  }
84
109
  /** Run a built-in engram ("deep_recall" | "timeline" | "gather"). Returns the merged result. */
85
110
  engram(name, query, userId) {
86
- return this.post("/api/v1/engram/run", { name, user_id: userId, query });
111
+ return this.post("/api/v1/engram/run", { name, user_id: this.uid(userId), query });
87
112
  }
88
113
  /** Recent conversation turns (short-term memory). */
89
114
  async history(userId) {
90
- const r = await this.post("/api/v1/memory/history", { user_id: userId });
115
+ const r = await this.post("/api/v1/memory/history", { user_id: this.uid(userId) });
91
116
  return (r.turns ?? []);
92
117
  }
93
- /** Memory counts for a user: { total_memories, short_term_turns }. */
118
+ /** Memory counts for a store: { total_memories, short_term_turns }. */
94
119
  stats(userId) {
95
- return this.post("/api/v1/memory/stats", { user_id: userId });
120
+ return this.post("/api/v1/memory/stats", { user_id: this.uid(userId) });
96
121
  }
97
122
  // ----- models -----
98
123
  /** Available models: `[{ id, name, available, memory }, ...]`. Needs no API key. */
@@ -103,25 +128,46 @@ export class Client {
103
128
  const data = await res.json();
104
129
  return (data.models ?? []);
105
130
  }
131
+ // ----- stores -----
132
+ /** Create a store — the `userId` you read and write under. Stores are explicit:
133
+ * a store must exist before you `add` to or `search` it, otherwise those calls
134
+ * return 404. Idempotent. Every account starts with a `default` store.
135
+ * Returns `{ user_id, status }` (`status` is `"created"` or `"exists"`). */
136
+ createStore(userId) {
137
+ return this.post("/api/v1/memory/collection", { user_id: this.uid(userId) });
138
+ }
139
+ /** List your stores: `[{ user_id, created_at }, ...]` (`default` first). */
140
+ async listStores() {
141
+ const r = await this.request("GET", "/api/v1/memory/collections");
142
+ return (r.collections ?? []);
143
+ }
144
+ /** Delete a store and ALL its memories. Returns `{ user_id, status }`. */
145
+ deleteStore(userId) {
146
+ return this.request("DELETE", "/api/v1/memory/collection", { user_id: userId });
147
+ }
106
148
  // ----- delete -----
107
- /** Delete a single memory by id. */
149
+ /** Delete a single memory by id. `userId` optional (uses the default store). */
108
150
  delete(userId, memoryId) {
109
- return this.post("/api/v1/memory/forget", { user_id: userId, memory_id: memoryId });
151
+ return this.post("/api/v1/memory/forget", { user_id: this.uid(userId), memory_id: memoryId });
110
152
  }
111
- /** Delete ALL memories for a user (GDPR erase). */
153
+ /** Delete ALL memories for a store (GDPR erase). `userId` is required on purpose -
154
+ * this is destructive, so it never falls back to the default store. */
112
155
  deleteAll(userId) {
113
156
  return this.post("/api/v1/memory/forget", { user_id: userId });
114
157
  }
115
158
  // ----- internal -----
116
- async post(path, body) {
159
+ post(path, body) {
160
+ return this.request("POST", path, body);
161
+ }
162
+ async request(method, path, body) {
117
163
  const ctrl = new AbortController();
118
164
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
119
165
  let res;
120
166
  try {
121
167
  res = await fetch(`${this.base}${path}`, {
122
- method: "POST",
168
+ method,
123
169
  headers: this.headers,
124
- body: JSON.stringify(body),
170
+ body: body === undefined ? undefined : JSON.stringify(body),
125
171
  signal: ctrl.signal,
126
172
  });
127
173
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wontopos",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
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",