wontopos 2.2.24 → 2.2.25
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/dist/wontopos.d.ts +25 -1
- package/dist/wontopos.js +87 -3
- package/package.json +1 -1
package/dist/wontopos.d.ts
CHANGED
|
@@ -37,6 +37,8 @@
|
|
|
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
|
+
/** Test hook — the warn-once set is process-global by design. */
|
|
41
|
+
export declare function _resetStoreIdWarnings(): void;
|
|
40
42
|
/** Quota from a response's `X-RateLimit-*` headers, or `null` if absent. */
|
|
41
43
|
export interface RateLimit {
|
|
42
44
|
limit: number | null;
|
|
@@ -73,7 +75,18 @@ export declare class ConflictError extends WosError {
|
|
|
73
75
|
/** 429 — too many requests. Back off and retry (the client already retries these). */
|
|
74
76
|
export declare class RateLimitError extends WosError {
|
|
75
77
|
}
|
|
76
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* 5xx — the service failed.
|
|
80
|
+
*
|
|
81
|
+
* `502` / `503` are transient: the client already retries them for calls where a
|
|
82
|
+
* retry cannot double-process a write, and retrying yourself is reasonable.
|
|
83
|
+
*
|
|
84
|
+
* `501` is NOT transient. It means the engine behind the model you selected does
|
|
85
|
+
* not implement that endpoint at all (the error carries `model` and `endpoint`).
|
|
86
|
+
* Retrying can never succeed — pick a model that supports it (`listModels`) or
|
|
87
|
+
* drop the call. This class used to say "safe to retry" for the whole 5xx range,
|
|
88
|
+
* which sent callers into a loop that could not end.
|
|
89
|
+
*/
|
|
77
90
|
export declare class ServerError extends WosError {
|
|
78
91
|
}
|
|
79
92
|
export interface Usage {
|
|
@@ -121,6 +134,10 @@ export interface StoreResult {
|
|
|
121
134
|
duplicate_of?: string;
|
|
122
135
|
/** Present when the store was a duplicate and something (e.g. a speaker tag) was dropped. */
|
|
123
136
|
note?: string;
|
|
137
|
+
/** `true` when this response was REPLAYED from a previous request with the same
|
|
138
|
+
* `idempotencyKey` — nothing new was stored. Absent on a fresh write. Read it to
|
|
139
|
+
* tell "my retry landed" from "my retry was a no-op". */
|
|
140
|
+
replayed?: boolean;
|
|
124
141
|
[key: string]: unknown;
|
|
125
142
|
}
|
|
126
143
|
/** `addTurn` / `addBulk` / `delete` / `deleteAll` → `{ status }`. */
|
|
@@ -209,8 +226,13 @@ export interface EngramCatalog {
|
|
|
209
226
|
}
|
|
210
227
|
/** `createStore` / `deleteStore` → `{ user_id, status }`. */
|
|
211
228
|
export interface StoreOpResult {
|
|
229
|
+
/** The store id the API actually used — NOT necessarily the one you sent.
|
|
230
|
+
* Ids are normalized (see the `note`), so compare this against your input. */
|
|
212
231
|
user_id?: string;
|
|
213
232
|
status?: string;
|
|
233
|
+
/** Present when the API changed your id, explaining how. Read it: two of your
|
|
234
|
+
* end users can land in one store if their ids differ only by punctuation. */
|
|
235
|
+
note?: string;
|
|
214
236
|
[key: string]: unknown;
|
|
215
237
|
}
|
|
216
238
|
export interface StoreInfo {
|
|
@@ -260,6 +282,8 @@ export interface SearchFilters {
|
|
|
260
282
|
/** Drop matches the engine scored below this importance (0–1). */
|
|
261
283
|
min_importance?: number;
|
|
262
284
|
}
|
|
285
|
+
/** Test hook — the warn-once set is process-global by design. */
|
|
286
|
+
export declare function _resetFilterWarnings(): void;
|
|
263
287
|
/** Known `search` options; extra fields pass through to the API untouched. */
|
|
264
288
|
export interface SearchOptions {
|
|
265
289
|
/** Recall caching: repeated/extended queries within the TTL bill at 0.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.
|
|
40
|
+
const VERSION = "2.2.25";
|
|
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). */
|
|
@@ -74,6 +74,40 @@ const MAX_ERR_MSG = 4096;
|
|
|
74
74
|
/** What the API accepts as an `Idempotency-Key`. Checked client-side so a bad key
|
|
75
75
|
* fails before the request instead of coming back as a 400 mid-retry. */
|
|
76
76
|
const IDEMPOTENCY_KEY_RE = /^[A-Za-z0-9._:\-]{1,128}$/;
|
|
77
|
+
/**
|
|
78
|
+
* The API normalizes a store id: lowercased, and every character outside
|
|
79
|
+
* `[a-z0-9_]` becomes `_`. So `Alice.Smith`, `alice-smith` and `alice_smith` are
|
|
80
|
+
* ALL the same store.
|
|
81
|
+
*
|
|
82
|
+
* That is a data-exposure hazard for the most common way this SDK is used — one
|
|
83
|
+
* store per end user. Two accounts whose ids differ only by punctuation or case
|
|
84
|
+
* (`bob.lee@x.com` / `bob-lee@x.com`) silently share every memory, and nothing in
|
|
85
|
+
* the response says so: the note only appears when `createStore` creates one, and
|
|
86
|
+
* an app that reuses an existing store never sees it.
|
|
87
|
+
*
|
|
88
|
+
* We cannot refuse the id — the API accepts it, and callers may have written it
|
|
89
|
+
* this way for a year. So we say it once, loudly, at the moment it happens.
|
|
90
|
+
*/
|
|
91
|
+
function normalizeStoreId(id) {
|
|
92
|
+
return id.toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
93
|
+
}
|
|
94
|
+
const warnedStoreIds = new Set();
|
|
95
|
+
function warnIfStoreIdCollapses(id) {
|
|
96
|
+
if (!id || warnedStoreIds.has(id))
|
|
97
|
+
return;
|
|
98
|
+
const normalized = normalizeStoreId(id);
|
|
99
|
+
if (normalized === id)
|
|
100
|
+
return;
|
|
101
|
+
warnedStoreIds.add(id);
|
|
102
|
+
console.warn(`wontopos: store id ${JSON.stringify(id)} is stored as ${JSON.stringify(normalized)} ` +
|
|
103
|
+
`(lowercased, and anything outside [a-z0-9_] becomes "_"). Ids that differ only by case or ` +
|
|
104
|
+
`punctuation share ONE store and therefore one set of memories — if these ids come from your ` +
|
|
105
|
+
`end users, normalize them yourself first so two people can never collide.`);
|
|
106
|
+
}
|
|
107
|
+
/** Test hook — the warn-once set is process-global by design. */
|
|
108
|
+
export function _resetStoreIdWarnings() {
|
|
109
|
+
warnedStoreIds.clear();
|
|
110
|
+
}
|
|
77
111
|
/** Statuses that legitimately carry NO body (RFC 9110). Everything else must
|
|
78
112
|
* answer with a JSON object — see the empty-body check in `request`. */
|
|
79
113
|
const NO_BODY_STATUS = new Set([204, 205, 304]);
|
|
@@ -208,7 +242,18 @@ export class ConflictError extends WosError {
|
|
|
208
242
|
/** 429 — too many requests. Back off and retry (the client already retries these). */
|
|
209
243
|
export class RateLimitError extends WosError {
|
|
210
244
|
}
|
|
211
|
-
/**
|
|
245
|
+
/**
|
|
246
|
+
* 5xx — the service failed.
|
|
247
|
+
*
|
|
248
|
+
* `502` / `503` are transient: the client already retries them for calls where a
|
|
249
|
+
* retry cannot double-process a write, and retrying yourself is reasonable.
|
|
250
|
+
*
|
|
251
|
+
* `501` is NOT transient. It means the engine behind the model you selected does
|
|
252
|
+
* not implement that endpoint at all (the error carries `model` and `endpoint`).
|
|
253
|
+
* Retrying can never succeed — pick a model that supports it (`listModels`) or
|
|
254
|
+
* drop the call. This class used to say "safe to retry" for the whole 5xx range,
|
|
255
|
+
* which sent callers into a loop that could not end.
|
|
256
|
+
*/
|
|
212
257
|
export class ServerError extends WosError {
|
|
213
258
|
}
|
|
214
259
|
const STATUS_ERRORS = {
|
|
@@ -228,6 +273,34 @@ function errorFor(status, message, requestId) {
|
|
|
228
273
|
: STATUS_ERRORS[status] ?? (status >= 500 && status < 600 ? ServerError : WosError);
|
|
229
274
|
return new Cls(status, message, requestId);
|
|
230
275
|
}
|
|
276
|
+
/** The filter keys the API acts on. Anything else it DROPS silently, so a typo
|
|
277
|
+
* widens the search instead of failing — we warn rather than reject, because the
|
|
278
|
+
* API may grow a key before this package is updated. */
|
|
279
|
+
const KNOWN_FILTER_KEYS = new Set([
|
|
280
|
+
"categories",
|
|
281
|
+
"event_from",
|
|
282
|
+
"event_to",
|
|
283
|
+
"time_from",
|
|
284
|
+
"time_to",
|
|
285
|
+
"min_importance",
|
|
286
|
+
]);
|
|
287
|
+
const warnedFilterKeys = new Set();
|
|
288
|
+
function warnOnUnknownFilters(filters) {
|
|
289
|
+
if (!filters || typeof filters !== "object" || Array.isArray(filters))
|
|
290
|
+
return;
|
|
291
|
+
for (const k of Object.keys(filters)) {
|
|
292
|
+
if (KNOWN_FILTER_KEYS.has(k) || warnedFilterKeys.has(k))
|
|
293
|
+
continue;
|
|
294
|
+
warnedFilterKeys.add(k);
|
|
295
|
+
console.warn(`wontopos: unknown search filter ${JSON.stringify(k)} — the API drops keys it does not know, ` +
|
|
296
|
+
`so this filter has NO effect and the search is wider than you think. ` +
|
|
297
|
+
`Known keys: ${[...KNOWN_FILTER_KEYS].join(", ")}.`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/** Test hook — the warn-once set is process-global by design. */
|
|
301
|
+
export function _resetFilterWarnings() {
|
|
302
|
+
warnedFilterKeys.clear();
|
|
303
|
+
}
|
|
231
304
|
const DEFAULT_USER = "default";
|
|
232
305
|
/** Wontopos memory client. Store and recall memories, isolated per `userId`. */
|
|
233
306
|
export class Client {
|
|
@@ -310,7 +383,9 @@ export class Client {
|
|
|
310
383
|
}
|
|
311
384
|
/** Resolve a call's store: the explicit userId, else the client default. */
|
|
312
385
|
uid(userId) {
|
|
313
|
-
|
|
386
|
+
const id = userId || this.defaultUser;
|
|
387
|
+
warnIfStoreIdCollapses(id);
|
|
388
|
+
return id;
|
|
314
389
|
}
|
|
315
390
|
clone(overrides) {
|
|
316
391
|
return new Client({
|
|
@@ -384,6 +459,7 @@ export class Client {
|
|
|
384
459
|
async search(query, userId, limit = 10, opts = {}) {
|
|
385
460
|
// Reserved fields win over ...opts: an app that forwards untrusted input as
|
|
386
461
|
// opts must not be able to override the store (user_id), query, or limit.
|
|
462
|
+
warnOnUnknownFilters(opts.filters);
|
|
387
463
|
const r = await this.post("/api/v1/memory/search", { ...opts, user_id: this.uid(userId), query, max_results: limit });
|
|
388
464
|
return mergeLanes(r);
|
|
389
465
|
}
|
|
@@ -393,6 +469,7 @@ export class Client {
|
|
|
393
469
|
* speaker "me"), kept separate so the reader never confuses who said what. On a model
|
|
394
470
|
* without a self lane, `self_memories` is `[]`. `userId` optional (default store). */
|
|
395
471
|
async searchSelf(query, userId, limit = 10, opts = {}) {
|
|
472
|
+
warnOnUnknownFilters(opts.filters);
|
|
396
473
|
const r = await this.post("/api/v1/memory/search", { ...opts, user_id: this.uid(userId), query, max_results: limit });
|
|
397
474
|
return { memories: asRecords(r.memories), self_memories: asRecords(r.self_memories) };
|
|
398
475
|
}
|
|
@@ -753,6 +830,13 @@ export class Client {
|
|
|
753
830
|
const got = data === null ? "null" : Array.isArray(data) ? "array" : typeof data;
|
|
754
831
|
throw new WosError(res.status, `expected a JSON object in the response, got ${got}`);
|
|
755
832
|
}
|
|
833
|
+
// 멱등키를 쓴 쓰기가 **저장된 것인지 재생된 것인지**를 호출자가 알아야 한다.
|
|
834
|
+
// 서버는 `Idempotent-Replayed: true` 로 말해주는데 지금까지 SDK 가 삼켰다 —
|
|
835
|
+
// 멱등키를 쓰는 사람이 가장 알고 싶은 사실이 그것이다(재시도가 실제로
|
|
836
|
+
// 저장을 일으켰나, 아니면 첫 응답이 되풀이된 것인가).
|
|
837
|
+
if (res.headers.get("Idempotent-Replayed") === "true") {
|
|
838
|
+
data.replayed = true;
|
|
839
|
+
}
|
|
756
840
|
return data;
|
|
757
841
|
}
|
|
758
842
|
throw new WosError(0, "retries exhausted"); // unreachable; keeps the compiler happy
|
package/package.json
CHANGED