wontopos 2.2.45 → 2.2.47
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 +134 -21
- package/dist/cjs/wontopos.d.cts +117 -76
- package/dist/cjs/wontopos.js +98 -22
- package/dist/wontopos.d.ts +117 -76
- package/dist/wontopos.js +97 -22
- package/package.json +1 -1
package/dist/wontopos.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const VERSION = "2.2.
|
|
1
|
+
const VERSION = "2.2.47";
|
|
2
2
|
const RUNTIME = (() => {
|
|
3
3
|
const p = globalThis?.process;
|
|
4
4
|
return p?.version ? ` (node/${p.version}; ${p.platform}-${p.arch})` : "";
|
|
@@ -263,12 +263,7 @@ const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "
|
|
|
263
263
|
const IMF_FIXDATE = /^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\d{2}) ([A-Z][a-z]{2}) (\d{4}) (\d{2}):(\d{2}):(\d{2}) GMT$/;
|
|
264
264
|
const RFC850_DATE = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\d{2})-([A-Z][a-z]{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2}) GMT$/;
|
|
265
265
|
const ASCTIME_DATE = /^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) ([A-Z][a-z]{2}) ([ \d]\d) (\d{2}):(\d{2}):(\d{2}) (\d{4})$/;
|
|
266
|
-
function
|
|
267
|
-
if (v == null)
|
|
268
|
-
return undefined;
|
|
269
|
-
const s = v.trim();
|
|
270
|
-
if (/^\d+$/.test(s))
|
|
271
|
-
return Math.min(Number(s), Number.MAX_SAFE_INTEGER / 1000) * 1000;
|
|
266
|
+
function httpDateMs(s, now = Date.now()) {
|
|
272
267
|
let parts;
|
|
273
268
|
let m;
|
|
274
269
|
if ((m = IMF_FIXDATE.exec(s)))
|
|
@@ -288,7 +283,37 @@ function retryAfterMs(v, now = Date.now()) {
|
|
|
288
283
|
const d = Number(day), h = Number(hh), mi = Number(mm), se = Number(ss);
|
|
289
284
|
if (month < 0 || d < 1 || d > 31 || h > 23 || mi > 59 || se > 60)
|
|
290
285
|
return undefined;
|
|
291
|
-
return
|
|
286
|
+
return Date.UTC(year, month, d, h, mi, se);
|
|
287
|
+
}
|
|
288
|
+
function retryAfterMs(v, now = Date.now()) {
|
|
289
|
+
if (v == null)
|
|
290
|
+
return undefined;
|
|
291
|
+
const s = v.trim();
|
|
292
|
+
if (/^\d+$/.test(s))
|
|
293
|
+
return Math.min(Number(s), Number.MAX_SAFE_INTEGER / 1000) * 1000;
|
|
294
|
+
const at = httpDateMs(s, now);
|
|
295
|
+
return at === undefined ? undefined : Math.max(0, at - now);
|
|
296
|
+
}
|
|
297
|
+
const warnedRetirements = new Set();
|
|
298
|
+
const WARNED_RETIREMENTS_MAX = 64;
|
|
299
|
+
function warnIfRetiring(model, h) {
|
|
300
|
+
const v = h.get("Sunset");
|
|
301
|
+
if (!v)
|
|
302
|
+
return;
|
|
303
|
+
const at = httpDateMs(v.trim());
|
|
304
|
+
if (at === undefined)
|
|
305
|
+
return;
|
|
306
|
+
const day = new Date(at).toISOString().slice(0, 10);
|
|
307
|
+
const k = `${model}|${day}`;
|
|
308
|
+
if (warnedRetirements.has(k) || warnedRetirements.size >= WARNED_RETIREMENTS_MAX)
|
|
309
|
+
return;
|
|
310
|
+
warnedRetirements.add(k);
|
|
311
|
+
const which = model ? `model ${JSON.stringify(model)}` : "the model this client uses";
|
|
312
|
+
console.warn(`wontopos: ${which} retires on ${day} (UTC). From then on its calls answer 410, except ` +
|
|
313
|
+
"deleteStore. listModels() lists the models you can move to.");
|
|
314
|
+
}
|
|
315
|
+
export function _resetRetirementWarnings() {
|
|
316
|
+
warnedRetirements.clear();
|
|
292
317
|
}
|
|
293
318
|
const BRIEF_BODY_MS = 1_000;
|
|
294
319
|
class SlowBody extends Error {
|
|
@@ -313,6 +338,23 @@ function tryUrl(s, base) {
|
|
|
313
338
|
return undefined;
|
|
314
339
|
}
|
|
315
340
|
}
|
|
341
|
+
function inBrowserPage() {
|
|
342
|
+
try {
|
|
343
|
+
const g = globalThis;
|
|
344
|
+
return typeof g.window !== "undefined" && typeof g.window.document !== "undefined" && typeof g.navigator !== "undefined";
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function isServiceHost(host) {
|
|
351
|
+
const h = host.toLowerCase();
|
|
352
|
+
return h === "wontopos.com" || h.endsWith(".wontopos.com");
|
|
353
|
+
}
|
|
354
|
+
const BROWSER_REFUSAL = "wontopos: refusing to run in a browser page. The API key would be readable by anyone who " +
|
|
355
|
+
"opens the page, and with it every memory in its workspace. Call Wontopos from your server, " +
|
|
356
|
+
"or put a proxy of your own in front of it and point baseUrl there. For a local demo with a " +
|
|
357
|
+
"key you will revoke, pass dangerouslyAllowBrowser: true.";
|
|
316
358
|
function pageHref() {
|
|
317
359
|
try {
|
|
318
360
|
const href = globalThis.location?.href;
|
|
@@ -389,6 +431,18 @@ const memoryFromGet = (r) => {
|
|
|
389
431
|
return o.memory;
|
|
390
432
|
return typeof o.id === "string" ? o : {};
|
|
391
433
|
};
|
|
434
|
+
function recallShape(r) {
|
|
435
|
+
const o = asObj(r);
|
|
436
|
+
const st = asObj(o.short_term);
|
|
437
|
+
const lt = asObj(o.long_term);
|
|
438
|
+
const cx = asObj(o.context);
|
|
439
|
+
return {
|
|
440
|
+
...o,
|
|
441
|
+
short_term: { ...st, turns: asRecords(st.turns) },
|
|
442
|
+
long_term: { ...lt, memories: asRecords(lt.memories) },
|
|
443
|
+
context: { ...cx, around_top_memory: asList(cx.around_top_memory).filter((x) => typeof x === "string") },
|
|
444
|
+
};
|
|
445
|
+
}
|
|
392
446
|
function mergeResults(r) {
|
|
393
447
|
const out = asRecords(r?.memories);
|
|
394
448
|
const seen = new Set(out.map((m) => m.id).filter((id) => typeof id === "string"));
|
|
@@ -553,6 +607,15 @@ function writeKey(opts, takesImage = false) {
|
|
|
553
607
|
(takesImage ? "add takes idempotencyKey and image." : "This call takes idempotencyKey only."));
|
|
554
608
|
return opts.idempotencyKey;
|
|
555
609
|
}
|
|
610
|
+
function newIdempotencyKey() {
|
|
611
|
+
const c = globalThis.crypto;
|
|
612
|
+
if (typeof c?.randomUUID === "function")
|
|
613
|
+
return `sdk-${String(c.randomUUID()).replace(/-/g, "")}`;
|
|
614
|
+
let hex = "";
|
|
615
|
+
for (let i = 0; i < 4; i++)
|
|
616
|
+
hex += Math.floor(Math.random() * 0x100000000).toString(16).padStart(8, "0");
|
|
617
|
+
return `sdk-${hex}`;
|
|
618
|
+
}
|
|
556
619
|
function warnOnUnknownFilters(filters) {
|
|
557
620
|
warnOnceUnknownKeys(filters, KNOWN_FILTER_KEYS, warnedFilterKeys, (k) => `wontopos: unknown search filter ${JSON.stringify(k)} — the API drops keys it does not know, ` +
|
|
558
621
|
`so this filter has NO effect and the search is wider than you think. ` +
|
|
@@ -581,6 +644,7 @@ export class Client {
|
|
|
581
644
|
userinfo;
|
|
582
645
|
pageRelative;
|
|
583
646
|
customFetch;
|
|
647
|
+
allowBrowser;
|
|
584
648
|
defaultUser;
|
|
585
649
|
_rateLimit = null;
|
|
586
650
|
get rateLimit() {
|
|
@@ -614,6 +678,10 @@ export class Client {
|
|
|
614
678
|
throw new Error(notAUrl(trimmed));
|
|
615
679
|
this.pageRelative = !url;
|
|
616
680
|
this.userinfo = Boolean(parsed.username || parsed.password);
|
|
681
|
+
this.allowBrowser = opts.dangerouslyAllowBrowser === true;
|
|
682
|
+
if (!this.allowBrowser && inBrowserPage() && isServiceHost(parsed.hostname)) {
|
|
683
|
+
throw new Error(BROWSER_REFUSAL);
|
|
684
|
+
}
|
|
617
685
|
this.timeoutMs = checkMs(opts.timeoutMs) ?? 30_000;
|
|
618
686
|
this.model = opts.model ?? DEFAULT_MODEL;
|
|
619
687
|
if (this.model && !MODEL_RE.test(this.model)) {
|
|
@@ -682,6 +750,7 @@ export class Client {
|
|
|
682
750
|
fetch: this.fetchImpl,
|
|
683
751
|
deadlineMs: this.deadlineMs,
|
|
684
752
|
signal: this.signal,
|
|
753
|
+
dangerouslyAllowBrowser: this.allowBrowser,
|
|
685
754
|
...overrides,
|
|
686
755
|
});
|
|
687
756
|
}
|
|
@@ -713,22 +782,22 @@ export class Client {
|
|
|
713
782
|
if (opts.image !== undefined)
|
|
714
783
|
body.image = normalizeImage(opts.image);
|
|
715
784
|
warnOnUnknownMetadata(metadata);
|
|
716
|
-
return this.
|
|
785
|
+
return this.write("/api/v1/memory/store", body, writeKey(opts, true));
|
|
717
786
|
}
|
|
718
787
|
async store(content, userId, metadata = {}, opts = {}) {
|
|
719
788
|
return this.add(content, userId, metadata, opts);
|
|
720
789
|
}
|
|
721
790
|
async addTurn(userMsg, assistantMsg, userId, opts = {}) {
|
|
722
|
-
return this.
|
|
791
|
+
return this.write("/api/v1/memory/store-turn", { user_id: this.uid(userId), user_msg: userMsg, assistant_msg: assistantMsg }, writeKey(opts));
|
|
723
792
|
}
|
|
724
793
|
async addBulk(content, userId, category = "general", timestamp, opts = {}) {
|
|
725
794
|
const body = { user_id: this.uid(userId), content, category: category ?? "general" };
|
|
726
795
|
if (timestamp)
|
|
727
796
|
body.timestamp = timestamp;
|
|
728
|
-
return this.
|
|
797
|
+
return this.write("/api/v1/memory/bulk-store", body, writeKey(opts));
|
|
729
798
|
}
|
|
730
799
|
async update(oldMemoryId, newContent, userId, opts = {}) {
|
|
731
|
-
return this.
|
|
800
|
+
return this.write("/api/v1/memory/supersede", { user_id: this.uid(userId), old_memory_id: oldMemoryId, new_content: newContent }, writeKey(opts));
|
|
732
801
|
}
|
|
733
802
|
async search(query, userId, limit = 10, opts = {}) {
|
|
734
803
|
checkSearchOpts(opts);
|
|
@@ -773,7 +842,7 @@ export class Client {
|
|
|
773
842
|
checkContextLimit(opts.context_limit);
|
|
774
843
|
body.context_limit = opts.context_limit;
|
|
775
844
|
}
|
|
776
|
-
return this.post("/api/v1/memory/recall", withForm(body, opts.form, opts.tz));
|
|
845
|
+
return recallShape(await this.post("/api/v1/memory/recall", withForm(body, opts.form, opts.tz)));
|
|
777
846
|
}
|
|
778
847
|
async engram(name, query, userId, opts = {}) {
|
|
779
848
|
checkOpts(opts, KNOWN_ENGRAM_KEYS, "engram");
|
|
@@ -978,8 +1047,12 @@ export class Client {
|
|
|
978
1047
|
warnIfStoreIdCollapses(userId);
|
|
979
1048
|
return this.post("/api/v1/memory/forget", { user_id: userId });
|
|
980
1049
|
}
|
|
981
|
-
post(path, body
|
|
982
|
-
return this.request("POST", path, body
|
|
1050
|
+
post(path, body) {
|
|
1051
|
+
return this.request("POST", path, body);
|
|
1052
|
+
}
|
|
1053
|
+
write(path, body, idempotencyKey) {
|
|
1054
|
+
const made = idempotencyKey == null;
|
|
1055
|
+
return this.request("POST", path, body, made ? newIdempotencyKey() : idempotencyKey, false, made);
|
|
983
1056
|
}
|
|
984
1057
|
remove(path, body) {
|
|
985
1058
|
return this.request("DELETE", path, body, undefined, true);
|
|
@@ -1097,8 +1170,9 @@ export class Client {
|
|
|
1097
1170
|
}
|
|
1098
1171
|
const attempts = this.maxRetries + 1;
|
|
1099
1172
|
const upper = method.toUpperCase();
|
|
1100
|
-
const idempotent = IDEMPOTENT_METHODS.has(upper);
|
|
1101
1173
|
const logPath = path.split("?")[0];
|
|
1174
|
+
const idempotent = IDEMPOTENT_METHODS.has(upper) || reads || (upper === "POST" && READ_POSTS.has(logPath));
|
|
1175
|
+
const replaySafe = idempotent || extraHeaders?.["Idempotency-Key"] !== undefined;
|
|
1102
1176
|
const deadlineAt = this.deadlineAt();
|
|
1103
1177
|
const headers = extraHeaders ? { ...this.headers, ...extraHeaders } : this.headers;
|
|
1104
1178
|
let ambiguous = false;
|
|
@@ -1119,7 +1193,7 @@ export class Client {
|
|
|
1119
1193
|
};
|
|
1120
1194
|
const cutShort = () => {
|
|
1121
1195
|
const why = att.why();
|
|
1122
|
-
if (why === "deadline" &&
|
|
1196
|
+
if (why === "deadline" && idempotent && previous) {
|
|
1123
1197
|
return previous;
|
|
1124
1198
|
}
|
|
1125
1199
|
return new APIConnectionError(0, this.abortMessage(why));
|
|
@@ -1140,7 +1214,7 @@ export class Client {
|
|
|
1140
1214
|
if (timedOut)
|
|
1141
1215
|
throw cutShort();
|
|
1142
1216
|
const connect = isConnectFailure(e);
|
|
1143
|
-
if ((
|
|
1217
|
+
if ((replaySafe || connect) && !last) {
|
|
1144
1218
|
if (!connect)
|
|
1145
1219
|
ambiguous = true;
|
|
1146
1220
|
await pauseWithoutAnswer(e?.code ?? e?.name ?? "network error");
|
|
@@ -1157,7 +1231,7 @@ export class Client {
|
|
|
1157
1231
|
throw cutShort();
|
|
1158
1232
|
if (e instanceof WosError)
|
|
1159
1233
|
throw e;
|
|
1160
|
-
if (
|
|
1234
|
+
if (replaySafe && !last)
|
|
1161
1235
|
return null;
|
|
1162
1236
|
throw this.networkError(e, path);
|
|
1163
1237
|
}
|
|
@@ -1184,9 +1258,10 @@ export class Client {
|
|
|
1184
1258
|
throw new WosError(res.status, "unexpected redirect — refused (the API key never follows a redirect). Check baseUrl: exact host, https://.");
|
|
1185
1259
|
}
|
|
1186
1260
|
this._rateLimit = parseRateLimit(res.headers) ?? this._rateLimit;
|
|
1261
|
+
warnIfRetiring(this.model, res.headers);
|
|
1187
1262
|
if (!res.ok) {
|
|
1188
1263
|
const ra = retryAfterMs(res.headers.get("Retry-After"));
|
|
1189
|
-
let wait = RETRY_ALWAYS.has(res.status) || (RETRY_IF_IDEMPOTENT.has(res.status) &&
|
|
1264
|
+
let wait = RETRY_ALWAYS.has(res.status) || (RETRY_IF_IDEMPOTENT.has(res.status) && replaySafe)
|
|
1190
1265
|
? ra ?? this.backoffMs(attempt)
|
|
1191
1266
|
: undefined;
|
|
1192
1267
|
const retrying = wait !== undefined && !last && wait <= MAX_RETRY_WAIT_MS && this.fits(wait, deadlineAt);
|
|
@@ -1313,7 +1388,7 @@ export class Client {
|
|
|
1313
1388
|
clearTimeout(timer);
|
|
1314
1389
|
}
|
|
1315
1390
|
}
|
|
1316
|
-
async request(method, path, body, idempotencyKey, removes = false) {
|
|
1391
|
+
async request(method, path, body, idempotencyKey, removes = false, madeKey = false) {
|
|
1317
1392
|
const extraHeaders = this.idemHeader(idempotencyKey);
|
|
1318
1393
|
let payload;
|
|
1319
1394
|
try {
|
|
@@ -1341,7 +1416,7 @@ export class Client {
|
|
|
1341
1416
|
const got = data === null ? "null" : Array.isArray(data) ? "array" : typeof data;
|
|
1342
1417
|
throw new WosError(res.status, `expected a JSON object in the response, got ${got}`);
|
|
1343
1418
|
}
|
|
1344
|
-
if (res.headers.get("Idempotent-Replayed") === "true" && !("replayed" in data)) {
|
|
1419
|
+
if (!madeKey && res.headers.get("Idempotent-Replayed") === "true" && !("replayed" in data)) {
|
|
1345
1420
|
data.replayed = true;
|
|
1346
1421
|
}
|
|
1347
1422
|
return data;
|