agents 0.12.3 → 0.13.0

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 (88) hide show
  1. package/README.md +7 -7
  2. package/dist/{agent-tool-types-DSteYkkS.d.ts → agent-tool-types-BVgYyKO9.d.ts} +153 -102
  3. package/dist/agent-tool-types.d.ts +1 -1
  4. package/dist/agent-tools-BAdX1vdI.js.map +1 -1
  5. package/dist/{agent-tools-eGTCdVZX.d.ts → agent-tools-C-Ch8Thl.d.ts} +2 -2
  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.d.ts +2 -2
  13. package/dist/browser/ai.js +1 -1
  14. package/dist/browser/ai.js.map +1 -1
  15. package/dist/browser/index.d.ts +41 -10
  16. package/dist/browser/index.js +1 -1
  17. package/dist/browser/tanstack-ai.d.ts +2 -2
  18. package/dist/browser/tanstack-ai.js +1 -1
  19. package/dist/browser/tanstack-ai.js.map +1 -1
  20. package/dist/chat/index.d.ts +297 -148
  21. package/dist/chat/index.js +43 -14
  22. package/dist/chat/index.js.map +1 -1
  23. package/dist/{classPrivateFieldGet2-Bqby-AHD.js → classPrivateFieldGet2-Evpt0SEr.js} +5 -5
  24. package/dist/cli/index.d.ts +1 -1
  25. package/dist/cli/index.js.map +1 -1
  26. package/dist/client-D1kFXo80.js.map +1 -1
  27. package/dist/client.d.ts +1 -1
  28. package/dist/client.js.map +1 -1
  29. package/dist/codemode/ai.d.ts +1 -1
  30. package/dist/codemode/ai.js.map +1 -1
  31. package/dist/{compaction-helpers-CzCq1-fF.d.ts → compaction-helpers-DAe-xiVY.d.ts} +42 -17
  32. package/dist/{compaction-helpers-CSaqCmdE.js → compaction-helpers-DvcZnvQ1.js} +1 -1
  33. package/dist/{compaction-helpers-CSaqCmdE.js.map → compaction-helpers-DvcZnvQ1.js.map} +1 -1
  34. package/dist/{do-oauth-client-provider-C38aWbFV.d.ts → do-oauth-client-provider-4OKQU9rT.d.ts} +1 -1
  35. package/dist/{email-X72-zjuq.d.ts → email-J0GGS3sa.d.ts} +1 -1
  36. package/dist/email.d.ts +3 -3
  37. package/dist/email.js.map +1 -1
  38. package/dist/experimental/memory/session/index.d.ts +369 -56
  39. package/dist/experimental/memory/session/index.js +553 -138
  40. package/dist/experimental/memory/session/index.js.map +1 -1
  41. package/dist/experimental/memory/utils/index.d.ts +41 -5
  42. package/dist/experimental/memory/utils/index.js +15 -5
  43. package/dist/experimental/memory/utils/index.js.map +1 -1
  44. package/dist/experimental/webmcp.d.ts +9 -3
  45. package/dist/experimental/webmcp.js.map +1 -1
  46. package/dist/{index-Biv6K70p.d.ts → index-DKey3P4s.d.ts} +26 -2
  47. package/dist/index.d.ts +71 -67
  48. package/dist/index.js +211 -108
  49. package/dist/index.js.map +1 -1
  50. package/dist/{internal_context-BvuGZieY.d.ts → internal_context-BZrMS0B5.d.ts} +1 -1
  51. package/dist/internal_context.d.ts +1 -1
  52. package/dist/internal_context.js.map +1 -1
  53. package/dist/mcp/client.d.ts +26 -2
  54. package/dist/mcp/do-oauth-client-provider.d.ts +10 -2
  55. package/dist/mcp/do-oauth-client-provider.js.map +1 -1
  56. package/dist/mcp/index.d.ts +54 -2
  57. package/dist/mcp/index.js +2 -2
  58. package/dist/mcp/index.js.map +1 -1
  59. package/dist/mcp/x402.d.ts +74 -17
  60. package/dist/mcp/x402.js.map +1 -1
  61. package/dist/observability/index.d.ts +16 -2
  62. package/dist/observability/index.js +1 -1
  63. package/dist/observability/index.js.map +1 -1
  64. package/dist/react.d.ts +2 -2
  65. package/dist/react.js.map +1 -1
  66. package/dist/{retries-fLD8cGNf.d.ts → retries-BVdRl5ZE.d.ts} +1 -1
  67. package/dist/retries.d.ts +1 -1
  68. package/dist/retries.js.map +1 -1
  69. package/dist/schedule.js.map +1 -1
  70. package/dist/serializable.d.ts +1 -1
  71. package/dist/{shared-C6l4ZKRN.js → shared-CiKaIK4h.js} +4 -5
  72. package/dist/{shared-C6l4ZKRN.js.map → shared-CiKaIK4h.js.map} +1 -1
  73. package/dist/{shared-Ch9slKdI.d.ts → shared-Cvj92byG.d.ts} +1 -1
  74. package/dist/sub-routing.d.ts +6 -6
  75. package/dist/sub-routing.js.map +1 -1
  76. package/dist/tool-output-truncation-CH-khbZ3.js +98 -0
  77. package/dist/tool-output-truncation-CH-khbZ3.js.map +1 -0
  78. package/dist/{types-DAHCZC_W.d.ts → types-_JjKmv-l.d.ts} +1 -1
  79. package/dist/types.d.ts +1 -1
  80. package/dist/types.js.map +1 -1
  81. package/dist/utils.js.map +1 -1
  82. package/dist/vite.js.map +1 -1
  83. package/dist/{workflow-types-DHs0L0KP.d.ts → workflow-types-Dkzg4hAx.d.ts} +1 -1
  84. package/dist/workflow-types.d.ts +1 -1
  85. package/dist/workflow-types.js.map +1 -1
  86. package/dist/workflows.d.ts +2 -2
  87. package/dist/workflows.js.map +1 -1
  88. package/package.json +11 -11
@@ -1,5 +1,5 @@
1
1
  import "../../../types.js";
2
- import { m as estimateStringTokens, p as estimateMessageTokens, t as COMPACTION_PREFIX } from "../../../compaction-helpers-CSaqCmdE.js";
2
+ import { m as estimateStringTokens, p as estimateMessageTokens, t as COMPACTION_PREFIX } from "../../../compaction-helpers-DvcZnvQ1.js";
3
3
  import { z } from "zod";
4
4
  //#region src/experimental/memory/session/search.ts
5
5
  /**
@@ -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();
@@ -131,17 +126,22 @@ function isSkillProvider(provider) {
131
126
  *
132
127
  * Descriptions are pulled from R2 custom metadata (`description` key).
133
128
  * If a prefix is provided, it is prepended on storage operations and
134
- * stripped from keys in metadata.
129
+ * stripped from keys in metadata. `keys`, when provided, is matched against
130
+ * these prefix-relative keys.
135
131
  *
136
132
  * @example
137
133
  * ```ts
138
- * const skills = new R2SkillProvider(env.SKILLS_BUCKET, { prefix: "skills/" });
134
+ * const skills = new R2SkillProvider(env.SKILLS_BUCKET, {
135
+ * prefix: "skills/",
136
+ * keys: ["code-review", "debugging"]
137
+ * });
139
138
  * ```
140
139
  */
141
140
  var R2SkillProvider = class {
142
141
  constructor(bucket, options) {
143
142
  this.bucket = bucket;
144
143
  this.prefix = options?.prefix ?? "";
144
+ this.keys = options?.keys?.length ? new Set(options.keys) : null;
145
145
  }
146
146
  async get() {
147
147
  const entries = [];
@@ -155,6 +155,7 @@ var R2SkillProvider = class {
155
155
  });
156
156
  for (const obj of listed.objects) {
157
157
  const key = obj.key.slice(this.prefix.length);
158
+ if (!this.allowsKey(key)) continue;
158
159
  const desc = obj.customMetadata?.description;
159
160
  entries.push(`- ${key}${desc ? `: ${desc}` : ""}`);
160
161
  }
@@ -164,6 +165,7 @@ var R2SkillProvider = class {
164
165
  return entries.length > 0 ? entries.join("\n") : null;
165
166
  }
166
167
  async load(key) {
168
+ if (!this.allowsKey(key)) return null;
167
169
  const obj = await this.bucket.get(this.prefix + key);
168
170
  if (!obj) return null;
169
171
  return obj.text();
@@ -171,9 +173,27 @@ var R2SkillProvider = class {
171
173
  async set(key, content, description) {
172
174
  await this.bucket.put(this.prefix + key, content, { customMetadata: description ? { description } : void 0 });
173
175
  }
176
+ allowsKey(key) {
177
+ return this.keys === null || this.keys.has(key);
178
+ }
174
179
  };
175
180
  //#endregion
176
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
+ }
177
197
  /**
178
198
  * Check if a provider is writable (has a `set` method).
179
199
  */
@@ -405,7 +425,8 @@ var ContextBlocks = class {
405
425
  if (!this.loaded) await this.load();
406
426
  const existing = this.blocks.get(label);
407
427
  if (!existing) throw new Error(`Block "${label}" not found`);
408
- 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);
409
430
  }
410
431
  /**
411
432
  * Get the system prompt string with context blocks.
@@ -429,18 +450,17 @@ var ContextBlocks = class {
429
450
  const parts = [];
430
451
  const sep = "═".repeat(46);
431
452
  for (const block of this.blocks.values()) {
432
- if (!block.content && !block.isSearchable) continue;
453
+ if (!block.content && !block.writable && !block.isSearchable && !block.isSkill) continue;
433
454
  let header = block.label.toUpperCase();
434
- const hints = [];
435
- if (block.description) hints.push(block.description);
436
- if (block.isSkill) hints.push("use load_context to load");
437
- if (block.isSearchable) hints.push("use search_context to search");
438
- if (hints.length > 0) header += ` (${hints.join(" — ")})`;
455
+ if (block.description) header += ` (${block.description})`;
439
456
  if (block.maxTokens) {
440
457
  const pct = Math.round(block.tokens / block.maxTokens * 100);
441
458
  header += ` [${pct}% — ${block.tokens}/${block.maxTokens} tokens]`;
442
459
  }
443
- 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]";
444
464
  parts.push(`${sep}\n${header}\n${sep}\n${block.content}`);
445
465
  }
446
466
  this.snapshot = parts.join("\n\n");
@@ -477,9 +497,9 @@ var ContextBlocks = class {
477
497
  return Array.from(this.blocks.values()).filter((b) => b.isSearchable).map((b) => b.label);
478
498
  }
479
499
  /**
480
- * Frozen system prompt. On first call:
481
- * 1. Checks store for a persisted prompt (survives DO eviction)
482
- * 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.
483
503
  */
484
504
  async freezeSystemPrompt() {
485
505
  if (this.promptStore) {
@@ -492,10 +512,13 @@ var ContextBlocks = class {
492
512
  return prompt;
493
513
  }
494
514
  /**
495
- * 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.
496
518
  */
497
519
  async refreshSystemPrompt() {
498
- if (!this.loaded) await this.load();
520
+ this.loaded = false;
521
+ await this.load();
499
522
  const prompt = this.refreshSnapshot();
500
523
  if (this.promptStore) await this.promptStore.set(prompt);
501
524
  return prompt;
@@ -515,14 +538,11 @@ var ContextBlocks = class {
515
538
  const hasSearch = this.hasSearchBlocks();
516
539
  const toolSet = {};
517
540
  if (writable.length > 0) {
518
- 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
+ });
519
545
  const keyedBlocks = writable.filter((b) => b.isSkill || b.isSearchable);
520
- const blockDescriptions = [];
521
- for (const b of regularBlocks) blockDescriptions.push(`- "${b.label}": ${b.description ?? "no description"}`);
522
- for (const b of keyedBlocks) {
523
- const kind = b.isSkill ? "skill collection (requires key and optional description)" : "searchable (requires key)";
524
- blockDescriptions.push(`- "${b.label}": ${kind}`);
525
- }
526
546
  const properties = {
527
547
  label: {
528
548
  type: "string",
@@ -531,7 +551,7 @@ var ContextBlocks = class {
531
551
  },
532
552
  content: {
533
553
  type: "string",
534
- description: "Content to write"
554
+ description: "The main content to write to the block."
535
555
  },
536
556
  action: {
537
557
  type: "string",
@@ -539,34 +559,38 @@ var ContextBlocks = class {
539
559
  description: "replace (default) or append"
540
560
  }
541
561
  };
542
- const required = ["label", "content"];
543
- if (keyedBlocks.length > 0) properties.key = {
544
- type: "string",
545
- description: "Entry key (required for keyed blocks: " + keyedBlocks.map((b) => `"${b.label}"`).join(", ") + ")"
546
- };
547
- if (keyedBlocks.some((b) => b.isSkill)) properties.description = {
548
- type: "string",
549
- 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
+ }
550
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." : "";
551
577
  toolSet.set_context = {
552
- 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}`,
553
579
  inputSchema: z.fromJSONSchema({
554
580
  type: "object",
555
581
  properties,
556
- required
582
+ required: ["label", "content"]
557
583
  }),
558
- execute: async ({ label, content, key, description, action }) => {
584
+ execute: async ({ label, content, metadata, action }) => {
559
585
  try {
560
586
  const block = this.blocks.get(label);
561
587
  if (!block) return `Error: block "${label}" not found`;
562
- if (block.isSkill) {
563
- if (!key) return `Error: key is required for skill block "${label}"`;
564
- await this.setSkill(label, key, content, description);
565
- return `Written skill "${key}" to ${label}.`;
566
- }
567
- if (block.isSearchable) {
568
- if (!key) return `Error: key is required for searchable block "${label}"`;
569
- 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);
570
594
  return `Indexed "${key}" in ${label}.`;
571
595
  }
572
596
  const updated = action === "append" ? await this.appendToBlock(label, content) : await this.setBlock(label, content);
@@ -598,6 +622,7 @@ var ContextBlocks = class {
598
622
  }),
599
623
  execute: async ({ label, key }) => {
600
624
  try {
625
+ if (!skillLabels.includes(label)) return `Error: "${label}" is not a skill block. Skill blocks: ${skillLabels.join(", ")}`;
601
626
  return await this.loadSkill(label, key) ?? `Not found: ${key}`;
602
627
  } catch (err) {
603
628
  return `Error: ${err instanceof Error ? err.message : String(err)}`;
@@ -623,6 +648,7 @@ var ContextBlocks = class {
623
648
  required: ["label", "key"]
624
649
  }),
625
650
  execute: async ({ label, key }) => {
651
+ if (!skillLabels.includes(label)) return `Error: "${label}" is not a skill block. Skill blocks: ${skillLabels.join(", ")}`;
626
652
  if (!this.unloadSkill(label, key)) return `Skill "${key}" is not currently loaded in "${label}".`;
627
653
  return `Unloaded "${key}" from ${label}. Context reclaimed.`;
628
654
  }
@@ -631,7 +657,7 @@ var ContextBlocks = class {
631
657
  if (hasSearch) {
632
658
  const searchLabels = this.getSearchLabels();
633
659
  toolSet.search_context = {
634
- 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.",
635
661
  inputSchema: z.fromJSONSchema({
636
662
  type: "object",
637
663
  properties: {
@@ -649,6 +675,7 @@ var ContextBlocks = class {
649
675
  }),
650
676
  execute: async ({ label, query }) => {
651
677
  try {
678
+ if (!searchLabels.includes(label)) return `Error: "${label}" is not searchable. Searchable blocks: ${searchLabels.join(", ")}`;
652
679
  return await this.searchContext(label, query) ?? "No results found.";
653
680
  } catch (err) {
654
681
  return `Error: ${err instanceof Error ? err.message : String(err)}`;
@@ -779,7 +806,7 @@ var AgentSessionProvider = class {
779
806
  if (this.agent.sql`
780
807
  SELECT id FROM assistant_messages WHERE id = ${message.id} AND session_id = ${this.sessionId}
781
808
  `.length > 0) return;
782
- let parent = parentId ?? this.latestLeafRow()?.id ?? null;
809
+ let parent = parentId !== void 0 ? parentId : this.latestLeafRow()?.id ?? null;
783
810
  if (parent) {
784
811
  if (this.agent.sql`
785
812
  SELECT id FROM assistant_messages WHERE id = ${parent} AND session_id = ${this.sessionId}
@@ -969,6 +996,9 @@ var AgentContextProvider = class {
969
996
  function isBroadcaster(obj) {
970
997
  return typeof obj === "object" && obj !== null && "broadcast" in obj && typeof obj.broadcast === "function";
971
998
  }
999
+ function isSqlProvider(arg) {
1000
+ return "sql" in arg && typeof arg.sql === "function";
1001
+ }
972
1002
  var Session = class Session {
973
1003
  constructor(storage, options) {
974
1004
  this._ready = false;
@@ -977,11 +1007,14 @@ var Session = class Session {
977
1007
  this._ready = true;
978
1008
  }
979
1009
  /**
980
- * Chainable session creation with auto-wired SQLite providers.
981
- * 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.).
982
1014
  *
983
1015
  * @example
984
1016
  * ```ts
1017
+ * // Auto-wired SQLite (DO Agent)
985
1018
  * const session = Session.create(this)
986
1019
  * .withContext("soul", { provider: { get: async () => "You are helpful." } })
987
1020
  * .withContext("memory", { description: "Learned facts", maxTokens: 1100 })
@@ -993,12 +1026,22 @@ var Session = class Session {
993
1026
  * provider: new R2SkillProvider(env.SKILLS_BUCKET, { prefix: "skills/" })
994
1027
  * })
995
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"));
996
1037
  * ```
997
1038
  */
998
- static create(agent) {
1039
+ static create(provider) {
999
1040
  const session = Object.create(Session.prototype);
1000
- session._agent = agent;
1001
- 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;
1002
1045
  session._pending = [];
1003
1046
  session._ready = false;
1004
1047
  return session;
@@ -1034,11 +1077,20 @@ var Session = class Session {
1034
1077
  this._tokenThreshold = tokenThreshold;
1035
1078
  return this;
1036
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
+ }
1037
1089
  _ensureReady() {
1038
1090
  if (this._ready) return;
1039
1091
  const configs = (this._pending ?? []).map(({ label, options: opts }) => {
1040
1092
  let provider = opts.provider;
1041
- if (!provider) {
1093
+ if (!provider && this._agent) {
1042
1094
  const key = this._sessionId ? `${label}_${this._sessionId}` : label;
1043
1095
  provider = new AgentContextProvider(this._agent, key);
1044
1096
  }
@@ -1050,25 +1102,40 @@ var Session = class Session {
1050
1102
  };
1051
1103
  });
1052
1104
  let promptStore;
1053
- if (this._cachedPrompt === true) {
1105
+ if (this._cachedPrompt === true && this._agent) {
1054
1106
  const key = this._sessionId ? `_system_prompt_${this._sessionId}` : "_system_prompt";
1055
1107
  promptStore = new AgentContextProvider(this._agent, key);
1056
- } else if (this._cachedPrompt) promptStore = this._cachedPrompt;
1057
- 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");
1058
1112
  this.context = new ContextBlocks(configs, promptStore);
1059
1113
  this.context.setUnloadCallback((label, key) => {
1060
- this._reclaimLoadedSkill(label, key);
1114
+ this._reclaimLoadedSkill(label, key).catch(() => {});
1061
1115
  });
1062
- this._restoreLoadedSkills();
1063
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);
1064
1130
  }
1065
1131
  /**
1066
1132
  * Reconstruct which skills are loaded by scanning conversation history
1067
1133
  * for load_context tool results that haven't been unloaded.
1068
- * 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.
1069
1136
  */
1070
- _restoreLoadedSkills() {
1071
- const history = this.storage.getHistory();
1137
+ async _restoreLoadedSkills() {
1138
+ const history = await this.storage.getHistory();
1072
1139
  const loaded = /* @__PURE__ */ new Set();
1073
1140
  for (const msg of history) {
1074
1141
  if (msg.role !== "assistant") continue;
@@ -1087,11 +1154,22 @@ var Session = class Session {
1087
1154
  if (loaded.size > 0) this.context.restoreLoadedSkills(loaded);
1088
1155
  }
1089
1156
  /**
1090
- * Replace a load_context tool result in conversation history
1091
- * 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`.
1092
1170
  */
1093
- _reclaimLoadedSkill(label, key) {
1094
- const history = this.storage.getHistory();
1171
+ async _reclaimLoadedSkill(label, key) {
1172
+ const history = await this.storage.getHistory();
1095
1173
  for (let i = history.length - 1; i >= 0; i--) {
1096
1174
  const msg = history[i];
1097
1175
  if (msg.role !== "assistant") continue;
@@ -1110,7 +1188,7 @@ var Session = class Session {
1110
1188
  return part;
1111
1189
  });
1112
1190
  if (changed) {
1113
- this.storage.updateMessage({
1191
+ await this.updateMessage({
1114
1192
  ...msg,
1115
1193
  parts: newParts
1116
1194
  });
@@ -1118,24 +1196,24 @@ var Session = class Session {
1118
1196
  }
1119
1197
  }
1120
1198
  }
1121
- getHistory(leafId) {
1122
- this._ensureReady();
1199
+ async getHistory(leafId) {
1200
+ await this._ensureRestored();
1123
1201
  return this.storage.getHistory(leafId);
1124
1202
  }
1125
- getMessage(id) {
1126
- this._ensureReady();
1203
+ async getMessage(id) {
1204
+ await this._ensureRestored();
1127
1205
  return this.storage.getMessage(id);
1128
1206
  }
1129
- getLatestLeaf() {
1130
- this._ensureReady();
1207
+ async getLatestLeaf() {
1208
+ await this._ensureRestored();
1131
1209
  return this.storage.getLatestLeaf();
1132
1210
  }
1133
- getBranches(messageId) {
1134
- this._ensureReady();
1211
+ async getBranches(messageId) {
1212
+ await this._ensureRestored();
1135
1213
  return this.storage.getBranches(messageId);
1136
1214
  }
1137
- getPathLength(leafId) {
1138
- this._ensureReady();
1215
+ async getPathLength(leafId) {
1216
+ await this._ensureRestored();
1139
1217
  return this.storage.getPathLength(leafId);
1140
1218
  }
1141
1219
  _broadcast(type, data) {
@@ -1145,8 +1223,8 @@ var Session = class Session {
1145
1223
  ...data
1146
1224
  }));
1147
1225
  }
1148
- _emitStatus(phase, extra) {
1149
- const tokenEstimate = estimateMessageTokens(this.getHistory());
1226
+ async _emitStatus(phase, extra) {
1227
+ const tokenEstimate = estimateMessageTokens(await this.getHistory());
1150
1228
  this._broadcast("cf_agent_session", {
1151
1229
  phase,
1152
1230
  tokenEstimate,
@@ -1159,35 +1237,65 @@ var Session = class Session {
1159
1237
  this._broadcast("cf_agent_session_error", { error });
1160
1238
  }
1161
1239
  async appendMessage(message, parentId) {
1162
- this._ensureReady();
1163
- this.storage.appendMessage(message, parentId);
1164
- 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;
1165
1257
  if (this._tokenThreshold != null && this._compactionFn && tokenEstimate > this._tokenThreshold) try {
1166
- await this.compact();
1258
+ compacted = Boolean(await this.compact());
1167
1259
  } catch {}
1260
+ if (!compacted) await this._notifyMessagesChanged({
1261
+ type: "append",
1262
+ message,
1263
+ parentId,
1264
+ inserted: true
1265
+ });
1168
1266
  }
1169
- updateMessage(message) {
1170
- this._ensureReady();
1171
- this.storage.updateMessage(message);
1172
- 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
+ });
1173
1275
  }
1174
- deleteMessages(messageIds) {
1175
- this._ensureReady();
1176
- this.storage.deleteMessages(messageIds);
1177
- 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
+ });
1178
1284
  }
1179
- clearMessages() {
1180
- this._ensureReady();
1181
- this.storage.clearMessages();
1285
+ async clearMessages() {
1286
+ await this._ensureRestored();
1287
+ await this.storage.clearMessages();
1182
1288
  this.context.clearSkillState();
1183
- this._emitStatus("idle");
1289
+ await this.context.refreshSystemPrompt();
1290
+ await this._emitStatus("idle");
1291
+ await this._notifyMessagesChanged({ type: "clear" });
1184
1292
  }
1185
- addCompaction(summary, fromMessageId, toMessageId) {
1186
- this._ensureReady();
1293
+ async addCompaction(summary, fromMessageId, toMessageId) {
1294
+ await this._ensureRestored();
1187
1295
  return this.storage.addCompaction(summary, fromMessageId, toMessageId);
1188
1296
  }
1189
- getCompactions() {
1190
- this._ensureReady();
1297
+ async getCompactions() {
1298
+ await this._ensureRestored();
1191
1299
  return this.storage.getCompactions();
1192
1300
  }
1193
1301
  /**
@@ -1195,29 +1303,30 @@ var Session = class Session {
1195
1303
  * Requires `onCompaction()` to be called first.
1196
1304
  */
1197
1305
  async compact() {
1198
- this._ensureReady();
1306
+ await this._ensureRestored();
1199
1307
  if (!this._compactionFn) throw new Error("No compaction function registered. Call onCompaction() first.");
1200
- const tokensBefore = this._emitStatus("compacting");
1308
+ const tokensBefore = await this._emitStatus("compacting");
1201
1309
  let result;
1202
1310
  try {
1203
- result = await this._compactionFn(this.getHistory());
1311
+ result = await this._compactionFn(await this.getHistory());
1204
1312
  } catch (err) {
1205
1313
  this._emitError(err instanceof Error ? err.message : String(err));
1206
1314
  return null;
1207
1315
  }
1208
1316
  if (!result) {
1209
- this._emitStatus("idle");
1317
+ await this._emitStatus("idle");
1210
1318
  return null;
1211
1319
  }
1212
- if (!new Set(this.getHistory().map((m) => m.id)).has(result.toMessageId)) {
1213
- this._emitStatus("idle");
1320
+ if (!new Set((await this.getHistory()).map((m) => m.id)).has(result.toMessageId)) {
1321
+ await this._emitStatus("idle");
1214
1322
  return null;
1215
1323
  }
1216
- const existing = this.getCompactions();
1324
+ const existing = await this.getCompactions();
1217
1325
  const fromId = existing.length > 0 ? existing[0].fromMessageId : result.fromMessageId;
1218
- this.addCompaction(result.summary, fromId, result.toMessageId);
1326
+ await this.addCompaction(result.summary, fromId, result.toMessageId);
1219
1327
  await this.refreshSystemPrompt();
1220
- this._emitStatus("idle", { compacted: { tokensBefore } });
1328
+ await this._emitStatus("idle", { compacted: { tokensBefore } });
1329
+ await this._notifyMessagesChanged({ type: "compact" });
1221
1330
  return {
1222
1331
  ...result,
1223
1332
  fromMessageId: fromId
@@ -1232,16 +1341,21 @@ var Session = class Session {
1232
1341
  return this.context.getBlocks();
1233
1342
  }
1234
1343
  async replaceContextBlock(label, content) {
1235
- this._ensureReady();
1344
+ await this._ensureRestored();
1236
1345
  return this.context.setBlock(label, content);
1237
1346
  }
1238
1347
  async appendContextBlock(label, content) {
1239
- this._ensureReady();
1348
+ await this._ensureRestored();
1240
1349
  return this.context.appendToBlock(label, content);
1241
1350
  }
1242
1351
  /**
1243
1352
  * Dynamically register a new context block after session initialization.
1244
- * 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).
1245
1359
  *
1246
1360
  * The block's provider is initialized and loaded immediately.
1247
1361
  * Call `refreshSystemPrompt()` afterward to include the new block
@@ -1252,10 +1366,11 @@ var Session = class Session {
1252
1366
  * via `Session.create(agent)` (not the direct constructor).
1253
1367
  */
1254
1368
  async addContext(label, options) {
1255
- this._ensureReady();
1369
+ await this._ensureRestored();
1256
1370
  const opts = options ?? {};
1257
1371
  let provider = opts.provider;
1258
1372
  if (!provider) {
1373
+ if (!this._agent) throw new Error(`addContext("${label}") requires an explicit provider when Session uses a SessionProvider`);
1259
1374
  const key = this._sessionId ? `${label}_${this._sessionId}` : label;
1260
1375
  provider = new AgentContextProvider(this._agent, key);
1261
1376
  }
@@ -1281,34 +1396,40 @@ var Session = class Session {
1281
1396
  /**
1282
1397
  * Unload a previously loaded skill, reclaiming context space.
1283
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.
1284
1404
  */
1285
- unloadSkill(label, key) {
1286
- this._ensureReady();
1405
+ async unloadSkill(label, key) {
1406
+ await this._ensureRestored();
1287
1407
  return this.context.unloadSkill(label, key);
1288
1408
  }
1289
1409
  /**
1290
1410
  * Get currently loaded skill keys (as "label:key" strings).
1411
+ * Async for the same reason as `unloadSkill` — must wait for restore.
1291
1412
  */
1292
- getLoadedSkillKeys() {
1293
- this._ensureReady();
1413
+ async getLoadedSkillKeys() {
1414
+ await this._ensureRestored();
1294
1415
  return this.context.getLoadedSkillKeys();
1295
1416
  }
1296
1417
  async freezeSystemPrompt() {
1297
- this._ensureReady();
1418
+ await this._ensureRestored();
1298
1419
  return this.context.freezeSystemPrompt();
1299
1420
  }
1300
1421
  async refreshSystemPrompt() {
1301
- this._ensureReady();
1422
+ await this._ensureRestored();
1302
1423
  return this.context.refreshSystemPrompt();
1303
1424
  }
1304
- search(query, options) {
1305
- this._ensureReady();
1425
+ async search(query, options) {
1426
+ await this._ensureRestored();
1306
1427
  if (!this.storage.searchMessages) throw new Error("Session provider does not support search");
1307
1428
  return this.storage.searchMessages(query, options?.limit ?? 20);
1308
1429
  }
1309
1430
  /** Returns set_context and load_context tools. */
1310
1431
  async tools() {
1311
- this._ensureReady();
1432
+ await this._ensureRestored();
1312
1433
  return this.context.tools();
1313
1434
  }
1314
1435
  };
@@ -1482,8 +1603,8 @@ var SessionManager = class SessionManager {
1482
1603
  SELECT * FROM assistant_sessions ORDER BY updated_at DESC
1483
1604
  `;
1484
1605
  }
1485
- delete(sessionId) {
1486
- this.getSession(sessionId).clearMessages();
1606
+ async delete(sessionId) {
1607
+ await this.getSession(sessionId).clearMessages();
1487
1608
  this.agent.sql`DELETE FROM assistant_sessions WHERE id = ${sessionId}`;
1488
1609
  this._sessions.delete(sessionId);
1489
1610
  }
@@ -1501,7 +1622,7 @@ var SessionManager = class SessionManager {
1501
1622
  }
1502
1623
  async upsert(sessionId, message, parentId) {
1503
1624
  const session = this.getSession(sessionId);
1504
- if (session.getMessage(message.id)) session.updateMessage(message);
1625
+ if (await session.getMessage(message.id)) await session.updateMessage(message);
1505
1626
  else await session.appendMessage(message, parentId);
1506
1627
  this._touch(sessionId);
1507
1628
  return message.id;
@@ -1516,21 +1637,21 @@ var SessionManager = class SessionManager {
1516
1637
  this._touch(sessionId);
1517
1638
  return lastParent;
1518
1639
  }
1519
- getHistory(sessionId, leafId) {
1640
+ async getHistory(sessionId, leafId) {
1520
1641
  return this.getSession(sessionId).getHistory(leafId);
1521
1642
  }
1522
- getMessageCount(sessionId) {
1643
+ async getMessageCount(sessionId) {
1523
1644
  return this.getSession(sessionId).getPathLength();
1524
1645
  }
1525
- clearMessages(sessionId) {
1526
- this.getSession(sessionId).clearMessages();
1646
+ async clearMessages(sessionId) {
1647
+ await this.getSession(sessionId).clearMessages();
1527
1648
  this._touch(sessionId);
1528
1649
  }
1529
- deleteMessages(sessionId, messageIds) {
1530
- this.getSession(sessionId).deleteMessages(messageIds);
1650
+ async deleteMessages(sessionId, messageIds) {
1651
+ await this.getSession(sessionId).deleteMessages(messageIds);
1531
1652
  this._touch(sessionId);
1532
1653
  }
1533
- getBranches(sessionId, messageId) {
1654
+ async getBranches(sessionId, messageId) {
1534
1655
  return this.getSession(sessionId).getBranches(messageId);
1535
1656
  }
1536
1657
  /**
@@ -1539,7 +1660,7 @@ var SessionManager = class SessionManager {
1539
1660
  */
1540
1661
  async fork(sessionId, atMessageId, newName) {
1541
1662
  const info = this.create(newName, { parentSessionId: sessionId });
1542
- const history = this.getSession(sessionId).getHistory(atMessageId);
1663
+ const history = await this.getSession(sessionId).getHistory(atMessageId);
1543
1664
  const newSession = this.getSession(info.id);
1544
1665
  let parentId = null;
1545
1666
  for (const msg of history) {
@@ -1554,10 +1675,10 @@ var SessionManager = class SessionManager {
1554
1675
  this._touch(info.id);
1555
1676
  return info;
1556
1677
  }
1557
- addCompaction(sessionId, summary, fromId, toId) {
1678
+ async addCompaction(sessionId, summary, fromId, toId) {
1558
1679
  return this.getSession(sessionId).addCompaction(summary, fromId, toId);
1559
1680
  }
1560
- getCompactions(sessionId) {
1681
+ async getCompactions(sessionId) {
1561
1682
  return this.getSession(sessionId).getCompactions();
1562
1683
  }
1563
1684
  async compactAndSplit(sessionId, summary, newName) {
@@ -1642,6 +1763,300 @@ var SessionManager = class SessionManager {
1642
1763
  }
1643
1764
  };
1644
1765
  //#endregion
1645
- 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 };
1646
2061
 
1647
2062
  //# sourceMappingURL=index.js.map