agents 0.12.4 → 0.13.1

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 (62) hide show
  1. package/README.md +8 -8
  2. package/dist/{agent-tool-types-CM_50fcV.d.ts → agent-tool-types-Dn9n-3SI.d.ts} +234 -85
  3. package/dist/agent-tool-types.d.ts +1 -1
  4. package/dist/{agent-tools-BylX6WXG.d.ts → agent-tools-B1ttU-pq.d.ts} +2 -2
  5. package/dist/agent-tools-BAdX1vdI.js.map +1 -1
  6. package/dist/agent-tools.d.ts +1 -1
  7. package/dist/agent-tools.js.map +1 -1
  8. package/dist/ai-chat-agent.js.map +1 -1
  9. package/dist/ai-chat-v5-migration.js.map +1 -1
  10. package/dist/ai-react.js.map +1 -1
  11. package/dist/ai-types.js.map +1 -1
  12. package/dist/browser/ai.js +1 -1
  13. package/dist/browser/ai.js.map +1 -1
  14. package/dist/browser/index.js +1 -1
  15. package/dist/browser/tanstack-ai.js +1 -1
  16. package/dist/browser/tanstack-ai.js.map +1 -1
  17. package/dist/chat/index.d.ts +2 -2
  18. package/dist/chat/index.js.map +1 -1
  19. package/dist/{classPrivateFieldGet2-CS51BNGR.js → classPrivateFieldGet2-Evpt0SEr.js} +5 -5
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/client-D1kFXo80.js.map +1 -1
  22. package/dist/client.d.ts +1 -1
  23. package/dist/client.js.map +1 -1
  24. package/dist/codemode/ai.js.map +1 -1
  25. package/dist/{compaction-helpers-bYvP1o2S.d.ts → compaction-helpers-DAe-xiVY.d.ts} +33 -15
  26. package/dist/compaction-helpers-DvcZnvQ1.js.map +1 -1
  27. package/dist/email.d.ts +1 -1
  28. package/dist/email.js.map +1 -1
  29. package/dist/experimental/memory/session/index.d.ts +247 -34
  30. package/dist/experimental/memory/session/index.js +540 -135
  31. package/dist/experimental/memory/session/index.js.map +1 -1
  32. package/dist/experimental/memory/utils/index.d.ts +1 -1
  33. package/dist/experimental/memory/utils/index.js.map +1 -1
  34. package/dist/experimental/webmcp.js.map +1 -1
  35. package/dist/index.d.ts +71 -57
  36. package/dist/index.js +583 -45
  37. package/dist/index.js.map +1 -1
  38. package/dist/internal_context.js.map +1 -1
  39. package/dist/mcp/client.d.ts +12 -12
  40. package/dist/mcp/do-oauth-client-provider.js.map +1 -1
  41. package/dist/mcp/index.d.ts +40 -40
  42. package/dist/mcp/index.js +21 -45
  43. package/dist/mcp/index.js.map +1 -1
  44. package/dist/mcp/x402.js.map +1 -1
  45. package/dist/observability/index.js.map +1 -1
  46. package/dist/react.d.ts +3 -3
  47. package/dist/react.js.map +1 -1
  48. package/dist/retries.js.map +1 -1
  49. package/dist/schedule.js.map +1 -1
  50. package/dist/serializable.d.ts +1 -1
  51. package/dist/{shared-DzJYHisH.js → shared-CiKaIK4h.js} +4 -5
  52. package/dist/{shared-DzJYHisH.js.map → shared-CiKaIK4h.js.map} +1 -1
  53. package/dist/sub-routing.d.ts +6 -6
  54. package/dist/sub-routing.js.map +1 -1
  55. package/dist/tool-output-truncation-CH-khbZ3.js.map +1 -1
  56. package/dist/types.js.map +1 -1
  57. package/dist/utils.js.map +1 -1
  58. package/dist/vite.js.map +1 -1
  59. package/dist/workflow-types.js.map +1 -1
  60. package/dist/workflows.d.ts +1 -1
  61. package/dist/workflows.js.map +1 -1
  62. package/package.json +6 -6
@@ -65,12 +65,7 @@ var AgentSearchProvider = class {
65
65
  WHERE label = ${this.label}
66
66
  `[0]?.count ?? 0;
67
67
  if (count === 0) return null;
68
- return `${count} entries indexed. Recent:\n${this.agent.sql`
69
- SELECT key FROM cf_agents_search_entries
70
- WHERE label = ${this.label}
71
- ORDER BY updated_at DESC
72
- LIMIT 20
73
- `.map((r) => `- ${r.key}`).join("\n")}`;
68
+ return `${count} entries indexed.`;
74
69
  }
75
70
  async search(query) {
76
71
  this.ensureTable();
@@ -184,6 +179,21 @@ var R2SkillProvider = class {
184
179
  };
185
180
  //#endregion
186
181
  //#region src/experimental/memory/session/context.ts
182
+ function slugify(text) {
183
+ return text.slice(0, 60).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
184
+ }
185
+ function stableHash(text) {
186
+ let hash = 2166136261;
187
+ for (let i = 0; i < text.length; i++) {
188
+ hash ^= text.charCodeAt(i);
189
+ hash = Math.imul(hash, 16777619);
190
+ }
191
+ return (hash >>> 0).toString(36);
192
+ }
193
+ function contextEntryKey(metadataTitle, content) {
194
+ if (metadataTitle?.trim()) return slugify(metadataTitle) || `entry-${stableHash(metadataTitle)}`;
195
+ return `${slugify(content) || "entry"}-${stableHash(content)}`;
196
+ }
187
197
  /**
188
198
  * Check if a provider is writable (has a `set` method).
189
199
  */
@@ -415,7 +425,8 @@ var ContextBlocks = class {
415
425
  if (!this.loaded) await this.load();
416
426
  const existing = this.blocks.get(label);
417
427
  if (!existing) throw new Error(`Block "${label}" not found`);
418
- return this.setBlock(label, existing.content + content);
428
+ const needsSep = existing.content.length > 0 && !content.startsWith("\n");
429
+ return this.setBlock(label, existing.content + (needsSep ? "\n" : "") + content);
419
430
  }
420
431
  /**
421
432
  * Get the system prompt string with context blocks.
@@ -439,18 +450,17 @@ var ContextBlocks = class {
439
450
  const parts = [];
440
451
  const sep = "═".repeat(46);
441
452
  for (const block of this.blocks.values()) {
442
- if (!block.content && !block.isSearchable) continue;
453
+ if (!block.content && !block.writable && !block.isSearchable && !block.isSkill) continue;
443
454
  let header = block.label.toUpperCase();
444
- const hints = [];
445
- if (block.description) hints.push(block.description);
446
- if (block.isSkill) hints.push("use load_context to load");
447
- if (block.isSearchable) hints.push("use search_context to search");
448
- if (hints.length > 0) header += ` (${hints.join(" — ")})`;
455
+ if (block.description) header += ` (${block.description})`;
449
456
  if (block.maxTokens) {
450
457
  const pct = Math.round(block.tokens / block.maxTokens * 100);
451
458
  header += ` [${pct}% — ${block.tokens}/${block.maxTokens} tokens]`;
452
459
  }
453
- if (!block.writable) header += " [readonly]";
460
+ if (block.isSearchable) header += " [searchable]";
461
+ else if (block.isSkill) header += " [loadable]";
462
+ else if (!block.writable) header += " [readonly]";
463
+ else header += " [writable]";
454
464
  parts.push(`${sep}\n${header}\n${sep}\n${block.content}`);
455
465
  }
456
466
  this.snapshot = parts.join("\n\n");
@@ -487,9 +497,9 @@ var ContextBlocks = class {
487
497
  return Array.from(this.blocks.values()).filter((b) => b.isSearchable).map((b) => b.label);
488
498
  }
489
499
  /**
490
- * Frozen system prompt. On first call:
491
- * 1. Checks store for a persisted prompt (survives DO eviction)
492
- * 2. If none, loads blocks from providers, renders, and persists
500
+ * Return the cached system prompt. If no cached prompt exists,
501
+ * loads blocks from providers, renders, and persists to the store.
502
+ * Subsequent calls return the stored value without re-rendering.
493
503
  */
494
504
  async freezeSystemPrompt() {
495
505
  if (this.promptStore) {
@@ -502,10 +512,13 @@ var ContextBlocks = class {
502
512
  return prompt;
503
513
  }
504
514
  /**
505
- * Re-render the system prompt from current block state and persist.
515
+ * Force reload blocks from providers, re-render the system prompt,
516
+ * and persist to the store. Use this after block content has changed
517
+ * or to invalidate the cached prompt.
506
518
  */
507
519
  async refreshSystemPrompt() {
508
- if (!this.loaded) await this.load();
520
+ this.loaded = false;
521
+ await this.load();
509
522
  const prompt = this.refreshSnapshot();
510
523
  if (this.promptStore) await this.promptStore.set(prompt);
511
524
  return prompt;
@@ -525,14 +538,11 @@ var ContextBlocks = class {
525
538
  const hasSearch = this.hasSearchBlocks();
526
539
  const toolSet = {};
527
540
  if (writable.length > 0) {
528
- const regularBlocks = writable.filter((b) => !b.isSkill && !b.isSearchable);
541
+ const blockDescriptions = writable.map((b) => {
542
+ const kind = b.isSkill ? "skill collection, keyed entries" : b.isSearchable ? "searchable, keyed entries" : "writable";
543
+ return `- "${b.label}" (${kind}): ${b.description ?? "no description"}`;
544
+ });
529
545
  const keyedBlocks = writable.filter((b) => b.isSkill || b.isSearchable);
530
- const blockDescriptions = [];
531
- for (const b of regularBlocks) blockDescriptions.push(`- "${b.label}": ${b.description ?? "no description"}`);
532
- for (const b of keyedBlocks) {
533
- const kind = b.isSkill ? "skill collection (requires key and optional description)" : "searchable (requires key)";
534
- blockDescriptions.push(`- "${b.label}": ${kind}`);
535
- }
536
546
  const properties = {
537
547
  label: {
538
548
  type: "string",
@@ -541,7 +551,7 @@ var ContextBlocks = class {
541
551
  },
542
552
  content: {
543
553
  type: "string",
544
- description: "Content to write"
554
+ description: "The main content to write to the block."
545
555
  },
546
556
  action: {
547
557
  type: "string",
@@ -549,34 +559,38 @@ var ContextBlocks = class {
549
559
  description: "replace (default) or append"
550
560
  }
551
561
  };
552
- const required = ["label", "content"];
553
- if (keyedBlocks.length > 0) properties.key = {
554
- type: "string",
555
- description: "Entry key (required for keyed blocks: " + keyedBlocks.map((b) => `"${b.label}"`).join(", ") + ")"
556
- };
557
- if (keyedBlocks.some((b) => b.isSkill)) properties.description = {
558
- type: "string",
559
- description: "Short description for the skill entry"
562
+ if (keyedBlocks.length > 0) properties.metadata = {
563
+ type: "object",
564
+ description: "Optional metadata for keyed entries (skill collections, searchable blocks: " + keyedBlocks.map((b) => `"${b.label}"`).join(", ") + "). Short content doesn't need metadata; longer loadable entries (skills) benefit from a title and description so the model can pick the right one without loading it.",
565
+ properties: {
566
+ title: {
567
+ type: "string",
568
+ description: "Short title. Used as a stable identifier — entries with the same title are updated in place, different titles create new entries."
569
+ },
570
+ description: {
571
+ type: "string",
572
+ description: "One-line summary shown alongside the title in the system prompt so the model can decide when to load the entry."
573
+ }
574
+ }
560
575
  };
576
+ const metadataHint = keyedBlocks.length > 0 ? "\n\nFor keyed blocks (skill collections / searchable), pass `metadata: { title, description }` — title stabilises updates, description helps the model pick entries. Metadata is optional; short content rarely needs it, long loadable entries benefit most." : "";
561
577
  toolSet.set_context = {
562
- description: `Write to a context block. Available blocks:\n${blockDescriptions.join("\n")}\n\nWrites are durable and persist across sessions.`,
578
+ description: `Write to a context block. Available blocks:\n${blockDescriptions.join("\n")}\n\nWrites are durable and persist across sessions.${metadataHint}`,
563
579
  inputSchema: z.fromJSONSchema({
564
580
  type: "object",
565
581
  properties,
566
- required
582
+ required: ["label", "content"]
567
583
  }),
568
- execute: async ({ label, content, key, description, action }) => {
584
+ execute: async ({ label, content, metadata, action }) => {
569
585
  try {
570
586
  const block = this.blocks.get(label);
571
587
  if (!block) return `Error: block "${label}" not found`;
572
- if (block.isSkill) {
573
- if (!key) return `Error: key is required for skill block "${label}"`;
574
- await this.setSkill(label, key, content, description);
575
- return `Written skill "${key}" to ${label}.`;
576
- }
577
- if (block.isSearchable) {
578
- if (!key) return `Error: key is required for searchable block "${label}"`;
579
- await this.setSearchEntry(label, key, content);
588
+ if (block.isSkill || block.isSearchable) {
589
+ const title = metadata?.title;
590
+ const description = metadata?.description;
591
+ const key = contextEntryKey(title, content);
592
+ if (block.isSkill) await this.setSkill(label, key, content, description ?? title);
593
+ else await this.setSearchEntry(label, key, content);
580
594
  return `Indexed "${key}" in ${label}.`;
581
595
  }
582
596
  const updated = action === "append" ? await this.appendToBlock(label, content) : await this.setBlock(label, content);
@@ -608,6 +622,7 @@ var ContextBlocks = class {
608
622
  }),
609
623
  execute: async ({ label, key }) => {
610
624
  try {
625
+ if (!skillLabels.includes(label)) return `Error: "${label}" is not a skill block. Skill blocks: ${skillLabels.join(", ")}`;
611
626
  return await this.loadSkill(label, key) ?? `Not found: ${key}`;
612
627
  } catch (err) {
613
628
  return `Error: ${err instanceof Error ? err.message : String(err)}`;
@@ -633,6 +648,7 @@ var ContextBlocks = class {
633
648
  required: ["label", "key"]
634
649
  }),
635
650
  execute: async ({ label, key }) => {
651
+ if (!skillLabels.includes(label)) return `Error: "${label}" is not a skill block. Skill blocks: ${skillLabels.join(", ")}`;
636
652
  if (!this.unloadSkill(label, key)) return `Skill "${key}" is not currently loaded in "${label}".`;
637
653
  return `Unloaded "${key}" from ${label}. Context reclaimed.`;
638
654
  }
@@ -641,7 +657,7 @@ var ContextBlocks = class {
641
657
  if (hasSearch) {
642
658
  const searchLabels = this.getSearchLabels();
643
659
  toolSet.search_context = {
644
- description: "Search for information in a searchable context block. Available searchable blocks: " + searchLabels.map((l) => `"${l}"`).join(", ") + ".",
660
+ description: "Search for information in a searchable context block. ONLY these blocks are searchable: " + searchLabels.map((l) => `"${l}"`).join(", ") + ". Other blocks cannot be searched.",
645
661
  inputSchema: z.fromJSONSchema({
646
662
  type: "object",
647
663
  properties: {
@@ -659,6 +675,7 @@ var ContextBlocks = class {
659
675
  }),
660
676
  execute: async ({ label, query }) => {
661
677
  try {
678
+ if (!searchLabels.includes(label)) return `Error: "${label}" is not searchable. Searchable blocks: ${searchLabels.join(", ")}`;
662
679
  return await this.searchContext(label, query) ?? "No results found.";
663
680
  } catch (err) {
664
681
  return `Error: ${err instanceof Error ? err.message : String(err)}`;
@@ -789,7 +806,7 @@ var AgentSessionProvider = class {
789
806
  if (this.agent.sql`
790
807
  SELECT id FROM assistant_messages WHERE id = ${message.id} AND session_id = ${this.sessionId}
791
808
  `.length > 0) return;
792
- let parent = parentId ?? this.latestLeafRow()?.id ?? null;
809
+ let parent = parentId !== void 0 ? parentId : this.latestLeafRow()?.id ?? null;
793
810
  if (parent) {
794
811
  if (this.agent.sql`
795
812
  SELECT id FROM assistant_messages WHERE id = ${parent} AND session_id = ${this.sessionId}
@@ -979,6 +996,9 @@ var AgentContextProvider = class {
979
996
  function isBroadcaster(obj) {
980
997
  return typeof obj === "object" && obj !== null && "broadcast" in obj && typeof obj.broadcast === "function";
981
998
  }
999
+ function isSqlProvider(arg) {
1000
+ return "sql" in arg && typeof arg.sql === "function";
1001
+ }
982
1002
  var Session = class Session {
983
1003
  constructor(storage, options) {
984
1004
  this._ready = false;
@@ -987,11 +1007,14 @@ var Session = class Session {
987
1007
  this._ready = true;
988
1008
  }
989
1009
  /**
990
- * Chainable session creation with auto-wired SQLite providers.
991
- * Chain methods in any order — providers are resolved lazily on first use.
1010
+ * Chainable session creation with auto-wired providers.
1011
+ *
1012
+ * Pass a `SqlProvider` (Agent with `sql` method) for auto-wired SQLite,
1013
+ * or a `SessionProvider` directly for custom storage (Postgres, etc.).
992
1014
  *
993
1015
  * @example
994
1016
  * ```ts
1017
+ * // Auto-wired SQLite (DO Agent)
995
1018
  * const session = Session.create(this)
996
1019
  * .withContext("soul", { provider: { get: async () => "You are helpful." } })
997
1020
  * .withContext("memory", { description: "Learned facts", maxTokens: 1100 })
@@ -1003,12 +1026,22 @@ var Session = class Session {
1003
1026
  * provider: new R2SkillProvider(env.SKILLS_BUCKET, { prefix: "skills/" })
1004
1027
  * })
1005
1028
  * .withCachedPrompt();
1029
+ *
1030
+ * // Custom storage provider (Postgres, etc.)
1031
+ * const session = Session.create(postgresProvider)
1032
+ * .withContext("memory", {
1033
+ * maxTokens: 1100,
1034
+ * provider: new PostgresContextProvider(conn, "memory")
1035
+ * })
1036
+ * .withCachedPrompt(new PostgresContextProvider(conn, "_prompt"));
1006
1037
  * ```
1007
1038
  */
1008
- static create(agent) {
1039
+ static create(provider) {
1009
1040
  const session = Object.create(Session.prototype);
1010
- session._agent = agent;
1011
- if (isBroadcaster(agent)) session._broadcaster = agent;
1041
+ if (isSqlProvider(provider)) {
1042
+ session._agent = provider;
1043
+ if (isBroadcaster(provider)) session._broadcaster = provider;
1044
+ } else session._storageProvider = provider;
1012
1045
  session._pending = [];
1013
1046
  session._ready = false;
1014
1047
  return session;
@@ -1044,11 +1077,20 @@ var Session = class Session {
1044
1077
  this._tokenThreshold = tokenThreshold;
1045
1078
  return this;
1046
1079
  }
1080
+ /**
1081
+ * @internal
1082
+ * Framework hook for cache-owning callers that need to mirror message
1083
+ * storage changes. Application code should use the normal Session methods.
1084
+ */
1085
+ internal_onMessagesChanged(listener) {
1086
+ this._messageChangeListener = listener ?? void 0;
1087
+ return this;
1088
+ }
1047
1089
  _ensureReady() {
1048
1090
  if (this._ready) return;
1049
1091
  const configs = (this._pending ?? []).map(({ label, options: opts }) => {
1050
1092
  let provider = opts.provider;
1051
- if (!provider) {
1093
+ if (!provider && this._agent) {
1052
1094
  const key = this._sessionId ? `${label}_${this._sessionId}` : label;
1053
1095
  provider = new AgentContextProvider(this._agent, key);
1054
1096
  }
@@ -1060,25 +1102,40 @@ var Session = class Session {
1060
1102
  };
1061
1103
  });
1062
1104
  let promptStore;
1063
- if (this._cachedPrompt === true) {
1105
+ if (this._cachedPrompt === true && this._agent) {
1064
1106
  const key = this._sessionId ? `_system_prompt_${this._sessionId}` : "_system_prompt";
1065
1107
  promptStore = new AgentContextProvider(this._agent, key);
1066
- } else if (this._cachedPrompt) promptStore = this._cachedPrompt;
1067
- this.storage = new AgentSessionProvider(this._agent, this._sessionId);
1108
+ } else if (this._cachedPrompt && this._cachedPrompt !== true) promptStore = this._cachedPrompt;
1109
+ if (this._storageProvider) this.storage = this._storageProvider;
1110
+ else if (this._agent) this.storage = new AgentSessionProvider(this._agent, this._sessionId);
1111
+ else throw new Error("Session.create() requires a SqlProvider or SessionProvider");
1068
1112
  this.context = new ContextBlocks(configs, promptStore);
1069
1113
  this.context.setUnloadCallback((label, key) => {
1070
- this._reclaimLoadedSkill(label, key);
1114
+ this._reclaimLoadedSkill(label, key).catch(() => {});
1071
1115
  });
1072
- this._restoreLoadedSkills();
1073
1116
  this._ready = true;
1117
+ this._restorePromise = this._restoreLoadedSkills().catch(() => {});
1118
+ }
1119
+ /**
1120
+ * Await the background skill-restore kicked off by `_ensureReady()`.
1121
+ * Idempotent and cheap — every async public method calls this so that
1122
+ * `_loadedSkills` reflects conversation history before any read or write.
1123
+ */
1124
+ async _ensureRestored() {
1125
+ this._ensureReady();
1126
+ if (this._restorePromise) await this._restorePromise;
1127
+ }
1128
+ async _notifyMessagesChanged(event) {
1129
+ await this._messageChangeListener?.(event);
1074
1130
  }
1075
1131
  /**
1076
1132
  * Reconstruct which skills are loaded by scanning conversation history
1077
1133
  * for load_context tool results that haven't been unloaded.
1078
- * Called during init to survive hibernation/eviction.
1134
+ * Runs once per init to survive hibernation / eviction, including for
1135
+ * async SessionProviders (e.g. Postgres) where we must `await` history.
1079
1136
  */
1080
- _restoreLoadedSkills() {
1081
- const history = this.storage.getHistory();
1137
+ async _restoreLoadedSkills() {
1138
+ const history = await this.storage.getHistory();
1082
1139
  const loaded = /* @__PURE__ */ new Set();
1083
1140
  for (const msg of history) {
1084
1141
  if (msg.role !== "assistant") continue;
@@ -1097,11 +1154,22 @@ var Session = class Session {
1097
1154
  if (loaded.size > 0) this.context.restoreLoadedSkills(loaded);
1098
1155
  }
1099
1156
  /**
1100
- * Replace a load_context tool result in conversation history
1101
- * with a short marker to reclaim context space.
1157
+ * Reclaim context-window tokens consumed by a previously loaded skill.
1158
+ *
1159
+ * When a skill is loaded via the `load_context` tool, its full body is
1160
+ * embedded as that tool call's `output-available` result inside the
1161
+ * assistant message — which means every subsequent turn replays the
1162
+ * entire skill as part of the conversation history and pays for it in
1163
+ * input tokens.
1164
+ *
1165
+ * This method walks back through history, finds the matching
1166
+ * `load_context` tool result for `(label, key)`, and replaces its bulky
1167
+ * `output` with a short marker `[skill unloaded: <key>]`. The skill
1168
+ * content is dropped from future turns and the tokens are reclaimed.
1169
+ * The skill itself stays available to reload via `load_context`.
1102
1170
  */
1103
- _reclaimLoadedSkill(label, key) {
1104
- const history = this.storage.getHistory();
1171
+ async _reclaimLoadedSkill(label, key) {
1172
+ const history = await this.storage.getHistory();
1105
1173
  for (let i = history.length - 1; i >= 0; i--) {
1106
1174
  const msg = history[i];
1107
1175
  if (msg.role !== "assistant") continue;
@@ -1120,7 +1188,7 @@ var Session = class Session {
1120
1188
  return part;
1121
1189
  });
1122
1190
  if (changed) {
1123
- this.storage.updateMessage({
1191
+ await this.updateMessage({
1124
1192
  ...msg,
1125
1193
  parts: newParts
1126
1194
  });
@@ -1128,24 +1196,24 @@ var Session = class Session {
1128
1196
  }
1129
1197
  }
1130
1198
  }
1131
- getHistory(leafId) {
1132
- this._ensureReady();
1199
+ async getHistory(leafId) {
1200
+ await this._ensureRestored();
1133
1201
  return this.storage.getHistory(leafId);
1134
1202
  }
1135
- getMessage(id) {
1136
- this._ensureReady();
1203
+ async getMessage(id) {
1204
+ await this._ensureRestored();
1137
1205
  return this.storage.getMessage(id);
1138
1206
  }
1139
- getLatestLeaf() {
1140
- this._ensureReady();
1207
+ async getLatestLeaf() {
1208
+ await this._ensureRestored();
1141
1209
  return this.storage.getLatestLeaf();
1142
1210
  }
1143
- getBranches(messageId) {
1144
- this._ensureReady();
1211
+ async getBranches(messageId) {
1212
+ await this._ensureRestored();
1145
1213
  return this.storage.getBranches(messageId);
1146
1214
  }
1147
- getPathLength(leafId) {
1148
- this._ensureReady();
1215
+ async getPathLength(leafId) {
1216
+ await this._ensureRestored();
1149
1217
  return this.storage.getPathLength(leafId);
1150
1218
  }
1151
1219
  _broadcast(type, data) {
@@ -1155,8 +1223,8 @@ var Session = class Session {
1155
1223
  ...data
1156
1224
  }));
1157
1225
  }
1158
- _emitStatus(phase, extra) {
1159
- const tokenEstimate = estimateMessageTokens(this.getHistory());
1226
+ async _emitStatus(phase, extra) {
1227
+ const tokenEstimate = estimateMessageTokens(await this.getHistory());
1160
1228
  this._broadcast("cf_agent_session", {
1161
1229
  phase,
1162
1230
  tokenEstimate,
@@ -1169,35 +1237,65 @@ var Session = class Session {
1169
1237
  this._broadcast("cf_agent_session_error", { error });
1170
1238
  }
1171
1239
  async appendMessage(message, parentId) {
1172
- this._ensureReady();
1173
- this.storage.appendMessage(message, parentId);
1174
- const tokenEstimate = this._emitStatus("idle");
1240
+ await this._appendMessage(message, parentId);
1241
+ }
1242
+ async _appendMessage(message, parentId) {
1243
+ await this._ensureRestored();
1244
+ if (await this.storage.getMessage(message.id)) {
1245
+ await this._emitStatus("idle");
1246
+ await this._notifyMessagesChanged({
1247
+ type: "append",
1248
+ message,
1249
+ parentId,
1250
+ inserted: false
1251
+ });
1252
+ return;
1253
+ }
1254
+ await this.storage.appendMessage(message, parentId);
1255
+ const tokenEstimate = await this._emitStatus("idle");
1256
+ let compacted = false;
1175
1257
  if (this._tokenThreshold != null && this._compactionFn && tokenEstimate > this._tokenThreshold) try {
1176
- await this.compact();
1258
+ compacted = Boolean(await this.compact());
1177
1259
  } catch {}
1260
+ if (!compacted) await this._notifyMessagesChanged({
1261
+ type: "append",
1262
+ message,
1263
+ parentId,
1264
+ inserted: true
1265
+ });
1178
1266
  }
1179
- updateMessage(message) {
1180
- this._ensureReady();
1181
- this.storage.updateMessage(message);
1182
- this._emitStatus("idle");
1267
+ async updateMessage(message) {
1268
+ await this._ensureRestored();
1269
+ await this.storage.updateMessage(message);
1270
+ await this._emitStatus("idle");
1271
+ await this._notifyMessagesChanged({
1272
+ type: "update",
1273
+ message
1274
+ });
1183
1275
  }
1184
- deleteMessages(messageIds) {
1185
- this._ensureReady();
1186
- this.storage.deleteMessages(messageIds);
1187
- this._emitStatus("idle");
1276
+ async deleteMessages(messageIds) {
1277
+ await this._ensureRestored();
1278
+ await this.storage.deleteMessages(messageIds);
1279
+ await this._emitStatus("idle");
1280
+ await this._notifyMessagesChanged({
1281
+ type: "delete",
1282
+ messageIds
1283
+ });
1188
1284
  }
1189
- clearMessages() {
1190
- this._ensureReady();
1191
- this.storage.clearMessages();
1285
+ async clearMessages() {
1286
+ await this._ensureRestored();
1287
+ await this.storage.clearMessages();
1192
1288
  this.context.clearSkillState();
1193
- this._emitStatus("idle");
1289
+ await this.context.refreshSystemPrompt();
1290
+ await this._emitStatus("idle");
1291
+ await this._notifyMessagesChanged({ type: "clear" });
1194
1292
  }
1195
- addCompaction(summary, fromMessageId, toMessageId) {
1196
- this._ensureReady();
1293
+ async addCompaction(summary, fromMessageId, toMessageId) {
1294
+ await this._ensureRestored();
1197
1295
  return this.storage.addCompaction(summary, fromMessageId, toMessageId);
1198
1296
  }
1199
- getCompactions() {
1200
- this._ensureReady();
1297
+ async getCompactions() {
1298
+ await this._ensureRestored();
1201
1299
  return this.storage.getCompactions();
1202
1300
  }
1203
1301
  /**
@@ -1205,29 +1303,30 @@ var Session = class Session {
1205
1303
  * Requires `onCompaction()` to be called first.
1206
1304
  */
1207
1305
  async compact() {
1208
- this._ensureReady();
1306
+ await this._ensureRestored();
1209
1307
  if (!this._compactionFn) throw new Error("No compaction function registered. Call onCompaction() first.");
1210
- const tokensBefore = this._emitStatus("compacting");
1308
+ const tokensBefore = await this._emitStatus("compacting");
1211
1309
  let result;
1212
1310
  try {
1213
- result = await this._compactionFn(this.getHistory());
1311
+ result = await this._compactionFn(await this.getHistory());
1214
1312
  } catch (err) {
1215
1313
  this._emitError(err instanceof Error ? err.message : String(err));
1216
1314
  return null;
1217
1315
  }
1218
1316
  if (!result) {
1219
- this._emitStatus("idle");
1317
+ await this._emitStatus("idle");
1220
1318
  return null;
1221
1319
  }
1222
- if (!new Set(this.getHistory().map((m) => m.id)).has(result.toMessageId)) {
1223
- this._emitStatus("idle");
1320
+ if (!new Set((await this.getHistory()).map((m) => m.id)).has(result.toMessageId)) {
1321
+ await this._emitStatus("idle");
1224
1322
  return null;
1225
1323
  }
1226
- const existing = this.getCompactions();
1324
+ const existing = await this.getCompactions();
1227
1325
  const fromId = existing.length > 0 ? existing[0].fromMessageId : result.fromMessageId;
1228
- this.addCompaction(result.summary, fromId, result.toMessageId);
1326
+ await this.addCompaction(result.summary, fromId, result.toMessageId);
1229
1327
  await this.refreshSystemPrompt();
1230
- this._emitStatus("idle", { compacted: { tokensBefore } });
1328
+ await this._emitStatus("idle", { compacted: { tokensBefore } });
1329
+ await this._notifyMessagesChanged({ type: "compact" });
1231
1330
  return {
1232
1331
  ...result,
1233
1332
  fromMessageId: fromId
@@ -1242,16 +1341,21 @@ var Session = class Session {
1242
1341
  return this.context.getBlocks();
1243
1342
  }
1244
1343
  async replaceContextBlock(label, content) {
1245
- this._ensureReady();
1344
+ await this._ensureRestored();
1246
1345
  return this.context.setBlock(label, content);
1247
1346
  }
1248
1347
  async appendContextBlock(label, content) {
1249
- this._ensureReady();
1348
+ await this._ensureRestored();
1250
1349
  return this.context.appendToBlock(label, content);
1251
1350
  }
1252
1351
  /**
1253
1352
  * Dynamically register a new context block after session initialization.
1254
- * Used by extensions to contribute context blocks at runtime.
1353
+ *
1354
+ * This is a **builder / runtime API**, not an LLM tool. The LLM writes
1355
+ * into existing context blocks via the `set_context` tool (see
1356
+ * `ContextBlocks.tools()`); it cannot declare new blocks itself. This
1357
+ * method is how extension / host code contributes blocks at runtime
1358
+ * (e.g. an extension's `onLoad` handler registering its own memory block).
1255
1359
  *
1256
1360
  * The block's provider is initialized and loaded immediately.
1257
1361
  * Call `refreshSystemPrompt()` afterward to include the new block
@@ -1262,10 +1366,11 @@ var Session = class Session {
1262
1366
  * via `Session.create(agent)` (not the direct constructor).
1263
1367
  */
1264
1368
  async addContext(label, options) {
1265
- this._ensureReady();
1369
+ await this._ensureRestored();
1266
1370
  const opts = options ?? {};
1267
1371
  let provider = opts.provider;
1268
1372
  if (!provider) {
1373
+ if (!this._agent) throw new Error(`addContext("${label}") requires an explicit provider when Session uses a SessionProvider`);
1269
1374
  const key = this._sessionId ? `${label}_${this._sessionId}` : label;
1270
1375
  provider = new AgentContextProvider(this._agent, key);
1271
1376
  }
@@ -1291,34 +1396,40 @@ var Session = class Session {
1291
1396
  /**
1292
1397
  * Unload a previously loaded skill, reclaiming context space.
1293
1398
  * The tool result in conversation history is replaced with a short marker.
1399
+ *
1400
+ * Async so that the session's background skill-state restore (which
1401
+ * reads conversation history) is awaited first — otherwise a freshly
1402
+ * rehydrated DO could report "not loaded" for a skill that's actually
1403
+ * present in history.
1294
1404
  */
1295
- unloadSkill(label, key) {
1296
- this._ensureReady();
1405
+ async unloadSkill(label, key) {
1406
+ await this._ensureRestored();
1297
1407
  return this.context.unloadSkill(label, key);
1298
1408
  }
1299
1409
  /**
1300
1410
  * Get currently loaded skill keys (as "label:key" strings).
1411
+ * Async for the same reason as `unloadSkill` — must wait for restore.
1301
1412
  */
1302
- getLoadedSkillKeys() {
1303
- this._ensureReady();
1413
+ async getLoadedSkillKeys() {
1414
+ await this._ensureRestored();
1304
1415
  return this.context.getLoadedSkillKeys();
1305
1416
  }
1306
1417
  async freezeSystemPrompt() {
1307
- this._ensureReady();
1418
+ await this._ensureRestored();
1308
1419
  return this.context.freezeSystemPrompt();
1309
1420
  }
1310
1421
  async refreshSystemPrompt() {
1311
- this._ensureReady();
1422
+ await this._ensureRestored();
1312
1423
  return this.context.refreshSystemPrompt();
1313
1424
  }
1314
- search(query, options) {
1315
- this._ensureReady();
1425
+ async search(query, options) {
1426
+ await this._ensureRestored();
1316
1427
  if (!this.storage.searchMessages) throw new Error("Session provider does not support search");
1317
1428
  return this.storage.searchMessages(query, options?.limit ?? 20);
1318
1429
  }
1319
1430
  /** Returns set_context and load_context tools. */
1320
1431
  async tools() {
1321
- this._ensureReady();
1432
+ await this._ensureRestored();
1322
1433
  return this.context.tools();
1323
1434
  }
1324
1435
  };
@@ -1492,8 +1603,8 @@ var SessionManager = class SessionManager {
1492
1603
  SELECT * FROM assistant_sessions ORDER BY updated_at DESC
1493
1604
  `;
1494
1605
  }
1495
- delete(sessionId) {
1496
- this.getSession(sessionId).clearMessages();
1606
+ async delete(sessionId) {
1607
+ await this.getSession(sessionId).clearMessages();
1497
1608
  this.agent.sql`DELETE FROM assistant_sessions WHERE id = ${sessionId}`;
1498
1609
  this._sessions.delete(sessionId);
1499
1610
  }
@@ -1511,7 +1622,7 @@ var SessionManager = class SessionManager {
1511
1622
  }
1512
1623
  async upsert(sessionId, message, parentId) {
1513
1624
  const session = this.getSession(sessionId);
1514
- if (session.getMessage(message.id)) session.updateMessage(message);
1625
+ if (await session.getMessage(message.id)) await session.updateMessage(message);
1515
1626
  else await session.appendMessage(message, parentId);
1516
1627
  this._touch(sessionId);
1517
1628
  return message.id;
@@ -1526,21 +1637,21 @@ var SessionManager = class SessionManager {
1526
1637
  this._touch(sessionId);
1527
1638
  return lastParent;
1528
1639
  }
1529
- getHistory(sessionId, leafId) {
1640
+ async getHistory(sessionId, leafId) {
1530
1641
  return this.getSession(sessionId).getHistory(leafId);
1531
1642
  }
1532
- getMessageCount(sessionId) {
1643
+ async getMessageCount(sessionId) {
1533
1644
  return this.getSession(sessionId).getPathLength();
1534
1645
  }
1535
- clearMessages(sessionId) {
1536
- this.getSession(sessionId).clearMessages();
1646
+ async clearMessages(sessionId) {
1647
+ await this.getSession(sessionId).clearMessages();
1537
1648
  this._touch(sessionId);
1538
1649
  }
1539
- deleteMessages(sessionId, messageIds) {
1540
- this.getSession(sessionId).deleteMessages(messageIds);
1650
+ async deleteMessages(sessionId, messageIds) {
1651
+ await this.getSession(sessionId).deleteMessages(messageIds);
1541
1652
  this._touch(sessionId);
1542
1653
  }
1543
- getBranches(sessionId, messageId) {
1654
+ async getBranches(sessionId, messageId) {
1544
1655
  return this.getSession(sessionId).getBranches(messageId);
1545
1656
  }
1546
1657
  /**
@@ -1549,7 +1660,7 @@ var SessionManager = class SessionManager {
1549
1660
  */
1550
1661
  async fork(sessionId, atMessageId, newName) {
1551
1662
  const info = this.create(newName, { parentSessionId: sessionId });
1552
- const history = this.getSession(sessionId).getHistory(atMessageId);
1663
+ const history = await this.getSession(sessionId).getHistory(atMessageId);
1553
1664
  const newSession = this.getSession(info.id);
1554
1665
  let parentId = null;
1555
1666
  for (const msg of history) {
@@ -1564,10 +1675,10 @@ var SessionManager = class SessionManager {
1564
1675
  this._touch(info.id);
1565
1676
  return info;
1566
1677
  }
1567
- addCompaction(sessionId, summary, fromId, toId) {
1678
+ async addCompaction(sessionId, summary, fromId, toId) {
1568
1679
  return this.getSession(sessionId).addCompaction(summary, fromId, toId);
1569
1680
  }
1570
- getCompactions(sessionId) {
1681
+ async getCompactions(sessionId) {
1571
1682
  return this.getSession(sessionId).getCompactions();
1572
1683
  }
1573
1684
  async compactAndSplit(sessionId, summary, newName) {
@@ -1652,6 +1763,300 @@ var SessionManager = class SessionManager {
1652
1763
  }
1653
1764
  };
1654
1765
  //#endregion
1655
- export { AgentContextProvider, AgentSearchProvider, AgentSessionProvider, R2SkillProvider, Session, SessionManager, isSearchProvider, isSkillProvider, isWritableProvider };
1766
+ //#region src/experimental/memory/session/providers/postgres-adapter.ts
1767
+ function isPostgresConnection(client) {
1768
+ return typeof client.execute === "function";
1769
+ }
1770
+ /**
1771
+ * Normalise an incoming client into a `PostgresConnection`. When given a
1772
+ * `pg`-style client we translate `?` placeholders to `$1, $2, ...` so the
1773
+ * providers can keep using the portable `?` syntax internally.
1774
+ */
1775
+ function toPostgresConnection(client) {
1776
+ if (isPostgresConnection(client)) return client;
1777
+ const pg = client;
1778
+ return { async execute(query, args) {
1779
+ let idx = 0;
1780
+ const pgQuery = query.replace(/\?/g, () => `$${++idx}`);
1781
+ return { rows: (await pg.query(pgQuery, args ?? [])).rows };
1782
+ } };
1783
+ }
1784
+ //#endregion
1785
+ //#region src/experimental/memory/session/providers/postgres.ts
1786
+ var PostgresSessionProvider = class {
1787
+ /**
1788
+ * @param client A raw `pg.Client` (recommended) or any `PostgresConnection`.
1789
+ * Must already be connected — this provider never opens or closes the
1790
+ * underlying client.
1791
+ * @param sessionId Session identifier. Different ids are fully isolated
1792
+ * rows within the shared tables. Defaults to `""`.
1793
+ */
1794
+ constructor(client, sessionId) {
1795
+ this.conn = toPostgresConnection(client);
1796
+ this.sessionId = sessionId ?? "";
1797
+ }
1798
+ async getMessage(id) {
1799
+ const { rows } = await this.conn.execute("SELECT content FROM assistant_messages WHERE id = ? AND session_id = ?", [id, this.sessionId]);
1800
+ return rows.length > 0 ? this.parse(rows[0].content) : null;
1801
+ }
1802
+ async getHistory(leafId) {
1803
+ const leaf = leafId ? (await this.conn.execute("SELECT id FROM assistant_messages WHERE id = ? AND session_id = ?", [leafId, this.sessionId])).rows[0] : await this.latestLeafRow();
1804
+ if (!leaf) return [];
1805
+ const { rows } = await this.conn.execute(`WITH RECURSIVE path AS (
1806
+ SELECT id, parent_id, content, 0 as depth FROM assistant_messages WHERE id = ? AND session_id = ?
1807
+ UNION ALL
1808
+ SELECT m.id, m.parent_id, m.content, p.depth + 1 FROM assistant_messages m
1809
+ JOIN path p ON m.id = p.parent_id
1810
+ WHERE m.session_id = ? AND p.depth < 10000
1811
+ )
1812
+ SELECT content FROM path ORDER BY depth DESC`, [
1813
+ leaf.id,
1814
+ this.sessionId,
1815
+ this.sessionId
1816
+ ]);
1817
+ const messages = this.parseRows(rows);
1818
+ const compactions = await this.getCompactions();
1819
+ if (compactions.length === 0) return messages;
1820
+ return this.applyCompactions(messages, compactions);
1821
+ }
1822
+ async getLatestLeaf() {
1823
+ const row = await this.latestLeafRow();
1824
+ return row ? this.parse(row.content) : null;
1825
+ }
1826
+ async getBranches(messageId) {
1827
+ const { rows } = await this.conn.execute("SELECT content FROM assistant_messages WHERE parent_id = ? AND session_id = ? ORDER BY created_at ASC", [messageId, this.sessionId]);
1828
+ return this.parseRows(rows);
1829
+ }
1830
+ async getPathLength(leafId) {
1831
+ const leaf = leafId ? (await this.conn.execute("SELECT id FROM assistant_messages WHERE id = ? AND session_id = ?", [leafId, this.sessionId])).rows[0] : await this.latestLeafRow();
1832
+ if (!leaf) return 0;
1833
+ const { rows } = await this.conn.execute(`WITH RECURSIVE path AS (
1834
+ SELECT id, parent_id, 0 as depth FROM assistant_messages WHERE id = ? AND session_id = ?
1835
+ UNION ALL
1836
+ SELECT m.id, m.parent_id, p.depth + 1 FROM assistant_messages m
1837
+ JOIN path p ON m.id = p.parent_id
1838
+ WHERE m.session_id = ? AND p.depth < 10000
1839
+ )
1840
+ SELECT COUNT(*) as count FROM path`, [
1841
+ leaf.id,
1842
+ this.sessionId,
1843
+ this.sessionId
1844
+ ]);
1845
+ return Number(rows[0]?.count ?? 0);
1846
+ }
1847
+ async appendMessage(message, parentId) {
1848
+ let parent = parentId !== void 0 ? parentId : (await this.latestLeafRow())?.id ?? null;
1849
+ if (parent) {
1850
+ const { rows } = await this.conn.execute("SELECT id FROM assistant_messages WHERE id = ? AND session_id = ?", [parent, this.sessionId]);
1851
+ if (rows.length === 0) parent = null;
1852
+ }
1853
+ const json = JSON.stringify(message);
1854
+ const text = this.extractSearchableText(json);
1855
+ await this.conn.execute(`INSERT INTO assistant_messages (id, session_id, parent_id, role, content, text_content)
1856
+ VALUES (?, ?, ?, ?, ?, ?)
1857
+ ON CONFLICT (session_id, id) DO NOTHING`, [
1858
+ message.id,
1859
+ this.sessionId,
1860
+ parent,
1861
+ message.role,
1862
+ json,
1863
+ text
1864
+ ]);
1865
+ }
1866
+ async updateMessage(message) {
1867
+ const json = JSON.stringify(message);
1868
+ await this.conn.execute("UPDATE assistant_messages SET content = ?, text_content = ? WHERE id = ? AND session_id = ?", [
1869
+ json,
1870
+ this.extractSearchableText(json),
1871
+ message.id,
1872
+ this.sessionId
1873
+ ]);
1874
+ }
1875
+ async deleteMessages(messageIds) {
1876
+ for (const id of messageIds) await this.conn.execute("DELETE FROM assistant_messages WHERE id = ? AND session_id = ?", [id, this.sessionId]);
1877
+ }
1878
+ async clearMessages() {
1879
+ await this.conn.execute("DELETE FROM assistant_messages WHERE session_id = ?", [this.sessionId]);
1880
+ await this.conn.execute("DELETE FROM assistant_compactions WHERE session_id = ?", [this.sessionId]);
1881
+ }
1882
+ async addCompaction(summary, fromMessageId, toMessageId) {
1883
+ const id = crypto.randomUUID();
1884
+ await this.conn.execute("INSERT INTO assistant_compactions (id, session_id, summary, from_message_id, to_message_id) VALUES (?, ?, ?, ?, ?)", [
1885
+ id,
1886
+ this.sessionId,
1887
+ summary,
1888
+ fromMessageId,
1889
+ toMessageId
1890
+ ]);
1891
+ return {
1892
+ id,
1893
+ summary,
1894
+ fromMessageId,
1895
+ toMessageId,
1896
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1897
+ };
1898
+ }
1899
+ async getCompactions() {
1900
+ const { rows } = await this.conn.execute("SELECT * FROM assistant_compactions WHERE session_id = ? ORDER BY created_at ASC", [this.sessionId]);
1901
+ return rows.map((r) => ({
1902
+ id: r.id,
1903
+ summary: r.summary,
1904
+ fromMessageId: r.from_message_id,
1905
+ toMessageId: r.to_message_id,
1906
+ createdAt: r.created_at instanceof Date ? r.created_at.toISOString() : String(r.created_at)
1907
+ }));
1908
+ }
1909
+ async searchMessages(query, limit = 20) {
1910
+ const { rows } = await this.conn.execute(`SELECT id, role, text_content FROM assistant_messages
1911
+ WHERE session_id = ? AND content_tsv @@ plainto_tsquery('english', ?)
1912
+ ORDER BY ts_rank(content_tsv, plainto_tsquery('english', ?)) DESC
1913
+ LIMIT ?`, [
1914
+ this.sessionId,
1915
+ query,
1916
+ query,
1917
+ limit
1918
+ ]);
1919
+ return rows.map((r) => ({
1920
+ id: r.id,
1921
+ role: r.role,
1922
+ content: r.text_content ?? "",
1923
+ createdAt: ""
1924
+ }));
1925
+ }
1926
+ async latestLeafRow() {
1927
+ const { rows } = await this.conn.execute(`SELECT m.id, m.content FROM assistant_messages m
1928
+ LEFT JOIN assistant_messages c ON c.parent_id = m.id AND c.session_id = ?
1929
+ WHERE c.id IS NULL AND m.session_id = ?
1930
+ ORDER BY m.created_at DESC LIMIT 1`, [this.sessionId, this.sessionId]);
1931
+ return rows[0] ?? null;
1932
+ }
1933
+ applyCompactions(messages, compactions) {
1934
+ const ids = messages.map((m) => m.id);
1935
+ const result = [];
1936
+ let i = 0;
1937
+ while (i < messages.length) {
1938
+ const matching = compactions.filter((c) => c.fromMessageId === ids[i]);
1939
+ const comp = matching.length > 1 ? matching[matching.length - 1] : matching[0];
1940
+ if (comp) {
1941
+ const endIdx = ids.indexOf(comp.toMessageId);
1942
+ if (endIdx >= i) {
1943
+ result.push({
1944
+ id: `compaction_${comp.id}`,
1945
+ role: "assistant",
1946
+ parts: [{
1947
+ type: "text",
1948
+ text: comp.summary
1949
+ }],
1950
+ createdAt: /* @__PURE__ */ new Date()
1951
+ });
1952
+ i = endIdx + 1;
1953
+ continue;
1954
+ }
1955
+ }
1956
+ result.push(messages[i]);
1957
+ i++;
1958
+ }
1959
+ return result;
1960
+ }
1961
+ parse(json) {
1962
+ try {
1963
+ const msg = JSON.parse(json);
1964
+ if (typeof msg?.id === "string" && typeof msg?.role === "string" && Array.isArray(msg?.parts)) return msg;
1965
+ } catch {}
1966
+ return null;
1967
+ }
1968
+ parseRows(rows) {
1969
+ const result = [];
1970
+ for (const row of rows) {
1971
+ const msg = this.parse(row.content);
1972
+ if (msg) result.push(msg);
1973
+ }
1974
+ return result;
1975
+ }
1976
+ /**
1977
+ * Extract just the human-readable text from a message's JSON blob
1978
+ * and store it in `text_content`, which feeds the generated `content_tsv`
1979
+ * column used for FTS. The full structured message (parts, tool calls,
1980
+ * metadata) is still stored verbatim in `content` — this is the source
1981
+ * of truth. Indexing the raw JSON would return FTS hits on keys like
1982
+ * `"role"`, `"parts"`, `"dynamic-tool"`, etc.
1983
+ */
1984
+ extractSearchableText(json) {
1985
+ const msg = this.parse(json);
1986
+ if (!msg) return json;
1987
+ return msg.parts.filter((p) => p.type === "text" && p.text).map((p) => p.text).join("\n");
1988
+ }
1989
+ };
1990
+ //#endregion
1991
+ //#region src/experimental/memory/session/providers/postgres-context.ts
1992
+ var PostgresContextProvider = class {
1993
+ /**
1994
+ * @param client A raw `pg.Client` (recommended) or any `PostgresConnection`.
1995
+ * Must already be connected.
1996
+ * @param label Block label used as the primary key row in
1997
+ * `cf_agents_context_blocks`. Pass a session-scoped label (e.g.
1998
+ * `` `memory_${sessionId}` ``) for per-session state.
1999
+ */
2000
+ constructor(client, label) {
2001
+ this.conn = toPostgresConnection(client);
2002
+ this.label = label;
2003
+ }
2004
+ async get() {
2005
+ const { rows } = await this.conn.execute("SELECT content FROM cf_agents_context_blocks WHERE label = ?", [this.label]);
2006
+ return rows[0]?.content ?? null;
2007
+ }
2008
+ async set(content) {
2009
+ await this.conn.execute(`INSERT INTO cf_agents_context_blocks (label, content)
2010
+ VALUES (?, ?)
2011
+ ON CONFLICT (label) DO UPDATE SET content = EXCLUDED.content, updated_at = NOW()`, [this.label, content]);
2012
+ }
2013
+ };
2014
+ //#endregion
2015
+ //#region src/experimental/memory/session/providers/postgres-search.ts
2016
+ var PostgresSearchProvider = class {
2017
+ /**
2018
+ * @param client A raw `pg.Client` (recommended) or any `PostgresConnection`.
2019
+ * Must already be connected.
2020
+ */
2021
+ constructor(client) {
2022
+ this.label = "";
2023
+ this.conn = toPostgresConnection(client);
2024
+ }
2025
+ init(label) {
2026
+ this.label = label;
2027
+ }
2028
+ async get() {
2029
+ const { rows } = await this.conn.execute("SELECT COUNT(*) as count FROM cf_agents_search_entries WHERE label = ?", [this.label]);
2030
+ const count = Number(rows[0]?.count ?? 0);
2031
+ if (count === 0) return null;
2032
+ return `${count} entries indexed.`;
2033
+ }
2034
+ async search(query) {
2035
+ if (!query.trim()) return null;
2036
+ const { rows } = await this.conn.execute(`SELECT key, content FROM cf_agents_search_entries
2037
+ WHERE label = ? AND content_tsv @@ plainto_tsquery('english', ?)
2038
+ ORDER BY ts_rank(content_tsv, plainto_tsquery('english', ?)) DESC
2039
+ LIMIT 10`, [
2040
+ this.label,
2041
+ query,
2042
+ query
2043
+ ]);
2044
+ if (rows.length === 0) return "No results found.";
2045
+ return rows.map((r) => `[${r.key}]\n${r.content}`).join("\n\n");
2046
+ }
2047
+ async set(key, content) {
2048
+ await this.conn.execute(`INSERT INTO cf_agents_search_entries (label, key, content)
2049
+ VALUES (?, ?, ?)
2050
+ ON CONFLICT (label, key) DO UPDATE SET
2051
+ content = EXCLUDED.content,
2052
+ updated_at = NOW()`, [
2053
+ this.label,
2054
+ key,
2055
+ content
2056
+ ]);
2057
+ }
2058
+ };
2059
+ //#endregion
2060
+ export { AgentContextProvider, AgentSearchProvider, AgentSessionProvider, PostgresContextProvider, PostgresSearchProvider, PostgresSessionProvider, R2SkillProvider, Session, SessionManager, isSearchProvider, isSkillProvider, isWritableProvider };
1656
2061
 
1657
2062
  //# sourceMappingURL=index.js.map