agents 0.8.6 → 0.8.7
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/dist/client.d.ts +2 -2
- package/dist/compaction-helpers-BFTBIzpK.js +312 -0
- package/dist/compaction-helpers-BFTBIzpK.js.map +1 -0
- package/dist/compaction-helpers-DkJreaDR.d.ts +139 -0
- package/dist/{do-oauth-client-provider-D7F2Pw40.d.ts → do-oauth-client-provider-C2jurFjW.d.ts} +1 -1
- package/dist/{email-YAQhwwXb.d.ts → email-DwPlM0bQ.d.ts} +1 -1
- package/dist/email.d.ts +2 -2
- package/dist/experimental/forever.d.ts +1 -1
- package/dist/experimental/memory/session/index.d.ts +193 -203
- package/dist/experimental/memory/session/index.js +673 -294
- package/dist/experimental/memory/session/index.js.map +1 -1
- package/dist/experimental/memory/utils/index.d.ts +59 -0
- package/dist/experimental/memory/utils/index.js +69 -0
- package/dist/experimental/memory/utils/index.js.map +1 -0
- package/dist/{index-DynYigzs.d.ts → index-C-6EMK-E.d.ts} +11 -11
- package/dist/{index-OtkSCU2A.d.ts → index-Ua2Nfvbm.d.ts} +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{internal_context-DgcmHqS1.d.ts → internal_context-DT8RxmAN.d.ts} +1 -1
- package/dist/internal_context.d.ts +1 -1
- package/dist/mcp/client.d.ts +1 -1
- package/dist/mcp/do-oauth-client-provider.d.ts +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +37 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/observability/index.d.ts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/{retries-JxhDYtYL.d.ts → retries-DXMQGhG3.d.ts} +1 -1
- package/dist/retries.d.ts +1 -1
- package/dist/{serializable-Ch19yA6_.d.ts → serializable-8Jt1B04R.d.ts} +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/{types-2lHHE_uh.d.ts → types-C-m0II8i.d.ts} +3 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/dist/{workflow-types-BVKtSaA7.d.ts → workflow-types-CZNXKj_D.d.ts} +1 -1
- package/dist/workflow-types.d.ts +1 -1
- package/dist/workflows.d.ts +2 -2
- package/package.json +10 -5
|
@@ -1,376 +1,755 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
truncateText: 1e4,
|
|
6
|
-
keepRecent: 4
|
|
7
|
-
};
|
|
8
|
-
/**
|
|
9
|
-
* Parse microCompaction config into resolved rules.
|
|
10
|
-
* Returns null if disabled.
|
|
11
|
-
*/
|
|
12
|
-
function parseMicroCompactionRules(config) {
|
|
13
|
-
if (config === false) return null;
|
|
14
|
-
if (config === true) return {
|
|
15
|
-
truncateToolOutputs: DEFAULTS.truncateToolOutputs,
|
|
16
|
-
truncateText: DEFAULTS.truncateText,
|
|
17
|
-
keepRecent: DEFAULTS.keepRecent
|
|
18
|
-
};
|
|
19
|
-
const keepRecent = config.keepRecent ?? DEFAULTS.keepRecent;
|
|
20
|
-
if (!Number.isInteger(keepRecent) || keepRecent < 0) throw new Error("keepRecent must be a non-negative integer");
|
|
21
|
-
const truncateToolOutputs = config.truncateToolOutputs === false ? false : config.truncateToolOutputs === true || config.truncateToolOutputs === void 0 ? DEFAULTS.truncateToolOutputs : config.truncateToolOutputs;
|
|
22
|
-
if (typeof truncateToolOutputs === "number" && truncateToolOutputs <= 0) throw new Error("truncateToolOutputs must be a positive number");
|
|
23
|
-
const truncateText = config.truncateText === false ? false : config.truncateText === true || config.truncateText === void 0 ? DEFAULTS.truncateText : config.truncateText;
|
|
24
|
-
if (typeof truncateText === "number" && truncateText <= 0) throw new Error("truncateText must be a positive number");
|
|
25
|
-
return {
|
|
26
|
-
truncateToolOutputs,
|
|
27
|
-
truncateText,
|
|
28
|
-
keepRecent
|
|
29
|
-
};
|
|
30
|
-
}
|
|
1
|
+
import { MessageType } from "../../../types.js";
|
|
2
|
+
import { m as estimateStringTokens, p as estimateMessageTokens, t as COMPACTION_PREFIX } from "../../../compaction-helpers-BFTBIzpK.js";
|
|
3
|
+
import { jsonSchema } from "ai";
|
|
4
|
+
//#region src/experimental/memory/session/context.ts
|
|
31
5
|
/**
|
|
32
|
-
*
|
|
33
|
-
* Returns the same reference if nothing changed (allows callers to skip no-op updates).
|
|
34
|
-
*/
|
|
35
|
-
function truncateMessageParts(msg, rules) {
|
|
36
|
-
let changed = false;
|
|
37
|
-
const compactedParts = msg.parts.map((part) => {
|
|
38
|
-
if (rules.truncateToolOutputs !== false && (part.type.startsWith("tool-") || part.type === "dynamic-tool") && "output" in part) {
|
|
39
|
-
const toolPart = part;
|
|
40
|
-
if (toolPart.output !== void 0) {
|
|
41
|
-
const outputJson = JSON.stringify(toolPart.output);
|
|
42
|
-
if (outputJson.length > rules.truncateToolOutputs) {
|
|
43
|
-
changed = true;
|
|
44
|
-
return {
|
|
45
|
-
...part,
|
|
46
|
-
output: `[Truncated ${outputJson.length} bytes] ${outputJson.slice(0, 500)}...`
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (rules.truncateText !== false && part.type === "text" && "text" in part) {
|
|
52
|
-
const textPart = part;
|
|
53
|
-
if (textPart.text.length > rules.truncateText) {
|
|
54
|
-
changed = true;
|
|
55
|
-
return {
|
|
56
|
-
...part,
|
|
57
|
-
text: `${textPart.text.slice(0, rules.truncateText)}... [truncated ${textPart.text.length} chars]`
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return part;
|
|
62
|
-
});
|
|
63
|
-
return changed ? {
|
|
64
|
-
...msg,
|
|
65
|
-
parts: compactedParts
|
|
66
|
-
} : msg;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Apply microCompaction to an array of messages.
|
|
70
|
-
* Returns same reference for unchanged messages (enables skip-update optimization).
|
|
6
|
+
* Context Block Management
|
|
71
7
|
*
|
|
72
|
-
*
|
|
8
|
+
* Persistent key-value blocks (MEMORY, USER, SOUL, etc.) that are:
|
|
9
|
+
* - Loaded from their providers at init
|
|
10
|
+
* - Frozen into a snapshot when toSystemPrompt() is called
|
|
11
|
+
* - Updated via setBlock() which writes to the provider immediately
|
|
12
|
+
* but does NOT update the frozen snapshot (preserves LLM prefix cache)
|
|
13
|
+
* - Re-snapshotted on next toSystemPrompt() call
|
|
73
14
|
*/
|
|
74
|
-
function microCompact(messages, rules) {
|
|
75
|
-
return messages.map((msg) => truncateMessageParts(msg, rules));
|
|
76
|
-
}
|
|
77
|
-
/** Approximate token multiplier per whitespace-separated word */
|
|
78
|
-
const WORDS_TOKEN_MULTIPLIER = 1.3;
|
|
79
15
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* Takes the max of two estimates:
|
|
83
|
-
* - Character-based: `length / 4` — better for dense content (JSON, code, URLs)
|
|
84
|
-
* - Word-based: `words * 1.3` — better for natural language prose
|
|
85
|
-
*
|
|
86
|
-
* This is a heuristic. Do not use where exact counts are required.
|
|
16
|
+
* Manages context blocks with frozen snapshot support.
|
|
87
17
|
*/
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
*
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
18
|
+
var ContextBlocks = class {
|
|
19
|
+
constructor(configs, promptStore) {
|
|
20
|
+
this.blocks = /* @__PURE__ */ new Map();
|
|
21
|
+
this.snapshot = null;
|
|
22
|
+
this.loaded = false;
|
|
23
|
+
this.configs = configs;
|
|
24
|
+
this.promptStore = promptStore ?? null;
|
|
25
|
+
}
|
|
26
|
+
isLoaded() {
|
|
27
|
+
return this.loaded;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Load all blocks from their providers.
|
|
31
|
+
* Called once at session init.
|
|
32
|
+
*/
|
|
33
|
+
async load() {
|
|
34
|
+
for (const config of this.configs) {
|
|
35
|
+
let content = null;
|
|
36
|
+
if (config.provider) content = await config.provider.get();
|
|
37
|
+
content = content ?? config.initialContent ?? "";
|
|
38
|
+
this.blocks.set(config.label, {
|
|
39
|
+
label: config.label,
|
|
40
|
+
description: config.description,
|
|
41
|
+
content,
|
|
42
|
+
tokens: estimateStringTokens(content),
|
|
43
|
+
maxTokens: config.maxTokens,
|
|
44
|
+
readonly: config.readonly
|
|
45
|
+
});
|
|
111
46
|
}
|
|
47
|
+
this.loaded = true;
|
|
112
48
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
constructor(storage, options) {
|
|
119
|
-
this.storage = storage;
|
|
120
|
-
this.microCompactionRules = parseMicroCompactionRules(options?.microCompaction ?? true);
|
|
121
|
-
this.compactionConfig = options?.compaction ?? null;
|
|
49
|
+
/**
|
|
50
|
+
* Get a block by label.
|
|
51
|
+
*/
|
|
52
|
+
getBlock(label) {
|
|
53
|
+
return this.blocks.get(label) ?? null;
|
|
122
54
|
}
|
|
123
|
-
|
|
124
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Get all blocks.
|
|
57
|
+
*/
|
|
58
|
+
getBlocks() {
|
|
59
|
+
return Array.from(this.blocks.values());
|
|
125
60
|
}
|
|
126
|
-
|
|
127
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Set block content. Writes to provider immediately.
|
|
63
|
+
* Does NOT update the frozen snapshot.
|
|
64
|
+
*/
|
|
65
|
+
async setBlock(label, content) {
|
|
66
|
+
if (!this.loaded) await this.load();
|
|
67
|
+
const config = this.configs.find((c) => c.label === label);
|
|
68
|
+
const existing = this.blocks.get(label);
|
|
69
|
+
if (existing?.readonly || config?.readonly) throw new Error(`Block "${label}" is readonly`);
|
|
70
|
+
const tokens = estimateStringTokens(content);
|
|
71
|
+
const maxTokens = config?.maxTokens ?? existing?.maxTokens;
|
|
72
|
+
if (maxTokens !== void 0 && tokens > maxTokens) throw new Error(`Block "${label}" exceeds maxTokens: ${tokens} > ${maxTokens}`);
|
|
73
|
+
const block = {
|
|
74
|
+
label,
|
|
75
|
+
description: config?.description ?? existing?.description,
|
|
76
|
+
content,
|
|
77
|
+
tokens,
|
|
78
|
+
maxTokens,
|
|
79
|
+
readonly: false
|
|
80
|
+
};
|
|
81
|
+
this.blocks.set(label, block);
|
|
82
|
+
if (config?.provider?.set) await config.provider.set(content);
|
|
83
|
+
return block;
|
|
128
84
|
}
|
|
129
|
-
|
|
130
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Append content to a block.
|
|
87
|
+
*/
|
|
88
|
+
async appendToBlock(label, content) {
|
|
89
|
+
if (!this.loaded) await this.load();
|
|
90
|
+
const existing = this.blocks.get(label);
|
|
91
|
+
if (!existing) throw new Error(`Block "${label}" not found`);
|
|
92
|
+
return this.setBlock(label, existing.content + content);
|
|
131
93
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Get the system prompt string with context blocks.
|
|
96
|
+
*
|
|
97
|
+
* Returns a frozen snapshot: first call renders and caches,
|
|
98
|
+
* subsequent calls return the same string (preserves LLM prefix cache).
|
|
99
|
+
* Call refreshSnapshot() to re-render after block changes take effect.
|
|
100
|
+
*/
|
|
101
|
+
toSystemPrompt() {
|
|
102
|
+
if (!this.loaded) throw new Error("Context blocks not loaded. Call load() first.");
|
|
103
|
+
if (this.snapshot !== null) return this.snapshot;
|
|
104
|
+
return this.captureSnapshot();
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Force re-render the snapshot from current block state.
|
|
108
|
+
* Call this at the start of a new session to pick up changes
|
|
109
|
+
* made by setBlock() during the previous session.
|
|
110
|
+
*/
|
|
111
|
+
refreshSnapshot() {
|
|
112
|
+
return this.captureSnapshot();
|
|
113
|
+
}
|
|
114
|
+
captureSnapshot() {
|
|
115
|
+
const parts = [];
|
|
116
|
+
const sep = "═".repeat(46);
|
|
117
|
+
for (const block of this.blocks.values()) {
|
|
118
|
+
if (!block.content) continue;
|
|
119
|
+
let header = block.label.toUpperCase();
|
|
120
|
+
if (block.description) header += ` (${block.description})`;
|
|
121
|
+
if (block.maxTokens) {
|
|
122
|
+
const pct = Math.round(block.tokens / block.maxTokens * 100);
|
|
123
|
+
header += ` [${pct}% — ${block.tokens}/${block.maxTokens} tokens]`;
|
|
143
124
|
}
|
|
125
|
+
if (block.readonly) header += " [readonly]";
|
|
126
|
+
parts.push(`${sep}\n${header}\n${sep}\n${block.content}`);
|
|
144
127
|
}
|
|
128
|
+
this.snapshot = parts.join("\n\n");
|
|
129
|
+
return this.snapshot;
|
|
145
130
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
this.
|
|
131
|
+
/**
|
|
132
|
+
* Get writable blocks (for tool description).
|
|
133
|
+
*/
|
|
134
|
+
getWritableBlocks() {
|
|
135
|
+
return Array.from(this.blocks.values()).filter((b) => !b.readonly);
|
|
151
136
|
}
|
|
152
|
-
|
|
153
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Get writable block configs — doesn't require blocks to be loaded.
|
|
139
|
+
*/
|
|
140
|
+
getWritableConfigs() {
|
|
141
|
+
return this.configs.filter((c) => !c.readonly);
|
|
154
142
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
error: err instanceof Error ? err.message : String(err)
|
|
167
|
-
};
|
|
143
|
+
/**
|
|
144
|
+
* Frozen system prompt. On first call:
|
|
145
|
+
* 1. Checks store for a persisted prompt (survives DO eviction)
|
|
146
|
+
* 2. If none, loads blocks from providers, renders, and persists
|
|
147
|
+
*
|
|
148
|
+
* Subsequent calls return the stored version — true prefix cache stability.
|
|
149
|
+
*/
|
|
150
|
+
async freezeSystemPrompt() {
|
|
151
|
+
if (this.promptStore) {
|
|
152
|
+
const stored = await this.promptStore.get();
|
|
153
|
+
if (stored !== null) return stored;
|
|
168
154
|
}
|
|
155
|
+
if (!this.loaded) await this.load();
|
|
156
|
+
const prompt = this.toSystemPrompt();
|
|
157
|
+
if (this.promptStore?.set) await this.promptStore.set(prompt);
|
|
158
|
+
return prompt;
|
|
169
159
|
}
|
|
170
160
|
/**
|
|
171
|
-
*
|
|
161
|
+
* Re-render the system prompt from current block state and persist.
|
|
162
|
+
* Call after compaction or at session boundaries to pick up writes.
|
|
172
163
|
*/
|
|
173
|
-
|
|
174
|
-
if (!this.
|
|
175
|
-
|
|
164
|
+
async refreshSystemPrompt() {
|
|
165
|
+
if (!this.loaded) await this.load();
|
|
166
|
+
const prompt = this.refreshSnapshot();
|
|
167
|
+
if (this.promptStore?.set) await this.promptStore.set(prompt);
|
|
168
|
+
return prompt;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* AI tool for updating context blocks. Loads blocks lazily on first execute.
|
|
172
|
+
*/
|
|
173
|
+
async tools() {
|
|
174
|
+
if (!this.loaded) await this.load();
|
|
175
|
+
const writable = this.getWritableBlocks();
|
|
176
|
+
if (writable.length === 0) return {};
|
|
177
|
+
const blockDescriptions = writable.map((b) => `- "${b.label}": ${b.description ?? "no description"}`).join("\n");
|
|
178
|
+
const ctx = this;
|
|
179
|
+
return { update_context: {
|
|
180
|
+
description: `Update a context block. Available blocks:\n${blockDescriptions}\n\nWrites are durable and persist across sessions.`,
|
|
181
|
+
inputSchema: jsonSchema({
|
|
182
|
+
type: "object",
|
|
183
|
+
properties: {
|
|
184
|
+
label: {
|
|
185
|
+
type: "string",
|
|
186
|
+
enum: writable.map((b) => b.label),
|
|
187
|
+
description: "Block label to update"
|
|
188
|
+
},
|
|
189
|
+
content: {
|
|
190
|
+
type: "string",
|
|
191
|
+
description: "Content to write"
|
|
192
|
+
},
|
|
193
|
+
action: {
|
|
194
|
+
type: "string",
|
|
195
|
+
enum: ["replace", "append"],
|
|
196
|
+
description: "replace (default) or append"
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
required: ["label", "content"]
|
|
200
|
+
}),
|
|
201
|
+
execute: async ({ label, content, action }) => {
|
|
202
|
+
try {
|
|
203
|
+
const block = action === "append" ? await ctx.appendToBlock(label, content) : await ctx.setBlock(label, content);
|
|
204
|
+
return `Written to ${label}. Usage: ${block.maxTokens ? `${Math.round(block.tokens / block.maxTokens * 100)}% (${block.tokens}/${block.maxTokens} tokens)` : `${block.tokens} tokens`}`;
|
|
205
|
+
} catch (err) {
|
|
206
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} };
|
|
176
210
|
}
|
|
177
211
|
};
|
|
178
212
|
//#endregion
|
|
179
213
|
//#region src/experimental/memory/session/providers/agent.ts
|
|
180
|
-
/**
|
|
181
|
-
* Session provider that wraps an Agent's SQLite storage.
|
|
182
|
-
* Provides pure CRUD — compaction is handled by the Session wrapper.
|
|
183
|
-
*
|
|
184
|
-
* @example
|
|
185
|
-
* ```typescript
|
|
186
|
-
* import { Session, AgentSessionProvider } from "agents/experimental/memory/session";
|
|
187
|
-
*
|
|
188
|
-
* // In your Agent class:
|
|
189
|
-
* session = new Session(new AgentSessionProvider(this));
|
|
190
|
-
*
|
|
191
|
-
* // With compaction options:
|
|
192
|
-
* session = new Session(new AgentSessionProvider(this), {
|
|
193
|
-
* microCompaction: { truncateToolOutputs: 2000, keepRecent: 10 },
|
|
194
|
-
* compaction: { tokenThreshold: 20000, fn: summarize }
|
|
195
|
-
* });
|
|
196
|
-
* ```
|
|
197
|
-
*/
|
|
198
214
|
var AgentSessionProvider = class {
|
|
199
|
-
|
|
215
|
+
/**
|
|
216
|
+
* @param agent - Agent or any object with a `sql` tagged template method
|
|
217
|
+
* @param sessionId - Optional session ID to isolate multiple sessions in the same DO.
|
|
218
|
+
* Messages are filtered by session_id within shared tables.
|
|
219
|
+
*/
|
|
220
|
+
constructor(agent, sessionId) {
|
|
200
221
|
this.initialized = false;
|
|
201
222
|
this.agent = agent;
|
|
223
|
+
this.sessionId = sessionId ?? "";
|
|
202
224
|
}
|
|
203
|
-
/**
|
|
204
|
-
* Ensure the messages table exists
|
|
205
|
-
*/
|
|
206
225
|
ensureTable() {
|
|
207
226
|
if (this.initialized) return;
|
|
208
227
|
this.agent.sql`
|
|
209
|
-
CREATE TABLE IF NOT EXISTS
|
|
228
|
+
CREATE TABLE IF NOT EXISTS assistant_messages (
|
|
229
|
+
id TEXT PRIMARY KEY,
|
|
230
|
+
session_id TEXT NOT NULL DEFAULT '',
|
|
231
|
+
parent_id TEXT,
|
|
232
|
+
role TEXT NOT NULL,
|
|
233
|
+
content TEXT NOT NULL,
|
|
234
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
235
|
+
)
|
|
236
|
+
`;
|
|
237
|
+
this.agent.sql`
|
|
238
|
+
CREATE INDEX IF NOT EXISTS idx_assistant_msg_parent
|
|
239
|
+
ON assistant_messages(parent_id)
|
|
240
|
+
`;
|
|
241
|
+
this.agent.sql`
|
|
242
|
+
CREATE INDEX IF NOT EXISTS idx_assistant_msg_session
|
|
243
|
+
ON assistant_messages(session_id)
|
|
244
|
+
`;
|
|
245
|
+
this.agent.sql`
|
|
246
|
+
CREATE TABLE IF NOT EXISTS assistant_compactions (
|
|
210
247
|
id TEXT PRIMARY KEY,
|
|
211
|
-
|
|
248
|
+
session_id TEXT NOT NULL DEFAULT '',
|
|
249
|
+
summary TEXT NOT NULL,
|
|
250
|
+
from_message_id TEXT NOT NULL,
|
|
251
|
+
to_message_id TEXT NOT NULL,
|
|
212
252
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
213
253
|
)
|
|
254
|
+
`;
|
|
255
|
+
this.agent.sql`
|
|
256
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS assistant_fts
|
|
257
|
+
USING fts5(id UNINDEXED, session_id UNINDEXED, role UNINDEXED, content, tokenize='porter unicode61')
|
|
258
|
+
`;
|
|
259
|
+
this.agent.sql`
|
|
260
|
+
CREATE TABLE IF NOT EXISTS assistant_config (
|
|
261
|
+
session_id TEXT NOT NULL,
|
|
262
|
+
key TEXT NOT NULL,
|
|
263
|
+
value TEXT NOT NULL,
|
|
264
|
+
PRIMARY KEY (session_id, key)
|
|
265
|
+
)
|
|
214
266
|
`;
|
|
215
267
|
this.initialized = true;
|
|
216
268
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
269
|
+
getMessage(id) {
|
|
270
|
+
this.ensureTable();
|
|
271
|
+
const rows = this.agent.sql`
|
|
272
|
+
SELECT content FROM assistant_messages WHERE id = ${id} AND session_id = ${this.sessionId}
|
|
273
|
+
`;
|
|
274
|
+
return rows.length > 0 ? this.parse(rows[0].content) : null;
|
|
275
|
+
}
|
|
276
|
+
getHistory(leafId) {
|
|
277
|
+
this.ensureTable();
|
|
278
|
+
const leaf = leafId ? this.agent.sql`
|
|
279
|
+
SELECT id FROM assistant_messages WHERE id = ${leafId} AND session_id = ${this.sessionId}
|
|
280
|
+
`[0] : this.latestLeafRow();
|
|
281
|
+
if (!leaf) return [];
|
|
282
|
+
const path = this.agent.sql`
|
|
283
|
+
WITH RECURSIVE path AS (
|
|
284
|
+
SELECT *, 0 as depth FROM assistant_messages WHERE id = ${leaf.id}
|
|
285
|
+
UNION ALL
|
|
286
|
+
SELECT m.*, p.depth + 1 FROM assistant_messages m
|
|
287
|
+
JOIN path p ON m.id = p.parent_id
|
|
288
|
+
WHERE m.session_id = ${this.sessionId} AND p.depth < 10000
|
|
289
|
+
)
|
|
290
|
+
SELECT content FROM path ORDER BY depth DESC
|
|
291
|
+
`;
|
|
292
|
+
const messages = this.parseRows(path);
|
|
293
|
+
const compactions = this.getCompactions();
|
|
294
|
+
if (compactions.length === 0) return messages;
|
|
295
|
+
return this.applyCompactions(messages, compactions);
|
|
296
|
+
}
|
|
297
|
+
getLatestLeaf() {
|
|
298
|
+
this.ensureTable();
|
|
299
|
+
const row = this.latestLeafRow();
|
|
300
|
+
return row ? this.parse(row.content) : null;
|
|
301
|
+
}
|
|
302
|
+
getBranches(messageId) {
|
|
221
303
|
this.ensureTable();
|
|
222
|
-
if (options?.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 0)) throw new Error("limit must be a non-negative integer");
|
|
223
|
-
if (options?.offset !== void 0 && (!Number.isInteger(options.offset) || options.offset < 0)) throw new Error("offset must be a non-negative integer");
|
|
224
|
-
const role = options?.role ?? null;
|
|
225
|
-
const before = options?.before?.toISOString() ?? null;
|
|
226
|
-
const after = options?.after?.toISOString() ?? null;
|
|
227
|
-
const limit = options?.limit ?? -1;
|
|
228
|
-
const offset = options?.offset ?? 0;
|
|
229
304
|
const rows = this.agent.sql`
|
|
230
|
-
SELECT
|
|
231
|
-
WHERE
|
|
232
|
-
AND (${before} IS NULL OR created_at < ${before})
|
|
233
|
-
AND (${after} IS NULL OR created_at > ${after})
|
|
234
|
-
ORDER BY created_at ASC, rowid ASC
|
|
235
|
-
LIMIT ${limit} OFFSET ${offset}
|
|
305
|
+
SELECT content FROM assistant_messages
|
|
306
|
+
WHERE parent_id = ${messageId} AND session_id = ${this.sessionId} ORDER BY created_at ASC
|
|
236
307
|
`;
|
|
237
308
|
return this.parseRows(rows);
|
|
238
309
|
}
|
|
239
|
-
|
|
240
|
-
* Append one or more messages to storage.
|
|
241
|
-
*/
|
|
242
|
-
async appendMessages(messages) {
|
|
310
|
+
getPathLength(leafId) {
|
|
243
311
|
this.ensureTable();
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
312
|
+
const leaf = leafId ? this.agent.sql`
|
|
313
|
+
SELECT id FROM assistant_messages WHERE id = ${leafId} AND session_id = ${this.sessionId}
|
|
314
|
+
`[0] : this.latestLeafRow();
|
|
315
|
+
if (!leaf) return 0;
|
|
316
|
+
return this.agent.sql`
|
|
317
|
+
WITH RECURSIVE path AS (
|
|
318
|
+
SELECT id, parent_id, 0 as depth FROM assistant_messages WHERE id = ${leaf.id}
|
|
319
|
+
UNION ALL
|
|
320
|
+
SELECT m.id, m.parent_id, p.depth + 1 FROM assistant_messages m
|
|
321
|
+
JOIN path p ON m.id = p.parent_id
|
|
322
|
+
WHERE m.session_id = ${this.sessionId} AND p.depth < 10000
|
|
323
|
+
)
|
|
324
|
+
SELECT COUNT(*) as count FROM path
|
|
325
|
+
`[0]?.count ?? 0;
|
|
326
|
+
}
|
|
327
|
+
appendMessage(message, parentId) {
|
|
328
|
+
this.ensureTable();
|
|
329
|
+
if (this.agent.sql`
|
|
330
|
+
SELECT id FROM assistant_messages WHERE id = ${message.id} AND session_id = ${this.sessionId}
|
|
331
|
+
`.length > 0) return;
|
|
332
|
+
let parent = parentId ?? this.latestLeafRow()?.id ?? null;
|
|
333
|
+
if (parent) {
|
|
334
|
+
if (this.agent.sql`
|
|
335
|
+
SELECT id FROM assistant_messages WHERE id = ${parent} AND session_id = ${this.sessionId}
|
|
336
|
+
`.length === 0) parent = null;
|
|
253
337
|
}
|
|
338
|
+
const json = JSON.stringify(message);
|
|
339
|
+
this.agent.sql`
|
|
340
|
+
INSERT INTO assistant_messages (id, session_id, parent_id, role, content)
|
|
341
|
+
VALUES (${message.id}, ${this.sessionId}, ${parent}, ${message.role}, ${json})
|
|
342
|
+
`;
|
|
343
|
+
this.indexFTS(message);
|
|
254
344
|
}
|
|
255
|
-
/**
|
|
256
|
-
* Update an existing message
|
|
257
|
-
*/
|
|
258
345
|
updateMessage(message) {
|
|
259
346
|
this.ensureTable();
|
|
260
|
-
const json = JSON.stringify(message);
|
|
261
347
|
this.agent.sql`
|
|
262
|
-
UPDATE
|
|
263
|
-
|
|
264
|
-
WHERE id = ${message.id}
|
|
348
|
+
UPDATE assistant_messages SET content = ${JSON.stringify(message)}
|
|
349
|
+
WHERE id = ${message.id} AND session_id = ${this.sessionId}
|
|
265
350
|
`;
|
|
351
|
+
this.indexFTS(message);
|
|
266
352
|
}
|
|
267
|
-
/**
|
|
268
|
-
* Delete messages by their IDs
|
|
269
|
-
*/
|
|
270
353
|
deleteMessages(messageIds) {
|
|
271
354
|
this.ensureTable();
|
|
272
|
-
for (const id of messageIds)
|
|
355
|
+
for (const id of messageIds) {
|
|
356
|
+
this.agent.sql`DELETE FROM assistant_messages WHERE id = ${id} AND session_id = ${this.sessionId}`;
|
|
357
|
+
this.deleteFTS(id);
|
|
358
|
+
}
|
|
273
359
|
}
|
|
274
|
-
/**
|
|
275
|
-
* Clear all messages from the session
|
|
276
|
-
*/
|
|
277
360
|
clearMessages() {
|
|
278
361
|
this.ensureTable();
|
|
279
|
-
this.agent.sql`DELETE FROM
|
|
362
|
+
this.agent.sql`DELETE FROM assistant_messages WHERE session_id = ${this.sessionId}`;
|
|
363
|
+
this.agent.sql`DELETE FROM assistant_compactions WHERE session_id = ${this.sessionId}`;
|
|
364
|
+
const ftsRows = this.agent.sql`
|
|
365
|
+
SELECT rowid FROM assistant_fts WHERE session_id = ${this.sessionId}
|
|
366
|
+
`;
|
|
367
|
+
for (const row of ftsRows) this.agent.sql`DELETE FROM assistant_fts WHERE rowid = ${row.rowid}`;
|
|
280
368
|
}
|
|
281
|
-
|
|
282
|
-
* Get a single message by ID
|
|
283
|
-
*/
|
|
284
|
-
getMessage(id) {
|
|
369
|
+
addCompaction(summary, fromMessageId, toMessageId) {
|
|
285
370
|
this.ensureTable();
|
|
286
|
-
const
|
|
287
|
-
|
|
371
|
+
const id = crypto.randomUUID();
|
|
372
|
+
this.agent.sql`
|
|
373
|
+
INSERT INTO assistant_compactions (id, session_id, summary, from_message_id, to_message_id)
|
|
374
|
+
VALUES (${id}, ${this.sessionId}, ${summary}, ${fromMessageId}, ${toMessageId})
|
|
288
375
|
`;
|
|
289
|
-
|
|
376
|
+
return {
|
|
377
|
+
id,
|
|
378
|
+
summary,
|
|
379
|
+
fromMessageId,
|
|
380
|
+
toMessageId,
|
|
381
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
getCompactions() {
|
|
385
|
+
this.ensureTable();
|
|
386
|
+
return this.agent.sql`
|
|
387
|
+
SELECT * FROM assistant_compactions WHERE session_id = ${this.sessionId} ORDER BY created_at ASC
|
|
388
|
+
`.map((r) => ({
|
|
389
|
+
id: r.id,
|
|
390
|
+
summary: r.summary,
|
|
391
|
+
fromMessageId: r.from_message_id,
|
|
392
|
+
toMessageId: r.to_message_id,
|
|
393
|
+
createdAt: r.created_at
|
|
394
|
+
}));
|
|
395
|
+
}
|
|
396
|
+
searchMessages(query, limit = 20) {
|
|
397
|
+
this.ensureTable();
|
|
398
|
+
const sanitized = `"${query.replace(/"/g, "\"\"")}"`;
|
|
290
399
|
try {
|
|
291
|
-
|
|
292
|
-
|
|
400
|
+
return this.agent.sql`
|
|
401
|
+
SELECT f.id, f.role, f.content FROM assistant_fts f
|
|
402
|
+
INNER JOIN assistant_messages m ON m.id = f.id AND m.session_id = f.session_id
|
|
403
|
+
WHERE assistant_fts MATCH ${sanitized} AND f.session_id = ${this.sessionId}
|
|
404
|
+
ORDER BY rank LIMIT ${limit}
|
|
405
|
+
`.map((r) => ({
|
|
406
|
+
id: r.id,
|
|
407
|
+
role: r.role,
|
|
408
|
+
content: r.content
|
|
409
|
+
}));
|
|
293
410
|
} catch {
|
|
294
|
-
return
|
|
411
|
+
return [];
|
|
295
412
|
}
|
|
296
413
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
414
|
+
latestLeafRow() {
|
|
415
|
+
return this.agent.sql`
|
|
416
|
+
SELECT m.id, m.content FROM assistant_messages m
|
|
417
|
+
LEFT JOIN assistant_messages c ON c.parent_id = m.id AND c.session_id = ${this.sessionId}
|
|
418
|
+
WHERE c.id IS NULL AND m.session_id = ${this.sessionId}
|
|
419
|
+
ORDER BY m.created_at DESC, m.rowid DESC LIMIT 1
|
|
420
|
+
`[0] ?? null;
|
|
421
|
+
}
|
|
422
|
+
indexFTS(message) {
|
|
423
|
+
const text = message.parts.filter((p) => p.type === "text").map((p) => p.text).join(" ");
|
|
424
|
+
this.deleteFTS(message.id);
|
|
425
|
+
if (text) this.agent.sql`
|
|
426
|
+
INSERT INTO assistant_fts (id, session_id, role, content)
|
|
427
|
+
VALUES (${message.id}, ${this.sessionId}, ${message.role}, ${text})
|
|
428
|
+
`;
|
|
429
|
+
}
|
|
430
|
+
deleteFTS(id) {
|
|
302
431
|
const rows = this.agent.sql`
|
|
303
|
-
SELECT
|
|
304
|
-
ORDER BY created_at DESC, rowid DESC
|
|
305
|
-
LIMIT ${n}
|
|
432
|
+
SELECT rowid FROM assistant_fts WHERE id = ${id} AND session_id = ${this.sessionId}
|
|
306
433
|
`;
|
|
307
|
-
|
|
434
|
+
for (const row of rows) this.agent.sql`DELETE FROM assistant_fts WHERE rowid = ${row.rowid}`;
|
|
308
435
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
436
|
+
applyCompactions(messages, compactions) {
|
|
437
|
+
const ids = messages.map((m) => m.id);
|
|
438
|
+
const result = [];
|
|
439
|
+
let i = 0;
|
|
440
|
+
while (i < messages.length) {
|
|
441
|
+
const matching = compactions.filter((c) => c.fromMessageId === ids[i]);
|
|
442
|
+
const comp = matching.length > 1 ? matching[matching.length - 1] : matching[0];
|
|
443
|
+
if (comp) {
|
|
444
|
+
const endIdx = ids.indexOf(comp.toMessageId);
|
|
445
|
+
if (endIdx >= i) {
|
|
446
|
+
result.push({
|
|
447
|
+
id: `${COMPACTION_PREFIX}${comp.id}`,
|
|
448
|
+
role: "assistant",
|
|
449
|
+
parts: [{
|
|
450
|
+
type: "text",
|
|
451
|
+
text: comp.summary
|
|
452
|
+
}],
|
|
453
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
454
|
+
});
|
|
455
|
+
i = endIdx + 1;
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
result.push(messages[i]);
|
|
460
|
+
i++;
|
|
461
|
+
}
|
|
462
|
+
return result;
|
|
463
|
+
}
|
|
464
|
+
parse(json) {
|
|
465
|
+
try {
|
|
466
|
+
const msg = JSON.parse(json);
|
|
467
|
+
if (typeof msg?.id === "string" && typeof msg?.role === "string" && Array.isArray(msg?.parts)) return msg;
|
|
468
|
+
} catch {}
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
parseRows(rows) {
|
|
472
|
+
const result = [];
|
|
473
|
+
for (const row of rows) {
|
|
474
|
+
const msg = this.parse(row.content);
|
|
475
|
+
if (msg) result.push(msg);
|
|
476
|
+
}
|
|
477
|
+
return result;
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/experimental/memory/session/providers/agent-context.ts
|
|
482
|
+
var AgentContextProvider = class {
|
|
483
|
+
constructor(agent, label) {
|
|
484
|
+
this.initialized = false;
|
|
485
|
+
this.agent = agent;
|
|
486
|
+
this.label = label;
|
|
487
|
+
}
|
|
488
|
+
ensureTable() {
|
|
489
|
+
if (this.initialized) return;
|
|
490
|
+
this.agent.sql`
|
|
491
|
+
CREATE TABLE IF NOT EXISTS cf_agents_context_blocks (
|
|
492
|
+
label TEXT PRIMARY KEY,
|
|
493
|
+
content TEXT NOT NULL,
|
|
494
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
321
495
|
)
|
|
322
496
|
`;
|
|
323
|
-
|
|
497
|
+
this.initialized = true;
|
|
324
498
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
499
|
+
async get() {
|
|
500
|
+
this.ensureTable();
|
|
501
|
+
return this.agent.sql`
|
|
502
|
+
SELECT content FROM cf_agents_context_blocks WHERE label = ${this.label}
|
|
503
|
+
`[0]?.content ?? null;
|
|
504
|
+
}
|
|
505
|
+
async set(content) {
|
|
330
506
|
this.ensureTable();
|
|
331
|
-
|
|
332
|
-
|
|
507
|
+
this.agent.sql`
|
|
508
|
+
INSERT INTO cf_agents_context_blocks (label, content)
|
|
509
|
+
VALUES (${this.label}, ${content})
|
|
510
|
+
ON CONFLICT(label) DO UPDATE SET content = ${content}, updated_at = CURRENT_TIMESTAMP
|
|
333
511
|
`;
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
//#endregion
|
|
515
|
+
//#region src/experimental/memory/session/session.ts
|
|
516
|
+
function isBroadcaster(obj) {
|
|
517
|
+
return typeof obj === "object" && obj !== null && "broadcast" in obj && typeof obj.broadcast === "function";
|
|
518
|
+
}
|
|
519
|
+
var Session = class Session {
|
|
520
|
+
constructor(storage, options) {
|
|
521
|
+
this._ready = false;
|
|
522
|
+
this.storage = storage;
|
|
523
|
+
this.context = new ContextBlocks(options?.context ?? [], options?.promptStore);
|
|
524
|
+
this._ready = true;
|
|
347
525
|
}
|
|
348
526
|
/**
|
|
349
|
-
*
|
|
527
|
+
* Chainable session creation with auto-wired SQLite providers.
|
|
528
|
+
* Chain methods in any order — providers are resolved lazily on first use.
|
|
529
|
+
*
|
|
530
|
+
* @example
|
|
531
|
+
* ```ts
|
|
532
|
+
* const session = Session.create(this)
|
|
533
|
+
* .withContext("soul", { initialContent: "You are helpful.", readonly: true })
|
|
534
|
+
* .withContext("memory", { description: "Learned facts", maxTokens: 1100 })
|
|
535
|
+
* .withCachedPrompt();
|
|
536
|
+
*
|
|
537
|
+
* // Custom storage (R2, KV, etc.)
|
|
538
|
+
* const session = Session.create(this)
|
|
539
|
+
* .withContext("workspace", {
|
|
540
|
+
* provider: {
|
|
541
|
+
* get: () => env.BUCKET.get("ws.md").then(o => o?.text() ?? null),
|
|
542
|
+
* set: (c) => env.BUCKET.put("ws.md", c),
|
|
543
|
+
* }
|
|
544
|
+
* })
|
|
545
|
+
* .withCachedPrompt();
|
|
546
|
+
* ```
|
|
350
547
|
*/
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
return
|
|
548
|
+
static create(agent) {
|
|
549
|
+
const session = Object.create(Session.prototype);
|
|
550
|
+
session._agent = agent;
|
|
551
|
+
if (isBroadcaster(agent)) session._broadcaster = agent;
|
|
552
|
+
session._pending = [];
|
|
553
|
+
session._ready = false;
|
|
554
|
+
return session;
|
|
555
|
+
}
|
|
556
|
+
forSession(sessionId) {
|
|
557
|
+
this._sessionId = sessionId;
|
|
558
|
+
return this;
|
|
559
|
+
}
|
|
560
|
+
withContext(label, options) {
|
|
561
|
+
this._pending.push({
|
|
562
|
+
label,
|
|
563
|
+
options: options ?? {}
|
|
564
|
+
});
|
|
565
|
+
return this;
|
|
566
|
+
}
|
|
567
|
+
withCachedPrompt(provider) {
|
|
568
|
+
this._cachedPrompt = provider ?? true;
|
|
569
|
+
return this;
|
|
358
570
|
}
|
|
359
571
|
/**
|
|
360
|
-
*
|
|
572
|
+
* Register a compaction function. Called by `compact()` to compress
|
|
573
|
+
* message history into a summary overlay.
|
|
361
574
|
*/
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
575
|
+
onCompaction(fn) {
|
|
576
|
+
this._compactionFn = fn;
|
|
577
|
+
return this;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Auto-compact when estimated token count exceeds the threshold.
|
|
581
|
+
* Checked after each `appendMessage`. Requires `onCompaction()`.
|
|
582
|
+
*/
|
|
583
|
+
compactAfter(tokenThreshold) {
|
|
584
|
+
this._tokenThreshold = tokenThreshold;
|
|
585
|
+
return this;
|
|
586
|
+
}
|
|
587
|
+
_ensureReady() {
|
|
588
|
+
if (this._ready) return;
|
|
589
|
+
const configs = (this._pending ?? []).map(({ label, options: opts }) => {
|
|
590
|
+
let provider = opts.provider;
|
|
591
|
+
if (!provider && !opts.readonly) {
|
|
592
|
+
const key = this._sessionId ? `${label}_${this._sessionId}` : label;
|
|
593
|
+
provider = new AgentContextProvider(this._agent, key);
|
|
594
|
+
}
|
|
595
|
+
return {
|
|
596
|
+
label,
|
|
597
|
+
description: opts.description,
|
|
598
|
+
initialContent: opts.initialContent,
|
|
599
|
+
maxTokens: opts.maxTokens,
|
|
600
|
+
readonly: opts.readonly,
|
|
601
|
+
provider
|
|
602
|
+
};
|
|
603
|
+
});
|
|
604
|
+
let promptStore;
|
|
605
|
+
if (this._cachedPrompt === true) {
|
|
606
|
+
const key = this._sessionId ? `_system_prompt_${this._sessionId}` : "_system_prompt";
|
|
607
|
+
promptStore = new AgentContextProvider(this._agent, key);
|
|
608
|
+
} else if (this._cachedPrompt) promptStore = this._cachedPrompt;
|
|
609
|
+
this.storage = new AgentSessionProvider(this._agent, this._sessionId);
|
|
610
|
+
this.context = new ContextBlocks(configs, promptStore);
|
|
611
|
+
this._ready = true;
|
|
612
|
+
}
|
|
613
|
+
getHistory(leafId) {
|
|
614
|
+
this._ensureReady();
|
|
615
|
+
return this.storage.getHistory(leafId);
|
|
616
|
+
}
|
|
617
|
+
getMessage(id) {
|
|
618
|
+
this._ensureReady();
|
|
619
|
+
return this.storage.getMessage(id);
|
|
620
|
+
}
|
|
621
|
+
getLatestLeaf() {
|
|
622
|
+
this._ensureReady();
|
|
623
|
+
return this.storage.getLatestLeaf();
|
|
624
|
+
}
|
|
625
|
+
getBranches(messageId) {
|
|
626
|
+
this._ensureReady();
|
|
627
|
+
return this.storage.getBranches(messageId);
|
|
628
|
+
}
|
|
629
|
+
getPathLength(leafId) {
|
|
630
|
+
this._ensureReady();
|
|
631
|
+
return this.storage.getPathLength(leafId);
|
|
632
|
+
}
|
|
633
|
+
_broadcast(type, data) {
|
|
634
|
+
if (!this._broadcaster) return;
|
|
635
|
+
this._broadcaster.broadcast(JSON.stringify({
|
|
636
|
+
type,
|
|
637
|
+
...data
|
|
638
|
+
}));
|
|
639
|
+
}
|
|
640
|
+
_emitStatus(phase, extra) {
|
|
641
|
+
const tokenEstimate = estimateMessageTokens(this.getHistory());
|
|
642
|
+
this._broadcast(MessageType.CF_AGENT_SESSION, {
|
|
643
|
+
phase,
|
|
644
|
+
tokenEstimate,
|
|
645
|
+
tokenThreshold: this._tokenThreshold ?? null,
|
|
646
|
+
...extra
|
|
647
|
+
});
|
|
648
|
+
return tokenEstimate;
|
|
649
|
+
}
|
|
650
|
+
_emitError(error) {
|
|
651
|
+
this._broadcast(MessageType.CF_AGENT_SESSION_ERROR, { error });
|
|
652
|
+
}
|
|
653
|
+
async appendMessage(message, parentId) {
|
|
654
|
+
this._ensureReady();
|
|
655
|
+
this.storage.appendMessage(message, parentId);
|
|
656
|
+
const tokenEstimate = this._emitStatus("idle");
|
|
657
|
+
if (this._tokenThreshold != null && this._compactionFn && tokenEstimate > this._tokenThreshold) try {
|
|
658
|
+
await this.compact();
|
|
659
|
+
} catch {}
|
|
660
|
+
}
|
|
661
|
+
updateMessage(message) {
|
|
662
|
+
this._ensureReady();
|
|
663
|
+
this.storage.updateMessage(message);
|
|
664
|
+
this._emitStatus("idle");
|
|
665
|
+
}
|
|
666
|
+
deleteMessages(messageIds) {
|
|
667
|
+
this._ensureReady();
|
|
668
|
+
this.storage.deleteMessages(messageIds);
|
|
669
|
+
this._emitStatus("idle");
|
|
670
|
+
}
|
|
671
|
+
clearMessages() {
|
|
672
|
+
this._ensureReady();
|
|
673
|
+
this.storage.clearMessages();
|
|
674
|
+
this._emitStatus("idle");
|
|
675
|
+
}
|
|
676
|
+
addCompaction(summary, fromMessageId, toMessageId) {
|
|
677
|
+
this._ensureReady();
|
|
678
|
+
return this.storage.addCompaction(summary, fromMessageId, toMessageId);
|
|
679
|
+
}
|
|
680
|
+
getCompactions() {
|
|
681
|
+
this._ensureReady();
|
|
682
|
+
return this.storage.getCompactions();
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Run the registered compaction function and store the result as an overlay.
|
|
686
|
+
* Requires `onCompaction()` to be called first.
|
|
687
|
+
*/
|
|
688
|
+
async compact() {
|
|
689
|
+
this._ensureReady();
|
|
690
|
+
if (!this._compactionFn) throw new Error("No compaction function registered. Call onCompaction() first.");
|
|
691
|
+
const tokensBefore = this._emitStatus("compacting");
|
|
692
|
+
let result;
|
|
693
|
+
try {
|
|
694
|
+
result = await this._compactionFn(this.getHistory());
|
|
695
|
+
} catch (err) {
|
|
696
|
+
this._emitError(err instanceof Error ? err.message : String(err));
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
if (!result) {
|
|
700
|
+
this._emitStatus("idle");
|
|
701
|
+
return null;
|
|
369
702
|
}
|
|
370
|
-
|
|
703
|
+
if (!new Set(this.getHistory().map((m) => m.id)).has(result.toMessageId)) {
|
|
704
|
+
this._emitStatus("idle");
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
const existing = this.getCompactions();
|
|
708
|
+
const fromId = existing.length > 0 ? existing[0].fromMessageId : result.fromMessageId;
|
|
709
|
+
this.addCompaction(result.summary, fromId, result.toMessageId);
|
|
710
|
+
await this.refreshSystemPrompt();
|
|
711
|
+
this._emitStatus("idle", { compacted: { tokensBefore } });
|
|
712
|
+
return {
|
|
713
|
+
...result,
|
|
714
|
+
fromMessageId: fromId
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
getContextBlock(label) {
|
|
718
|
+
this._ensureReady();
|
|
719
|
+
return this.context.getBlock(label);
|
|
720
|
+
}
|
|
721
|
+
getContextBlocks() {
|
|
722
|
+
this._ensureReady();
|
|
723
|
+
return this.context.getBlocks();
|
|
724
|
+
}
|
|
725
|
+
async replaceContextBlock(label, content) {
|
|
726
|
+
this._ensureReady();
|
|
727
|
+
return this.context.setBlock(label, content);
|
|
728
|
+
}
|
|
729
|
+
async appendContextBlock(label, content) {
|
|
730
|
+
this._ensureReady();
|
|
731
|
+
return this.context.appendToBlock(label, content);
|
|
732
|
+
}
|
|
733
|
+
async freezeSystemPrompt() {
|
|
734
|
+
this._ensureReady();
|
|
735
|
+
return this.context.freezeSystemPrompt();
|
|
736
|
+
}
|
|
737
|
+
async refreshSystemPrompt() {
|
|
738
|
+
this._ensureReady();
|
|
739
|
+
return this.context.refreshSystemPrompt();
|
|
740
|
+
}
|
|
741
|
+
search(query, options) {
|
|
742
|
+
this._ensureReady();
|
|
743
|
+
if (!this.storage.searchMessages) throw new Error("Session provider does not support search");
|
|
744
|
+
return this.storage.searchMessages(query, options?.limit ?? 20);
|
|
745
|
+
}
|
|
746
|
+
/** Returns update_context tool for writing to context blocks. */
|
|
747
|
+
async tools() {
|
|
748
|
+
this._ensureReady();
|
|
749
|
+
return this.context.tools();
|
|
371
750
|
}
|
|
372
751
|
};
|
|
373
752
|
//#endregion
|
|
374
|
-
export { AgentSessionProvider, Session };
|
|
753
|
+
export { AgentContextProvider, AgentSessionProvider, Session };
|
|
375
754
|
|
|
376
755
|
//# sourceMappingURL=index.js.map
|