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.
- package/README.md +7 -7
- package/dist/{agent-tool-types-DSteYkkS.d.ts → agent-tool-types-BVgYyKO9.d.ts} +153 -102
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/agent-tools-BAdX1vdI.js.map +1 -1
- package/dist/{agent-tools-eGTCdVZX.d.ts → agent-tools-C-Ch8Thl.d.ts} +2 -2
- 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.d.ts +2 -2
- package/dist/browser/ai.js +1 -1
- package/dist/browser/ai.js.map +1 -1
- package/dist/browser/index.d.ts +41 -10
- package/dist/browser/index.js +1 -1
- package/dist/browser/tanstack-ai.d.ts +2 -2
- package/dist/browser/tanstack-ai.js +1 -1
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/chat/index.d.ts +297 -148
- package/dist/chat/index.js +43 -14
- package/dist/chat/index.js.map +1 -1
- package/dist/{classPrivateFieldGet2-Bqby-AHD.js → classPrivateFieldGet2-Evpt0SEr.js} +5 -5
- package/dist/cli/index.d.ts +1 -1
- 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.d.ts +1 -1
- package/dist/codemode/ai.js.map +1 -1
- package/dist/{compaction-helpers-CzCq1-fF.d.ts → compaction-helpers-DAe-xiVY.d.ts} +42 -17
- package/dist/{compaction-helpers-CSaqCmdE.js → compaction-helpers-DvcZnvQ1.js} +1 -1
- package/dist/{compaction-helpers-CSaqCmdE.js.map → compaction-helpers-DvcZnvQ1.js.map} +1 -1
- package/dist/{do-oauth-client-provider-C38aWbFV.d.ts → do-oauth-client-provider-4OKQU9rT.d.ts} +1 -1
- package/dist/{email-X72-zjuq.d.ts → email-J0GGS3sa.d.ts} +1 -1
- package/dist/email.d.ts +3 -3
- package/dist/email.js.map +1 -1
- package/dist/experimental/memory/session/index.d.ts +369 -56
- package/dist/experimental/memory/session/index.js +553 -138
- package/dist/experimental/memory/session/index.js.map +1 -1
- package/dist/experimental/memory/utils/index.d.ts +41 -5
- package/dist/experimental/memory/utils/index.js +15 -5
- package/dist/experimental/memory/utils/index.js.map +1 -1
- package/dist/experimental/webmcp.d.ts +9 -3
- package/dist/experimental/webmcp.js.map +1 -1
- package/dist/{index-Biv6K70p.d.ts → index-DKey3P4s.d.ts} +26 -2
- package/dist/index.d.ts +71 -67
- package/dist/index.js +211 -108
- package/dist/index.js.map +1 -1
- package/dist/{internal_context-BvuGZieY.d.ts → internal_context-BZrMS0B5.d.ts} +1 -1
- package/dist/internal_context.d.ts +1 -1
- package/dist/internal_context.js.map +1 -1
- package/dist/mcp/client.d.ts +26 -2
- package/dist/mcp/do-oauth-client-provider.d.ts +10 -2
- package/dist/mcp/do-oauth-client-provider.js.map +1 -1
- package/dist/mcp/index.d.ts +54 -2
- package/dist/mcp/index.js +2 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/x402.d.ts +74 -17
- package/dist/mcp/x402.js.map +1 -1
- package/dist/observability/index.d.ts +16 -2
- package/dist/observability/index.js +1 -1
- package/dist/observability/index.js.map +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/react.js.map +1 -1
- package/dist/{retries-fLD8cGNf.d.ts → retries-BVdRl5ZE.d.ts} +1 -1
- package/dist/retries.d.ts +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-C6l4ZKRN.js → shared-CiKaIK4h.js} +4 -5
- package/dist/{shared-C6l4ZKRN.js.map → shared-CiKaIK4h.js.map} +1 -1
- package/dist/{shared-Ch9slKdI.d.ts → shared-Cvj92byG.d.ts} +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 +98 -0
- package/dist/tool-output-truncation-CH-khbZ3.js.map +1 -0
- package/dist/{types-DAHCZC_W.d.ts → types-_JjKmv-l.d.ts} +1 -1
- package/dist/types.d.ts +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-DHs0L0KP.d.ts → workflow-types-Dkzg4hAx.d.ts} +1 -1
- package/dist/workflow-types.d.ts +1 -1
- package/dist/workflow-types.js.map +1 -1
- package/dist/workflows.d.ts +2 -2
- package/dist/workflows.js.map +1 -1
- 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-
|
|
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
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
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: "
|
|
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
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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,
|
|
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
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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.
|
|
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
|
|
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
|
|
981
|
-
*
|
|
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(
|
|
1039
|
+
static create(provider) {
|
|
999
1040
|
const session = Object.create(Session.prototype);
|
|
1000
|
-
|
|
1001
|
-
|
|
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 =
|
|
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
|
-
*
|
|
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
|
-
*
|
|
1091
|
-
*
|
|
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.
|
|
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.
|
|
1199
|
+
async getHistory(leafId) {
|
|
1200
|
+
await this._ensureRestored();
|
|
1123
1201
|
return this.storage.getHistory(leafId);
|
|
1124
1202
|
}
|
|
1125
|
-
getMessage(id) {
|
|
1126
|
-
this.
|
|
1203
|
+
async getMessage(id) {
|
|
1204
|
+
await this._ensureRestored();
|
|
1127
1205
|
return this.storage.getMessage(id);
|
|
1128
1206
|
}
|
|
1129
|
-
getLatestLeaf() {
|
|
1130
|
-
this.
|
|
1207
|
+
async getLatestLeaf() {
|
|
1208
|
+
await this._ensureRestored();
|
|
1131
1209
|
return this.storage.getLatestLeaf();
|
|
1132
1210
|
}
|
|
1133
|
-
getBranches(messageId) {
|
|
1134
|
-
this.
|
|
1211
|
+
async getBranches(messageId) {
|
|
1212
|
+
await this._ensureRestored();
|
|
1135
1213
|
return this.storage.getBranches(messageId);
|
|
1136
1214
|
}
|
|
1137
|
-
getPathLength(leafId) {
|
|
1138
|
-
this.
|
|
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.
|
|
1163
|
-
|
|
1164
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1181
|
-
this.storage.clearMessages();
|
|
1285
|
+
async clearMessages() {
|
|
1286
|
+
await this._ensureRestored();
|
|
1287
|
+
await this.storage.clearMessages();
|
|
1182
1288
|
this.context.clearSkillState();
|
|
1183
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1344
|
+
await this._ensureRestored();
|
|
1236
1345
|
return this.context.setBlock(label, content);
|
|
1237
1346
|
}
|
|
1238
1347
|
async appendContextBlock(label, content) {
|
|
1239
|
-
this.
|
|
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
|
-
*
|
|
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.
|
|
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.
|
|
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.
|
|
1413
|
+
async getLoadedSkillKeys() {
|
|
1414
|
+
await this._ensureRestored();
|
|
1294
1415
|
return this.context.getLoadedSkillKeys();
|
|
1295
1416
|
}
|
|
1296
1417
|
async freezeSystemPrompt() {
|
|
1297
|
-
this.
|
|
1418
|
+
await this._ensureRestored();
|
|
1298
1419
|
return this.context.freezeSystemPrompt();
|
|
1299
1420
|
}
|
|
1300
1421
|
async refreshSystemPrompt() {
|
|
1301
|
-
this.
|
|
1422
|
+
await this._ensureRestored();
|
|
1302
1423
|
return this.context.refreshSystemPrompt();
|
|
1303
1424
|
}
|
|
1304
|
-
search(query, options) {
|
|
1305
|
-
this.
|
|
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.
|
|
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
|
-
|
|
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
|