wontopos 2.1.1 → 2.2.1

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,36 +72,59 @@ 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>;
90
+ add(content: string, userId?: string, metadata?: Record<string, unknown>): Promise<any>;
75
91
  /** Store a conversation turn (user + assistant) into short-term + long-term memory. */
76
- addTurn(userId: string, userMsg: string, assistantMsg: string): Promise<any>;
92
+ addTurn(userId: string | undefined, userMsg: string, assistantMsg: string): Promise<any>;
77
93
  /** 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>;
94
+ addBulk(content: string, userId?: string, category?: string, timestamp?: string): Promise<any>;
79
95
  /** 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
+ update(userId: string | undefined, oldMemoryId: string, newContent: string): Promise<any>;
97
+ /** Semantic search over a store's memories. Returns memories, most relevant first. */
98
+ search(query: string, userId?: string, limit?: number, opts?: Record<string, unknown>): Promise<Memory[]>;
83
99
  /** One-call LLM context: short-term turns + long-term matches + surrounding context. */
84
- recall(query: string, userId: string): Promise<RecallResult>;
100
+ recall(query: string, userId?: string): Promise<RecallResult>;
101
+ /** Run a built-in engram ("deep_recall" | "timeline" | "gather"). Returns the merged result. */
102
+ engram(name: string, query: string, userId?: string): Promise<any>;
85
103
  /** Recent conversation turns (short-term memory). */
86
- history(userId: string): Promise<unknown[]>;
87
- /** Memory counts for a user: { total_memories, short_term_turns }. */
88
- stats(userId: string): Promise<any>;
104
+ history(userId?: string): Promise<unknown[]>;
105
+ /** Memory counts for a store: { total_memories, short_term_turns }. */
106
+ stats(userId?: string): Promise<any>;
89
107
  /** Available models: `[{ id, name, available, memory }, ...]`. Needs no API key. */
90
108
  listModels(): Promise<ModelInfo[]>;
91
- /** Delete a single memory by id. */
92
- delete(userId: string, memoryId: string): Promise<any>;
93
- /** Delete ALL memories for a user (GDPR erase). */
109
+ /** Create a store — the `userId` you read and write under. Stores are explicit:
110
+ * a store must exist before you `add` to or `search` it, otherwise those calls
111
+ * return 404. Idempotent. Every account starts with a `default` store.
112
+ * Returns `{ user_id, status }` (`status` is `"created"` or `"exists"`). */
113
+ createStore(userId?: string): Promise<any>;
114
+ /** List your stores: `[{ user_id, created_at }, ...]` (`default` first). */
115
+ listStores(): Promise<Array<{
116
+ user_id: string;
117
+ created_at: string;
118
+ }>>;
119
+ /** Delete a store and ALL its memories. Returns `{ user_id, status }`. */
120
+ deleteStore(userId: string): Promise<any>;
121
+ /** Delete a single memory by id. `userId` optional (uses the default store). */
122
+ delete(userId: string | undefined, memoryId: string): Promise<any>;
123
+ /** Delete ALL memories for a store (GDPR erase). `userId` is required on purpose -
124
+ * this is destructive, so it never falls back to the default store. */
94
125
  deleteAll(userId: string): Promise<any>;
95
126
  private post;
127
+ private request;
96
128
  }
97
129
  /** Back-compat alias: older code used `WME`. */
98
130
  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,46 +64,56 @@ 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 });
58
79
  }
59
80
  /** Store a conversation turn (user + assistant) into short-term + long-term memory. */
60
81
  addTurn(userId, userMsg, assistantMsg) {
61
- return this.post("/api/v1/memory/store-turn", { user_id: userId, user_msg: userMsg, assistant_msg: assistantMsg });
82
+ return this.post("/api/v1/memory/store-turn", { user_id: this.uid(userId), user_msg: userMsg, assistant_msg: assistantMsg });
62
83
  }
63
84
  /** Bulk-ingest a large blob of text (chunked + embedded server-side). For backfilling. */
64
85
  addBulk(content, userId, category = "general", timestamp) {
65
- const body = { user_id: userId, content, category };
86
+ const body = { user_id: this.uid(userId), content, category };
66
87
  if (timestamp)
67
88
  body.timestamp = timestamp;
68
89
  return this.post("/api/v1/memory/bulk-store", body);
69
90
  }
70
91
  /** Supersede an old memory with new content (a fact that changed). */
71
92
  update(userId, oldMemoryId, newContent) {
72
- return this.post("/api/v1/memory/supersede", { user_id: userId, old_memory_id: oldMemoryId, new_content: newContent });
93
+ return this.post("/api/v1/memory/supersede", { user_id: this.uid(userId), old_memory_id: oldMemoryId, new_content: newContent });
73
94
  }
74
95
  // ----- read -----
75
- /** Semantic search over a user's memories. Returns memories, most relevant first. */
96
+ /** Semantic search over a store's memories. Returns memories, most relevant first. */
76
97
  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 });
98
+ const r = await this.post("/api/v1/memory/search", { user_id: this.uid(userId), query, max_results: limit, ...opts });
78
99
  return (r.memories ?? []);
79
100
  }
80
101
  /** One-call LLM context: short-term turns + long-term matches + surrounding context. */
81
102
  recall(query, userId) {
82
- return this.post("/api/v1/memory/recall", { user_id: userId, query });
103
+ return this.post("/api/v1/memory/recall", { user_id: this.uid(userId), query });
104
+ }
105
+ /** Run a built-in engram ("deep_recall" | "timeline" | "gather"). Returns the merged result. */
106
+ engram(name, query, userId) {
107
+ return this.post("/api/v1/engram/run", { name, user_id: this.uid(userId), query });
83
108
  }
84
109
  /** Recent conversation turns (short-term memory). */
85
110
  async history(userId) {
86
- const r = await this.post("/api/v1/memory/history", { user_id: userId });
111
+ const r = await this.post("/api/v1/memory/history", { user_id: this.uid(userId) });
87
112
  return (r.turns ?? []);
88
113
  }
89
- /** Memory counts for a user: { total_memories, short_term_turns }. */
114
+ /** Memory counts for a store: { total_memories, short_term_turns }. */
90
115
  stats(userId) {
91
- return this.post("/api/v1/memory/stats", { user_id: userId });
116
+ return this.post("/api/v1/memory/stats", { user_id: this.uid(userId) });
92
117
  }
93
118
  // ----- models -----
94
119
  /** Available models: `[{ id, name, available, memory }, ...]`. Needs no API key. */
@@ -99,25 +124,46 @@ export class Client {
99
124
  const data = await res.json();
100
125
  return (data.models ?? []);
101
126
  }
127
+ // ----- stores -----
128
+ /** Create a store — the `userId` you read and write under. Stores are explicit:
129
+ * a store must exist before you `add` to or `search` it, otherwise those calls
130
+ * return 404. Idempotent. Every account starts with a `default` store.
131
+ * Returns `{ user_id, status }` (`status` is `"created"` or `"exists"`). */
132
+ createStore(userId) {
133
+ return this.post("/api/v1/memory/collection", { user_id: this.uid(userId) });
134
+ }
135
+ /** List your stores: `[{ user_id, created_at }, ...]` (`default` first). */
136
+ async listStores() {
137
+ const r = await this.request("GET", "/api/v1/memory/collections");
138
+ return (r.collections ?? []);
139
+ }
140
+ /** Delete a store and ALL its memories. Returns `{ user_id, status }`. */
141
+ deleteStore(userId) {
142
+ return this.request("DELETE", "/api/v1/memory/collection", { user_id: userId });
143
+ }
102
144
  // ----- delete -----
103
- /** Delete a single memory by id. */
145
+ /** Delete a single memory by id. `userId` optional (uses the default store). */
104
146
  delete(userId, memoryId) {
105
- return this.post("/api/v1/memory/forget", { user_id: userId, memory_id: memoryId });
147
+ return this.post("/api/v1/memory/forget", { user_id: this.uid(userId), memory_id: memoryId });
106
148
  }
107
- /** Delete ALL memories for a user (GDPR erase). */
149
+ /** Delete ALL memories for a store (GDPR erase). `userId` is required on purpose -
150
+ * this is destructive, so it never falls back to the default store. */
108
151
  deleteAll(userId) {
109
152
  return this.post("/api/v1/memory/forget", { user_id: userId });
110
153
  }
111
154
  // ----- internal -----
112
- async post(path, body) {
155
+ post(path, body) {
156
+ return this.request("POST", path, body);
157
+ }
158
+ async request(method, path, body) {
113
159
  const ctrl = new AbortController();
114
160
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
115
161
  let res;
116
162
  try {
117
163
  res = await fetch(`${this.base}${path}`, {
118
- method: "POST",
164
+ method,
119
165
  headers: this.headers,
120
- body: JSON.stringify(body),
166
+ body: body === undefined ? undefined : JSON.stringify(body),
121
167
  signal: ctrl.signal,
122
168
  });
123
169
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wontopos",
3
- "version": "2.1.1",
3
+ "version": "2.2.1",
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",