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.
- package/README.md +8 -8
- package/dist/{agent-tool-types-CM_50fcV.d.ts → agent-tool-types-Dn9n-3SI.d.ts} +234 -85
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-BylX6WXG.d.ts → agent-tools-B1ttU-pq.d.ts} +2 -2
- package/dist/agent-tools-BAdX1vdI.js.map +1 -1
- package/dist/agent-tools.d.ts +1 -1
- package/dist/agent-tools.js.map +1 -1
- package/dist/ai-chat-agent.js.map +1 -1
- package/dist/ai-chat-v5-migration.js.map +1 -1
- package/dist/ai-react.js.map +1 -1
- package/dist/ai-types.js.map +1 -1
- package/dist/browser/ai.js +1 -1
- package/dist/browser/ai.js.map +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/tanstack-ai.js +1 -1
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/chat/index.d.ts +2 -2
- package/dist/chat/index.js.map +1 -1
- package/dist/{classPrivateFieldGet2-CS51BNGR.js → classPrivateFieldGet2-Evpt0SEr.js} +5 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/client-D1kFXo80.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js.map +1 -1
- package/dist/codemode/ai.js.map +1 -1
- package/dist/{compaction-helpers-bYvP1o2S.d.ts → compaction-helpers-DAe-xiVY.d.ts} +33 -15
- package/dist/compaction-helpers-DvcZnvQ1.js.map +1 -1
- package/dist/email.d.ts +1 -1
- package/dist/email.js.map +1 -1
- package/dist/experimental/memory/session/index.d.ts +247 -34
- package/dist/experimental/memory/session/index.js +540 -135
- package/dist/experimental/memory/session/index.js.map +1 -1
- package/dist/experimental/memory/utils/index.d.ts +1 -1
- package/dist/experimental/memory/utils/index.js.map +1 -1
- package/dist/experimental/webmcp.js.map +1 -1
- package/dist/index.d.ts +71 -57
- package/dist/index.js +583 -45
- package/dist/index.js.map +1 -1
- package/dist/internal_context.js.map +1 -1
- package/dist/mcp/client.d.ts +12 -12
- package/dist/mcp/do-oauth-client-provider.js.map +1 -1
- package/dist/mcp/index.d.ts +40 -40
- package/dist/mcp/index.js +21 -45
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/x402.js.map +1 -1
- package/dist/observability/index.js.map +1 -1
- package/dist/react.d.ts +3 -3
- package/dist/react.js.map +1 -1
- package/dist/retries.js.map +1 -1
- package/dist/schedule.js.map +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/{shared-DzJYHisH.js → shared-CiKaIK4h.js} +4 -5
- package/dist/{shared-DzJYHisH.js.map → shared-CiKaIK4h.js.map} +1 -1
- package/dist/sub-routing.d.ts +6 -6
- package/dist/sub-routing.js.map +1 -1
- package/dist/tool-output-truncation-CH-khbZ3.js.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/vite.js.map +1 -1
- package/dist/workflow-types.js.map +1 -1
- package/dist/workflows.d.ts +1 -1
- package/dist/workflows.js.map +1 -1
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
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: "
|
|
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
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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,
|
|
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
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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.
|
|
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
|
|
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
|
|
991
|
-
*
|
|
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(
|
|
1039
|
+
static create(provider) {
|
|
1009
1040
|
const session = Object.create(Session.prototype);
|
|
1010
|
-
|
|
1011
|
-
|
|
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 =
|
|
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
|
-
*
|
|
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
|
-
*
|
|
1101
|
-
*
|
|
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.
|
|
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.
|
|
1199
|
+
async getHistory(leafId) {
|
|
1200
|
+
await this._ensureRestored();
|
|
1133
1201
|
return this.storage.getHistory(leafId);
|
|
1134
1202
|
}
|
|
1135
|
-
getMessage(id) {
|
|
1136
|
-
this.
|
|
1203
|
+
async getMessage(id) {
|
|
1204
|
+
await this._ensureRestored();
|
|
1137
1205
|
return this.storage.getMessage(id);
|
|
1138
1206
|
}
|
|
1139
|
-
getLatestLeaf() {
|
|
1140
|
-
this.
|
|
1207
|
+
async getLatestLeaf() {
|
|
1208
|
+
await this._ensureRestored();
|
|
1141
1209
|
return this.storage.getLatestLeaf();
|
|
1142
1210
|
}
|
|
1143
|
-
getBranches(messageId) {
|
|
1144
|
-
this.
|
|
1211
|
+
async getBranches(messageId) {
|
|
1212
|
+
await this._ensureRestored();
|
|
1145
1213
|
return this.storage.getBranches(messageId);
|
|
1146
1214
|
}
|
|
1147
|
-
getPathLength(leafId) {
|
|
1148
|
-
this.
|
|
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.
|
|
1173
|
-
|
|
1174
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1191
|
-
this.storage.clearMessages();
|
|
1285
|
+
async clearMessages() {
|
|
1286
|
+
await this._ensureRestored();
|
|
1287
|
+
await this.storage.clearMessages();
|
|
1192
1288
|
this.context.clearSkillState();
|
|
1193
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1344
|
+
await this._ensureRestored();
|
|
1246
1345
|
return this.context.setBlock(label, content);
|
|
1247
1346
|
}
|
|
1248
1347
|
async appendContextBlock(label, content) {
|
|
1249
|
-
this.
|
|
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
|
-
*
|
|
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.
|
|
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.
|
|
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.
|
|
1413
|
+
async getLoadedSkillKeys() {
|
|
1414
|
+
await this._ensureRestored();
|
|
1304
1415
|
return this.context.getLoadedSkillKeys();
|
|
1305
1416
|
}
|
|
1306
1417
|
async freezeSystemPrompt() {
|
|
1307
|
-
this.
|
|
1418
|
+
await this._ensureRestored();
|
|
1308
1419
|
return this.context.freezeSystemPrompt();
|
|
1309
1420
|
}
|
|
1310
1421
|
async refreshSystemPrompt() {
|
|
1311
|
-
this.
|
|
1422
|
+
await this._ensureRestored();
|
|
1312
1423
|
return this.context.refreshSystemPrompt();
|
|
1313
1424
|
}
|
|
1314
|
-
search(query, options) {
|
|
1315
|
-
this.
|
|
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.
|
|
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
|
-
|
|
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
|