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.
@@ -2,8 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WME = exports.Client = exports.ServerError = exports.RateLimitError = exports.GoneError = exports.ConflictError = exports.NotFoundError = exports.PermissionDeniedError = exports.PaymentRequiredError = exports.AuthenticationError = exports.BadRequestError = exports.APIConnectionError = exports.WosError = exports.CONTEXT_LIMIT_MAX = exports.CONTEXT_LIMIT_MIN = exports.SEARCH_LIMIT_MAX = exports.SEARCH_LIMIT_MIN = void 0;
4
4
  exports._resetStoreIdWarnings = _resetStoreIdWarnings;
5
+ exports._resetRetirementWarnings = _resetRetirementWarnings;
5
6
  exports._resetFilterWarnings = _resetFilterWarnings;
6
- const VERSION = "2.2.45";
7
+ const VERSION = "2.2.47";
7
8
  const RUNTIME = (() => {
8
9
  const p = globalThis?.process;
9
10
  return p?.version ? ` (node/${p.version}; ${p.platform}-${p.arch})` : "";
@@ -268,12 +269,7 @@ const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "
268
269
  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$/;
269
270
  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$/;
270
271
  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})$/;
271
- function retryAfterMs(v, now = Date.now()) {
272
- if (v == null)
273
- return undefined;
274
- const s = v.trim();
275
- if (/^\d+$/.test(s))
276
- return Math.min(Number(s), Number.MAX_SAFE_INTEGER / 1000) * 1000;
272
+ function httpDateMs(s, now = Date.now()) {
277
273
  let parts;
278
274
  let m;
279
275
  if ((m = IMF_FIXDATE.exec(s)))
@@ -293,7 +289,37 @@ function retryAfterMs(v, now = Date.now()) {
293
289
  const d = Number(day), h = Number(hh), mi = Number(mm), se = Number(ss);
294
290
  if (month < 0 || d < 1 || d > 31 || h > 23 || mi > 59 || se > 60)
295
291
  return undefined;
296
- return Math.max(0, Date.UTC(year, month, d, h, mi, se) - now);
292
+ return Date.UTC(year, month, d, h, mi, se);
293
+ }
294
+ function retryAfterMs(v, now = Date.now()) {
295
+ if (v == null)
296
+ return undefined;
297
+ const s = v.trim();
298
+ if (/^\d+$/.test(s))
299
+ return Math.min(Number(s), Number.MAX_SAFE_INTEGER / 1000) * 1000;
300
+ const at = httpDateMs(s, now);
301
+ return at === undefined ? undefined : Math.max(0, at - now);
302
+ }
303
+ const warnedRetirements = new Set();
304
+ const WARNED_RETIREMENTS_MAX = 64;
305
+ function warnIfRetiring(model, h) {
306
+ const v = h.get("Sunset");
307
+ if (!v)
308
+ return;
309
+ const at = httpDateMs(v.trim());
310
+ if (at === undefined)
311
+ return;
312
+ const day = new Date(at).toISOString().slice(0, 10);
313
+ const k = `${model}|${day}`;
314
+ if (warnedRetirements.has(k) || warnedRetirements.size >= WARNED_RETIREMENTS_MAX)
315
+ return;
316
+ warnedRetirements.add(k);
317
+ const which = model ? `model ${JSON.stringify(model)}` : "the model this client uses";
318
+ console.warn(`wontopos: ${which} retires on ${day} (UTC). From then on its calls answer 410, except ` +
319
+ "deleteStore. listModels() lists the models you can move to.");
320
+ }
321
+ function _resetRetirementWarnings() {
322
+ warnedRetirements.clear();
297
323
  }
298
324
  const BRIEF_BODY_MS = 1_000;
299
325
  class SlowBody extends Error {
@@ -318,6 +344,23 @@ function tryUrl(s, base) {
318
344
  return undefined;
319
345
  }
320
346
  }
347
+ function inBrowserPage() {
348
+ try {
349
+ const g = globalThis;
350
+ return typeof g.window !== "undefined" && typeof g.window.document !== "undefined" && typeof g.navigator !== "undefined";
351
+ }
352
+ catch {
353
+ return false;
354
+ }
355
+ }
356
+ function isServiceHost(host) {
357
+ const h = host.toLowerCase();
358
+ return h === "wontopos.com" || h.endsWith(".wontopos.com");
359
+ }
360
+ const BROWSER_REFUSAL = "wontopos: refusing to run in a browser page. The API key would be readable by anyone who " +
361
+ "opens the page, and with it every memory in its workspace. Call Wontopos from your server, " +
362
+ "or put a proxy of your own in front of it and point baseUrl there. For a local demo with a " +
363
+ "key you will revoke, pass dangerouslyAllowBrowser: true.";
321
364
  function pageHref() {
322
365
  try {
323
366
  const href = globalThis.location?.href;
@@ -394,6 +437,18 @@ const memoryFromGet = (r) => {
394
437
  return o.memory;
395
438
  return typeof o.id === "string" ? o : {};
396
439
  };
440
+ function recallShape(r) {
441
+ const o = asObj(r);
442
+ const st = asObj(o.short_term);
443
+ const lt = asObj(o.long_term);
444
+ const cx = asObj(o.context);
445
+ return {
446
+ ...o,
447
+ short_term: { ...st, turns: asRecords(st.turns) },
448
+ long_term: { ...lt, memories: asRecords(lt.memories) },
449
+ context: { ...cx, around_top_memory: asList(cx.around_top_memory).filter((x) => typeof x === "string") },
450
+ };
451
+ }
397
452
  function mergeResults(r) {
398
453
  const out = asRecords(r?.memories);
399
454
  const seen = new Set(out.map((m) => m.id).filter((id) => typeof id === "string"));
@@ -569,6 +624,15 @@ function writeKey(opts, takesImage = false) {
569
624
  (takesImage ? "add takes idempotencyKey and image." : "This call takes idempotencyKey only."));
570
625
  return opts.idempotencyKey;
571
626
  }
627
+ function newIdempotencyKey() {
628
+ const c = globalThis.crypto;
629
+ if (typeof c?.randomUUID === "function")
630
+ return `sdk-${String(c.randomUUID()).replace(/-/g, "")}`;
631
+ let hex = "";
632
+ for (let i = 0; i < 4; i++)
633
+ hex += Math.floor(Math.random() * 0x100000000).toString(16).padStart(8, "0");
634
+ return `sdk-${hex}`;
635
+ }
572
636
  function warnOnUnknownFilters(filters) {
573
637
  warnOnceUnknownKeys(filters, KNOWN_FILTER_KEYS, warnedFilterKeys, (k) => `wontopos: unknown search filter ${JSON.stringify(k)} — the API drops keys it does not know, ` +
574
638
  `so this filter has NO effect and the search is wider than you think. ` +
@@ -597,6 +661,7 @@ class Client {
597
661
  userinfo;
598
662
  pageRelative;
599
663
  customFetch;
664
+ allowBrowser;
600
665
  defaultUser;
601
666
  _rateLimit = null;
602
667
  get rateLimit() {
@@ -630,6 +695,10 @@ class Client {
630
695
  throw new Error(notAUrl(trimmed));
631
696
  this.pageRelative = !url;
632
697
  this.userinfo = Boolean(parsed.username || parsed.password);
698
+ this.allowBrowser = opts.dangerouslyAllowBrowser === true;
699
+ if (!this.allowBrowser && inBrowserPage() && isServiceHost(parsed.hostname)) {
700
+ throw new Error(BROWSER_REFUSAL);
701
+ }
633
702
  this.timeoutMs = checkMs(opts.timeoutMs) ?? 30_000;
634
703
  this.model = opts.model ?? DEFAULT_MODEL;
635
704
  if (this.model && !MODEL_RE.test(this.model)) {
@@ -698,6 +767,7 @@ class Client {
698
767
  fetch: this.fetchImpl,
699
768
  deadlineMs: this.deadlineMs,
700
769
  signal: this.signal,
770
+ dangerouslyAllowBrowser: this.allowBrowser,
701
771
  ...overrides,
702
772
  });
703
773
  }
@@ -729,22 +799,22 @@ class Client {
729
799
  if (opts.image !== undefined)
730
800
  body.image = normalizeImage(opts.image);
731
801
  warnOnUnknownMetadata(metadata);
732
- return this.post("/api/v1/memory/store", body, writeKey(opts, true));
802
+ return this.write("/api/v1/memory/store", body, writeKey(opts, true));
733
803
  }
734
804
  async store(content, userId, metadata = {}, opts = {}) {
735
805
  return this.add(content, userId, metadata, opts);
736
806
  }
737
807
  async addTurn(userMsg, assistantMsg, userId, opts = {}) {
738
- return this.post("/api/v1/memory/store-turn", { user_id: this.uid(userId), user_msg: userMsg, assistant_msg: assistantMsg }, writeKey(opts));
808
+ return this.write("/api/v1/memory/store-turn", { user_id: this.uid(userId), user_msg: userMsg, assistant_msg: assistantMsg }, writeKey(opts));
739
809
  }
740
810
  async addBulk(content, userId, category = "general", timestamp, opts = {}) {
741
811
  const body = { user_id: this.uid(userId), content, category: category ?? "general" };
742
812
  if (timestamp)
743
813
  body.timestamp = timestamp;
744
- return this.post("/api/v1/memory/bulk-store", body, writeKey(opts));
814
+ return this.write("/api/v1/memory/bulk-store", body, writeKey(opts));
745
815
  }
746
816
  async update(oldMemoryId, newContent, userId, opts = {}) {
747
- return this.post("/api/v1/memory/supersede", { user_id: this.uid(userId), old_memory_id: oldMemoryId, new_content: newContent }, writeKey(opts));
817
+ return this.write("/api/v1/memory/supersede", { user_id: this.uid(userId), old_memory_id: oldMemoryId, new_content: newContent }, writeKey(opts));
748
818
  }
749
819
  async search(query, userId, limit = 10, opts = {}) {
750
820
  checkSearchOpts(opts);
@@ -789,7 +859,7 @@ class Client {
789
859
  checkContextLimit(opts.context_limit);
790
860
  body.context_limit = opts.context_limit;
791
861
  }
792
- return this.post("/api/v1/memory/recall", withForm(body, opts.form, opts.tz));
862
+ return recallShape(await this.post("/api/v1/memory/recall", withForm(body, opts.form, opts.tz)));
793
863
  }
794
864
  async engram(name, query, userId, opts = {}) {
795
865
  checkOpts(opts, KNOWN_ENGRAM_KEYS, "engram");
@@ -994,8 +1064,12 @@ class Client {
994
1064
  warnIfStoreIdCollapses(userId);
995
1065
  return this.post("/api/v1/memory/forget", { user_id: userId });
996
1066
  }
997
- post(path, body, idempotencyKey) {
998
- return this.request("POST", path, body, idempotencyKey);
1067
+ post(path, body) {
1068
+ return this.request("POST", path, body);
1069
+ }
1070
+ write(path, body, idempotencyKey) {
1071
+ const made = idempotencyKey == null;
1072
+ return this.request("POST", path, body, made ? newIdempotencyKey() : idempotencyKey, false, made);
999
1073
  }
1000
1074
  remove(path, body) {
1001
1075
  return this.request("DELETE", path, body, undefined, true);
@@ -1113,8 +1187,9 @@ class Client {
1113
1187
  }
1114
1188
  const attempts = this.maxRetries + 1;
1115
1189
  const upper = method.toUpperCase();
1116
- const idempotent = IDEMPOTENT_METHODS.has(upper);
1117
1190
  const logPath = path.split("?")[0];
1191
+ const idempotent = IDEMPOTENT_METHODS.has(upper) || reads || (upper === "POST" && READ_POSTS.has(logPath));
1192
+ const replaySafe = idempotent || extraHeaders?.["Idempotency-Key"] !== undefined;
1118
1193
  const deadlineAt = this.deadlineAt();
1119
1194
  const headers = extraHeaders ? { ...this.headers, ...extraHeaders } : this.headers;
1120
1195
  let ambiguous = false;
@@ -1135,7 +1210,7 @@ class Client {
1135
1210
  };
1136
1211
  const cutShort = () => {
1137
1212
  const why = att.why();
1138
- if (why === "deadline" && (idempotent || reads || (upper === "POST" && READ_POSTS.has(logPath))) && previous) {
1213
+ if (why === "deadline" && idempotent && previous) {
1139
1214
  return previous;
1140
1215
  }
1141
1216
  return new APIConnectionError(0, this.abortMessage(why));
@@ -1156,7 +1231,7 @@ class Client {
1156
1231
  if (timedOut)
1157
1232
  throw cutShort();
1158
1233
  const connect = isConnectFailure(e);
1159
- if ((idempotent || connect) && !last) {
1234
+ if ((replaySafe || connect) && !last) {
1160
1235
  if (!connect)
1161
1236
  ambiguous = true;
1162
1237
  await pauseWithoutAnswer(e?.code ?? e?.name ?? "network error");
@@ -1173,7 +1248,7 @@ class Client {
1173
1248
  throw cutShort();
1174
1249
  if (e instanceof WosError)
1175
1250
  throw e;
1176
- if (idempotent && !last)
1251
+ if (replaySafe && !last)
1177
1252
  return null;
1178
1253
  throw this.networkError(e, path);
1179
1254
  }
@@ -1200,9 +1275,10 @@ class Client {
1200
1275
  throw new WosError(res.status, "unexpected redirect — refused (the API key never follows a redirect). Check baseUrl: exact host, https://.");
1201
1276
  }
1202
1277
  this._rateLimit = parseRateLimit(res.headers) ?? this._rateLimit;
1278
+ warnIfRetiring(this.model, res.headers);
1203
1279
  if (!res.ok) {
1204
1280
  const ra = retryAfterMs(res.headers.get("Retry-After"));
1205
- let wait = RETRY_ALWAYS.has(res.status) || (RETRY_IF_IDEMPOTENT.has(res.status) && idempotent)
1281
+ let wait = RETRY_ALWAYS.has(res.status) || (RETRY_IF_IDEMPOTENT.has(res.status) && replaySafe)
1206
1282
  ? ra ?? this.backoffMs(attempt)
1207
1283
  : undefined;
1208
1284
  const retrying = wait !== undefined && !last && wait <= MAX_RETRY_WAIT_MS && this.fits(wait, deadlineAt);
@@ -1329,7 +1405,7 @@ class Client {
1329
1405
  clearTimeout(timer);
1330
1406
  }
1331
1407
  }
1332
- async request(method, path, body, idempotencyKey, removes = false) {
1408
+ async request(method, path, body, idempotencyKey, removes = false, madeKey = false) {
1333
1409
  const extraHeaders = this.idemHeader(idempotencyKey);
1334
1410
  let payload;
1335
1411
  try {
@@ -1357,7 +1433,7 @@ class Client {
1357
1433
  const got = data === null ? "null" : Array.isArray(data) ? "array" : typeof data;
1358
1434
  throw new WosError(res.status, `expected a JSON object in the response, got ${got}`);
1359
1435
  }
1360
- if (res.headers.get("Idempotent-Replayed") === "true" && !("replayed" in data)) {
1436
+ if (!madeKey && res.headers.get("Idempotent-Replayed") === "true" && !("replayed" in data)) {
1361
1437
  data.replayed = true;
1362
1438
  }
1363
1439
  return data;