wontopos 2.2.6 → 2.2.8
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 +40 -7
- package/dist/wontopos.d.ts +28 -2
- package/dist/wontopos.js +147 -44
- package/package.json +2 -9
package/README.md
CHANGED
|
@@ -29,16 +29,21 @@ All methods take `{ userId }` — it names the **store**: one isolated memory sp
|
|
|
29
29
|
## Who said it (speakers)
|
|
30
30
|
|
|
31
31
|
Every memory can carry a speaker: `"me"` for the assistant's own words, or a
|
|
32
|
-
person's name.
|
|
32
|
+
person's name. Speakers are explicit, like stores: register a person once,
|
|
33
|
+
then store under their name — a typo can never silently become a new person.
|
|
34
|
+
Search accepts a speaker too, to recall one person's words only.
|
|
33
35
|
|
|
34
36
|
```ts
|
|
37
|
+
await mem.addSpeaker("Bob", "alice"); // once per person
|
|
35
38
|
await mem.add("I promised to send the report on Friday", "alice", { speaker: "me" });
|
|
36
39
|
await mem.add("Bob said the deadline moved to Tuesday", "alice", { speaker: "Bob" });
|
|
37
40
|
await mem.search("what did Bob say about deadlines?", "alice", 10, { speaker: "Bob" });
|
|
38
41
|
```
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
`
|
|
43
|
+
`listSpeakers()` shows the registered people with per-person memory counts;
|
|
44
|
+
`removeSpeaker()` unregisters (memories stay, the tag goes). A store registers
|
|
45
|
+
up to 50 people to start (a limit we plan to raise); `"me"` never needs
|
|
46
|
+
registration and never counts against it.
|
|
42
47
|
|
|
43
48
|
## Recall caching
|
|
44
49
|
|
|
@@ -50,9 +55,26 @@ const hits = await mem.search("...the conversation so far...", "alice", 10,
|
|
|
50
55
|
{ cache_control: { ttl: "5m" } }); // or "1h"
|
|
51
56
|
```
|
|
52
57
|
|
|
58
|
+
## Reliability
|
|
59
|
+
|
|
60
|
+
Built in, no configuration needed:
|
|
61
|
+
|
|
62
|
+
- **Automatic retries** — 429 / 502 / 503 and connection errors retry twice with
|
|
63
|
+
exponential backoff + jitter, honoring the server's `Retry-After`. Tune with
|
|
64
|
+
`new Client({ maxRetries })`; `0` disables.
|
|
65
|
+
- **Redirects refused** — the API key never follows a 3xx to another host.
|
|
66
|
+
- **Timeouts** — 30s per attempt by default (`timeoutMs`).
|
|
67
|
+
- **Key never in logs** — `JSON.stringify(client)` and Node's `console.log(client)`
|
|
68
|
+
print a masked key.
|
|
69
|
+
- **Wipe guard** — `delete()` without a `memoryId` throws instead of silently
|
|
70
|
+
meaning "delete everything"; wiping a store is only ever the explicit
|
|
71
|
+
`deleteAll(userId)` / `deleteStore(userId)`.
|
|
53
72
|
|
|
54
73
|
## Errors
|
|
55
74
|
|
|
75
|
+
Any non-2xx response throws `WosError` with `status`, `message`, and — when the
|
|
76
|
+
server sent one — `requestId` (include it when contacting support).
|
|
77
|
+
|
|
56
78
|
```ts
|
|
57
79
|
import { Client, WosError } from "wontopos";
|
|
58
80
|
|
|
@@ -61,8 +83,8 @@ try {
|
|
|
61
83
|
} catch (e) {
|
|
62
84
|
if (e instanceof WosError) {
|
|
63
85
|
if (e.status === 401) console.error("API key invalid or revoked");
|
|
64
|
-
else if (e.status === 429) console.error("Rate limited");
|
|
65
|
-
else console.error(e.status, e.message);
|
|
86
|
+
else if (e.status === 429) console.error("Rate limited"); // already retried twice by then
|
|
87
|
+
else console.error(e.status, e.message, e.requestId);
|
|
66
88
|
}
|
|
67
89
|
}
|
|
68
90
|
```
|
|
@@ -76,7 +98,18 @@ const mem = new Client({ apiKey: "...", baseUrl: "https://wos.your-host.com" });
|
|
|
76
98
|
## Links
|
|
77
99
|
|
|
78
100
|
- Homepage: <https://wontopos.com>
|
|
79
|
-
- API reference: <https://wontopos.com/why> (Developers tab)
|
|
80
|
-
|
|
101
|
+
- API reference: <https://wontopos.com/en/why> (Developers tab)
|
|
102
|
+
|
|
103
|
+
## Changelog
|
|
104
|
+
|
|
105
|
+
- **2.2.8** — reliability + hardening: automatic retries (429/502/503 + connection
|
|
106
|
+
errors, backoff + jitter, honors `Retry-After`), redirects refused, key masked in
|
|
107
|
+
`toJSON`/inspect, `WosError.requestId`, guard against `delete()` without
|
|
108
|
+
`memoryId`, plain-HTTP warning, `User-Agent`.
|
|
109
|
+
- **2.2.7** — speakers: `addSpeaker` / `listSpeakers` / `removeSpeaker`.
|
|
110
|
+
- **2.2.6** — docs: `userId` = store, clarified everywhere.
|
|
111
|
+
- **2.2.5** — docs: speakers + recall caching sections.
|
|
112
|
+
- **2.2.4** — Python/TypeScript/Rust converge on one version; all three now
|
|
113
|
+
release in lockstep (same version, same surface). Patch releases are additive.
|
|
81
114
|
|
|
82
115
|
License: MIT.
|
package/dist/wontopos.d.ts
CHANGED
|
@@ -21,15 +21,24 @@
|
|
|
21
21
|
*
|
|
22
22
|
* Pure semantic retrieval — no keyword/BM25 matching, so it works the SAME in every
|
|
23
23
|
* language. Storing and searching call no LLM; you only pay for embeddings.
|
|
24
|
+
*
|
|
25
|
+
* Reliability: every call retries transient failures (429 / 502 / 503 and
|
|
26
|
+
* connection errors) with exponential backoff + jitter, honoring `Retry-After`.
|
|
27
|
+
* Redirects are refused so the API key can never follow one to another host.
|
|
28
|
+
* Tune with `maxRetries` (0 disables) and `timeoutMs`.
|
|
24
29
|
*/
|
|
25
30
|
export declare class WosError extends Error {
|
|
26
31
|
readonly status: number;
|
|
27
|
-
|
|
32
|
+
/** The server's id for the request when it sent one — include it when contacting support. */
|
|
33
|
+
readonly requestId?: string;
|
|
34
|
+
constructor(status: number, message: string, requestId?: string);
|
|
28
35
|
}
|
|
29
36
|
export interface Memory {
|
|
30
37
|
id?: string;
|
|
31
38
|
content?: string;
|
|
32
39
|
score?: number;
|
|
40
|
+
/** WHO said it: "me" (the assistant) or a registered person's name. Absent = untagged. */
|
|
41
|
+
speaker?: string;
|
|
33
42
|
[key: string]: unknown;
|
|
34
43
|
}
|
|
35
44
|
export interface RecallResult {
|
|
@@ -58,13 +67,16 @@ export interface ClientOptions {
|
|
|
58
67
|
apiKey: string;
|
|
59
68
|
/** API base URL (defaults to the hosted service). */
|
|
60
69
|
baseUrl?: string;
|
|
61
|
-
/** Per-request timeout in ms (default 30000). */
|
|
70
|
+
/** Per-request timeout in ms, applied to each retry attempt (default 30000). */
|
|
62
71
|
timeoutMs?: number;
|
|
63
72
|
/** Default model for every call (sent as `X-WOS-Model`). See `listModels()`. */
|
|
64
73
|
model?: string;
|
|
65
74
|
/** Default store for every call. Override per call by passing `userId`. Defaults
|
|
66
75
|
* to the account's built-in `default` store. */
|
|
67
76
|
userId?: string;
|
|
77
|
+
/** How many times to retry transient failures (429/502/503 and connection
|
|
78
|
+
* errors) before throwing. 0 disables retries (default 2). */
|
|
79
|
+
maxRetries?: number;
|
|
68
80
|
}
|
|
69
81
|
/** Wontopos memory client. Store and recall memories, isolated per `userId`. */
|
|
70
82
|
export declare class Client {
|
|
@@ -73,11 +85,15 @@ export declare class Client {
|
|
|
73
85
|
private readonly timeoutMs;
|
|
74
86
|
private readonly apiKey;
|
|
75
87
|
private readonly model;
|
|
88
|
+
private readonly maxRetries;
|
|
76
89
|
/** The store every call uses unless one passes `userId`. */
|
|
77
90
|
private readonly defaultUser;
|
|
78
91
|
constructor(opts: ClientOptions);
|
|
92
|
+
/** Never expose the key: `JSON.stringify(client)` gets the redacted view. */
|
|
93
|
+
toJSON(): Record<string, string>;
|
|
79
94
|
/** Resolve a call's store: the explicit userId, else the client default. */
|
|
80
95
|
private uid;
|
|
96
|
+
private clone;
|
|
81
97
|
/**
|
|
82
98
|
* A client that uses `model` (sent as `X-WOS-Model`). Use it to set the default
|
|
83
99
|
* (`new Client({ apiKey, model })`) or to override one call
|
|
@@ -121,12 +137,22 @@ export declare class Client {
|
|
|
121
137
|
}>>;
|
|
122
138
|
/** Delete a store and ALL its memories. Returns `{ user_id, status }`. */
|
|
123
139
|
deleteStore(userId: string): Promise<any>;
|
|
140
|
+
/** Register a person for this store. Speakers are explicit: register once, then
|
|
141
|
+
* store with `{ speaker }`. `"me"` (the assistant itself) never needs
|
|
142
|
+
* registration. A store registers up to 50 people to start. */
|
|
143
|
+
addSpeaker(speaker: string, userId?: string): Promise<any>;
|
|
144
|
+
/** The store's registered people, each with its memory count. */
|
|
145
|
+
listSpeakers(userId?: string): Promise<any>;
|
|
146
|
+
/** Unregister a person. Their memories stay; the name tag goes. */
|
|
147
|
+
removeSpeaker(speaker: string, userId?: string): Promise<any>;
|
|
124
148
|
/** Delete a single memory by id. `userId` optional (uses the default store). */
|
|
125
149
|
delete(userId: string | undefined, memoryId: string): Promise<any>;
|
|
126
150
|
/** Delete ALL memories for a store (GDPR erase). `userId` is required on purpose -
|
|
127
151
|
* this is destructive, so it never falls back to the default store. */
|
|
128
152
|
deleteAll(userId: string): Promise<any>;
|
|
129
153
|
private post;
|
|
154
|
+
/** Seconds→ms to sleep before retry `attempt` (0-based). Honors Retry-After. */
|
|
155
|
+
private backoffMs;
|
|
130
156
|
private request;
|
|
131
157
|
}
|
|
132
158
|
/** Back-compat alias: older code used `WME`. */
|
package/dist/wontopos.js
CHANGED
|
@@ -21,15 +21,32 @@
|
|
|
21
21
|
*
|
|
22
22
|
* Pure semantic retrieval — no keyword/BM25 matching, so it works the SAME in every
|
|
23
23
|
* language. Storing and searching call no LLM; you only pay for embeddings.
|
|
24
|
+
*
|
|
25
|
+
* Reliability: every call retries transient failures (429 / 502 / 503 and
|
|
26
|
+
* connection errors) with exponential backoff + jitter, honoring `Retry-After`.
|
|
27
|
+
* Redirects are refused so the API key can never follow one to another host.
|
|
28
|
+
* Tune with `maxRetries` (0 disables) and `timeoutMs`.
|
|
24
29
|
*/
|
|
30
|
+
const VERSION = "2.2.8";
|
|
31
|
+
const USER_AGENT = `wontopos-node/${VERSION}`;
|
|
25
32
|
const DEFAULT_BASE_URL = "https://api.wontopos.com";
|
|
26
33
|
const DEFAULT_MODEL = "tablet-1";
|
|
34
|
+
/** Statuses where the request was NOT processed, so retrying is always safe. */
|
|
35
|
+
const RETRY_STATUSES = new Set([429, 502, 503]);
|
|
36
|
+
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "0.0.0.0"]);
|
|
37
|
+
/** `wos-abc...wxyz` — enough to tell keys apart, never enough to use. */
|
|
38
|
+
function maskKey(key) {
|
|
39
|
+
return key.length > 12 ? `${key.slice(0, 4)}...${key.slice(-4)}` : "***";
|
|
40
|
+
}
|
|
27
41
|
export class WosError extends Error {
|
|
28
42
|
status;
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
/** The server's id for the request when it sent one — include it when contacting support. */
|
|
44
|
+
requestId;
|
|
45
|
+
constructor(status, message, requestId) {
|
|
46
|
+
super(`[${status}] ${message}${requestId ? ` (request_id: ${requestId})` : ""}`);
|
|
31
47
|
this.name = "WosError";
|
|
32
48
|
this.status = status;
|
|
49
|
+
this.requestId = requestId;
|
|
33
50
|
}
|
|
34
51
|
}
|
|
35
52
|
const DEFAULT_USER = "default";
|
|
@@ -40,6 +57,7 @@ export class Client {
|
|
|
40
57
|
timeoutMs;
|
|
41
58
|
apiKey;
|
|
42
59
|
model;
|
|
60
|
+
maxRetries;
|
|
43
61
|
/** The store every call uses unless one passes `userId`. */
|
|
44
62
|
defaultUser;
|
|
45
63
|
constructor(opts) {
|
|
@@ -50,25 +68,57 @@ export class Client {
|
|
|
50
68
|
this.timeoutMs = opts.timeoutMs ?? 30_000;
|
|
51
69
|
this.model = opts.model ?? DEFAULT_MODEL;
|
|
52
70
|
this.defaultUser = opts.userId || DEFAULT_USER;
|
|
53
|
-
this.
|
|
71
|
+
this.maxRetries = Math.max(0, opts.maxRetries ?? 2);
|
|
72
|
+
this.headers = {
|
|
73
|
+
"X-API-Key": opts.apiKey,
|
|
74
|
+
"Content-Type": "application/json",
|
|
75
|
+
// Browsers silently drop User-Agent (forbidden header) — harmless.
|
|
76
|
+
"User-Agent": USER_AGENT,
|
|
77
|
+
};
|
|
54
78
|
if (this.model)
|
|
55
79
|
this.headers["X-WOS-Model"] = this.model;
|
|
80
|
+
// An API key on plain HTTP travels readable by anyone on the path. Loopback
|
|
81
|
+
// is fine (local dev / self-hosted on the same box); anything else gets a
|
|
82
|
+
// warning, not an error, so private-network gateways keep working.
|
|
83
|
+
const host = this.base.replace(/^https?:\/\//, "").split(/[/:]/)[0].toLowerCase();
|
|
84
|
+
if (this.base.startsWith("http://") && !LOOPBACK_HOSTS.has(host)) {
|
|
85
|
+
console.warn("wontopos: baseUrl uses plain HTTP on a non-local host, so the API key travels unencrypted. Use https://.");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Never expose the key: `JSON.stringify(client)` gets the redacted view. */
|
|
89
|
+
toJSON() {
|
|
90
|
+
return { baseUrl: this.base, model: this.model, userId: this.defaultUser, apiKey: maskKey(this.apiKey) };
|
|
91
|
+
}
|
|
92
|
+
/** Never expose the key: Node's `console.log(client)` gets the redacted view. */
|
|
93
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
94
|
+
return `Client(${this.base}, model=${this.model}, userId=${this.defaultUser}, apiKey=${maskKey(this.apiKey)})`;
|
|
56
95
|
}
|
|
57
96
|
/** Resolve a call's store: the explicit userId, else the client default. */
|
|
58
97
|
uid(userId) {
|
|
59
98
|
return userId || this.defaultUser;
|
|
60
99
|
}
|
|
100
|
+
clone(overrides) {
|
|
101
|
+
return new Client({
|
|
102
|
+
apiKey: this.apiKey,
|
|
103
|
+
baseUrl: this.base,
|
|
104
|
+
timeoutMs: this.timeoutMs,
|
|
105
|
+
model: this.model,
|
|
106
|
+
userId: this.defaultUser,
|
|
107
|
+
maxRetries: this.maxRetries,
|
|
108
|
+
...overrides,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
61
111
|
/**
|
|
62
112
|
* A client that uses `model` (sent as `X-WOS-Model`). Use it to set the default
|
|
63
113
|
* (`new Client({ apiKey, model })`) or to override one call
|
|
64
114
|
* (`client.withModel("tablet-1").recall(...)`).
|
|
65
115
|
*/
|
|
66
116
|
withModel(model) {
|
|
67
|
-
return
|
|
117
|
+
return this.clone({ model });
|
|
68
118
|
}
|
|
69
119
|
/** A client bound to `userId` as its default store (everything else kept). */
|
|
70
120
|
withUser(userId) {
|
|
71
|
-
return
|
|
121
|
+
return this.clone({ userId });
|
|
72
122
|
}
|
|
73
123
|
// ----- write -----
|
|
74
124
|
// `userId` is optional in every call below - omit it to use the client's default
|
|
@@ -123,10 +173,7 @@ export class Client {
|
|
|
123
173
|
// ----- models -----
|
|
124
174
|
/** Available models: `[{ id, name, available, memory }, ...]`. Needs no API key. */
|
|
125
175
|
async listModels() {
|
|
126
|
-
const
|
|
127
|
-
if (!res.ok)
|
|
128
|
-
throw new WosError(res.status, await res.text());
|
|
129
|
-
const data = await res.json();
|
|
176
|
+
const data = await this.request("GET", "/api/v1/models");
|
|
130
177
|
return (data.models ?? []);
|
|
131
178
|
}
|
|
132
179
|
// ----- stores -----
|
|
@@ -144,63 +191,119 @@ export class Client {
|
|
|
144
191
|
}
|
|
145
192
|
/** Delete a store and ALL its memories. Returns `{ user_id, status }`. */
|
|
146
193
|
deleteStore(userId) {
|
|
194
|
+
if (!userId)
|
|
195
|
+
throw new Error("userId is required — deleteStore never falls back to the default store.");
|
|
147
196
|
return this.request("DELETE", "/api/v1/memory/collection", { user_id: userId });
|
|
148
197
|
}
|
|
198
|
+
// ----- speakers (who said it) -----
|
|
199
|
+
/** Register a person for this store. Speakers are explicit: register once, then
|
|
200
|
+
* store with `{ speaker }`. `"me"` (the assistant itself) never needs
|
|
201
|
+
* registration. A store registers up to 50 people to start. */
|
|
202
|
+
addSpeaker(speaker, userId) {
|
|
203
|
+
return this.post("/api/v1/memory/speakers", { user_id: this.uid(userId), speaker });
|
|
204
|
+
}
|
|
205
|
+
/** The store's registered people, each with its memory count. */
|
|
206
|
+
listSpeakers(userId) {
|
|
207
|
+
return this.request("GET", `/api/v1/memory/speakers?user_id=${encodeURIComponent(this.uid(userId))}`);
|
|
208
|
+
}
|
|
209
|
+
/** Unregister a person. Their memories stay; the name tag goes. */
|
|
210
|
+
removeSpeaker(speaker, userId) {
|
|
211
|
+
return this.request("DELETE", "/api/v1/memory/speakers", { user_id: this.uid(userId), speaker });
|
|
212
|
+
}
|
|
149
213
|
// ----- delete -----
|
|
150
214
|
/** Delete a single memory by id. `userId` optional (uses the default store). */
|
|
151
215
|
delete(userId, memoryId) {
|
|
216
|
+
// Guard: without a memory_id the API's forget endpoint means "delete the
|
|
217
|
+
// whole store". An undefined/"" slipping in here must never become a wipe.
|
|
218
|
+
if (!memoryId || typeof memoryId !== "string") {
|
|
219
|
+
throw new Error("memoryId is required. To delete every memory in a store, call deleteAll(userId) explicitly.");
|
|
220
|
+
}
|
|
152
221
|
return this.post("/api/v1/memory/forget", { user_id: this.uid(userId), memory_id: memoryId });
|
|
153
222
|
}
|
|
154
223
|
/** Delete ALL memories for a store (GDPR erase). `userId` is required on purpose -
|
|
155
224
|
* this is destructive, so it never falls back to the default store. */
|
|
156
225
|
deleteAll(userId) {
|
|
226
|
+
if (!userId)
|
|
227
|
+
throw new Error("userId is required for deleteAll — it never falls back to the default store.");
|
|
157
228
|
return this.post("/api/v1/memory/forget", { user_id: userId });
|
|
158
229
|
}
|
|
159
230
|
// ----- internal -----
|
|
160
231
|
post(path, body) {
|
|
161
232
|
return this.request("POST", path, body);
|
|
162
233
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
method,
|
|
170
|
-
headers: this.headers,
|
|
171
|
-
body: body === undefined ? undefined : JSON.stringify(body),
|
|
172
|
-
signal: ctrl.signal,
|
|
173
|
-
});
|
|
234
|
+
/** Seconds→ms to sleep before retry `attempt` (0-based). Honors Retry-After. */
|
|
235
|
+
backoffMs(attempt, retryAfter) {
|
|
236
|
+
if (retryAfter) {
|
|
237
|
+
const s = Number(retryAfter);
|
|
238
|
+
if (Number.isFinite(s) && s >= 0)
|
|
239
|
+
return Math.min(30_000, s * 1000);
|
|
174
240
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// {"type":"error","error":{"type":...,"message":...,"request_id":...}}
|
|
184
|
-
// or simple {"error":"reason"}. Fall back to raw text.
|
|
185
|
-
let msg = await res.text();
|
|
241
|
+
return Math.min(8_000, 500 * 2 ** attempt) + Math.random() * 250;
|
|
242
|
+
}
|
|
243
|
+
async request(method, path, body) {
|
|
244
|
+
const attempts = this.maxRetries + 1;
|
|
245
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
246
|
+
const ctrl = new AbortController();
|
|
247
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
248
|
+
let res;
|
|
186
249
|
try {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
250
|
+
res = await fetch(`${this.base}${path}`, {
|
|
251
|
+
method,
|
|
252
|
+
headers: this.headers,
|
|
253
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
254
|
+
signal: ctrl.signal,
|
|
255
|
+
// Never follow a redirect: fetch would forward the API key to
|
|
256
|
+
// wherever a 3xx points. The API never legitimately redirects.
|
|
257
|
+
redirect: "manual",
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
catch (e) {
|
|
261
|
+
clearTimeout(timer);
|
|
262
|
+
// Timeouts are ambiguous (the write may have landed) — don't retry those.
|
|
263
|
+
const timedOut = e?.name === "AbortError" || ctrl.signal.aborted;
|
|
264
|
+
if (!timedOut && attempt + 1 < attempts) {
|
|
265
|
+
await new Promise((r) => setTimeout(r, this.backoffMs(attempt)));
|
|
266
|
+
continue;
|
|
196
267
|
}
|
|
268
|
+
throw new WosError(0, timedOut ? `request timed out after ${this.timeoutMs}ms` : `network error: ${e?.message ?? e}`);
|
|
197
269
|
}
|
|
198
|
-
|
|
199
|
-
|
|
270
|
+
clearTimeout(timer);
|
|
271
|
+
if (RETRY_STATUSES.has(res.status) && attempt + 1 < attempts) {
|
|
272
|
+
await new Promise((r) => setTimeout(r, this.backoffMs(attempt, res.headers.get("Retry-After"))));
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if ((res.status >= 300 && res.status < 400) || res.type === "opaqueredirect") {
|
|
276
|
+
throw new WosError(res.status, "unexpected redirect — refused (the API key never follows a redirect). Check baseUrl: exact host, https://.");
|
|
277
|
+
}
|
|
278
|
+
if (!res.ok) {
|
|
279
|
+
// Server returns either Anthropic-style envelope
|
|
280
|
+
// {"type":"error","error":{"type":...,"message":...,"request_id":...}}
|
|
281
|
+
// or simple {"error":"reason"}. Fall back to raw text.
|
|
282
|
+
let msg = await res.text();
|
|
283
|
+
let requestId;
|
|
284
|
+
try {
|
|
285
|
+
const data = JSON.parse(msg);
|
|
286
|
+
if (data && typeof data === "object") {
|
|
287
|
+
const err = data.error;
|
|
288
|
+
if (err && typeof err === "object") {
|
|
289
|
+
msg = err.message ?? err.type ?? msg;
|
|
290
|
+
if (typeof err.request_id === "string")
|
|
291
|
+
requestId = err.request_id;
|
|
292
|
+
}
|
|
293
|
+
else if (typeof err === "string")
|
|
294
|
+
msg = err;
|
|
295
|
+
else if (typeof data.message === "string")
|
|
296
|
+
msg = data.message;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
/* keep raw text */
|
|
301
|
+
}
|
|
302
|
+
throw new WosError(res.status, msg, requestId);
|
|
200
303
|
}
|
|
201
|
-
|
|
304
|
+
return res.json();
|
|
202
305
|
}
|
|
203
|
-
|
|
306
|
+
throw new WosError(0, "retries exhausted"); // unreachable; keeps the compiler happy
|
|
204
307
|
}
|
|
205
308
|
}
|
|
206
309
|
/** Back-compat alias: older code used `WME`. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wontopos",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.8",
|
|
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",
|
|
@@ -14,14 +14,6 @@
|
|
|
14
14
|
"context"
|
|
15
15
|
],
|
|
16
16
|
"homepage": "https://wontopos.com",
|
|
17
|
-
"repository": {
|
|
18
|
-
"type": "git",
|
|
19
|
-
"url": "git+https://github.com/Irina1920/Wos_API.git",
|
|
20
|
-
"directory": "sdk/typescript"
|
|
21
|
-
},
|
|
22
|
-
"bugs": {
|
|
23
|
-
"url": "https://github.com/Irina1920/Wos_API/issues"
|
|
24
|
-
},
|
|
25
17
|
"license": "MIT",
|
|
26
18
|
"author": "Wontopos",
|
|
27
19
|
"type": "module",
|
|
@@ -45,6 +37,7 @@
|
|
|
45
37
|
},
|
|
46
38
|
"scripts": {
|
|
47
39
|
"build": "tsc",
|
|
40
|
+
"test": "npm run build && node --test test/client.test.mjs",
|
|
48
41
|
"prepublishOnly": "npm run build"
|
|
49
42
|
},
|
|
50
43
|
"devDependencies": {
|