@maximem/synap-js-sdk 0.5.7 → 0.5.9

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.
Files changed (47) hide show
  1. package/README.md +5 -1
  2. package/dist/anticipation-cache-LPNKMRBH.cjs +20 -0
  3. package/dist/anticipation-cache-LPNKMRBH.cjs.map +1 -0
  4. package/dist/anticipation-cache-RWKF55J4.js +20 -0
  5. package/dist/{chunk-FXGLT6AT.js → chunk-I7TWR4S6.js} +59 -3
  6. package/dist/chunk-I7TWR4S6.js.map +1 -0
  7. package/dist/{chunk-T6U5PVAD.cjs → chunk-ICUFS3B5.cjs} +449 -12
  8. package/dist/chunk-ICUFS3B5.cjs.map +1 -0
  9. package/dist/{chunk-ODVOQOSO.js → chunk-R72YL6Q4.js} +445 -8
  10. package/dist/chunk-R72YL6Q4.js.map +1 -0
  11. package/dist/{chunk-HCULAVJC.cjs → chunk-RE4DDIWO.cjs} +66 -10
  12. package/dist/chunk-RE4DDIWO.cjs.map +1 -0
  13. package/dist/chunk-T372OL3Q.cjs.map +1 -1
  14. package/dist/chunk-ZKOJDUN4.cjs.map +1 -1
  15. package/dist/env-55ULHNJZ.cjs.map +1 -1
  16. package/dist/{errors-Bl3vY1eb.d.cts → errors-Bdayqlzy.d.cts} +88 -2
  17. package/dist/{errors-Bl3vY1eb.d.ts → errors-Bdayqlzy.d.ts} +88 -2
  18. package/dist/grpc/index.cjs +4 -4
  19. package/dist/grpc/index.cjs.map +1 -1
  20. package/dist/grpc/index.d.cts +8 -3
  21. package/dist/grpc/index.d.ts +8 -3
  22. package/dist/grpc/index.js +3 -3
  23. package/dist/index.cjs +1304 -116
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +281 -5
  26. package/dist/index.d.ts +281 -5
  27. package/dist/index.js +1257 -69
  28. package/dist/index.js.map +1 -1
  29. package/dist/onnx-encoder-4RSL6ID4.cjs +103 -0
  30. package/dist/onnx-encoder-4RSL6ID4.cjs.map +1 -0
  31. package/dist/onnx-encoder-NP35KH4H.js +103 -0
  32. package/dist/onnx-encoder-NP35KH4H.js.map +1 -0
  33. package/dist/{stream-client-VWKINE47.js → stream-client-3WHXGW4U.js} +2 -2
  34. package/dist/stream-client-L6OQPZN7.cjs +16 -0
  35. package/dist/stream-client-L6OQPZN7.cjs.map +1 -0
  36. package/package.json +4 -2
  37. package/dist/anticipation-cache-LRE3TLK4.cjs +0 -14
  38. package/dist/anticipation-cache-LRE3TLK4.cjs.map +0 -1
  39. package/dist/anticipation-cache-WLU4NMD5.js +0 -14
  40. package/dist/chunk-FXGLT6AT.js.map +0 -1
  41. package/dist/chunk-HCULAVJC.cjs.map +0 -1
  42. package/dist/chunk-ODVOQOSO.js.map +0 -1
  43. package/dist/chunk-T6U5PVAD.cjs.map +0 -1
  44. package/dist/stream-client-N46JBT5A.cjs +0 -16
  45. package/dist/stream-client-N46JBT5A.cjs.map +0 -1
  46. /package/dist/{anticipation-cache-WLU4NMD5.js.map → anticipation-cache-RWKF55J4.js.map} +0 -0
  47. /package/dist/{stream-client-VWKINE47.js.map → stream-client-3WHXGW4U.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4;
2
2
 
3
3
 
4
4
 
@@ -8,7 +8,9 @@
8
8
 
9
9
 
10
10
 
11
- var _chunkT6U5PVADcjs = require('./chunk-T6U5PVAD.cjs');
11
+
12
+
13
+ var _chunkICUFS3B5cjs = require('./chunk-ICUFS3B5.cjs');
12
14
 
13
15
 
14
16
  var _chunkZKOJDUN4cjs = require('./chunk-ZKOJDUN4.cjs');
@@ -55,6 +57,7 @@ var ENDPOINTS = {
55
57
  context_fetch_client: { method: "POST", path: "/v1/context/client/fetch", idempotent: true },
56
58
  context_fetch_conversation: { method: "POST", path: "/v1/context/conversation/fetch", idempotent: true },
57
59
  context_for_prompt: { method: "GET", path: "/v1/conversations/{conversation_id}/context-for-prompt", idempotent: true },
60
+ conversation_turns: { method: "GET", path: "/v1/conversations/{conversation_id}/messages", idempotent: true },
58
61
  conversations_ingest: { method: "POST", path: "/v1/conversations/ingest", idempotent: false },
59
62
  conversations_messages: { method: "POST", path: "/v1/conversations/messages", idempotent: false },
60
63
  conversations_messages_batch: { method: "POST", path: "/v1/conversations/messages/batch", idempotent: false },
@@ -132,7 +135,7 @@ var HEARTBEAT_INTERVAL_MS = 24e4;
132
135
  var MAX_CONNECTIONS = 20;
133
136
  var cached;
134
137
  function supportsDispatcher() {
135
- return typeof process !== "undefined" && _optionalChain([process, 'access', _ => _.versions, 'optionalAccess', _2 => _2.node]) !== void 0 && // Edge builds of Next expose a partial `process` shim, but no real Node.
138
+ return typeof process !== "undefined" && _optionalChain([process, 'access', _2 => _2.versions, 'optionalAccess', _3 => _3.node]) !== void 0 && // Edge builds of Next expose a partial `process` shim, but no real Node.
136
139
  typeof globalThis.EdgeRuntime === "undefined";
137
140
  }
138
141
  async function getKeepAliveDispatcher(options = {}) {
@@ -173,7 +176,7 @@ var Heartbeat = (_class = class {
173
176
  void Promise.resolve(this.ping()).catch(() => {
174
177
  });
175
178
  }, this.intervalMs);
176
- _optionalChain([this, 'access', _3 => _3.timer, 'access', _4 => _4.unref, 'optionalCall', _5 => _5()]);
179
+ _optionalChain([this, 'access', _4 => _4.timer, 'access', _5 => _5.unref, 'optionalCall', _6 => _6()]);
177
180
  }
178
181
  stop() {
179
182
  if (this.timer !== null) {
@@ -226,7 +229,7 @@ var HttpTransport = (_class2 = class {
226
229
  this.keepAliveOptions = _nullishCoalesce(options.keepAlive, () => ( {}));
227
230
  this.heartbeat = new Heartbeat(
228
231
  () => this.pingHealth(),
229
- _nullishCoalesce(_optionalChain([options, 'access', _6 => _6.keepAlive, 'optionalAccess', _7 => _7.heartbeatIntervalMs]), () => ( HEARTBEAT_INTERVAL_MS))
232
+ _nullishCoalesce(_optionalChain([options, 'access', _7 => _7.keepAlive, 'optionalAccess', _8 => _8.heartbeatIntervalMs]), () => ( HEARTBEAT_INTERVAL_MS))
230
233
  );
231
234
  }
232
235
  /** Start keeping the connection warm. Opt-in: it is pointless in serverless. */
@@ -315,7 +318,7 @@ var HttpTransport = (_class2 = class {
315
318
  await sleep(decision.delaySeconds * 1e3, options.signal);
316
319
  }
317
320
  }
318
- throw _nullishCoalesce(_optionalChain([last, 'optionalAccess', _8 => _8.error]), () => ( new (0, _chunkT372OL3Qcjs.SynapError)("Request failed with no recorded error")));
321
+ throw _nullishCoalesce(_optionalChain([last, 'optionalAccess', _9 => _9.error]), () => ( new (0, _chunkT372OL3Qcjs.SynapError)("Request failed with no recorded error")));
319
322
  }
320
323
  async attempt(name, options, correlationId) {
321
324
  const spec = ENDPOINTS[name];
@@ -394,17 +397,17 @@ function isOutcomeUnknown(error) {
394
397
  return true;
395
398
  }
396
399
  function isConnectionRefusal(error) {
397
- const code = _nullishCoalesce(_optionalChain([error, 'optionalAccess', _9 => _9.cause, 'optionalAccess', _10 => _10.code]), () => ( _optionalChain([error, 'optionalAccess', _11 => _11.code])));
400
+ const code = _nullishCoalesce(_optionalChain([error, 'optionalAccess', _10 => _10.cause, 'optionalAccess', _11 => _11.code]), () => ( _optionalChain([error, 'optionalAccess', _12 => _12.code])));
398
401
  return code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "EAI_AGAIN" || code === "ECONNRESET" || code === "EHOSTUNREACH";
399
402
  }
400
403
  function toTransportError(error, correlationId) {
401
404
  if (error instanceof _chunkT372OL3Qcjs.SynapError) return error;
402
- const name = _optionalChain([error, 'optionalAccess', _12 => _12.name]);
405
+ const name = _optionalChain([error, 'optionalAccess', _13 => _13.name]);
403
406
  if (name === "AbortError" || name === "TimeoutError") {
404
407
  return new (0, _chunkT372OL3Qcjs.NetworkTimeoutError)("Request timed out", { correlationId, cause: error });
405
408
  }
406
409
  return new (0, _chunkT372OL3Qcjs.ServiceUnavailableError)(
407
- `Network request failed: ${_nullishCoalesce(_optionalChain([error, 'optionalAccess', _13 => _13.message]), () => ( String(error)))}`,
410
+ `Network request failed: ${_nullishCoalesce(_optionalChain([error, 'optionalAccess', _14 => _14.message]), () => ( String(error)))}`,
408
411
  { correlationId, cause: error }
409
412
  );
410
413
  }
@@ -485,11 +488,11 @@ function stringOrNull(v) {
485
488
  }
486
489
  function withTimeout(ms, external) {
487
490
  const controller = new AbortController();
488
- const onExternalAbort = () => controller.abort(_optionalChain([external, 'optionalAccess', _14 => _14.reason]));
491
+ const onExternalAbort = () => controller.abort(_optionalChain([external, 'optionalAccess', _15 => _15.reason]));
489
492
  const timer = setTimeout(() => {
490
493
  controller.abort(new (0, _chunkT372OL3Qcjs.NetworkTimeoutError)(`Timed out after ${ms}ms`));
491
494
  }, ms);
492
- _optionalChain([timer, 'access', _15 => _15.unref, 'optionalCall', _16 => _16()]);
495
+ _optionalChain([timer, 'access', _16 => _16.unref, 'optionalCall', _17 => _17()]);
493
496
  if (external) {
494
497
  if (external.aborted) onExternalAbort();
495
498
  else external.addEventListener("abort", onExternalAbort, { once: true });
@@ -498,7 +501,7 @@ function withTimeout(ms, external) {
498
501
  signal: controller.signal,
499
502
  dispose() {
500
503
  clearTimeout(timer);
501
- _optionalChain([external, 'optionalAccess', _17 => _17.removeEventListener, 'call', _18 => _18("abort", onExternalAbort)]);
504
+ _optionalChain([external, 'optionalAccess', _18 => _18.removeEventListener, 'call', _19 => _19("abort", onExternalAbort)]);
502
505
  }
503
506
  };
504
507
  }
@@ -506,17 +509,97 @@ function sleep(ms, signal) {
506
509
  if (ms <= 0) return Promise.resolve();
507
510
  return new Promise((resolve, reject) => {
508
511
  const timer = setTimeout(() => {
509
- _optionalChain([signal, 'optionalAccess', _19 => _19.removeEventListener, 'call', _20 => _20("abort", onAbort)]);
512
+ _optionalChain([signal, 'optionalAccess', _20 => _20.removeEventListener, 'call', _21 => _21("abort", onAbort)]);
510
513
  resolve();
511
514
  }, ms);
512
515
  function onAbort() {
513
516
  clearTimeout(timer);
514
- reject(_nullishCoalesce(_optionalChain([signal, 'optionalAccess', _21 => _21.reason]), () => ( new Error("Aborted"))));
517
+ reject(_nullishCoalesce(_optionalChain([signal, 'optionalAccess', _22 => _22.reason]), () => ( new Error("Aborted"))));
515
518
  }
516
- _optionalChain([signal, 'optionalAccess', _22 => _22.addEventListener, 'call', _23 => _23("abort", onAbort, { once: true })]);
519
+ _optionalChain([signal, 'optionalAccess', _23 => _23.addEventListener, 'call', _24 => _24("abort", onAbort, { once: true })]);
517
520
  });
518
521
  }
519
522
 
523
+ // src/context/ranked.ts
524
+ var RESPONSE_SHAPES = ["by_type", "ranked"];
525
+ var MEMORY_TYPES = [
526
+ "facts",
527
+ "preferences",
528
+ "episodes",
529
+ "emotions",
530
+ "temporal_events"
531
+ ];
532
+ var EXCERPT_CATEGORY = "excerpts";
533
+ function wireRankScore(item) {
534
+ const md = item.metadata;
535
+ let raw = md !== null && typeof md === "object" ? md["rank_score"] : void 0;
536
+ if (raw === void 0 || raw === null) raw = item["rank_score"];
537
+ return typeof raw === "number" && Number.isFinite(raw) ? raw : null;
538
+ }
539
+ function wireCategory(item, fallback = "") {
540
+ if (item.source === "chunks") return EXCERPT_CATEGORY;
541
+ const category = item["category"];
542
+ if (typeof category === "string" && category !== "") return category;
543
+ const contextType = item["context_type"];
544
+ if (typeof contextType === "string" && contextType !== "") return contextType;
545
+ return fallback;
546
+ }
547
+ function byRankScore(a, b) {
548
+ const sa = wireRankScore(a);
549
+ const sb = wireRankScore(b);
550
+ if (sa === null && sb === null) return 0;
551
+ if (sa === null) return 1;
552
+ if (sb === null) return -1;
553
+ return sb - sa;
554
+ }
555
+ function rankItems(byType) {
556
+ const flat = [];
557
+ for (const type of MEMORY_TYPES) {
558
+ const list = byType[type];
559
+ if (!Array.isArray(list)) continue;
560
+ for (const it of list) {
561
+ if (it === null || typeof it !== "object") continue;
562
+ flat.push({
563
+ ...it,
564
+ category: wireCategory(it, type),
565
+ id: it.id || it.item_id || ""
566
+ });
567
+ }
568
+ }
569
+ flat.sort(byRankScore);
570
+ return flat.map((it, n2) => ({ ...it, rank: n2 + 1 }));
571
+ }
572
+ function applyResponseShape(raw, shape = "by_type") {
573
+ const wire = raw["items"];
574
+ if (shape !== "ranked" && !Array.isArray(wire)) return raw;
575
+ const source = Array.isArray(wire) ? wire : rankItems(raw);
576
+ const items = source.map((it) => ({
577
+ ...it,
578
+ id: it.id || it.item_id || "",
579
+ category: wireCategory(it)
580
+ }));
581
+ const out = { ...raw, items };
582
+ for (const type of MEMORY_TYPES) {
583
+ out[type] = items.filter((it) => it["category"] === type);
584
+ }
585
+ out["excerpts"] = items.filter((it) => it["category"] === EXCERPT_CATEGORY);
586
+ return out;
587
+ }
588
+ var RANKED_LABELS = {
589
+ facts: "fact",
590
+ preferences: "preference",
591
+ episodes: "episode",
592
+ emotions: "emotion",
593
+ temporal_events: "temporal event",
594
+ [EXCERPT_CATEGORY]: "excerpt"
595
+ };
596
+ function rankedLabel(category) {
597
+ return _nullishCoalesce(RANKED_LABELS[category], () => ( (category !== "" ? category : "item")));
598
+ }
599
+ function sortByRankScore(items) {
600
+ return [...items].sort(byRankScore);
601
+ }
602
+
520
603
  // src/context/fetch.ts
521
604
  var ENDPOINT_BY_SCOPE = {
522
605
  user: "context_fetch_user",
@@ -539,6 +622,7 @@ function buildFetchRequest(scope, options) {
539
622
  const precisionLevel = _nullishCoalesce(opt(options, "precision_level", "precisionLevel"), () => ( "high"));
540
623
  const queryDate = opt(options, "query_date", "queryDate");
541
624
  const perQuery = opt(options, "max_results_per_query", "maxResultsPerQuery");
625
+ const responseShape = responseShapeOf(options);
542
626
  const mode = _nullishCoalesce(options.mode, () => ( "fast"));
543
627
  if (searchQuery !== void 0 && !Array.isArray(searchQuery)) {
544
628
  throw new (0, _chunkT372OL3Qcjs.InvalidInputError)("search_query must be an array when provided");
@@ -556,6 +640,7 @@ function buildFetchRequest(scope, options) {
556
640
  if (precisionLevel !== "high") body["precision_level"] = precisionLevel;
557
641
  if (queryDate) body["query_date"] = queryDate;
558
642
  if (perQuery) body["max_results_per_query"] = perQuery;
643
+ if (responseShape !== "by_type") body["response_shape"] = responseShape;
559
644
  if (options.include_conversation_context === false) {
560
645
  body["include_conversation_context"] = false;
561
646
  }
@@ -603,22 +688,49 @@ function buildFetchRequest(scope, options) {
603
688
  return { endpoint: ENDPOINT_BY_SCOPE[scope], body };
604
689
  }
605
690
  var VALID_CONTEXT_MODES = ["in-conversation", "conversation-summary"];
691
+ function responseShapeOf(options) {
692
+ const shape = _nullishCoalesce(opt(options, "response_shape", "responseShape"), () => ( "by_type"));
693
+ if (!RESPONSE_SHAPES.includes(shape)) {
694
+ throw new (0, _chunkT372OL3Qcjs.InvalidInputError)(
695
+ `Invalid response_shape '${shape}'. Must be one of: ${RESPONSE_SHAPES.join(", ")}`
696
+ );
697
+ }
698
+ return shape;
699
+ }
606
700
  async function fetchContext(transport, scope, options = {}, hooks = {}) {
607
701
  const startedAt = Date.now();
608
702
  transport.checkCustomerId(
609
703
  opt(options, "customer_id", "customerId"),
610
704
  `${scope}.context.fetch`
611
705
  );
612
- const anticipated = _nullishCoalesce(_optionalChain([hooks, 'access', _24 => _24.beforeFetch, 'optionalCall', _25 => _25(scope, options)]), () => ( null));
706
+ const responseShape = responseShapeOf(options);
707
+ const network = async () => {
708
+ const { endpoint, body } = buildFetchRequest(scope, options);
709
+ const result = await transport.request(endpoint, { body });
710
+ return applyResponseShape(_nullishCoalesce(_optionalChain([result, 'optionalAccess', _25 => _25["context"]]), () => ( {})), responseShape);
711
+ };
712
+ const anticipated = await _asyncNullishCoalesce(await _optionalChain([hooks, 'access', _26 => _26.beforeFetch, 'optionalCall', _27 => _27(scope, options)]), async () => ( null));
713
+ if (anticipated !== null && anticipated.borderline) {
714
+ const pending = network();
715
+ pending.catch(() => void 0);
716
+ let timer;
717
+ const late = new Promise((resolve) => {
718
+ timer = setTimeout(() => resolve(null), _nullishCoalesce(anticipated.raceDeadlineMs, () => ( 250)));
719
+ });
720
+ const won = await Promise.race([pending.then((r) => r, () => null), late]);
721
+ if (timer !== void 0) clearTimeout(timer);
722
+ if (won !== null) {
723
+ _optionalChain([hooks, 'access', _28 => _28.onAssembled, 'optionalCall', _29 => _29(scope, options, won, startedAt)]);
724
+ return won;
725
+ }
726
+ }
613
727
  if (anticipated !== null) {
614
- _optionalChain([hooks, 'access', _26 => _26.onServedFromCache, 'optionalCall', _27 => _27(scope, options, anticipated)]);
615
- _optionalChain([hooks, 'access', _28 => _28.onAssembled, 'optionalCall', _29 => _29(scope, options, anticipated.response, startedAt)]);
616
- return anticipated.response;
617
- }
618
- const { endpoint, body } = buildFetchRequest(scope, options);
619
- const result = await transport.request(endpoint, { body });
620
- const response = _nullishCoalesce(_optionalChain([result, 'optionalAccess', _30 => _30["context"]]), () => ( {}));
621
- _optionalChain([hooks, 'access', _31 => _31.onAssembled, 'optionalCall', _32 => _32(scope, options, response, startedAt)]);
728
+ _optionalChain([hooks, 'access', _30 => _30.onServedFromCache, 'optionalCall', _31 => _31(scope, options, anticipated)]);
729
+ _optionalChain([hooks, 'access', _32 => _32.onAssembled, 'optionalCall', _33 => _33(scope, options, anticipated.response, startedAt)]);
730
+ return applyResponseShape(anticipated.response, responseShape);
731
+ }
732
+ const response = await network();
733
+ _optionalChain([hooks, 'access', _34 => _34.onAssembled, 'optionalCall', _35 => _35(scope, options, response, startedAt)]);
622
734
  return response;
623
735
  }
624
736
 
@@ -638,6 +750,8 @@ function matchedQueries(item = {}) {
638
750
  function normalizeTemporalFields(item = {}) {
639
751
  return {
640
752
  eventDate: pick(item["eventDate"], item.event_date, null),
753
+ eventEnd: pick(item["eventEnd"], item.event_end, null),
754
+ eventPrecision: pick(item["eventPrecision"], item.event_precision, null),
641
755
  saidAt: pick(item["saidAt"], item.said_at, null),
642
756
  validUntil: pick(item["validUntil"], item.valid_until, null),
643
757
  temporalCategory: pick(item["temporalCategory"], item.temporal_category, null),
@@ -698,6 +812,8 @@ function normalizeTemporalEvent(item = {}) {
698
812
  id: item.id || "",
699
813
  content: item.content || "",
700
814
  eventDate: pick(item["eventDate"], item.event_date, null),
815
+ eventEnd: pick(item["eventEnd"], item.event_end, null),
816
+ eventPrecision: pick(item["eventPrecision"], item.event_precision, null),
701
817
  saidAt: pick(item["saidAt"], item.said_at, null),
702
818
  validUntil: pick(item["validUntil"], item.valid_until, null),
703
819
  temporalCategory: pick(item["temporalCategory"], item.temporal_category, ""),
@@ -734,6 +850,17 @@ function normalizeConversationContext(value) {
734
850
  conversationId: pick(value["conversationId"], value.conversation_id, null)
735
851
  };
736
852
  }
853
+ function normalizeRetrievalHealth(raw) {
854
+ if (!raw || typeof raw !== "object") return null;
855
+ const r = raw;
856
+ const warnings = r["warnings"];
857
+ return {
858
+ degraded: r["degraded"] === void 0 ? true : Boolean(r["degraded"]),
859
+ level: _nullishCoalesce(r["level"], () => ( null)),
860
+ code: _nullishCoalesce(r["code"], () => ( null)),
861
+ warnings: Array.isArray(warnings) ? warnings.map((w) => String(w)) : []
862
+ };
863
+ }
737
864
  function normalizeContextResponse(result = {}) {
738
865
  const context = _nullishCoalesce(result["context"], () => ( result));
739
866
  return {
@@ -750,7 +877,15 @@ function normalizeContextResponse(result = {}) {
750
877
  )
751
878
  ),
752
879
  metadata: normalizeContextMetadata(obj(context.metadata)),
753
- rawResponse: pick(context["rawResponse"], context["raw_response"], {})
880
+ // `retrieval` is a SIBLING of `context` in the body, so it is read off the
881
+ // envelope first and only then off a bare context. Reading it from
882
+ // `context` alone is how the Python SDK dropped it.
883
+ retrieval: normalizeRetrievalHealth(
884
+ pick(result["retrieval"], context["retrieval"], null)
885
+ ),
886
+ rawResponse: pick(context["rawResponse"], context["raw_response"], {}),
887
+ // Only for a ranked fetch, so a by-type result has exactly the keys it had.
888
+ ...Array.isArray(context.items) ? { items: context.items, excerpts: Array.isArray(context.excerpts) ? context.excerpts : [] } : {}
754
889
  };
755
890
  }
756
891
 
@@ -998,7 +1133,7 @@ function createMemoriesInterface(transport, onForgotten) {
998
1133
  transport.checkCustomerId(_nullishCoalesce(o2["customer_id"], () => ( o2["customerId"])), "memories.forget");
999
1134
  const body = buildForgetBody(options);
1000
1135
  const result = await transport.request("memories_forget", { body });
1001
- const removed = !!_optionalChain([result, 'optionalAccess', _33 => _33.deleted_ids, 'optionalAccess', _34 => _34.length]) || _optionalChain([result, 'optionalAccess', _35 => _35.status]) === "deleted" || _optionalChain([result, 'optionalAccess', _36 => _36.status]) === "partial";
1136
+ const removed = !!_optionalChain([result, 'optionalAccess', _36 => _36.deleted_ids, 'optionalAccess', _37 => _37.length]) || _optionalChain([result, 'optionalAccess', _38 => _38.status]) === "deleted" || _optionalChain([result, 'optionalAccess', _39 => _39.status]) === "partial";
1002
1137
  if (removed && onForgotten) {
1003
1138
  try {
1004
1139
  onForgotten({
@@ -1038,14 +1173,14 @@ function sleep2(ms, signal) {
1038
1173
  if (ms <= 0) return Promise.resolve();
1039
1174
  return new Promise((resolve, reject) => {
1040
1175
  const timer = setTimeout(() => {
1041
- _optionalChain([signal, 'optionalAccess', _37 => _37.removeEventListener, 'call', _38 => _38("abort", onAbort)]);
1176
+ _optionalChain([signal, 'optionalAccess', _40 => _40.removeEventListener, 'call', _41 => _41("abort", onAbort)]);
1042
1177
  resolve();
1043
1178
  }, ms);
1044
1179
  function onAbort() {
1045
1180
  clearTimeout(timer);
1046
- reject(_nullishCoalesce(_optionalChain([signal, 'optionalAccess', _39 => _39.reason]), () => ( new Error("Aborted"))));
1181
+ reject(_nullishCoalesce(_optionalChain([signal, 'optionalAccess', _42 => _42.reason]), () => ( new Error("Aborted"))));
1047
1182
  }
1048
- _optionalChain([signal, 'optionalAccess', _40 => _40.addEventListener, 'call', _41 => _41("abort", onAbort, { once: true })]);
1183
+ _optionalChain([signal, 'optionalAccess', _43 => _43.addEventListener, 'call', _44 => _44("abort", onAbort, { once: true })]);
1049
1184
  });
1050
1185
  }
1051
1186
 
@@ -1065,6 +1200,27 @@ function validateInstanceId(instanceId) {
1065
1200
  }
1066
1201
  }
1067
1202
 
1203
+ // src/agent-model.ts
1204
+ var AGENT_MODEL_KEY = "agent_model";
1205
+ var AGENT_CONTEXT_WINDOW_KEY = "agent_context_window";
1206
+ function agentModelOf(options) {
1207
+ const o2 = _nullishCoalesce(options, () => ( {}));
1208
+ const window = _nullishCoalesce(o2["context_window"], () => ( o2["contextWindow"]));
1209
+ return {
1210
+ model: typeof o2["model"] === "string" ? o2["model"] : void 0,
1211
+ contextWindow: typeof window === "number" ? window : void 0
1212
+ };
1213
+ }
1214
+ function withAgentModel(metadata, perCall, defaults, asStrings = false) {
1215
+ const model = perCall.model || defaults.model;
1216
+ const window = perCall.contextWindow || defaults.contextWindow;
1217
+ if (!model && !window) return metadata;
1218
+ const out = { ..._nullishCoalesce(metadata, () => ( {})) };
1219
+ if (model) out[AGENT_MODEL_KEY] = String(model);
1220
+ if (window) out[AGENT_CONTEXT_WINDOW_KEY] = asStrings ? String(Math.trunc(window)) : Math.trunc(window);
1221
+ return out;
1222
+ }
1223
+
1068
1224
  // src/conversation/interface.ts
1069
1225
  var COMPACTION_LEVELS = [
1070
1226
  "low",
@@ -1193,10 +1349,50 @@ var CompactionSubscribers = class {
1193
1349
  }
1194
1350
  }
1195
1351
  };
1352
+ function storedBatchRows(bodies, result) {
1353
+ const r = _nullishCoalesce(result, () => ( {}));
1354
+ const results = Array.isArray(r["results"]) ? r["results"] : [];
1355
+ const errors = Array.isArray(r["errors"]) ? r["errors"] : [];
1356
+ const failed = new Set(errors.map((e) => _optionalChain([e, 'optionalAccess', _45 => _45["index"]])));
1357
+ const stored = bodies.filter((_, index) => !failed.has(index));
1358
+ if (stored.length !== results.length) return [];
1359
+ const pairs = [];
1360
+ stored.forEach((body, i) => {
1361
+ const row = results[i];
1362
+ if (row && typeof row === "object" && row["message_id"]) pairs.push([body, row]);
1363
+ });
1364
+ return pairs;
1365
+ }
1196
1366
  function createConversationNamespace(transport, subscribers = new CompactionSubscribers(), onWrite = () => {
1197
1367
  }, onTurn = () => {
1198
1368
  }, onFetched = () => {
1199
- }, localShortTerm = { prompt: () => null, compacted: () => null }) {
1369
+ }, localShortTerm = { prompt: () => null, compacted: () => null }, agentModel = () => ({})) {
1370
+ const writesInFlight = /* @__PURE__ */ new Map();
1371
+ const withTurnModel = (body, options) => {
1372
+ const metadata = withAgentModel(
1373
+ body["metadata"],
1374
+ agentModelOf(options),
1375
+ agentModel()
1376
+ );
1377
+ return metadata === void 0 ? body : { ...body, metadata };
1378
+ };
1379
+ const writeStarted = (conversationId) => {
1380
+ writesInFlight.set(conversationId, (_nullishCoalesce(writesInFlight.get(conversationId), () => ( 0))) + 1);
1381
+ };
1382
+ const writeLanded = (conversationId) => {
1383
+ const remaining = (_nullishCoalesce(writesInFlight.get(conversationId), () => ( 0))) - 1;
1384
+ if (remaining > 0) writesInFlight.set(conversationId, remaining);
1385
+ else writesInFlight.delete(conversationId);
1386
+ };
1387
+ const confirmation = (result, conversationId) => {
1388
+ const r = _nullishCoalesce(result, () => ( {}));
1389
+ const count = r["conversation_message_count"];
1390
+ return {
1391
+ ...typeof r["message_id"] === "string" ? { messageId: r["message_id"] } : {},
1392
+ serverMessageCount: typeof count === "number" ? count : null,
1393
+ inFlight: _nullishCoalesce(writesInFlight.get(conversationId), () => ( 0))
1394
+ };
1395
+ };
1200
1396
  const convId = (v) => {
1201
1397
  const id = typeof v === "string" ? v : _nullishCoalesce(v.conversation_id, () => ( v.conversationId));
1202
1398
  if (!id) throw new (0, _chunkT372OL3Qcjs.InvalidInputError)("conversation_id is required");
@@ -1209,13 +1405,21 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
1209
1405
  _nullishCoalesce(options["customer_id"], () => ( options["customerId"])),
1210
1406
  "conversation.record_message"
1211
1407
  );
1212
- const body = buildRecordMessageBody(options);
1213
- const result = await transport.request("conversations_messages", { body });
1408
+ const body = withTurnModel(buildRecordMessageBody(options), options);
1409
+ const conversationId = String(_nullishCoalesce(body["conversation_id"], () => ( "")));
1410
+ writeStarted(conversationId);
1411
+ let result;
1412
+ try {
1413
+ result = await transport.request("conversations_messages", { body });
1414
+ } finally {
1415
+ writeLanded(conversationId);
1416
+ }
1214
1417
  onTurn({
1215
- conversationId: String(_nullishCoalesce(body["conversation_id"], () => ( ""))),
1418
+ conversationId,
1216
1419
  role: String(_nullishCoalesce(body["role"], () => ( "user"))),
1217
1420
  content: String(_nullishCoalesce(body["content"], () => ( ""))),
1218
- timestamp: body["timestamp"]
1421
+ timestamp: body["timestamp"],
1422
+ ...confirmation(result, conversationId)
1219
1423
  });
1220
1424
  onWrite({
1221
1425
  userId: body["user_id"],
@@ -1234,16 +1438,18 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
1234
1438
  "conversation.record_messages_batch"
1235
1439
  );
1236
1440
  }
1237
- const bodies = messages.map(buildRecordMessageBody);
1441
+ const bodies = messages.map((m) => withTurnModel(buildRecordMessageBody(m), m));
1238
1442
  const result = await transport.request("conversations_messages_batch", {
1239
1443
  body: { messages: bodies }
1240
1444
  });
1241
- for (const body of bodies) {
1445
+ for (const [body, row] of storedBatchRows(bodies, result)) {
1446
+ const conversationId = String(_nullishCoalesce(body["conversation_id"], () => ( "")));
1242
1447
  onTurn({
1243
- conversationId: String(_nullishCoalesce(body["conversation_id"], () => ( ""))),
1448
+ conversationId,
1244
1449
  role: String(_nullishCoalesce(body["role"], () => ( "user"))),
1245
1450
  content: String(_nullishCoalesce(body["content"], () => ( ""))),
1246
- timestamp: body["timestamp"]
1451
+ timestamp: body["timestamp"],
1452
+ ...confirmation(row, conversationId)
1247
1453
  });
1248
1454
  }
1249
1455
  for (const body of bodies) {
@@ -1278,13 +1484,24 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
1278
1484
  },
1279
1485
  get_context_for_prompt: async (options = {}) => {
1280
1486
  const id = convId(options);
1487
+ const search = (_nullishCoalesce(_nullishCoalesce(options.search_history, () => ( options.searchHistory)), () => ( true))) && localShortTerm.historyExcerpts;
1488
+ const withHistory = async (response2) => {
1489
+ if (!search) return response2;
1490
+ try {
1491
+ return await localShortTerm.historyExcerpts(id, response2, options.query);
1492
+ } catch (e6) {
1493
+ return response2;
1494
+ }
1495
+ };
1281
1496
  const local = localShortTerm.prompt(id, _nullishCoalesce(options.style, () => ( "structured")));
1282
- if (local !== null) return local;
1497
+ if (local !== null) return withHistory(local);
1283
1498
  const result = await transport.request("context_for_prompt", {
1284
1499
  pathParams: { conversation_id: id },
1285
1500
  query: { style: _nullishCoalesce(options.style, () => ( "structured")) }
1286
1501
  });
1287
- return _nullishCoalesce(_nullishCoalesce(_optionalChain([result, 'optionalAccess', _42 => _42["context_for_prompt"]]), () => ( result)), () => ( {}));
1502
+ const response = _nullishCoalesce(_nullishCoalesce(_optionalChain([result, 'optionalAccess', _46 => _46["context_for_prompt"]]), () => ( result)), () => ( {}));
1503
+ _optionalChain([localShortTerm, 'access', _47 => _47.reseed, 'optionalCall', _48 => _48(id, response)]);
1504
+ return withHistory(response);
1288
1505
  },
1289
1506
  async compact(options) {
1290
1507
  return transport.request("conversations_compact", { body: buildCompactBody(options) });
@@ -1459,32 +1676,40 @@ function jsonOrText(value) {
1459
1676
  (_k, v) => typeof v === "bigint" ? String(v) : v
1460
1677
  );
1461
1678
  return out === void 0 ? String(value) : out;
1462
- } catch (e6) {
1679
+ } catch (e7) {
1463
1680
  try {
1464
1681
  return String(value);
1465
- } catch (e7) {
1682
+ } catch (e8) {
1466
1683
  return "";
1467
1684
  }
1468
1685
  }
1469
1686
  }
1470
- function createInstanceNamespace(credentials, cache, onTurn) {
1687
+ function createInstanceNamespace(credentials, cache, onTurn, agentModel = () => ({}), onToolResult) {
1471
1688
  let client = null;
1472
1689
  const openSessions = /* @__PURE__ */ new Map();
1473
1690
  let dispatchCompaction = null;
1474
1691
  let applyCompaction = null;
1692
+ let onListen = null;
1693
+ let onConversationState = null;
1475
1694
  const internals = {
1476
1695
  onCompactionUpdate(conversationId, bundle) {
1477
- _optionalChain([applyCompaction, 'optionalCall', _43 => _43(bundle)]);
1478
- _optionalChain([dispatchCompaction, 'optionalCall', _44 => _44(conversationId, bundle)]);
1696
+ _optionalChain([applyCompaction, 'optionalCall', _49 => _49(bundle)]);
1697
+ _optionalChain([dispatchCompaction, 'optionalCall', _50 => _50(conversationId, bundle)]);
1479
1698
  },
1480
1699
  setCompactionApplier(apply) {
1481
1700
  applyCompaction = apply;
1482
1701
  },
1702
+ setOnListen(handle) {
1703
+ onListen = handle;
1704
+ },
1705
+ setConversationStateHandler(handle) {
1706
+ onConversationState = handle;
1707
+ },
1483
1708
  sendContextUsed(event) {
1484
- _optionalChain([client, 'optionalAccess', _45 => _45.sendContextUsed, 'call', _46 => _46(event)]);
1709
+ _optionalChain([client, 'optionalAccess', _51 => _51.sendContextUsed, 'call', _52 => _52(event)]);
1485
1710
  },
1486
1711
  sendContextAssembled(event) {
1487
- _optionalChain([client, 'optionalAccess', _47 => _47.sendContextAssembled, 'call', _48 => _48(event)]);
1712
+ _optionalChain([client, 'optionalAccess', _53 => _53.sendContextAssembled, 'call', _54 => _54(event)]);
1488
1713
  },
1489
1714
  setCompactionDispatcher(dispatch) {
1490
1715
  dispatchCompaction = dispatch;
@@ -1506,7 +1731,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1506
1731
  ...customerId ? { customer_id: customerId } : {}
1507
1732
  });
1508
1733
  openSessions.set(conversationId, sessionId);
1509
- } catch (e8) {
1734
+ } catch (e9) {
1510
1735
  }
1511
1736
  }
1512
1737
  const namespace = {
@@ -1514,7 +1739,11 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1514
1739
  if (client !== null) {
1515
1740
  throw new (0, _chunkT372OL3Qcjs.ListeningAlreadyActiveError)("Listening is already active on this instance");
1516
1741
  }
1517
- const { GrpcStreamClient } = await Promise.resolve().then(() => _interopRequireWildcard(require("./stream-client-N46JBT5A.cjs")));
1742
+ try {
1743
+ _optionalChain([onListen, 'optionalCall', _55 => _55()]);
1744
+ } catch (e10) {
1745
+ }
1746
+ const { GrpcStreamClient } = await Promise.resolve().then(() => _interopRequireWildcard(require("./stream-client-L6OQPZN7.cjs")));
1518
1747
  const onContext = _nullishCoalesce(options.on_context, () => ( options.onContext));
1519
1748
  const onDisconnect = _nullishCoalesce(options.on_disconnect, () => ( options.onDisconnect));
1520
1749
  const onReconnect = _nullishCoalesce(options.on_reconnect, () => ( options.onReconnect));
@@ -1528,15 +1757,18 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1528
1757
  onCompactionUpdate: (conversationId, bundle) => {
1529
1758
  internals.onCompactionUpdate(conversationId, bundle);
1530
1759
  },
1760
+ onConversationState: (conversationId, messageCount) => {
1761
+ _optionalChain([onConversationState, 'optionalCall', _56 => _56(conversationId, messageCount)]);
1762
+ },
1531
1763
  onStateChange: (state) => {
1532
1764
  if (state !== "connected") openSessions.clear();
1533
1765
  if (state === "reconnecting") {
1534
1766
  reconnectAttempts += 1;
1535
- _optionalChain([onReconnect, 'optionalCall', _49 => _49(reconnectAttempts)]);
1767
+ _optionalChain([onReconnect, 'optionalCall', _57 => _57(reconnectAttempts)]);
1536
1768
  } else if (state === "disconnected") {
1537
- const stopped = _nullishCoalesce(_optionalChain([stream, 'optionalAccess', _50 => _50.lastError]), () => ( null));
1769
+ const stopped = _nullishCoalesce(_optionalChain([stream, 'optionalAccess', _58 => _58.lastError]), () => ( null));
1538
1770
  const reason = stopped === null ? "stream disconnected" : `credit_stop:${_nullishCoalesce(stopped.reason, () => ( "unknown"))}`;
1539
- _optionalChain([onDisconnect, 'optionalCall', _51 => _51(reason)]);
1771
+ _optionalChain([onDisconnect, 'optionalCall', _59 => _59(reason)]);
1540
1772
  }
1541
1773
  }
1542
1774
  });
@@ -1546,7 +1778,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1546
1778
  } catch (error) {
1547
1779
  if (error instanceof _chunkT372OL3Qcjs.SynapError) throw error;
1548
1780
  throw new (0, _chunkT372OL3Qcjs.SynapError)(
1549
- `Could not open the Synap anticipation stream: ${_nullishCoalesce(_optionalChain([error, 'optionalAccess', _52 => _52.message]), () => ( String(error)))}`,
1781
+ `Could not open the Synap anticipation stream: ${_nullishCoalesce(_optionalChain([error, 'optionalAccess', _60 => _60.message]), () => ( String(error)))}`,
1550
1782
  { cause: error }
1551
1783
  );
1552
1784
  }
@@ -1571,7 +1803,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1571
1803
  session_id: sessionId,
1572
1804
  conversation_id: conversationId
1573
1805
  });
1574
- } catch (e9) {
1806
+ } catch (e11) {
1575
1807
  }
1576
1808
  },
1577
1809
  async send_message(options) {
@@ -1592,7 +1824,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1592
1824
  _nullishCoalesce(_nullishCoalesce(options.customer_id, () => ( options.customerId)), () => ( ""))
1593
1825
  );
1594
1826
  if (eventType === "user_message" || eventType === "assistant_message") {
1595
- _optionalChain([onTurn, 'optionalCall', _53 => _53({
1827
+ _optionalChain([onTurn, 'optionalCall', _61 => _61({
1596
1828
  conversationId,
1597
1829
  role: _nullishCoalesce(options.role, () => ( "user")),
1598
1830
  content: options.content
@@ -1606,7 +1838,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1606
1838
  user_id: _nullishCoalesce(_nullishCoalesce(options.user_id, () => ( options.userId)), () => ( "")),
1607
1839
  customer_id: _nullishCoalesce(_nullishCoalesce(options.customer_id, () => ( options.customerId)), () => ( "")),
1608
1840
  session_id: _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(options.session_id, () => ( options.sessionId)), () => ( openSessions.get(conversationId))), () => ( "")),
1609
- metadata: _nullishCoalesce(options.metadata, () => ( {})),
1841
+ metadata: _nullishCoalesce(withAgentModel(options.metadata, agentModelOf(options), agentModel(), true), () => ( {})),
1610
1842
  timestamp_ms: Date.now(),
1611
1843
  tool_name: _nullishCoalesce(_nullishCoalesce(options.tool_name, () => ( options.toolName)), () => ( "")),
1612
1844
  // Python serialises tool_args with json.dumps into tool_args_json.
@@ -1636,6 +1868,10 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1636
1868
  });
1637
1869
  },
1638
1870
  async record_tool_result(options) {
1871
+ const conversationId = _nullishCoalesce(_nullishCoalesce(options.conversation_id, () => ( options.conversationId)), () => ( ""));
1872
+ if (conversationId) {
1873
+ _optionalChain([onToolResult, 'optionalCall', _62 => _62({ conversationId, result: options.result, toolName: _nullishCoalesce(_nullishCoalesce(options.tool_name, () => ( options.toolName)), () => ( "")) })]);
1874
+ }
1639
1875
  await namespace.send_message({
1640
1876
  content: "",
1641
1877
  role: "tool",
@@ -1686,7 +1922,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1686
1922
  }
1687
1923
 
1688
1924
  // src/version.ts
1689
- var SDK_VERSION = "0.5.7";
1925
+ var SDK_VERSION = "0.5.9";
1690
1926
 
1691
1927
  // src/registry.ts
1692
1928
  var instances = /* @__PURE__ */ new Map();
@@ -1727,6 +1963,34 @@ function unregisterIfOwner(key, client) {
1727
1963
  // src/cache/short-term-store.ts
1728
1964
  var DEFAULT_MAX_CONVERSATIONS = 100;
1729
1965
  var DEFAULT_MAX_AGE_MS = 12 * 60 * 60 * 1e3;
1966
+ var CONTEXT_BLOCK_PREFIX = "## Conversation so far (compacted";
1967
+ function contextBlockOf(cc) {
1968
+ const block = cc["context_block"];
1969
+ if (typeof block === "string" && block) return block;
1970
+ const summary = cc["summary"];
1971
+ if (typeof summary === "string" && summary.startsWith(CONTEXT_BLOCK_PREFIX)) return summary;
1972
+ return null;
1973
+ }
1974
+ function localMessageCount(entry) {
1975
+ return entry.compactedMessageCount + entry.recentTurns.length;
1976
+ }
1977
+ function isComplete(entry) {
1978
+ return entry.participant && entry.synced && !entry.shared && entry.serverMessageCount !== null && entry.serverMessageCount <= localMessageCount(entry);
1979
+ }
1980
+ function heldIn(turn, turns) {
1981
+ return turns.some(
1982
+ (held) => turn.message_id && held.message_id ? held.message_id === turn.message_id : held.role === turn.role && held.content === turn.content
1983
+ );
1984
+ }
1985
+ function turnsAfter(localTurns, serverTurns) {
1986
+ const newer = [];
1987
+ for (let i = localTurns.length - 1; i >= 0; i -= 1) {
1988
+ const turn = localTurns[i];
1989
+ if (heldIn(turn, serverTurns)) break;
1990
+ newer.unshift(turn);
1991
+ }
1992
+ return newer;
1993
+ }
1730
1994
  function parseIso(value) {
1731
1995
  if (value === null || value === void 0 || value === "") return null;
1732
1996
  if (value instanceof Date) return value.getTime();
@@ -1746,11 +2010,23 @@ var ShortTermStore = class {
1746
2010
  #maxConversations;
1747
2011
  #maxAgeMs;
1748
2012
  #now;
2013
+ /**
2014
+ * Every turn appended here is also kept in the archive (a TurnStore), which
2015
+ * a compaction does not prune: the turns it covers stay searchable locally
2016
+ * (history-search.ts). Python: `ShortTermContextStore.attach_archive`.
2017
+ */
2018
+ #archive = null;
1749
2019
  constructor(options = {}) {
1750
2020
  this.#maxConversations = _nullishCoalesce(options.maxConversations, () => ( DEFAULT_MAX_CONVERSATIONS));
1751
2021
  this.#maxAgeMs = _nullishCoalesce(options.maxAgeMs, () => ( DEFAULT_MAX_AGE_MS));
1752
2022
  this.#now = _nullishCoalesce(options.now, () => ( (() => Date.now())));
1753
2023
  }
2024
+ attachArchive(archive) {
2025
+ this.#archive = archive;
2026
+ }
2027
+ get archive() {
2028
+ return this.#archive;
2029
+ }
1754
2030
  /** The entry, or null. Age-evicts on read, as Python does. */
1755
2031
  get(conversationId) {
1756
2032
  if (!conversationId) return null;
@@ -1776,23 +2052,88 @@ var ShortTermStore = class {
1776
2052
  * exists yet (the first turn, before any compaction has arrived) a fresh one
1777
2053
  * is created with an empty summary; the next `compaction_update` fills the
1778
2054
  * summary fields in and prunes the turns it covers.
2055
+ *
2056
+ * `confirmation` is what the write learned from the server. See
2057
+ * `TurnConfirmation` and `noteServerCount`.
1779
2058
  */
1780
- appendTurn(conversationId, role, content, timestamp) {
2059
+ appendTurn(conversationId, role, content, timestamp, confirmation = {}) {
1781
2060
  if (!conversationId) return;
1782
2061
  const stamp = _nullishCoalesce(timestamp, () => ( new Date(this.#now()).toISOString()));
2062
+ if (this.#archive !== null) {
2063
+ try {
2064
+ this.#archive.add(conversationId, role, content, stamp, { messageId: _nullishCoalesce(confirmation.messageId, () => ( null)) });
2065
+ } catch (e12) {
2066
+ }
2067
+ }
1783
2068
  const entry = _nullishCoalesce(this.#entries.get(conversationId), () => ( this.#blank(conversationId)));
1784
- entry.recentTurns.push({ role, content, timestamp: stamp });
2069
+ const messageId = confirmation.messageId;
2070
+ const alreadyHeld = Boolean(messageId) && entry.recentTurns.some((t) => t.message_id === messageId);
2071
+ if (!alreadyHeld) {
2072
+ const turn = { role, content, timestamp: stamp };
2073
+ if (messageId) turn.message_id = messageId;
2074
+ entry.recentTurns.push(turn);
2075
+ }
2076
+ const count = confirmation.serverMessageCount;
2077
+ if (count !== null && count !== void 0) {
2078
+ this.#noteCount(entry, Number(count), _nullishCoalesce(confirmation.inFlight, () => ( 0)));
2079
+ }
1785
2080
  entry.lastActivityAt = _nullishCoalesce(parseIso(stamp), () => ( this.#now()));
1786
2081
  this.#touch(conversationId, entry);
1787
2082
  this.#evict();
1788
2083
  }
2084
+ /**
2085
+ * Record the server's message count for a conversation.
2086
+ *
2087
+ * The streamed write path's half of `appendTurn`'s confirmation: a streamed
2088
+ * turn is appended when it is sent, and the count arrives later, once the
2089
+ * server has written it. A count for a conversation this process holds
2090
+ * nothing for is ignored.
2091
+ */
2092
+ noteServerCount(conversationId, count) {
2093
+ if (!conversationId) return;
2094
+ const entry = this.#entries.get(conversationId);
2095
+ if (entry !== void 0) this.#noteCount(entry, Number(count), 0);
2096
+ }
2097
+ /**
2098
+ * Compare the server's count with what the entry holds.
2099
+ *
2100
+ * The count is taken after one of this process's own writes, so it proves
2101
+ * the process takes part in the conversation.
2102
+ *
2103
+ * Server ahead of an entry that was in sync: something else wrote, and the
2104
+ * entry is marked shared for good. Server ahead of an entry that was never
2105
+ * in sync: this process joined late (a restart, an eviction), and `reseed`
2106
+ * will fix it. `inFlight` covers this process's own writes that the server
2107
+ * has counted and the entry has not appended yet, so two concurrent writes
2108
+ * do not look like a second writer.
2109
+ */
2110
+ #noteCount(entry, count, inFlight) {
2111
+ entry.participant = true;
2112
+ if (count > localMessageCount(entry) + inFlight) {
2113
+ if (entry.synced) entry.shared = true;
2114
+ } else if (entry.serverMessageCount === null) {
2115
+ entry.synced = true;
2116
+ }
2117
+ entry.serverMessageCount = Math.max(_nullishCoalesce(entry.serverMessageCount, () => ( 0)), count);
2118
+ }
1789
2119
  /**
1790
2120
  * Apply a `compaction_update` bundle.
1791
2121
  *
1792
2122
  * Replaces the summary fields from the bundle's `conversation_context` and
1793
- * prunes locally buffered turns at or before its `end_timestamp`. Turns that
1794
- * arrived DURING the compaction window are kept by design: the server has not
1795
- * seen them yet, which is the whole reason the local tail exists.
2123
+ * drops the buffered turns the new summary covers.
2124
+ *
2125
+ * Which turns those are is decided by count, not by clock. The bundle says
2126
+ * how many messages the summary covers and how many the conversation held
2127
+ * when the bundle was built, so the last `total - compacted` of its
2128
+ * `recent_turns` are exactly the uncovered messages. Turns this process
2129
+ * appended after the server took that count are kept behind them. Timestamps
2130
+ * cannot do this job: the REST write returns the time of the response and
2131
+ * not of the stored row, a caller may supply its own message time, and a
2132
+ * streamed turn is stamped when a background write runs.
2133
+ *
2134
+ * A bundle from a server that sends no counts falls back to the older
2135
+ * timestamp prune, and the entry is left out of sync so it is never used to
2136
+ * answer on its own.
1796
2137
  */
1797
2138
  applyCompaction(bundle) {
1798
2139
  if (typeof bundle !== "object" || bundle === null) return;
@@ -1802,22 +2143,84 @@ var ShortTermStore = class {
1802
2143
  );
1803
2144
  if (!conversationId) return;
1804
2145
  const endTs = parseIso(_nullishCoalesce(cc["end_timestamp"], () => ( cc["compacted_at"])));
2146
+ const compactedAt = parseIso(cc["compacted_at"]);
2147
+ const compactedCount = Number(_nullishCoalesce(cc["compacted_message_count"], () => ( 0))) || 0;
2148
+ const totalCount = Number(_nullishCoalesce(cc["total_message_count"], () => ( 0))) || 0;
1805
2149
  const entry = _nullishCoalesce(this.#entries.get(conversationId), () => ( this.#blank(conversationId)));
1806
- entry.summary = cc["summary"] || entry.summary;
1807
- entry.factualParagraph = cc["factual_paragraph"] || entry.factualParagraph;
1808
- entry.conversationalParagraph = cc["conversational_paragraph"] || entry.conversationalParagraph;
2150
+ const heldAt = parseIso(entry.compactedAt);
2151
+ if (compactedAt !== null && heldAt !== null && compactedAt < heldAt) return;
2152
+ const block = contextBlockOf(cc);
2153
+ if (block) {
2154
+ entry.contextBlock = block;
2155
+ entry.summary = cc["summary"] || block;
2156
+ entry.factualParagraph = null;
2157
+ entry.conversationalParagraph = null;
2158
+ } else {
2159
+ entry.summary = cc["summary"] || entry.summary;
2160
+ entry.factualParagraph = cc["factual_paragraph"] || entry.factualParagraph;
2161
+ entry.conversationalParagraph = cc["conversational_paragraph"] || entry.conversationalParagraph;
2162
+ }
1809
2163
  entry.currentState = { ..._nullishCoalesce(cc["current_state"], () => ( {})) };
1810
2164
  entry.keyExtractions = { ..._nullishCoalesce(cc["key_extractions"], () => ( {})) };
1811
2165
  entry.compactionId = cc["compaction_id"] || entry.compactionId;
1812
2166
  entry.compactedAt = cc["compacted_at"] || entry.compactedAt;
1813
2167
  entry.endTimestamp = cc["end_timestamp"] || entry.endTimestamp;
1814
- if (endTs !== null && entry.recentTurns.length > 0) {
1815
- entry.recentTurns = entry.recentTurns.filter((t) => isAfter(t.timestamp, endTs));
2168
+ if (compactedCount > 0 && compactedCount <= totalCount) {
2169
+ const uncovered = totalCount - compactedCount;
2170
+ const serverTurns = Array.isArray(cc["recent_turns"]) ? cc["recent_turns"] : [];
2171
+ const tail = uncovered > 0 ? serverTurns.slice(-uncovered).map((t) => ({ ...t })) : [];
2172
+ const ahead = localMessageCount(entry) - totalCount;
2173
+ const newer = entry.synced && ahead > 0 ? entry.recentTurns.slice(-ahead).filter((t) => !heldIn(t, tail)) : [];
2174
+ entry.recentTurns = [...tail, ...newer];
2175
+ entry.compactedMessageCount = compactedCount;
2176
+ entry.serverMessageCount = Math.max(_nullishCoalesce(entry.serverMessageCount, () => ( 0)), totalCount);
2177
+ entry.synced = tail.length === uncovered;
2178
+ } else {
2179
+ if (endTs !== null && entry.recentTurns.length > 0) {
2180
+ entry.recentTurns = entry.recentTurns.filter((t) => isAfter(t.timestamp, endTs));
2181
+ }
2182
+ entry.synced = false;
1816
2183
  }
1817
2184
  entry.lastActivityAt = this.#now();
1818
2185
  this.#touch(conversationId, entry);
1819
2186
  this.#evict();
1820
2187
  }
2188
+ /**
2189
+ * Bring an entry back in step from a `/context-for-prompt` response.
2190
+ *
2191
+ * Called after the server was asked because the entry was not complete. The
2192
+ * response lists every message the server's summary does not cover, which is
2193
+ * what `recentTurns` is meant to hold.
2194
+ *
2195
+ * Only an entry that already exists is reseeded. Creating one here would let
2196
+ * a process that never writes to the conversation answer from a snapshot it
2197
+ * has no way to refresh.
2198
+ *
2199
+ * If the server's summary is not the one cached here, the entry cannot
2200
+ * render it, so it stays out of sync until the next bundle brings the
2201
+ * summary itself.
2202
+ *
2203
+ * Turns held locally and absent from the response are kept when they come
2204
+ * after everything the response has: they were written or sent after the
2205
+ * server read its messages.
2206
+ */
2207
+ reseed(conversationId, state) {
2208
+ if (!conversationId) return;
2209
+ const entry = this.#entries.get(conversationId);
2210
+ if (entry === void 0 || entry.shared) return;
2211
+ entry.serverMessageCount = Math.max(
2212
+ _nullishCoalesce(entry.serverMessageCount, () => ( 0)),
2213
+ Number(state.totalMessageCount) || 0
2214
+ );
2215
+ if ((state.compactionId || null) !== entry.compactionId) {
2216
+ entry.synced = false;
2217
+ return;
2218
+ }
2219
+ const serverTurns = state.recentMessages.map((m) => ({ ...m }));
2220
+ entry.recentTurns = [...serverTurns, ...turnsAfter(entry.recentTurns, serverTurns)];
2221
+ entry.compactedMessageCount = Number(state.compactedMessageCount) || 0;
2222
+ entry.synced = true;
2223
+ }
1821
2224
  invalidate(conversationId) {
1822
2225
  this.#entries.delete(conversationId);
1823
2226
  }
@@ -1828,6 +2231,7 @@ var ShortTermStore = class {
1828
2231
  return {
1829
2232
  conversationId,
1830
2233
  summary: null,
2234
+ contextBlock: null,
1831
2235
  factualParagraph: null,
1832
2236
  conversationalParagraph: null,
1833
2237
  currentState: {},
@@ -1836,7 +2240,12 @@ var ShortTermStore = class {
1836
2240
  compactedAt: null,
1837
2241
  endTimestamp: null,
1838
2242
  recentTurns: [],
1839
- lastActivityAt: this.#now()
2243
+ lastActivityAt: this.#now(),
2244
+ compactedMessageCount: 0,
2245
+ serverMessageCount: null,
2246
+ participant: false,
2247
+ synced: false,
2248
+ shared: false
1840
2249
  };
1841
2250
  }
1842
2251
  /** Re-insert so this key becomes the most recently used. */
@@ -1853,6 +2262,520 @@ var ShortTermStore = class {
1853
2262
  }
1854
2263
  };
1855
2264
 
2265
+ // src/cache/turn-store.ts
2266
+ var SWEEP_EVERY_WRITES = 50;
2267
+ function bytesOf(text) {
2268
+ return new TextEncoder().encode(text).length;
2269
+ }
2270
+ var TurnStore = (_class3 = class {
2271
+
2272
+ __init4() {this.onDisk = false}
2273
+ #now;
2274
+ #seq = 0;
2275
+ #writes = 0;
2276
+ #conversations = /* @__PURE__ */ new Map();
2277
+ #vectors = /* @__PURE__ */ new Map();
2278
+ constructor(options = {}) {;_class3.prototype.__init4.call(this);
2279
+ this.limits = _nullishCoalesce(options.limits, () => ( { ..._chunkICUFS3B5cjs.HISTORY_SEARCH.store_limits }));
2280
+ this.#now = _nullishCoalesce(options.now, () => ( (() => Date.now() / 1e3)));
2281
+ }
2282
+ add(conversationId, role, content, timestamp, options = {}) {
2283
+ this.addMany(conversationId, [
2284
+ { role, content, timestamp: _nullishCoalesce(timestamp, () => ( "")), message_id: _nullishCoalesce(options.messageId, () => ( null)), position: _nullishCoalesce(options.position, () => ( null)) }
2285
+ ]);
2286
+ }
2287
+ /**
2288
+ * Insert turns not held yet; returns how many were new. A turn held without
2289
+ * an id or position (a streamed write) gets them when the same role and
2290
+ * text come back from the server.
2291
+ */
2292
+ addMany(conversationId, turns) {
2293
+ if (!conversationId) return 0;
2294
+ const conv = _nullishCoalesce(this.#conversations.get(conversationId), () => ( { lastActivity: 0, turns: [] }));
2295
+ let added = 0;
2296
+ for (const turn of turns) {
2297
+ const role = String(turn.role || "user");
2298
+ const content = String(_nullishCoalesce(turn.content, () => ( "")));
2299
+ if (!content) continue;
2300
+ const messageId = turn.message_id ? String(turn.message_id) : null;
2301
+ const raw = turn.position;
2302
+ const position = typeof raw === "number" && Number.isInteger(raw) ? raw : /^\d+$/.test(String(_nullishCoalesce(raw, () => ( "")))) ? Number(raw) : null;
2303
+ const ts = turn.timestamp instanceof Date ? turn.timestamp.toISOString() : String(_nullishCoalesce(turn.timestamp, () => ( "")));
2304
+ if (messageId !== null) {
2305
+ const held = conv.turns.find((t) => t.message_id === messageId);
2306
+ if (held !== void 0) {
2307
+ if (position !== null) held.position = position;
2308
+ continue;
2309
+ }
2310
+ }
2311
+ const same = conv.turns.find(
2312
+ (t) => t.role === role && t.content === content && (t.message_id === null || t.position === null)
2313
+ );
2314
+ if (same !== void 0) {
2315
+ same.message_id = _nullishCoalesce(same.message_id, () => ( messageId));
2316
+ same.position = _nullishCoalesce(position, () => ( same.position));
2317
+ continue;
2318
+ }
2319
+ conv.turns.push({ seq: ++this.#seq, role, content, timestamp: ts, message_id: messageId, position });
2320
+ added += 1;
2321
+ }
2322
+ conv.lastActivity = this.#now();
2323
+ this.#conversations.set(conversationId, conv);
2324
+ this.#trim(conv);
2325
+ this.#writes += 1;
2326
+ if (this.#writes % SWEEP_EVERY_WRITES === 0) this.sweep();
2327
+ return added;
2328
+ }
2329
+ #trim(conv) {
2330
+ const limits = this.limits;
2331
+ let size = conv.turns.reduce((n2, t) => n2 + bytesOf(t.content), 0);
2332
+ while (conv.turns.length > limits.max_turns_per_conversation || size > limits.max_bytes_per_conversation) {
2333
+ const excess = Math.max(conv.turns.length - limits.max_turns_per_conversation, 1);
2334
+ const order = [...conv.turns].sort(
2335
+ (a, b) => (_nullishCoalesce(a.position, () => ( Number.MAX_SAFE_INTEGER))) - (_nullishCoalesce(b.position, () => ( Number.MAX_SAFE_INTEGER))) || a.seq - b.seq
2336
+ );
2337
+ const gone = new Set(order.slice(0, excess).map((t) => t.seq));
2338
+ if (gone.size === 0) break;
2339
+ for (const t of conv.turns) if (gone.has(t.seq)) size -= bytesOf(t.content);
2340
+ conv.turns = conv.turns.filter((t) => !gone.has(t.seq));
2341
+ for (const vectors of this.#vectors.values()) for (const seq of gone) vectors.delete(seq);
2342
+ }
2343
+ }
2344
+ /** Idle conversations, then the least recently used beyond the count, then more until the bytes fit. */
2345
+ sweep() {
2346
+ const limits = this.limits;
2347
+ const now = this.#now();
2348
+ for (const [id, conv] of this.#conversations) {
2349
+ if (conv.lastActivity < now - limits.idle_ttl_seconds) this.drop(id);
2350
+ }
2351
+ const byRecent = [...this.#conversations.entries()].sort((a, b) => b[1].lastActivity - a[1].lastActivity);
2352
+ for (const [id] of byRecent.slice(limits.max_conversations)) this.drop(id);
2353
+ while (this.sizeBytes() > limits.max_file_bytes) {
2354
+ const oldest = this.conversations()[0];
2355
+ if (oldest === void 0) break;
2356
+ this.drop(oldest);
2357
+ }
2358
+ }
2359
+ drop(conversationId) {
2360
+ const conv = this.#conversations.get(conversationId);
2361
+ if (conv === void 0) return;
2362
+ for (const vectors of this.#vectors.values()) for (const t of conv.turns) vectors.delete(t.seq);
2363
+ this.#conversations.delete(conversationId);
2364
+ }
2365
+ clear() {
2366
+ this.#conversations.clear();
2367
+ this.#vectors.clear();
2368
+ }
2369
+ close() {
2370
+ }
2371
+ /** In conversation order: by server position where known, else as written here. */
2372
+ turns(conversationId) {
2373
+ const conv = this.#conversations.get(conversationId);
2374
+ if (conv === void 0) return [];
2375
+ const bySeq = [...conv.turns].sort((a, b) => a.seq - b.seq);
2376
+ const key = /* @__PURE__ */ new Map();
2377
+ let top = 0;
2378
+ let step = 0;
2379
+ for (const t of bySeq) {
2380
+ if (t.position !== null) {
2381
+ key.set(t.seq, t.position);
2382
+ top = Math.max(top, t.position);
2383
+ step = 0;
2384
+ } else {
2385
+ step += 1;
2386
+ key.set(t.seq, top + step * 1e-6);
2387
+ }
2388
+ }
2389
+ return bySeq.sort((a, b) => (_nullishCoalesce(key.get(a.seq), () => ( 0))) - (_nullishCoalesce(key.get(b.seq), () => ( 0))) || a.seq - b.seq).map((t) => ({ ...t }));
2390
+ }
2391
+ /** Turns the server also holds: archived tool results are local only. Python: `count`. */
2392
+ count(conversationId) {
2393
+ return (_nullishCoalesce(_optionalChain([this, 'access', _63 => _63.#conversations, 'access', _64 => _64.get, 'call', _65 => _65(conversationId), 'optionalAccess', _66 => _66.turns]), () => ( []))).filter((t) => t.role !== "tool").length;
2394
+ }
2395
+ sizeBytes() {
2396
+ let n2 = 0;
2397
+ for (const conv of this.#conversations.values()) for (const t of conv.turns) n2 += bytesOf(t.content);
2398
+ return n2;
2399
+ }
2400
+ /** Conversation ids, least recently active first. */
2401
+ conversations() {
2402
+ return [...this.#conversations.entries()].sort((a, b) => a[1].lastActivity - b[1].lastActivity).map(([id]) => id);
2403
+ }
2404
+ vectors(seqs, model) {
2405
+ const held = this.#vectors.get(model);
2406
+ const out = /* @__PURE__ */ new Map();
2407
+ if (held === void 0) return out;
2408
+ for (const seq of seqs) {
2409
+ const v = held.get(seq);
2410
+ if (v !== void 0) out.set(seq, v);
2411
+ }
2412
+ return out;
2413
+ }
2414
+ putVectors(model, items) {
2415
+ const held = _nullishCoalesce(this.#vectors.get(model), () => ( /* @__PURE__ */ new Map()));
2416
+ for (const [seq, vec] of items) held.set(seq, vec);
2417
+ this.#vectors.set(model, held);
2418
+ }
2419
+ }, _class3);
2420
+
2421
+ // src/cache/history-search.ts
2422
+ var WORD_RE = /[\p{L}\p{N}\p{M}]+/gu;
2423
+ var FORMS = _nullishCoalesce(_chunkICUFS3B5cjs.HISTORY_SEARCH.irregular_forms, () => ( {}));
2424
+ function tokenize2(text) {
2425
+ const out = [];
2426
+ for (const word of _nullishCoalesce((text || "").toLowerCase().match(WORD_RE), () => ( []))) {
2427
+ if (/^[\x00-\x7f]+$/.test(word)) out.push(..._chunkICUFS3B5cjs.tokenize.call(void 0, _nullishCoalesce(FORMS[word], () => ( word))));
2428
+ else out.push(word);
2429
+ }
2430
+ return out;
2431
+ }
2432
+ var LITERAL_SOURCE = String.raw`"([^"]{3,})"|\b([A-Z0-9][A-Z0-9-]{3,})\b|(\d[\d,.:/-]*\d|\d)`;
2433
+ var SENTENCE_RE = /(?<=[.!?])\s+/;
2434
+ function literalsOf(query) {
2435
+ const out = [];
2436
+ for (const m of (query || "").matchAll(new RegExp(LITERAL_SOURCE, "g"))) {
2437
+ const quoted = m[1];
2438
+ const value = quoted || m[2] || m[3];
2439
+ if (value && (quoted || /\d/.test(value))) out.push(value.toLowerCase());
2440
+ }
2441
+ return out;
2442
+ }
2443
+ function cosine(a, b) {
2444
+ let dot = 0;
2445
+ let na = 0;
2446
+ let nb = 0;
2447
+ const n2 = Math.min(a.length, b.length);
2448
+ for (let i = 0; i < n2; i++) {
2449
+ dot += a[i] * b[i];
2450
+ na += a[i] * a[i];
2451
+ nb += b[i] * b[i];
2452
+ }
2453
+ return na && nb ? dot / (Math.sqrt(na) * Math.sqrt(nb)) : 0;
2454
+ }
2455
+ function words(text) {
2456
+ return text.split(/\s+/).filter(Boolean);
2457
+ }
2458
+ function excerptOf(content, terms, literals, maxWords) {
2459
+ let sentences = content.trim().split(SENTENCE_RE).filter((s2) => s2.trim());
2460
+ if (sentences.length === 0) sentences = [content.trim()];
2461
+ const score = (sentence) => {
2462
+ const low = sentence.toLowerCase();
2463
+ const held = new Set(tokenize2(sentence));
2464
+ let n2 = 0;
2465
+ for (const t of terms) if (held.has(t)) n2 += 1;
2466
+ for (const lit of literals) if (low.includes(lit)) n2 += 2;
2467
+ return n2;
2468
+ };
2469
+ let best = 0;
2470
+ for (let i = 1; i < sentences.length; i++) if (score(sentences[i]) > score(sentences[best])) best = i;
2471
+ let lo = best;
2472
+ let hi = best;
2473
+ let count = words(sentences[best]).length;
2474
+ for (; ; ) {
2475
+ let grew = false;
2476
+ for (const j of [hi + 1, lo - 1]) {
2477
+ if (j >= 0 && j < sentences.length && count + words(sentences[j]).length <= maxWords) {
2478
+ count += words(sentences[j]).length;
2479
+ lo = Math.min(lo, j);
2480
+ hi = Math.max(hi, j);
2481
+ grew = true;
2482
+ }
2483
+ }
2484
+ if (!grew) break;
2485
+ }
2486
+ let text = sentences.slice(lo, hi + 1).join(" ");
2487
+ if (words(text).length > maxWords) text = words(text).slice(0, maxWords).join(" ") + " ...";
2488
+ return (lo > 0 ? "... " : "") + text + (hi < sentences.length - 1 && !text.endsWith("...") ? " ..." : "");
2489
+ }
2490
+ var FENCE = /^\s*(?:```|~~~)/;
2491
+ var TABLE_RULE = /^\s*\|?\s*:?-{2,}/;
2492
+ var SENTENCE = /(?<=[.!?])\s+/;
2493
+ function splitPassages(content, maxWords) {
2494
+ const text = (_nullishCoalesce(content, () => ( ""))).trim();
2495
+ if (words(text).length <= maxWords) return text ? [text] : [];
2496
+ const out = [];
2497
+ const lines = text.split("\n");
2498
+ let i = 0;
2499
+ while (i < lines.length) {
2500
+ const line = lines[i];
2501
+ if (FENCE.test(line)) {
2502
+ let j2 = i + 1;
2503
+ while (j2 < lines.length && !FENCE.test(lines[j2])) j2 += 1;
2504
+ out.push(...windowsOf(lines.slice(i + 1, j2), maxWords));
2505
+ i = j2 + 1;
2506
+ continue;
2507
+ }
2508
+ if (line.trim().startsWith("|")) {
2509
+ let j2 = i;
2510
+ while (j2 < lines.length && lines[j2].trim().startsWith("|")) j2 += 1;
2511
+ const rows = lines.slice(i, j2).filter((r) => !TABLE_RULE.test(r)).map((r) => r.trim());
2512
+ const [header, body] = rows.length > 1 ? [rows[0], rows.slice(1)] : ["", rows];
2513
+ for (const row of body) {
2514
+ const both = `${header} ${row}`.trim();
2515
+ out.push(words(both).length <= Math.trunc(maxWords * 3 / 2) ? both : row);
2516
+ }
2517
+ i = j2;
2518
+ continue;
2519
+ }
2520
+ let j = i;
2521
+ while (j < lines.length && lines[j].trim() && !FENCE.test(lines[j]) && !lines[j].trim().startsWith("|")) j += 1;
2522
+ if (j === i) {
2523
+ i += 1;
2524
+ continue;
2525
+ }
2526
+ const block = lines.slice(i, j);
2527
+ if (block.length > 1 && block.every((b) => words(b).length <= 25)) {
2528
+ out.push(...windowsOf(block, maxWords));
2529
+ } else {
2530
+ const sentences = block.map((b) => b.trim()).join(" ").split(SENTENCE).filter((x) => x.trim());
2531
+ out.push(...windowsOf(sentences, maxWords));
2532
+ }
2533
+ i = j;
2534
+ }
2535
+ const kept = out.filter((x) => x.trim());
2536
+ return kept.length ? kept : [text];
2537
+ }
2538
+ function windowsOf(pieces, maxWords) {
2539
+ const out = [];
2540
+ let cur = [];
2541
+ let used = 0;
2542
+ for (const raw of pieces) {
2543
+ const piece = raw.trim();
2544
+ if (!piece) continue;
2545
+ const n2 = words(piece).length;
2546
+ if (cur.length && used + n2 > maxWords) {
2547
+ out.push(cur.join(" "));
2548
+ cur = [];
2549
+ used = 0;
2550
+ }
2551
+ if (n2 > maxWords) {
2552
+ const w = words(piece);
2553
+ for (let k = 0; k < w.length; k += maxWords) out.push(w.slice(k, k + maxWords).join(" "));
2554
+ continue;
2555
+ }
2556
+ cur.push(piece);
2557
+ used += n2;
2558
+ }
2559
+ if (cur.length) out.push(cur.join(" "));
2560
+ return out;
2561
+ }
2562
+ function askedKinds(query) {
2563
+ const out = [];
2564
+ for (const [name, spec] of Object.entries(_nullishCoalesce(_chunkICUFS3B5cjs.HISTORY_SEARCH.asked_kinds, () => ( {})))) {
2565
+ if (name.startsWith("$") || typeof spec !== "object") continue;
2566
+ if (new RegExp(spec.query, "i").test(_nullishCoalesce(query, () => ( "")))) out.push(new RegExp(spec.value));
2567
+ }
2568
+ return out;
2569
+ }
2570
+ function asksHowItStarted(query) {
2571
+ const spec = _chunkICUFS3B5cjs.HISTORY_SEARCH.early_query;
2572
+ if (!_optionalChain([spec, 'optionalAccess', _67 => _67.pattern])) return false;
2573
+ return new RegExp(spec.pattern, (_nullishCoalesce(spec.flags, () => ( ""))).includes("i") ? "i" : "").test(_nullishCoalesce(query, () => ( "")));
2574
+ }
2575
+ async function searchTurns(turns, query, options = {}) {
2576
+ const params = _chunkICUFS3B5cjs.HISTORY_SEARCH;
2577
+ const k = params.max_excerpts;
2578
+ const skip = new Set(_nullishCoalesce(options.exclude, () => ( [])));
2579
+ const pool = turns.filter((t) => t.content && !skip.has(t.content) && !skip.has(_nullishCoalesce(t.message_id, () => ( ""))));
2580
+ const terms = new Set(tokenize2(query || ""));
2581
+ const literals = literalsOf(query);
2582
+ if (pool.length === 0 || terms.size === 0 && literals.length === 0 && !options.encoder) return [];
2583
+ const maxWords = params.max_words_per_excerpt;
2584
+ const units = [];
2585
+ pool.forEach((t, ti) => {
2586
+ if (!params.passages) {
2587
+ units.push({ seq: t.seq, content: t.content, turn: ti, first: true, last: true, part: -1 });
2588
+ return;
2589
+ }
2590
+ const parts = splitPassages(t.content, maxWords);
2591
+ parts.forEach((part, pi) => {
2592
+ units.push({
2593
+ seq: t.seq,
2594
+ content: part,
2595
+ turn: ti,
2596
+ first: pi === 0,
2597
+ last: pi === parts.length - 1,
2598
+ part: parts.length > 1 ? pi : -1
2599
+ });
2600
+ });
2601
+ });
2602
+ const docs = units.map((u) => tokenize2(u.content));
2603
+ const bm = new (0, _chunkICUFS3B5cjs.BM25)(docs);
2604
+ const termList = [...terms];
2605
+ const rows = units.map((turn, i) => {
2606
+ const held = new Set(docs[i]);
2607
+ let matched = 0;
2608
+ for (const t of terms) if (held.has(t)) matched += 1;
2609
+ const low = turn.content.toLowerCase();
2610
+ let literal = 0;
2611
+ for (const lit of literals) if (low.includes(lit)) literal += 1;
2612
+ return { i, bm25: matched ? bm.score(termList, i) : 0, matched, literal };
2613
+ });
2614
+ const need = Math.min(params.min_matched_terms, Math.max(1, terms.size));
2615
+ const rareCap = Math.max(1, Math.trunc((_nullishCoalesce(params.rare_term_max_share, () => ( 0))) * units.length));
2616
+ const rare = /* @__PURE__ */ new Set();
2617
+ for (const t of terms) {
2618
+ let n3 = 0;
2619
+ for (const doc of docs) if (doc.includes(t)) n3 += 1;
2620
+ if (n3 > 0 && n3 <= rareCap) rare.add(t);
2621
+ }
2622
+ let eligible;
2623
+ if (params.passages) {
2624
+ const turnTerms = /* @__PURE__ */ new Map();
2625
+ for (const r of rows) {
2626
+ const held = _nullishCoalesce(turnTerms.get(units[r.i].turn), () => ( /* @__PURE__ */ new Set()));
2627
+ for (const t of docs[r.i]) if (terms.has(t)) held.add(t);
2628
+ turnTerms.set(units[r.i].turn, held);
2629
+ }
2630
+ eligible = rows.filter(
2631
+ (r) => r.literal > 0 || r.matched > 0 && (turnTerms.get(units[r.i].turn).size >= need || docs[r.i].some((t) => rare.has(t)))
2632
+ );
2633
+ } else {
2634
+ eligible = rows.filter(
2635
+ (r) => r.literal > 0 || r.matched >= need || r.matched > 0 && docs[r.i].some((t) => rare.has(t))
2636
+ );
2637
+ }
2638
+ const rrf = params.rrf_k;
2639
+ const fused = /* @__PURE__ */ new Map();
2640
+ const via = /* @__PURE__ */ new Map();
2641
+ for (const r of eligible) fused.set(r.i, 0);
2642
+ const add = (i, rank, how) => {
2643
+ fused.set(i, (_nullishCoalesce(fused.get(i), () => ( 0))) + 1 / (rrf + rank + 1));
2644
+ if (!via.has(i)) via.set(i, how);
2645
+ };
2646
+ eligible.filter((r) => r.literal > 0).sort((a, b) => b.literal - a.literal || a.i - b.i).forEach((r, rank) => add(r.i, rank, "literal"));
2647
+ eligible.filter((r) => r.bm25 > 0).sort((a, b) => b.bm25 - a.bm25 || a.i - b.i).forEach((r, rank) => add(r.i, rank, "bm25"));
2648
+ const kinds = askedKinds(query);
2649
+ if (kinds.length) {
2650
+ rows.filter((r) => r.matched > 0 && kinds.some((k2) => k2.test(units[r.i].content))).sort((a, b) => b.bm25 - a.bm25 || a.i - b.i).forEach((r, rank) => add(r.i, rank, "kind"));
2651
+ }
2652
+ if (options.encoder) {
2653
+ const ranked2 = await embeddingRanks(units, query, options.encoder, _nullishCoalesce(options.store, () => ( null)), _nullishCoalesce(params.min_cosine, () => ( 0.4)));
2654
+ ranked2.forEach(([i], rank) => add(i, rank, "embedding"));
2655
+ }
2656
+ if (fused.size === 0) return [];
2657
+ const n2 = Math.max(pool.length - 1, 1);
2658
+ const weight = asksHowItStarted(query) ? 0 : params.recency_weight;
2659
+ const top = /* @__PURE__ */ new Map();
2660
+ const ranked = /* @__PURE__ */ new Map();
2661
+ for (const [u, v] of fused) {
2662
+ const ti = units[u].turn;
2663
+ const score = v + weight * (ti / n2) / (rrf + 1);
2664
+ ranked.set(ti, [..._nullishCoalesce(ranked.get(ti), () => ( [])), [score, u]]);
2665
+ const held = top.get(ti);
2666
+ if (!held || score > held[0]) top.set(ti, [score, u]);
2667
+ }
2668
+ const best = [...top.keys()].sort((a, b) => top.get(b)[0] - top.get(a)[0] || a - b).slice(0, k);
2669
+ const perTurn = _nullishCoalesce(params.passages_per_turn, () => ( 1));
2670
+ const shown = (ti) => {
2671
+ const unit = units[top.get(ti)[1]];
2672
+ if (!params.passages || unit.first && unit.last) return excerptOf(pool[ti].content, terms, literals, maxWords);
2673
+ const picked = [...ranked.get(ti)].sort((a, b) => b[0] - a[0] || b[1] - a[1]).slice(0, perTurn).map(([, u]) => u).sort((a, b) => a - b);
2674
+ const parts = [];
2675
+ for (const u of picked) parts.push((units[u].first || parts.length ? "" : "... ") + units[u].content);
2676
+ return parts.join(" ... ") + (units[picked[picked.length - 1]].last ? "" : " ...");
2677
+ };
2678
+ const found = /* @__PURE__ */ new Map();
2679
+ for (const ti of best) {
2680
+ found.set(ti, {
2681
+ position: pool[ti].position,
2682
+ role: pool[ti].role,
2683
+ timestamp: pool[ti].timestamp,
2684
+ score: top.get(ti)[0],
2685
+ via: _nullishCoalesce(via.get(top.get(ti)[1]), () => ( "")),
2686
+ text: shown(ti)
2687
+ });
2688
+ }
2689
+ const before2 = _nullishCoalesce(params.previous_turns, () => ( 0));
2690
+ const tail = _nullishCoalesce(params.previous_turn_words, () => ( 40));
2691
+ for (const i of best) {
2692
+ for (let j = i - before2; j < i; j++) {
2693
+ if (j < 0 || found.has(j)) continue;
2694
+ const hit = pool[i].position;
2695
+ const prev = pool[j].position;
2696
+ if (hit && prev && hit - prev > before2) continue;
2697
+ const w = words(pool[j].content);
2698
+ found.set(j, {
2699
+ position: prev,
2700
+ role: pool[j].role,
2701
+ timestamp: pool[j].timestamp,
2702
+ score: 0,
2703
+ via: "previous",
2704
+ text: w.length <= tail ? w.join(" ") : "... " + w.slice(-tail).join(" ")
2705
+ });
2706
+ }
2707
+ }
2708
+ return [...found.keys()].sort((a, b) => a - b).map((i) => found.get(i));
2709
+ }
2710
+ async function embeddingRanks(pool, query, encoder, store, minCosine) {
2711
+ const keyOf = (u) => (_nullishCoalesce(u.part, () => ( -1))) < 0 ? encoder.model : `${encoder.model}#p${u.part}`;
2712
+ const vectors = /* @__PURE__ */ new Map();
2713
+ if (store !== null) {
2714
+ const byKey = /* @__PURE__ */ new Map();
2715
+ pool.forEach((u, i) => byKey.set(keyOf(u), [..._nullishCoalesce(byKey.get(keyOf(u)), () => ( [])), i]));
2716
+ for (const [key, idx] of byKey) {
2717
+ const held = store.vectors(idx.map((i) => pool[i].seq), key);
2718
+ for (const i of idx) {
2719
+ const v = held.get(pool[i].seq);
2720
+ if (v) vectors.set(i, v);
2721
+ }
2722
+ }
2723
+ }
2724
+ const missing = pool.map((_, i) => i).filter((i) => !vectors.has(i));
2725
+ if (missing.length > 0) {
2726
+ const fresh = await encoder.encode(missing.map((i) => pool[i].content));
2727
+ const put = /* @__PURE__ */ new Map();
2728
+ missing.forEach((i, j) => {
2729
+ const v = Float32Array.from(_nullishCoalesce(fresh[j], () => ( [])));
2730
+ vectors.set(i, v);
2731
+ const key = keyOf(pool[i]);
2732
+ put.set(key, (_nullishCoalesce(put.get(key), () => ( /* @__PURE__ */ new Map()))).set(pool[i].seq, v));
2733
+ });
2734
+ if (store) for (const [key, items] of put) store.putVectors(key, items);
2735
+ }
2736
+ const q = await _asyncNullishCoalesce((await (_nullishCoalesce(encoder.encodeQuery, () => ( encoder.encode))).call(encoder, [query]))[0], async () => ( []));
2737
+ return pool.map((_, i) => [i, cosine(q, _nullishCoalesce(vectors.get(i), () => ( [])))]).filter(([, c]) => c >= minCosine).sort((a, b) => b[1] - a[1]);
2738
+ }
2739
+ function toolResultText(result, toolName) {
2740
+ let text = typeof result === "string" ? result : _nullishCoalesce(JSON.stringify(result), () => ( String(result)));
2741
+ const limit = _nullishCoalesce(_chunkICUFS3B5cjs.HISTORY_SEARCH.tool_result_max_chars, () => ( 16e3));
2742
+ if (text.length > limit) text = text.slice(0, limit) + " ...[cut]";
2743
+ return `[tool result: ${toolName || "tool"}] ${text}`;
2744
+ }
2745
+ function renderExcerpts(excerpts, latest = "") {
2746
+ if (excerpts.length === 0) return "";
2747
+ const lines = [_chunkICUFS3B5cjs.HISTORY_SEARCH.heading];
2748
+ for (const e of excerpts) {
2749
+ let where = e.position ? `turn ${e.position}, ${e.role}` : `${e.role}, earlier`;
2750
+ if (e.timestamp) {
2751
+ const ago = before(e.timestamp, latest);
2752
+ where += `, ${e.timestamp}` + (ago ? ` (${ago})` : "");
2753
+ }
2754
+ lines.push(`- ${where}: "${e.text}"`);
2755
+ }
2756
+ return lines.join("\n");
2757
+ }
2758
+ var ZONED = /(?:Z|[+-]\d{2}:\d{2})$/;
2759
+ function instant(stamp) {
2760
+ const text = (_nullishCoalesce(stamp, () => ( ""))).trim();
2761
+ if (!ZONED.test(text)) return null;
2762
+ const ms = Date.parse(text);
2763
+ return Number.isNaN(ms) ? null : ms / 1e3;
2764
+ }
2765
+ function before(stamp, latest) {
2766
+ const at = instant(stamp);
2767
+ const now = instant(latest);
2768
+ if (at === null || now === null || now < at) return "";
2769
+ const seconds = Math.trunc(now - at);
2770
+ for (const [unit, size] of [["day", 86400], ["hour", 3600], ["minute", 60]]) {
2771
+ if (seconds >= size) {
2772
+ const n2 = Math.trunc(seconds / size);
2773
+ return `${n2} ${unit}${n2 !== 1 ? "s" : ""} before the latest message`;
2774
+ }
2775
+ }
2776
+ return "less than a minute before the latest message";
2777
+ }
2778
+
1856
2779
  // src/context/overlay.ts
1857
2780
  var MAX_TURNS = 20;
1858
2781
  var TOKEN_BUDGET = 2e3;
@@ -1881,7 +2804,23 @@ function overlayLocalRecentTurns(response, store, conversationId, configured) {
1881
2804
  if (entry === null) return;
1882
2805
  const existing = _nullishCoalesce(response.conversation_context, () => ( null));
1883
2806
  if (existing !== null && !verbatimOverlayEnabled(configured)) return;
1884
- const localTurns = budgetRecentTurns(entry.recentTurns);
2807
+ const serverCompaction = existing === null ? null : existing["compaction_id"];
2808
+ const localIsWhole = isComplete(entry) && (!serverCompaction || serverCompaction === entry.compactionId);
2809
+ let sourceTurns = entry.recentTurns;
2810
+ if (existing !== null && !localIsWhole) {
2811
+ const raw = Array.isArray(existing["recent_turns"]) ? existing["recent_turns"] : [];
2812
+ const serverTurns = raw.map((t) => ({
2813
+ role: String(_nullishCoalesce(t["role"], () => ( "user"))),
2814
+ content: String(_nullishCoalesce(t["content"], () => ( ""))),
2815
+ timestamp: String(_nullishCoalesce(t["timestamp"], () => ( "")))
2816
+ }));
2817
+ sourceTurns = [...serverTurns, ...turnsAfter(entry.recentTurns, serverTurns)];
2818
+ }
2819
+ const localTurns = budgetRecentTurns(sourceTurns).map((t) => ({
2820
+ role: t.role,
2821
+ content: t.content,
2822
+ timestamp: t.timestamp
2823
+ }));
1885
2824
  const serverField = (name) => existing === null ? void 0 : existing[name];
1886
2825
  const orLocal = (name, local) => {
1887
2826
  const value = serverField(name);
@@ -1898,7 +2837,7 @@ function overlayLocalRecentTurns(response, store, conversationId, configured) {
1898
2837
  compacted_at: orLocal("compacted_at", entry.compactedAt),
1899
2838
  conversation_id: conversationId
1900
2839
  };
1901
- } catch (e10) {
2840
+ } catch (e13) {
1902
2841
  }
1903
2842
  }
1904
2843
 
@@ -1911,7 +2850,7 @@ function stringify(value) {
1911
2850
  if (t === "string" || t === "number" || t === "boolean") return String(value);
1912
2851
  try {
1913
2852
  return _nullishCoalesce(JSON.stringify(value), () => ( ""));
1914
- } catch (e11) {
2853
+ } catch (e14) {
1915
2854
  return String(value);
1916
2855
  }
1917
2856
  }
@@ -1936,14 +2875,28 @@ function buildRecentMessages(turns) {
1936
2875
  role: t.role || "user",
1937
2876
  content: _nullishCoalesce(t.content, () => ( "")),
1938
2877
  timestamp: t.timestamp,
1939
- message_id: `local-${idx}`
2878
+ message_id: t.message_id || `local-${idx}`
1940
2879
  }));
1941
2880
  }
1942
2881
  function compactedMessageCount(_entry) {
1943
2882
  return 0;
1944
2883
  }
2884
+ function blockOf(entry) {
2885
+ if (entry.contextBlock) return entry.contextBlock.trim();
2886
+ if (entry.summary && entry.summary.startsWith(CONTEXT_BLOCK_PREFIX)) return entry.summary.trim();
2887
+ return null;
2888
+ }
1945
2889
  function formatStructured(entry, recent) {
1946
2890
  const parts = [];
2891
+ const block = blockOf(entry);
2892
+ if (block) {
2893
+ parts.push(block);
2894
+ if (recent.length > 0) {
2895
+ parts.push(`## Recent Turns (${recent.length})`);
2896
+ for (const m of recent) parts.push(`**${m.role}**: ${m.content}`);
2897
+ }
2898
+ return parts.filter(Boolean).join("\n\n").trim();
2899
+ }
1947
2900
  if (entry.summary) {
1948
2901
  parts.push("## Summary");
1949
2902
  parts.push(entry.summary.trim());
@@ -1978,9 +2931,11 @@ function formatStructured(entry, recent) {
1978
2931
  }
1979
2932
  function formatNarrative(entry, recent) {
1980
2933
  const parts = [];
1981
- if (entry.summary) parts.push(entry.summary.trim());
1982
- if (entry.conversationalParagraph) parts.push(entry.conversationalParagraph.trim());
1983
- else if (entry.factualParagraph) parts.push(entry.factualParagraph.trim());
2934
+ const block = blockOf(entry);
2935
+ if (block) parts.push(block);
2936
+ else if (entry.summary) parts.push(entry.summary.trim());
2937
+ if (!block && entry.conversationalParagraph) parts.push(entry.conversationalParagraph.trim());
2938
+ else if (!block && entry.factualParagraph) parts.push(entry.factualParagraph.trim());
1984
2939
  if (recent.length > 0) {
1985
2940
  const tail = recent.map((m) => `${m.role}: ${m.content}`).join("\n");
1986
2941
  parts.push(`Recent exchanges:
@@ -1990,6 +2945,10 @@ ${tail}`);
1990
2945
  }
1991
2946
  function formatBullets(entry, recent) {
1992
2947
  const parts = [];
2948
+ const block = blockOf(entry);
2949
+ if (block) {
2950
+ return [block, ...recent.map((m) => `- ${m.role}: ${m.content}`)].join("\n").trim();
2951
+ }
1993
2952
  if (entry.summary) parts.push(`- Summary: ${entry.summary.trim()}`);
1994
2953
  for (const [k, v] of Object.entries(_nullishCoalesce(entry.currentState, () => ( {})))) {
1995
2954
  parts.push(`- ${k}: ${stringify(v)}`);
@@ -2027,7 +2986,9 @@ function renderForPrompt(entry, style = DEFAULT_STYLE, now = Date.now) {
2027
2986
  recent_messages: recent,
2028
2987
  recent_message_count: recent.length,
2029
2988
  compacted_message_count: compacted,
2030
- total_message_count: recent.length + compacted
2989
+ total_message_count: recent.length + compacted,
2990
+ context_block: blockOf(entry),
2991
+ compaction_id: entry.compactionId
2031
2992
  };
2032
2993
  }
2033
2994
  function renderCompacted(entry, format = "structured", correlationId = "") {
@@ -2172,10 +3133,10 @@ function servedItemIds(hit) {
2172
3133
  }
2173
3134
  return ids;
2174
3135
  }
2175
- function tryServeFromCache(cache, scope, params, maxItems) {
3136
+ async function tryServeFromCache(cache, scope, params, maxItems) {
2176
3137
  try {
2177
3138
  const scoped = scopeLookupParams(scope, params);
2178
- const hit = cache.lookup({
3139
+ const hit = await cache.lookupAsync({
2179
3140
  searchQuery: _nullishCoalesce(scoped.searchQuery, () => ( null)),
2180
3141
  entityId: _nullishCoalesce(scoped.entityId, () => ( null)),
2181
3142
  customerId: _nullishCoalesce(scoped.customerId, () => ( null)),
@@ -2186,7 +3147,7 @@ function tryServeFromCache(cache, scope, params, maxItems) {
2186
3147
  });
2187
3148
  if (hit === null) return { response: null, hit: null };
2188
3149
  return { response: buildAnticipationResponse(hit), hit };
2189
- } catch (e12) {
3150
+ } catch (e15) {
2190
3151
  return { response: null, hit: null };
2191
3152
  }
2192
3153
  }
@@ -2263,6 +3224,8 @@ function mergeScopeResults(scopeResults) {
2263
3224
  episodes: [],
2264
3225
  emotions: [],
2265
3226
  temporal_events: [],
3227
+ items: [],
3228
+ excerpts: [],
2266
3229
  scope_map: {},
2267
3230
  conversation_context: null,
2268
3231
  profile: null,
@@ -2272,6 +3235,8 @@ function mergeScopeResults(scopeResults) {
2272
3235
  total_items: 0,
2273
3236
  metadata: null
2274
3237
  };
3238
+ const rankedSeen = /* @__PURE__ */ new Set();
3239
+ const rankedItems = [];
2275
3240
  for (const [scopeName, response] of scopeResults) {
2276
3241
  out.scopes_queried.push(scopeName);
2277
3242
  if (out.metadata === null && response.metadata !== void 0) {
@@ -2296,8 +3261,32 @@ function mergeScopeResults(scopeResults) {
2296
3261
  out[collection].push(item);
2297
3262
  }
2298
3263
  }
3264
+ const excerpts = response["excerpts"];
3265
+ if (Array.isArray(excerpts)) {
3266
+ for (const item of excerpts) {
3267
+ const id = itemId2(item);
3268
+ if (id !== void 0) {
3269
+ if (seen.has(id)) continue;
3270
+ seen.add(id);
3271
+ out.scope_map[id] = scopeName;
3272
+ }
3273
+ out.excerpts.push(item);
3274
+ }
3275
+ }
3276
+ const ranked = response["items"];
3277
+ if (Array.isArray(ranked)) {
3278
+ for (const item of ranked) {
3279
+ const id = itemId2(item);
3280
+ if (id !== void 0) {
3281
+ if (rankedSeen.has(id)) continue;
3282
+ rankedSeen.add(id);
3283
+ }
3284
+ rankedItems.push(item);
3285
+ }
3286
+ }
2299
3287
  }
2300
- out.total_items = ITEM_COLLECTIONS.reduce((n2, c) => n2 + out[c].length, 0);
3288
+ out.items = sortByRankScore(rankedItems).map((item, n2) => ({ ...item, rank: n2 + 1 }));
3289
+ out.total_items = ITEM_COLLECTIONS.reduce((n2, c) => n2 + out[c].length, 0) + out.excerpts.length;
2301
3290
  return out;
2302
3291
  }
2303
3292
  function oneDecimal(value) {
@@ -2323,7 +3312,18 @@ function formatForPrompt(ctx, options = {}) {
2323
3312
  const includeScope = _nullishCoalesce(options.includeScope, () => ( false));
2324
3313
  const includeConversationContext = _nullishCoalesce(options.includeConversationContext, () => ( true));
2325
3314
  const sections = [];
2326
- if (ctx.facts.length > 0) {
3315
+ const ranked = _nullishCoalesce(ctx.items, () => ( []));
3316
+ if (ranked.length > 0) {
3317
+ const lines = ranked.map((it, n2) => {
3318
+ let label = rankedLabel(String(_nullishCoalesce(it["category"], () => ( ""))));
3319
+ if (includeScope) label += `, scope: ${scopeOf(ctx, it)}`;
3320
+ return `${n2 + 1}. [${label}] ${String(_nullishCoalesce(it.content, () => ( "")))}`;
3321
+ });
3322
+ sections.push(`### Memories
3323
+ ${lines.join("\n")}`);
3324
+ }
3325
+ const typed = ranked.length === 0;
3326
+ if (typed && ctx.facts.length > 0) {
2327
3327
  const lines = ctx.facts.map((f) => {
2328
3328
  let line = `- ${String(_nullishCoalesce(f.content, () => ( "")))}`;
2329
3329
  if (includeScope) line += ` [scope: ${scopeOf(ctx, f)}]`;
@@ -2332,7 +3332,7 @@ function formatForPrompt(ctx, options = {}) {
2332
3332
  sections.push(`### Facts
2333
3333
  ${lines.join("\n")}`);
2334
3334
  }
2335
- if (ctx.preferences.length > 0) {
3335
+ if (typed && ctx.preferences.length > 0) {
2336
3336
  const lines = ctx.preferences.map((p) => {
2337
3337
  const extras = [];
2338
3338
  if (includeScope) extras.push(`scope: ${scopeOf(ctx, p)}`);
@@ -2345,7 +3345,7 @@ ${lines.join("\n")}`);
2345
3345
  sections.push(`### Preferences
2346
3346
  ${lines.join("\n")}`);
2347
3347
  }
2348
- if (ctx.episodes.length > 0) {
3348
+ if (typed && ctx.episodes.length > 0) {
2349
3349
  const lines = ctx.episodes.map((e) => {
2350
3350
  let line = `- ${String(_nullishCoalesce(e["summary"], () => ( "")))}`;
2351
3351
  if (includeScope) line += ` [scope: ${scopeOf(ctx, e)}]`;
@@ -2354,7 +3354,7 @@ ${lines.join("\n")}`);
2354
3354
  sections.push(`### Episodes
2355
3355
  ${lines.join("\n")}`);
2356
3356
  }
2357
- if (ctx.emotions.length > 0) {
3357
+ if (typed && ctx.emotions.length > 0) {
2358
3358
  const lines = ctx.emotions.map((em) => {
2359
3359
  const extras = [];
2360
3360
  if (includeScope) extras.push(`scope: ${scopeOf(ctx, em)}`);
@@ -2368,7 +3368,7 @@ ${lines.join("\n")}`);
2368
3368
  sections.push(`### Emotions
2369
3369
  ${lines.join("\n")}`);
2370
3370
  }
2371
- if (ctx.temporal_events.length > 0) {
3371
+ if (typed && ctx.temporal_events.length > 0) {
2372
3372
  const lines = ctx.temporal_events.map((te) => {
2373
3373
  const extras = [];
2374
3374
  if (includeScope) extras.push(`scope: ${scopeOf(ctx, te)}`);
@@ -2622,7 +3622,10 @@ function buildTool(target, options = {}, onWarning = () => {
2622
3622
  }
2623
3623
 
2624
3624
  // src/client.ts
2625
- var SynapClient = class {
3625
+ function localModelOff() {
3626
+ return ["off", "0", "false", "no", "none"].includes((_nullishCoalesce(_chunkZKOJDUN4cjs.getEnv.call(void 0, "SYNAP_SDK_LOCAL_MODEL"), () => ( ""))).trim().toLowerCase());
3627
+ }
3628
+ var SynapClient = (_class4 = class _SynapClient {
2626
3629
  // Native-private (`#`), not TypeScript `private`. TS `private` is erased at
2627
3630
  // compile time, so the field stays enumerable at runtime and a JS consumer
2628
3631
  // can still reach it. These two were public and made `client.transport
@@ -2648,7 +3651,16 @@ var SynapClient = class {
2648
3651
  #registryKeys;
2649
3652
  #compactionSubscribers;
2650
3653
  #shortTerm;
3654
+ /** Every raw turn, past compaction, for local history search. */
3655
+ #turnStore;
3656
+ /** A local embedding model for history search; null = keywords only. */
3657
+ #localEncoder = null;
3658
+ /** `history_search_model`: "auto", an "onnx:..." name, or null for none. */
3659
+ #localModelName = "auto";
3660
+ #localModelStarted = false;
3661
+ #backfillAfter = /* @__PURE__ */ new Map();
2651
3662
  #stVerbatimOverlay;
3663
+ #agentModel = {};
2652
3664
  #logger;
2653
3665
  #stAuthoritative;
2654
3666
  #turns;
@@ -2686,13 +3698,13 @@ var SynapClient = class {
2686
3698
  userAgent: `@maximem/synap-js-sdk/${SDK_VERSION}`
2687
3699
  });
2688
3700
  if (options.heartbeat) this.#transport.startHeartbeat();
2689
- this.#anticipationCache = new (0, _chunkT6U5PVADcjs.AnticipationCache)();
3701
+ this.#anticipationCache = new (0, _chunkICUFS3B5cjs.AnticipationCache)();
2690
3702
  this.#fetchHooks = {
2691
- beforeFetch: (scope, options2) => {
3703
+ beforeFetch: async (scope, options2) => {
2692
3704
  const o2 = options2;
2693
3705
  if (_nullishCoalesce(o2["query_date"], () => ( o2["queryDate"]))) return null;
2694
3706
  if (_nullishCoalesce(o2["max_results_per_query"], () => ( o2["maxResultsPerQuery"]))) return null;
2695
- const attempt = tryServeFromCache(
3707
+ const attempt = await tryServeFromCache(
2696
3708
  this.#anticipationCache,
2697
3709
  scope,
2698
3710
  {
@@ -2706,7 +3718,7 @@ var SynapClient = class {
2706
3718
  // on entity and conversation alone, so a bundle prefetched for one
2707
3719
  // rung answered a caller standing at another, entirely inside this
2708
3720
  // process and invisible to every server log.
2709
- scopeRung: _chunkT6U5PVADcjs.rungKey.call(void 0,
3721
+ scopeRung: _chunkICUFS3B5cjs.rungKey.call(void 0,
2710
3722
  _nullishCoalesce(o2["scope_path"], () => ( o2["scopePath"]))
2711
3723
  )
2712
3724
  },
@@ -2716,7 +3728,9 @@ var SynapClient = class {
2716
3728
  return {
2717
3729
  response: attempt.response,
2718
3730
  servedItemIds: servedItemIds(attempt.hit),
2719
- bundleId: _nullishCoalesce(attempt.hit.bundleIds[0], () => ( ""))
3731
+ bundleId: _nullishCoalesce(attempt.hit.bundleIds[0], () => ( "")),
3732
+ borderline: attempt.hit.band === "borderline",
3733
+ raceDeadlineMs: _chunkICUFS3B5cjs.SEMANTIC_MATCH.race_deadline_ms
2720
3734
  };
2721
3735
  },
2722
3736
  onAssembled: (scope, options2, response, startedAt) => {
@@ -2731,9 +3745,19 @@ var SynapClient = class {
2731
3745
  this.client = { context: { fetch: async (o2 = {}) => fetchContext(this.#transport, "client", o2, this.#fetchHooks) } };
2732
3746
  this.#compactionSubscribers = new CompactionSubscribers();
2733
3747
  this.#shortTerm = new ShortTermStore();
3748
+ this.#turnStore = new TurnStore();
3749
+ this.#shortTerm.attachArchive(this.#turnStore);
2734
3750
  this.#stVerbatimOverlay = _nullishCoalesce(options.st_verbatim_overlay, () => ( options.stVerbatimOverlay));
3751
+ this.#agentModel = {
3752
+ model: _nullishCoalesce(options.agent_model, () => ( options.agentModel)),
3753
+ contextWindow: _nullishCoalesce(options.agent_context_window, () => ( options.agentContextWindow))
3754
+ };
2735
3755
  this.#logger = options.logger;
2736
3756
  this.#stAuthoritative = _nullishCoalesce(options.sdk_st_authoritative, () => ( options.sdkStAuthoritative));
3757
+ this.#localEncoder = _nullishCoalesce(_nullishCoalesce(options.history_search_encoder, () => ( options.historySearchEncoder)), () => ( null));
3758
+ const modelOption = options.history_search_model !== void 0 ? options.history_search_model : options.historySearchModel;
3759
+ this.#localModelName = modelOption === void 0 ? "auto" : modelOption;
3760
+ if (_optionalChain([this, 'access', _68 => _68.#localEncoder, 'optionalAccess', _69 => _69.model]) === _chunkICUFS3B5cjs.SEMANTIC_MATCH.model) this.#anticipationCache.setEncoder(this.#localEncoder);
2737
3761
  this.#turns = new TurnCounter();
2738
3762
  this.conversation = createConversationNamespace(
2739
3763
  this.#transport,
@@ -2750,17 +3774,24 @@ var SynapClient = class {
2750
3774
  },
2751
3775
  // Buffer the turn locally so the next fetch sees it, ahead of the server
2752
3776
  // having persisted and compacted it.
2753
- ({ conversationId, role, content, timestamp }) => {
2754
- this.#shortTerm.appendTurn(conversationId, role, content, timestamp);
3777
+ ({ conversationId, role, content, timestamp, messageId, serverMessageCount, inFlight }) => {
3778
+ this.#shortTerm.appendTurn(conversationId, role, content, timestamp, {
3779
+ messageId,
3780
+ serverMessageCount,
3781
+ inFlight
3782
+ });
2755
3783
  },
2756
3784
  (response, conversationId, userId, scopePath) => {
2757
3785
  overlayLocalRecentTurns(response, this.#shortTerm, conversationId, this.#stVerbatimOverlay);
2758
- this.#injectUserSummary(response, conversationId, userId, _chunkT6U5PVADcjs.rungKey.call(void 0, scopePath));
3786
+ this.#injectUserSummary(response, conversationId, userId, _chunkICUFS3B5cjs.rungKey.call(void 0, scopePath));
2759
3787
  },
2760
3788
  {
2761
3789
  prompt: (conversationId, style) => this.#localPrompt(conversationId, style),
2762
- compacted: (conversationId, format) => this.#localCompacted(conversationId, format)
2763
- }
3790
+ compacted: (conversationId, format) => this.#localCompacted(conversationId, format),
3791
+ reseed: (conversationId, response) => this.#reseedShortTerm(conversationId, response),
3792
+ historyExcerpts: (conversationId, response, query) => this.#withHistoryExcerpts(conversationId, response, query)
3793
+ },
3794
+ () => this.#agentModel
2764
3795
  );
2765
3796
  this.memories = createMemoriesInterface(this.#transport, ({ userId, customerId }) => {
2766
3797
  for (const id of /* @__PURE__ */ new Set([userId, customerId])) {
@@ -2774,11 +3805,17 @@ var SynapClient = class {
2774
3805
  this.#anticipationCache,
2775
3806
  ({ conversationId, role, content }) => {
2776
3807
  this.#shortTerm.appendTurn(conversationId, role, content);
2777
- }
3808
+ },
3809
+ () => this.#agentModel,
3810
+ ({ conversationId, result, toolName }) => this.archive_tool_result(conversationId, result, { tool_name: toolName })
2778
3811
  );
3812
+ this.instance._internal.setOnListen(() => this.#startLocalModel());
2779
3813
  this.instance._internal.setCompactionApplier((bundle) => {
2780
3814
  this.#shortTerm.applyCompaction(bundle);
2781
3815
  });
3816
+ this.instance._internal.setConversationStateHandler((conversationId, messageCount) => {
3817
+ this.#shortTerm.noteServerCount(conversationId, messageCount);
3818
+ });
2782
3819
  if (options._force_new !== true) {
2783
3820
  const incumbent = registerIfAbsent(registryKey, this);
2784
3821
  if (incumbent !== void 0) return incumbent;
@@ -2896,7 +3933,7 @@ var SynapClient = class {
2896
3933
  this.#registryKeys.push(resolvedInstanceId);
2897
3934
  }
2898
3935
  }
2899
- } catch (e13) {
3936
+ } catch (e16) {
2900
3937
  }
2901
3938
  }
2902
3939
  /**
@@ -2938,6 +3975,14 @@ var SynapClient = class {
2938
3975
  this.#transport.setRetryPolicy(_nullishCoalesce(_nullishCoalesce(options.retryPolicy, () => ( options.retry_policy)), () => ( null)));
2939
3976
  }
2940
3977
  if (options.logger !== void 0) this.#logger = options.logger;
3978
+ const model = _nullishCoalesce(options.agent_model, () => ( options.agentModel));
3979
+ const window = _nullishCoalesce(options.agent_context_window, () => ( options.agentContextWindow));
3980
+ if (model !== void 0 || window !== void 0) {
3981
+ this.#agentModel = {
3982
+ model: _nullishCoalesce(model, () => ( this.#agentModel.model)),
3983
+ contextWindow: _nullishCoalesce(window, () => ( this.#agentModel.contextWindow))
3984
+ };
3985
+ }
2941
3986
  }
2942
3987
  /**
2943
3988
  * Fetch and merge context across every scope with an identifier, in one call.
@@ -2971,9 +4016,11 @@ var SynapClient = class {
2971
4016
  include_scope_labels: includeScopeLabels = false,
2972
4017
  context_mode: contextMode = "in-conversation",
2973
4018
  include_profile: includeProfile = true,
2974
- last_n_conversations: lastNConversations = 1
4019
+ last_n_conversations: lastNConversations = 1,
4020
+ response_shape: responseShape = "by_type"
2975
4021
  } = options;
2976
4022
  validateConversationId(conversationId);
4023
+ responseShapeOf({ response_shape: responseShape });
2977
4024
  const wants = (scope) => scopes === void 0 || scopes.includes(scope);
2978
4025
  const shared = {
2979
4026
  search_query: searchQuery,
@@ -2985,7 +4032,8 @@ var SynapClient = class {
2985
4032
  // threaded three call sites by hand and missed the client branch, which
2986
4033
  // sits one indent deeper; spreading removes that whole class of miss.
2987
4034
  query_date: queryDate,
2988
- max_results_per_query: maxResultsPerQuery
4035
+ max_results_per_query: maxResultsPerQuery,
4036
+ response_shape: responseShape
2989
4037
  };
2990
4038
  const labels = [];
2991
4039
  const tasks = [];
@@ -3050,7 +4098,7 @@ var SynapClient = class {
3050
4098
  merged.conversation_context = await this.conversation.context.get_context_for_prompt({
3051
4099
  conversation_id: conversationId
3052
4100
  });
3053
- } catch (e14) {
4101
+ } catch (e17) {
3054
4102
  }
3055
4103
  }
3056
4104
  merged.formatted_context = formatForPrompt(merged, {
@@ -3102,7 +4150,7 @@ var SynapClient = class {
3102
4150
  // not carry the caller's words.
3103
4151
  query_count: distinctQueryCount(_nullishCoalesce(options["search_query"], () => ( options["searchQuery"])))
3104
4152
  });
3105
- } catch (e15) {
4153
+ } catch (e18) {
3106
4154
  }
3107
4155
  }
3108
4156
  /**
@@ -3127,7 +4175,7 @@ var SynapClient = class {
3127
4175
  }),
3128
4176
  scope
3129
4177
  });
3130
- } catch (e16) {
4178
+ } catch (e19) {
3131
4179
  }
3132
4180
  }
3133
4181
  /**
@@ -3148,25 +4196,139 @@ var SynapClient = class {
3148
4196
  /**
3149
4197
  * Whether the SDK is authoritative for short-term context.
3150
4198
  *
3151
- * Resolution order matches Python's `_is_st_authoritative`: the explicit
3152
- * option first, then `SYNAP_SDK_ST_AUTHORITATIVE` (truthy strings only).
3153
- * Defaults to false, so nothing changes for a caller who never sets it.
4199
+ * Resolution order matches Python's `_is_st_authoritative`:
4200
+ * `SYNAP_SDK_ST_AUTHORITATIVE` first, as an explicit override in either
4201
+ * direction, then the option. Off by default. Being enabled does not mean
4202
+ * the local store is used: that also takes an entry that holds the whole
4203
+ * conversation, see `isComplete`.
3154
4204
  */
3155
4205
  #isStAuthoritative() {
3156
- if (this.#stAuthoritative === true) return true;
3157
4206
  const raw = (_nullishCoalesce(_chunkZKOJDUN4cjs.getEnv.call(void 0, "SYNAP_SDK_ST_AUTHORITATIVE"), () => ( ""))).trim().toLowerCase();
3158
- return ["1", "true", "yes", "on"].includes(raw);
4207
+ if (["0", "false", "no", "off"].includes(raw)) return false;
4208
+ if (["1", "true", "yes", "on"].includes(raw)) return true;
4209
+ return this.#stAuthoritative === true;
3159
4210
  }
3160
4211
  /**
3161
4212
  * Render `get_context_for_prompt` from the local store, or null to fall
3162
- * through to the network. Warm means a compaction landed OR raw turns are
3163
- * buffered: with neither there is nothing to render and the server's answer
3164
- * is the only real one.
4213
+ * through to the network.
4214
+ *
4215
+ * The store answers only when it holds the whole conversation: the server
4216
+ * has confirmed a count the entry accounts for. An entry that is merely
4217
+ * present is not enough. After a restart or an eviction it holds one turn,
4218
+ * and rendering that as the conversation drops everything said before it.
4219
+ */
4220
+ /**
4221
+ * `response` with the earlier turns that best match `query` (by default the
4222
+ * latest user message) appended, when a compaction exists. The search runs
4223
+ * here, over the turn archive. Python: `_with_history_excerpts`.
3165
4224
  */
4225
+ async #withHistoryExcerpts(conversationId, response, query) {
4226
+ const r = _nullishCoalesce(response, () => ( {}));
4227
+ const store = this.#turnStore;
4228
+ const recent = Array.isArray(r["recent_messages"]) ? r["recent_messages"] : [];
4229
+ store.addMany(
4230
+ conversationId,
4231
+ recent.map((m) => ({
4232
+ role: String(_nullishCoalesce(m["role"], () => ( "user"))),
4233
+ content: String(_nullishCoalesce(m["content"], () => ( ""))),
4234
+ timestamp: String(_nullishCoalesce(m["timestamp"], () => ( ""))),
4235
+ message_id: typeof m["message_id"] === "string" ? m["message_id"] : null
4236
+ }))
4237
+ );
4238
+ const entry = this.#shortTerm.get(conversationId);
4239
+ const total = Math.max(Number(_nullishCoalesce(r["total_message_count"], () => ( 0))) || 0, Number(_nullishCoalesce(_optionalChain([entry, 'optionalAccess', _70 => _70.serverMessageCount]), () => ( 0))) || 0);
4240
+ this.#maybeBackfillTurns(conversationId, total);
4241
+ if (!r["context_block"]) return response;
4242
+ const turns = store.turns(conversationId);
4243
+ let q = query;
4244
+ if (!q) {
4245
+ const latest = [...recent].reverse().find((m) => m["role"] === "user");
4246
+ q = latest ? String(_nullishCoalesce(latest["content"], () => ( ""))) : _nullishCoalesce(_optionalChain([[...turns], 'access', _71 => _71.reverse, 'call', _72 => _72(), 'access', _73 => _73.find, 'call', _74 => _74((t) => t.role === "user"), 'optionalAccess', _75 => _75.content]), () => ( ""));
4247
+ }
4248
+ if (!q) return response;
4249
+ const exclude = /* @__PURE__ */ new Set();
4250
+ for (const m of recent) {
4251
+ if (typeof m["message_id"] === "string" && m["message_id"]) exclude.add(m["message_id"]);
4252
+ exclude.add(String(_nullishCoalesce(m["content"], () => ( ""))));
4253
+ }
4254
+ this.#startLocalModel();
4255
+ const hits = await searchTurns(turns, q, { exclude, encoder: this.#localEncoder, store });
4256
+ if (hits.length === 0) return response;
4257
+ const block = renderExcerpts(hits, turns.length ? turns[turns.length - 1].timestamp : "");
4258
+ return {
4259
+ ...r,
4260
+ history_excerpts: hits.map((h) => ({ position: h.position, role: h.role, text: h.text })),
4261
+ history_block: block,
4262
+ formatted_context: (String(_nullishCoalesce(r["formatted_context"], () => ( ""))).trimEnd() + "\n\n" + block).trim()
4263
+ };
4264
+ }
4265
+ /**
4266
+ * Keep what a tool returned in this process's turn archive, so history search
4267
+ * can find it after a compaction. Never sent anywhere: the server's
4268
+ * conversation holds what people said, and a compaction is made from that.
4269
+ * `instance.record_tool_result` calls this too. Python: `archive_tool_result`.
4270
+ */
4271
+ archive_tool_result(conversationId, result, options = {}) {
4272
+ if (!conversationId || result === null || result === void 0) return;
4273
+ try {
4274
+ this.#turnStore.add(
4275
+ conversationId,
4276
+ "tool",
4277
+ toolResultText(result, _nullishCoalesce(options.tool_name, () => ( options.toolName))),
4278
+ _nullishCoalesce(options.timestamp, () => ( (/* @__PURE__ */ new Date()).toISOString()))
4279
+ );
4280
+ } catch (e20) {
4281
+ }
4282
+ }
4283
+ static __initStatic() {this.BACKFILL_COOLDOWN_MS = 6e4}
4284
+ static __initStatic2() {this.BACKFILL_PAGE = 500}
4285
+ /** Fetch, in the background, turns the server holds and the archive does not. */
4286
+ /**
4287
+ * Load the local embedding model in the background, once. Searches and
4288
+ * lookups before it is ready match by words. Python: `_start_history_encoder`.
4289
+ */
4290
+ #startLocalModel() {
4291
+ if (this.#localModelStarted) return;
4292
+ this.#localModelStarted = true;
4293
+ if (localModelOff()) return;
4294
+ const name = this.#localModelName === "auto" ? _chunkICUFS3B5cjs.SEMANTIC_MATCH.model : this.#localModelName;
4295
+ if (!name) return;
4296
+ const callerEncoder = this.#localEncoder;
4297
+ void Promise.resolve().then(() => _interopRequireWildcard(require("./onnx-encoder-4RSL6ID4.cjs"))).then(({ canRunLocalModel, loadOnnxEncoder }) => canRunLocalModel() ? loadOnnxEncoder(name) : null).then((encoder) => {
4298
+ if (encoder === null) return;
4299
+ if (callerEncoder === null) this.#localEncoder = encoder;
4300
+ if (encoder.model === _chunkICUFS3B5cjs.SEMANTIC_MATCH.model) this.#anticipationCache.setEncoder(encoder);
4301
+ }).catch(() => {
4302
+ });
4303
+ }
4304
+ #maybeBackfillTurns(conversationId, total) {
4305
+ if (total <= this.#turnStore.count(conversationId)) return;
4306
+ const now = Date.now();
4307
+ if (now < (_nullishCoalesce(this.#backfillAfter.get(conversationId), () => ( 0)))) return;
4308
+ this.#backfillAfter.set(conversationId, now + _SynapClient.BACKFILL_COOLDOWN_MS);
4309
+ void this.#backfillTurns(conversationId, total);
4310
+ }
4311
+ async #backfillTurns(conversationId, total) {
4312
+ try {
4313
+ let start = 1;
4314
+ while (start <= total) {
4315
+ const result = await this.#transport.request("conversation_turns", {
4316
+ pathParams: { conversation_id: conversationId },
4317
+ query: { start, limit: _SynapClient.BACKFILL_PAGE }
4318
+ });
4319
+ const page = _nullishCoalesce((_nullishCoalesce(result, () => ( {})))["turns"], () => ( []));
4320
+ this.#turnStore.addMany(conversationId, page);
4321
+ if (page.length < _SynapClient.BACKFILL_PAGE) break;
4322
+ start += _SynapClient.BACKFILL_PAGE;
4323
+ }
4324
+ } catch (error) {
4325
+ this.#warn(`Turn backfill for ${conversationId} failed: ${String(error)}`);
4326
+ }
4327
+ }
3166
4328
  #localPrompt(conversationId, style) {
3167
4329
  if (!this.#isStAuthoritative()) return null;
3168
4330
  const entry = this.#shortTerm.get(conversationId);
3169
- if (entry === null) return null;
4331
+ if (entry === null || !isComplete(entry)) return null;
3170
4332
  if (entry.compactionId === null && entry.recentTurns.length === 0) return null;
3171
4333
  return renderForPrompt(entry, style);
3172
4334
  }
@@ -3174,9 +4336,29 @@ var SynapClient = class {
3174
4336
  #localCompacted(conversationId, format) {
3175
4337
  if (!this.#isStAuthoritative()) return null;
3176
4338
  const entry = this.#shortTerm.get(conversationId);
3177
- if (entry === null || entry.compactionId === null) return null;
4339
+ if (entry === null || entry.compactionId === null || !isComplete(entry)) return null;
3178
4340
  return renderCompacted(entry, format);
3179
4341
  }
4342
+ /**
4343
+ * Bring the local store back in step from the server's answer.
4344
+ *
4345
+ * Only for a conversation this process writes to. The server's answer lists
4346
+ * every message its summary does not cover, which is exactly what the entry
4347
+ * was missing.
4348
+ */
4349
+ #reseedShortTerm(conversationId, response) {
4350
+ if (!this.#isStAuthoritative()) return;
4351
+ const entry = this.#shortTerm.get(conversationId);
4352
+ if (entry === null || !entry.participant) return;
4353
+ const r = _nullishCoalesce(response, () => ( {}));
4354
+ const recent = Array.isArray(r["recent_messages"]) ? r["recent_messages"] : [];
4355
+ this.#shortTerm.reseed(conversationId, {
4356
+ compactionId: typeof r["compaction_id"] === "string" ? r["compaction_id"] : null,
4357
+ recentMessages: recent,
4358
+ compactedMessageCount: Number(_nullishCoalesce(r["compacted_message_count"], () => ( 0))) || 0,
4359
+ totalMessageCount: Number(_nullishCoalesce(r["total_message_count"], () => ( 0))) || 0
4360
+ });
4361
+ }
3180
4362
  /** Every diagnostic the SDK emits passes through here. */
3181
4363
  #warn(message) {
3182
4364
  if (this.#logger !== void 0) {
@@ -3197,7 +4379,7 @@ var SynapClient = class {
3197
4379
  this.#transport.close();
3198
4380
  this.#anticipationCache.clear();
3199
4381
  }
3200
- };
4382
+ }, _class4.__initStatic(), _class4.__initStatic2(), _class4);
3201
4383
  function pick3(o2, snake, camel) {
3202
4384
  const a = o2[snake];
3203
4385
  return a !== void 0 ? a : o2[camel];
@@ -3205,7 +4387,7 @@ function pick3(o2, snake, camel) {
3205
4387
  function resolveBaseUrl(options) {
3206
4388
  const explicit = _nullishCoalesce(options.baseUrl, () => ( options.api_base_url));
3207
4389
  if (explicit !== void 0) return explicit;
3208
- const fromEnv = _optionalChain([_chunkZKOJDUN4cjs.getEnv.call(void 0, "SYNAP_BASE_URL"), 'optionalAccess', _54 => _54.trim, 'call', _55 => _55()]);
4390
+ const fromEnv = _optionalChain([_chunkZKOJDUN4cjs.getEnv.call(void 0, "SYNAP_BASE_URL"), 'optionalAccess', _76 => _76.trim, 'call', _77 => _77()]);
3209
4391
  return fromEnv !== void 0 && fromEnv !== "" ? fromEnv : void 0;
3210
4392
  }
3211
4393
  function resolveCredentials(options) {
@@ -3273,5 +4455,11 @@ function resolveCredentials(options) {
3273
4455
 
3274
4456
 
3275
4457
 
3276
- exports.ANY_SCOPE = _chunkT6U5PVADcjs.ANY_SCOPE; exports.AgentUnavailableError = _chunkT372OL3Qcjs.AgentUnavailableError; exports.AnticipationCache = _chunkT6U5PVADcjs.AnticipationCache; exports.AuthenticationError = _chunkT372OL3Qcjs.AuthenticationError; exports.BM25 = _chunkT6U5PVADcjs.BM25; exports.COMPACTION_LEVELS = COMPACTION_LEVELS; exports.CONTRACT = _chunkT6U5PVADcjs.CONTRACT; exports.ConflictError = _chunkT372OL3Qcjs.ConflictError; exports.ConnectionError = _chunkT372OL3Qcjs.ConnectionError; exports.ContextNotFoundError = _chunkT372OL3Qcjs.ContextNotFoundError; exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL; exports.DEFAULT_DOCUMENT_TYPE = DEFAULT_DOCUMENT_TYPE; exports.DEFAULT_INGEST_MODE = DEFAULT_INGEST_MODE; exports.DEFAULT_RETRY_POLICY = DEFAULT_RETRY_POLICY; exports.DEFAULT_TIMEOUTS = DEFAULT_TIMEOUTS; exports.DOCUMENT_TYPES = DOCUMENT_TYPES; exports.ENDPOINTS = ENDPOINTS; exports.FORGET_MODES = FORGET_MODES; exports.HttpTransport = HttpTransport; exports.INGEST_MODES = INGEST_MODES; exports.ITEM_COLLECTIONS = ITEM_COLLECTIONS; exports.InsufficientCreditsError = _chunkT372OL3Qcjs.InsufficientCreditsError; exports.InvalidConversationIdError = _chunkT372OL3Qcjs.InvalidConversationIdError; exports.InvalidInputError = _chunkT372OL3Qcjs.InvalidInputError; exports.InvalidInstanceIdError = _chunkT372OL3Qcjs.InvalidInstanceIdError; exports.ListeningAlreadyActiveError = _chunkT372OL3Qcjs.ListeningAlreadyActiveError; exports.ListeningNotActiveError = _chunkT372OL3Qcjs.ListeningNotActiveError; exports.NetworkTimeoutError = _chunkT372OL3Qcjs.NetworkTimeoutError; exports.PermanentError = _chunkT372OL3Qcjs.PermanentError; exports.RateLimitError = _chunkT372OL3Qcjs.RateLimitError; exports.SDKError = _chunkT372OL3Qcjs.SDKError; exports.SDK_VERSION = SDK_VERSION; exports.ServiceUnavailableError = _chunkT372OL3Qcjs.ServiceUnavailableError; exports.SessionExpiredError = _chunkT372OL3Qcjs.SessionExpiredError; exports.SynapClient = SynapClient; exports.SynapError = _chunkT372OL3Qcjs.SynapError; exports.SynapPermanentError = _chunkT372OL3Qcjs.SynapPermanentError; exports.SynapTransientError = _chunkT372OL3Qcjs.SynapTransientError; exports.THRESHOLDS = _chunkT6U5PVADcjs.THRESHOLDS; exports.TranscriptConflictError = _chunkT372OL3Qcjs.TranscriptConflictError; exports.TransientError = _chunkT372OL3Qcjs.TransientError; exports.flattenContextItems = flattenContextItems; exports.formatForPrompt = formatForPrompt; exports.isOutcomeUnknown = isOutcomeUnknown; exports.isRecallQuery = _chunkT6U5PVADcjs.isRecallQuery; exports.isSynapError = _chunkT372OL3Qcjs.isSynapError; exports.mergeScopeResults = mergeScopeResults; exports.resolvePath = resolvePath; exports.shouldRetry = shouldRetry; exports.stem = _chunkT6U5PVADcjs.stem; exports.tokenize = _chunkT6U5PVADcjs.tokenize;
4458
+
4459
+
4460
+
4461
+
4462
+
4463
+
4464
+ exports.ANY_SCOPE = _chunkICUFS3B5cjs.ANY_SCOPE; exports.AgentUnavailableError = _chunkT372OL3Qcjs.AgentUnavailableError; exports.AnticipationCache = _chunkICUFS3B5cjs.AnticipationCache; exports.AuthenticationError = _chunkT372OL3Qcjs.AuthenticationError; exports.BM25 = _chunkICUFS3B5cjs.BM25; exports.COMPACTION_LEVELS = COMPACTION_LEVELS; exports.CONTRACT = _chunkICUFS3B5cjs.CONTRACT; exports.ConflictError = _chunkT372OL3Qcjs.ConflictError; exports.ConnectionError = _chunkT372OL3Qcjs.ConnectionError; exports.ContextNotFoundError = _chunkT372OL3Qcjs.ContextNotFoundError; exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL; exports.DEFAULT_DOCUMENT_TYPE = DEFAULT_DOCUMENT_TYPE; exports.DEFAULT_INGEST_MODE = DEFAULT_INGEST_MODE; exports.DEFAULT_RETRY_POLICY = DEFAULT_RETRY_POLICY; exports.DEFAULT_TIMEOUTS = DEFAULT_TIMEOUTS; exports.DOCUMENT_TYPES = DOCUMENT_TYPES; exports.ENDPOINTS = ENDPOINTS; exports.EXCERPT_CATEGORY = EXCERPT_CATEGORY; exports.FORGET_MODES = FORGET_MODES; exports.HttpTransport = HttpTransport; exports.INGEST_MODES = INGEST_MODES; exports.ITEM_COLLECTIONS = ITEM_COLLECTIONS; exports.InsufficientCreditsError = _chunkT372OL3Qcjs.InsufficientCreditsError; exports.InvalidConversationIdError = _chunkT372OL3Qcjs.InvalidConversationIdError; exports.InvalidInputError = _chunkT372OL3Qcjs.InvalidInputError; exports.InvalidInstanceIdError = _chunkT372OL3Qcjs.InvalidInstanceIdError; exports.ListeningAlreadyActiveError = _chunkT372OL3Qcjs.ListeningAlreadyActiveError; exports.ListeningNotActiveError = _chunkT372OL3Qcjs.ListeningNotActiveError; exports.MEMORY_TYPES = MEMORY_TYPES; exports.NetworkTimeoutError = _chunkT372OL3Qcjs.NetworkTimeoutError; exports.PermanentError = _chunkT372OL3Qcjs.PermanentError; exports.RESPONSE_SHAPES = RESPONSE_SHAPES; exports.RateLimitError = _chunkT372OL3Qcjs.RateLimitError; exports.SDKError = _chunkT372OL3Qcjs.SDKError; exports.SDK_VERSION = SDK_VERSION; exports.ServiceUnavailableError = _chunkT372OL3Qcjs.ServiceUnavailableError; exports.SessionExpiredError = _chunkT372OL3Qcjs.SessionExpiredError; exports.SynapClient = SynapClient; exports.SynapError = _chunkT372OL3Qcjs.SynapError; exports.SynapPermanentError = _chunkT372OL3Qcjs.SynapPermanentError; exports.SynapTransientError = _chunkT372OL3Qcjs.SynapTransientError; exports.THRESHOLDS = _chunkICUFS3B5cjs.THRESHOLDS; exports.TranscriptConflictError = _chunkT372OL3Qcjs.TranscriptConflictError; exports.TransientError = _chunkT372OL3Qcjs.TransientError; exports.applyResponseShape = applyResponseShape; exports.flattenContextItems = flattenContextItems; exports.formatForPrompt = formatForPrompt; exports.isOutcomeUnknown = isOutcomeUnknown; exports.isRecallQuery = _chunkICUFS3B5cjs.isRecallQuery; exports.isSynapError = _chunkT372OL3Qcjs.isSynapError; exports.mergeScopeResults = mergeScopeResults; exports.rankItems = rankItems; exports.resolvePath = resolvePath; exports.shouldRetry = shouldRetry; exports.stem = _chunkICUFS3B5cjs.stem; exports.tokenize = _chunkICUFS3B5cjs.tokenize; exports.wireRankScore = wireRankScore;
3277
4465
  //# sourceMappingURL=index.cjs.map