agents 0.8.7 → 0.10.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/dist/chat/index.d.ts +603 -0
- package/dist/chat/index.js +1285 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/{client-BwgM3cRz.js → client-QBjFV5de.js} +161 -49
- package/dist/client-QBjFV5de.js.map +1 -0
- package/dist/client.d.ts +2 -2
- package/dist/{compaction-helpers-BFTBIzpK.js → compaction-helpers-BPE1_ziA.js} +1 -1
- package/dist/{compaction-helpers-BFTBIzpK.js.map → compaction-helpers-BPE1_ziA.js.map} +1 -1
- package/dist/{compaction-helpers-DkJreaDR.d.ts → compaction-helpers-CHNQeyRm.d.ts} +1 -1
- package/dist/{do-oauth-client-provider-C2jurFjW.d.ts → do-oauth-client-provider-31gqR33H.d.ts} +1 -1
- package/dist/{email-DwPlM0bQ.d.ts → email-Cql45SKP.d.ts} +1 -1
- package/dist/email.d.ts +2 -2
- package/dist/experimental/memory/session/index.d.ts +298 -73
- package/dist/experimental/memory/session/index.js +754 -66
- package/dist/experimental/memory/session/index.js.map +1 -1
- package/dist/experimental/memory/utils/index.d.ts +1 -1
- package/dist/experimental/memory/utils/index.js +1 -1
- package/dist/{index-C-6EMK-E.d.ts → index-BPkkIqMn.d.ts} +209 -76
- package/dist/{index-Ua2Nfvbm.d.ts → index-DDSX-g7W.d.ts} +11 -1
- package/dist/index.d.ts +30 -26
- package/dist/index.js +2 -3049
- package/dist/{internal_context-DT8RxmAN.d.ts → internal_context-DuQZFvWI.d.ts} +1 -1
- package/dist/internal_context.d.ts +1 -1
- package/dist/mcp/client.d.ts +2 -2
- package/dist/mcp/client.js +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 +2 -2
- package/dist/observability/index.d.ts +1 -1
- package/dist/react.d.ts +3 -1
- package/dist/react.js +3 -0
- package/dist/react.js.map +1 -1
- package/dist/{retries-DXMQGhG3.d.ts → retries-B_CN5KM9.d.ts} +1 -1
- package/dist/retries.d.ts +1 -1
- package/dist/{serializable-8Jt1B04R.d.ts → serializable-DGdO8CDh.d.ts} +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/src-B8NZxxsO.js +3217 -0
- package/dist/src-B8NZxxsO.js.map +1 -0
- package/dist/{types-C-m0II8i.d.ts → types-B9A8AU7B.d.ts} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/{workflow-types-CZNXKj_D.d.ts → workflow-types-XmOkuI7A.d.ts} +1 -1
- package/dist/workflow-types.d.ts +1 -1
- package/dist/workflows.d.ts +2 -2
- package/dist/workflows.js +1 -1
- package/package.json +20 -18
- package/dist/client-BwgM3cRz.js.map +0 -1
- package/dist/experimental/forever.d.ts +0 -64
- package/dist/experimental/forever.js +0 -338
- package/dist/experimental/forever.js.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -1,6 +1,178 @@
|
|
|
1
1
|
import { MessageType } from "../../../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-BPE1_ziA.js";
|
|
3
3
|
import { jsonSchema } from "ai";
|
|
4
|
+
//#region src/experimental/memory/session/search.ts
|
|
5
|
+
/**
|
|
6
|
+
* Check if a provider is a SearchProvider (has a `search` method).
|
|
7
|
+
*/
|
|
8
|
+
function isSearchProvider(provider) {
|
|
9
|
+
return typeof provider === "object" && provider !== null && "search" in provider && typeof provider.search === "function";
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* SearchProvider backed by Durable Object SQLite with FTS5.
|
|
13
|
+
*
|
|
14
|
+
* - `get()` returns a count of indexed entries
|
|
15
|
+
* - `search(query)` full-text search using FTS5
|
|
16
|
+
* - `set(key, content)` indexes or replaces content under a key
|
|
17
|
+
*
|
|
18
|
+
* Each instance uses a namespaced FTS5 table to avoid collisions
|
|
19
|
+
* with the session message search.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* Session.create(this)
|
|
24
|
+
* .withContext("knowledge", {
|
|
25
|
+
* provider: new AgentSearchProvider(this)
|
|
26
|
+
* })
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
var AgentSearchProvider = class {
|
|
30
|
+
constructor(agent) {
|
|
31
|
+
this.label = "";
|
|
32
|
+
this.initialized = false;
|
|
33
|
+
this.agent = agent;
|
|
34
|
+
}
|
|
35
|
+
init(label) {
|
|
36
|
+
this.label = label;
|
|
37
|
+
}
|
|
38
|
+
ensureTable() {
|
|
39
|
+
if (this.initialized) return;
|
|
40
|
+
this.agent.sql`
|
|
41
|
+
CREATE TABLE IF NOT EXISTS cf_agents_search_entries (
|
|
42
|
+
label TEXT NOT NULL,
|
|
43
|
+
key TEXT NOT NULL,
|
|
44
|
+
content TEXT NOT NULL,
|
|
45
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
46
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
47
|
+
PRIMARY KEY (label, key)
|
|
48
|
+
)
|
|
49
|
+
`;
|
|
50
|
+
this.agent.sql`
|
|
51
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS cf_agents_search_fts
|
|
52
|
+
USING fts5(
|
|
53
|
+
label UNINDEXED,
|
|
54
|
+
key UNINDEXED,
|
|
55
|
+
content,
|
|
56
|
+
tokenize='porter unicode61'
|
|
57
|
+
)
|
|
58
|
+
`;
|
|
59
|
+
this.initialized = true;
|
|
60
|
+
}
|
|
61
|
+
async get() {
|
|
62
|
+
this.ensureTable();
|
|
63
|
+
const count = this.agent.sql`
|
|
64
|
+
SELECT COUNT(*) as count FROM cf_agents_search_entries
|
|
65
|
+
WHERE label = ${this.label}
|
|
66
|
+
`[0]?.count ?? 0;
|
|
67
|
+
if (count === 0) return null;
|
|
68
|
+
return `${count} entries indexed. Recent:\n${this.agent.sql`
|
|
69
|
+
SELECT key FROM cf_agents_search_entries
|
|
70
|
+
WHERE label = ${this.label}
|
|
71
|
+
ORDER BY updated_at DESC
|
|
72
|
+
LIMIT 20
|
|
73
|
+
`.map((r) => `- ${r.key}`).join("\n")}`;
|
|
74
|
+
}
|
|
75
|
+
async search(query) {
|
|
76
|
+
this.ensureTable();
|
|
77
|
+
const sanitized = query.split(/\s+/).filter(Boolean).map((w) => `"${w.replace(/"/g, "\"\"")}"`).join(" ");
|
|
78
|
+
if (!sanitized) return null;
|
|
79
|
+
try {
|
|
80
|
+
const rows = this.agent.sql`
|
|
81
|
+
SELECT f.key, f.content
|
|
82
|
+
FROM cf_agents_search_fts f
|
|
83
|
+
WHERE cf_agents_search_fts MATCH ${sanitized}
|
|
84
|
+
AND f.label = ${this.label}
|
|
85
|
+
ORDER BY rank
|
|
86
|
+
LIMIT 10
|
|
87
|
+
`;
|
|
88
|
+
if (rows.length === 0) return null;
|
|
89
|
+
return rows.map((r) => `[${r.key}]\n${r.content}`).join("\n\n");
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async set(key, content) {
|
|
95
|
+
this.ensureTable();
|
|
96
|
+
this.deleteFTS(key);
|
|
97
|
+
this.agent.sql`
|
|
98
|
+
INSERT INTO cf_agents_search_entries (label, key, content)
|
|
99
|
+
VALUES (${this.label}, ${key}, ${content})
|
|
100
|
+
ON CONFLICT(label, key) DO UPDATE SET
|
|
101
|
+
content = ${content},
|
|
102
|
+
updated_at = CURRENT_TIMESTAMP
|
|
103
|
+
`;
|
|
104
|
+
this.agent.sql`
|
|
105
|
+
INSERT INTO cf_agents_search_fts (label, key, content)
|
|
106
|
+
VALUES (${this.label}, ${key}, ${content})
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
deleteFTS(key) {
|
|
110
|
+
const rows = this.agent.sql`
|
|
111
|
+
SELECT rowid FROM cf_agents_search_fts
|
|
112
|
+
WHERE key = ${key} AND label = ${this.label}
|
|
113
|
+
`;
|
|
114
|
+
for (const row of rows) this.agent.sql`DELETE FROM cf_agents_search_fts WHERE rowid = ${row.rowid}`;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/experimental/memory/session/skills.ts
|
|
119
|
+
/**
|
|
120
|
+
* Check if a provider is a SkillProvider (has a `load` method).
|
|
121
|
+
*/
|
|
122
|
+
function isSkillProvider(provider) {
|
|
123
|
+
return typeof provider === "object" && provider !== null && "load" in provider && typeof provider.load === "function";
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* SkillProvider backed by an R2 bucket.
|
|
127
|
+
*
|
|
128
|
+
* - `get()` returns a metadata listing of all skills (key + description)
|
|
129
|
+
* - `load(key)` fetches a skill's full content
|
|
130
|
+
* - `set(key, content, description?)` writes a skill
|
|
131
|
+
*
|
|
132
|
+
* Descriptions are pulled from R2 custom metadata (`description` key).
|
|
133
|
+
* If a prefix is provided, it is prepended on storage operations and
|
|
134
|
+
* stripped from keys in metadata.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* const skills = new R2SkillProvider(env.SKILLS_BUCKET, { prefix: "skills/" });
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
var R2SkillProvider = class {
|
|
142
|
+
constructor(bucket, options) {
|
|
143
|
+
this.bucket = bucket;
|
|
144
|
+
this.prefix = options?.prefix ?? "";
|
|
145
|
+
}
|
|
146
|
+
async get() {
|
|
147
|
+
const entries = [];
|
|
148
|
+
let cursor;
|
|
149
|
+
let truncated = true;
|
|
150
|
+
while (truncated) {
|
|
151
|
+
const listed = await this.bucket.list({
|
|
152
|
+
prefix: this.prefix,
|
|
153
|
+
cursor,
|
|
154
|
+
include: ["customMetadata"]
|
|
155
|
+
});
|
|
156
|
+
for (const obj of listed.objects) {
|
|
157
|
+
const key = obj.key.slice(this.prefix.length);
|
|
158
|
+
const desc = obj.customMetadata?.description;
|
|
159
|
+
entries.push(`- ${key}${desc ? `: ${desc}` : ""}`);
|
|
160
|
+
}
|
|
161
|
+
truncated = listed.truncated;
|
|
162
|
+
cursor = listed.truncated ? listed.cursor : void 0;
|
|
163
|
+
}
|
|
164
|
+
return entries.length > 0 ? entries.join("\n") : null;
|
|
165
|
+
}
|
|
166
|
+
async load(key) {
|
|
167
|
+
const obj = await this.bucket.get(this.prefix + key);
|
|
168
|
+
if (!obj) return null;
|
|
169
|
+
return obj.text();
|
|
170
|
+
}
|
|
171
|
+
async set(key, content, description) {
|
|
172
|
+
await this.bucket.put(this.prefix + key, content, { customMetadata: description ? { description } : void 0 });
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
//#endregion
|
|
4
176
|
//#region src/experimental/memory/session/context.ts
|
|
5
177
|
/**
|
|
6
178
|
* Context Block Management
|
|
@@ -11,8 +183,20 @@ import { jsonSchema } from "ai";
|
|
|
11
183
|
* - Updated via setBlock() which writes to the provider immediately
|
|
12
184
|
* but does NOT update the frozen snapshot (preserves LLM prefix cache)
|
|
13
185
|
* - Re-snapshotted on next toSystemPrompt() call
|
|
186
|
+
*
|
|
187
|
+
* Provider type determines behavior:
|
|
188
|
+
* - ContextProvider (get only) → readonly block in system prompt
|
|
189
|
+
* - WritableContextProvider (get+set) → writable via set_context tool
|
|
190
|
+
* - SkillProvider (get+load+set?) → metadata in prompt, load_context tool
|
|
191
|
+
* - SearchProvider (get+search+set?) → searchable via search_context tool
|
|
14
192
|
*/
|
|
15
193
|
/**
|
|
194
|
+
* Check if a provider is writable (has a `set` method).
|
|
195
|
+
*/
|
|
196
|
+
function isWritableProvider(provider) {
|
|
197
|
+
return typeof provider === "object" && provider !== null && "set" in provider && typeof provider.set === "function";
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
16
200
|
* Manages context blocks with frozen snapshot support.
|
|
17
201
|
*/
|
|
18
202
|
var ContextBlocks = class {
|
|
@@ -32,16 +216,20 @@ var ContextBlocks = class {
|
|
|
32
216
|
*/
|
|
33
217
|
async load() {
|
|
34
218
|
for (const config of this.configs) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
219
|
+
if (config.provider?.init) config.provider.init(config.label);
|
|
220
|
+
const content = config.provider ? await config.provider.get() ?? "" : "";
|
|
221
|
+
const skill = config.provider ? isSkillProvider(config.provider) : false;
|
|
222
|
+
const searchable = config.provider ? isSearchProvider(config.provider) : false;
|
|
223
|
+
const writable = config.provider ? isWritableProvider(config.provider) || skill && !!config.provider.set || searchable && !!config.provider.set : false;
|
|
38
224
|
this.blocks.set(config.label, {
|
|
39
225
|
label: config.label,
|
|
40
226
|
description: config.description,
|
|
41
227
|
content,
|
|
42
228
|
tokens: estimateStringTokens(content),
|
|
43
229
|
maxTokens: config.maxTokens,
|
|
44
|
-
|
|
230
|
+
writable,
|
|
231
|
+
isSkill: skill,
|
|
232
|
+
isSearchable: searchable
|
|
45
233
|
});
|
|
46
234
|
}
|
|
47
235
|
this.loaded = true;
|
|
@@ -66,7 +254,8 @@ var ContextBlocks = class {
|
|
|
66
254
|
if (!this.loaded) await this.load();
|
|
67
255
|
const config = this.configs.find((c) => c.label === label);
|
|
68
256
|
const existing = this.blocks.get(label);
|
|
69
|
-
if (existing?.
|
|
257
|
+
if (!existing?.writable) throw new Error(`Block "${label}" is readonly`);
|
|
258
|
+
if (existing.isSkill || existing.isSearchable) throw new Error(`Block "${label}" is a keyed provider. Use setSkill() or setSearchEntry() instead.`);
|
|
70
259
|
const tokens = estimateStringTokens(content);
|
|
71
260
|
const maxTokens = config?.maxTokens ?? existing?.maxTokens;
|
|
72
261
|
if (maxTokens !== void 0 && tokens > maxTokens) throw new Error(`Block "${label}" exceeds maxTokens: ${tokens} > ${maxTokens}`);
|
|
@@ -76,13 +265,64 @@ var ContextBlocks = class {
|
|
|
76
265
|
content,
|
|
77
266
|
tokens,
|
|
78
267
|
maxTokens,
|
|
79
|
-
|
|
268
|
+
writable: true,
|
|
269
|
+
isSkill: false,
|
|
270
|
+
isSearchable: false
|
|
80
271
|
};
|
|
81
272
|
this.blocks.set(label, block);
|
|
82
|
-
if (config?.provider
|
|
273
|
+
if (config?.provider && isWritableProvider(config.provider)) await config.provider.set(content);
|
|
83
274
|
return block;
|
|
84
275
|
}
|
|
85
276
|
/**
|
|
277
|
+
* Set a skill entry within a skill block.
|
|
278
|
+
*/
|
|
279
|
+
async setSkill(label, key, content, description) {
|
|
280
|
+
if (!this.loaded) await this.load();
|
|
281
|
+
const config = this.configs.find((c) => c.label === label);
|
|
282
|
+
const existing = this.blocks.get(label);
|
|
283
|
+
if (!existing?.isSkill) throw new Error(`Block "${label}" is not a skill provider`);
|
|
284
|
+
const provider = config?.provider;
|
|
285
|
+
if (!provider || !isSkillProvider(provider) || !provider.set) throw new Error(`Block "${label}" does not support writes`);
|
|
286
|
+
await provider.set(key, content, description);
|
|
287
|
+
const metadata = await provider.get();
|
|
288
|
+
if (metadata) {
|
|
289
|
+
existing.content = metadata;
|
|
290
|
+
existing.tokens = estimateStringTokens(metadata);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Load a skill's full content from a skill block.
|
|
295
|
+
*/
|
|
296
|
+
async loadSkill(label, key) {
|
|
297
|
+
if (!this.loaded) await this.load();
|
|
298
|
+
const config = this.configs.find((c) => c.label === label);
|
|
299
|
+
if (!config?.provider || !isSkillProvider(config.provider)) throw new Error(`Block "${label}" is not a skill provider`);
|
|
300
|
+
return config.provider.load(key);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Index a search entry within a searchable block.
|
|
304
|
+
*/
|
|
305
|
+
async setSearchEntry(label, key, content) {
|
|
306
|
+
if (!this.loaded) await this.load();
|
|
307
|
+
const config = this.configs.find((c) => c.label === label);
|
|
308
|
+
const existing = this.blocks.get(label);
|
|
309
|
+
if (!existing?.isSearchable) throw new Error(`Block "${label}" is not a search provider`);
|
|
310
|
+
const provider = config?.provider;
|
|
311
|
+
if (!provider || !isSearchProvider(provider) || !provider.set) throw new Error(`Block "${label}" does not support writes`);
|
|
312
|
+
await provider.set(key, content);
|
|
313
|
+
existing.content = await provider.get() ?? "";
|
|
314
|
+
existing.tokens = estimateStringTokens(existing.content);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Search a searchable block.
|
|
318
|
+
*/
|
|
319
|
+
async searchContext(label, query) {
|
|
320
|
+
if (!this.loaded) await this.load();
|
|
321
|
+
const config = this.configs.find((c) => c.label === label);
|
|
322
|
+
if (!config?.provider || !isSearchProvider(config.provider)) throw new Error(`Block "${label}" is not a search provider`);
|
|
323
|
+
return config.provider.search(query);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
86
326
|
* Append content to a block.
|
|
87
327
|
*/
|
|
88
328
|
async appendToBlock(label, content) {
|
|
@@ -105,8 +345,6 @@ var ContextBlocks = class {
|
|
|
105
345
|
}
|
|
106
346
|
/**
|
|
107
347
|
* 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
348
|
*/
|
|
111
349
|
refreshSnapshot() {
|
|
112
350
|
return this.captureSnapshot();
|
|
@@ -115,14 +353,18 @@ var ContextBlocks = class {
|
|
|
115
353
|
const parts = [];
|
|
116
354
|
const sep = "═".repeat(46);
|
|
117
355
|
for (const block of this.blocks.values()) {
|
|
118
|
-
if (!block.content) continue;
|
|
356
|
+
if (!block.content && !block.isSearchable) continue;
|
|
119
357
|
let header = block.label.toUpperCase();
|
|
120
|
-
|
|
358
|
+
const hints = [];
|
|
359
|
+
if (block.description) hints.push(block.description);
|
|
360
|
+
if (block.isSkill) hints.push("use load_context to load");
|
|
361
|
+
if (block.isSearchable) hints.push("use search_context to search");
|
|
362
|
+
if (hints.length > 0) header += ` (${hints.join(" — ")})`;
|
|
121
363
|
if (block.maxTokens) {
|
|
122
364
|
const pct = Math.round(block.tokens / block.maxTokens * 100);
|
|
123
365
|
header += ` [${pct}% — ${block.tokens}/${block.maxTokens} tokens]`;
|
|
124
366
|
}
|
|
125
|
-
if (block.
|
|
367
|
+
if (!block.writable) header += " [readonly]";
|
|
126
368
|
parts.push(`${sep}\n${header}\n${sep}\n${block.content}`);
|
|
127
369
|
}
|
|
128
370
|
this.snapshot = parts.join("\n\n");
|
|
@@ -132,20 +374,36 @@ var ContextBlocks = class {
|
|
|
132
374
|
* Get writable blocks (for tool description).
|
|
133
375
|
*/
|
|
134
376
|
getWritableBlocks() {
|
|
135
|
-
return Array.from(this.blocks.values()).filter((b) =>
|
|
377
|
+
return Array.from(this.blocks.values()).filter((b) => b.writable);
|
|
136
378
|
}
|
|
137
379
|
/**
|
|
138
|
-
*
|
|
380
|
+
* Check if any skill providers are registered.
|
|
139
381
|
*/
|
|
140
|
-
|
|
141
|
-
return this.
|
|
382
|
+
hasSkillBlocks() {
|
|
383
|
+
return Array.from(this.blocks.values()).some((b) => b.isSkill);
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Get skill block labels.
|
|
387
|
+
*/
|
|
388
|
+
getSkillLabels() {
|
|
389
|
+
return Array.from(this.blocks.values()).filter((b) => b.isSkill).map((b) => b.label);
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Check if any search providers are registered.
|
|
393
|
+
*/
|
|
394
|
+
hasSearchBlocks() {
|
|
395
|
+
return Array.from(this.blocks.values()).some((b) => b.isSearchable);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Get searchable block labels.
|
|
399
|
+
*/
|
|
400
|
+
getSearchLabels() {
|
|
401
|
+
return Array.from(this.blocks.values()).filter((b) => b.isSearchable).map((b) => b.label);
|
|
142
402
|
}
|
|
143
403
|
/**
|
|
144
404
|
* Frozen system prompt. On first call:
|
|
145
405
|
* 1. Checks store for a persisted prompt (survives DO eviction)
|
|
146
406
|
* 2. If none, loads blocks from providers, renders, and persists
|
|
147
|
-
*
|
|
148
|
-
* Subsequent calls return the stored version — true prefix cache stability.
|
|
149
407
|
*/
|
|
150
408
|
async freezeSystemPrompt() {
|
|
151
409
|
if (this.promptStore) {
|
|
@@ -154,59 +412,152 @@ var ContextBlocks = class {
|
|
|
154
412
|
}
|
|
155
413
|
if (!this.loaded) await this.load();
|
|
156
414
|
const prompt = this.toSystemPrompt();
|
|
157
|
-
if (this.promptStore
|
|
415
|
+
if (this.promptStore) await this.promptStore.set(prompt);
|
|
158
416
|
return prompt;
|
|
159
417
|
}
|
|
160
418
|
/**
|
|
161
419
|
* Re-render the system prompt from current block state and persist.
|
|
162
|
-
* Call after compaction or at session boundaries to pick up writes.
|
|
163
420
|
*/
|
|
164
421
|
async refreshSystemPrompt() {
|
|
165
422
|
if (!this.loaded) await this.load();
|
|
166
423
|
const prompt = this.refreshSnapshot();
|
|
167
|
-
if (this.promptStore
|
|
424
|
+
if (this.promptStore) await this.promptStore.set(prompt);
|
|
168
425
|
return prompt;
|
|
169
426
|
}
|
|
170
427
|
/**
|
|
171
|
-
* AI
|
|
428
|
+
* AI tools for context blocks.
|
|
429
|
+
*
|
|
430
|
+
* Auto-wired based on provider capabilities:
|
|
431
|
+
* - `set_context` — when any block is writable
|
|
432
|
+
* - `load_context` — when any block is a skill provider
|
|
433
|
+
* - `search_context` — when any block is a search provider
|
|
172
434
|
*/
|
|
173
435
|
async tools() {
|
|
174
436
|
if (!this.loaded) await this.load();
|
|
175
437
|
const writable = this.getWritableBlocks();
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
438
|
+
const hasSkills = this.hasSkillBlocks();
|
|
439
|
+
const hasSearch = this.hasSearchBlocks();
|
|
440
|
+
const toolSet = {};
|
|
441
|
+
if (writable.length > 0) {
|
|
442
|
+
const regularBlocks = writable.filter((b) => !b.isSkill && !b.isSearchable);
|
|
443
|
+
const keyedBlocks = writable.filter((b) => b.isSkill || b.isSearchable);
|
|
444
|
+
const blockDescriptions = [];
|
|
445
|
+
for (const b of regularBlocks) blockDescriptions.push(`- "${b.label}": ${b.description ?? "no description"}`);
|
|
446
|
+
for (const b of keyedBlocks) {
|
|
447
|
+
const kind = b.isSkill ? "skill collection (requires key and optional description)" : "searchable (requires key)";
|
|
448
|
+
blockDescriptions.push(`- "${b.label}": ${kind}`);
|
|
449
|
+
}
|
|
450
|
+
const properties = {
|
|
451
|
+
label: {
|
|
452
|
+
type: "string",
|
|
453
|
+
enum: writable.map((b) => b.label),
|
|
454
|
+
description: "Block label to write to"
|
|
455
|
+
},
|
|
456
|
+
content: {
|
|
457
|
+
type: "string",
|
|
458
|
+
description: "Content to write"
|
|
459
|
+
},
|
|
460
|
+
action: {
|
|
461
|
+
type: "string",
|
|
462
|
+
enum: ["replace", "append"],
|
|
463
|
+
description: "replace (default) or append"
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
const required = ["label", "content"];
|
|
467
|
+
if (keyedBlocks.length > 0) properties.key = {
|
|
468
|
+
type: "string",
|
|
469
|
+
description: "Entry key (required for keyed blocks: " + keyedBlocks.map((b) => `"${b.label}"`).join(", ") + ")"
|
|
470
|
+
};
|
|
471
|
+
if (keyedBlocks.some((b) => b.isSkill)) properties.description = {
|
|
472
|
+
type: "string",
|
|
473
|
+
description: "Short description for the skill entry"
|
|
474
|
+
};
|
|
475
|
+
toolSet.set_context = {
|
|
476
|
+
description: `Write to a context block. Available blocks:\n${blockDescriptions.join("\n")}\n\nWrites are durable and persist across sessions.`,
|
|
477
|
+
inputSchema: jsonSchema({
|
|
478
|
+
type: "object",
|
|
479
|
+
properties,
|
|
480
|
+
required
|
|
481
|
+
}),
|
|
482
|
+
execute: async ({ label, content, key, description, action }) => {
|
|
483
|
+
try {
|
|
484
|
+
const block = this.blocks.get(label);
|
|
485
|
+
if (!block) return `Error: block "${label}" not found`;
|
|
486
|
+
if (block.isSkill) {
|
|
487
|
+
if (!key) return `Error: key is required for skill block "${label}"`;
|
|
488
|
+
await this.setSkill(label, key, content, description);
|
|
489
|
+
return `Written skill "${key}" to ${label}.`;
|
|
490
|
+
}
|
|
491
|
+
if (block.isSearchable) {
|
|
492
|
+
if (!key) return `Error: key is required for searchable block "${label}"`;
|
|
493
|
+
await this.setSearchEntry(label, key, content);
|
|
494
|
+
return `Indexed "${key}" in ${label}.`;
|
|
495
|
+
}
|
|
496
|
+
const updated = action === "append" ? await this.appendToBlock(label, content) : await this.setBlock(label, content);
|
|
497
|
+
return `Written to ${label}. Usage: ${updated.maxTokens ? `${Math.round(updated.tokens / updated.maxTokens * 100)}% (${updated.tokens}/${updated.maxTokens} tokens)` : `${updated.tokens} tokens`}`;
|
|
498
|
+
} catch (err) {
|
|
499
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
if (hasSkills) {
|
|
505
|
+
const skillLabels = this.getSkillLabels();
|
|
506
|
+
toolSet.load_context = {
|
|
507
|
+
description: "Load a document from a skill block by key. Available skill blocks: " + skillLabels.map((l) => `"${l}"`).join(", ") + ". Check the system prompt for available keys.",
|
|
508
|
+
inputSchema: jsonSchema({
|
|
509
|
+
type: "object",
|
|
510
|
+
properties: {
|
|
511
|
+
label: {
|
|
512
|
+
type: "string",
|
|
513
|
+
enum: skillLabels,
|
|
514
|
+
description: "Skill block label"
|
|
515
|
+
},
|
|
516
|
+
key: {
|
|
517
|
+
type: "string",
|
|
518
|
+
description: "Skill key to load"
|
|
519
|
+
}
|
|
188
520
|
},
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
521
|
+
required: ["label", "key"]
|
|
522
|
+
}),
|
|
523
|
+
execute: async ({ label, key }) => {
|
|
524
|
+
try {
|
|
525
|
+
return await this.loadSkill(label, key) ?? `Not found: ${key}`;
|
|
526
|
+
} catch (err) {
|
|
527
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
if (hasSearch) {
|
|
533
|
+
const searchLabels = this.getSearchLabels();
|
|
534
|
+
toolSet.search_context = {
|
|
535
|
+
description: "Search for information in a searchable context block. Available searchable blocks: " + searchLabels.map((l) => `"${l}"`).join(", ") + ".",
|
|
536
|
+
inputSchema: jsonSchema({
|
|
537
|
+
type: "object",
|
|
538
|
+
properties: {
|
|
539
|
+
label: {
|
|
540
|
+
type: "string",
|
|
541
|
+
enum: searchLabels,
|
|
542
|
+
description: "Searchable block label"
|
|
543
|
+
},
|
|
544
|
+
query: {
|
|
545
|
+
type: "string",
|
|
546
|
+
description: "Search query"
|
|
547
|
+
}
|
|
192
548
|
},
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
549
|
+
required: ["label", "query"]
|
|
550
|
+
}),
|
|
551
|
+
execute: async ({ label, query }) => {
|
|
552
|
+
try {
|
|
553
|
+
return await this.searchContext(label, query) ?? "No results found.";
|
|
554
|
+
} catch (err) {
|
|
555
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
197
556
|
}
|
|
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
557
|
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
return toolSet;
|
|
210
561
|
}
|
|
211
562
|
};
|
|
212
563
|
//#endregion
|
|
@@ -483,7 +834,10 @@ var AgentContextProvider = class {
|
|
|
483
834
|
constructor(agent, label) {
|
|
484
835
|
this.initialized = false;
|
|
485
836
|
this.agent = agent;
|
|
486
|
-
this.label = label;
|
|
837
|
+
this.label = label ?? "";
|
|
838
|
+
}
|
|
839
|
+
init(label) {
|
|
840
|
+
if (!this.label) this.label = label;
|
|
487
841
|
}
|
|
488
842
|
ensureTable() {
|
|
489
843
|
if (this.initialized) return;
|
|
@@ -530,17 +884,14 @@ var Session = class Session {
|
|
|
530
884
|
* @example
|
|
531
885
|
* ```ts
|
|
532
886
|
* const session = Session.create(this)
|
|
533
|
-
* .withContext("soul", {
|
|
887
|
+
* .withContext("soul", { provider: { get: async () => "You are helpful." } })
|
|
534
888
|
* .withContext("memory", { description: "Learned facts", maxTokens: 1100 })
|
|
535
889
|
* .withCachedPrompt();
|
|
536
890
|
*
|
|
537
|
-
* //
|
|
891
|
+
* // Skills from R2 (on-demand loading via load_context tool)
|
|
538
892
|
* const session = Session.create(this)
|
|
539
|
-
* .withContext("
|
|
540
|
-
* provider: {
|
|
541
|
-
* get: () => env.BUCKET.get("ws.md").then(o => o?.text() ?? null),
|
|
542
|
-
* set: (c) => env.BUCKET.put("ws.md", c),
|
|
543
|
-
* }
|
|
893
|
+
* .withContext("skills", {
|
|
894
|
+
* provider: new R2SkillProvider(env.SKILLS_BUCKET, { prefix: "skills/" })
|
|
544
895
|
* })
|
|
545
896
|
* .withCachedPrompt();
|
|
546
897
|
* ```
|
|
@@ -588,16 +939,14 @@ var Session = class Session {
|
|
|
588
939
|
if (this._ready) return;
|
|
589
940
|
const configs = (this._pending ?? []).map(({ label, options: opts }) => {
|
|
590
941
|
let provider = opts.provider;
|
|
591
|
-
if (!provider
|
|
942
|
+
if (!provider) {
|
|
592
943
|
const key = this._sessionId ? `${label}_${this._sessionId}` : label;
|
|
593
944
|
provider = new AgentContextProvider(this._agent, key);
|
|
594
945
|
}
|
|
595
946
|
return {
|
|
596
947
|
label,
|
|
597
948
|
description: opts.description,
|
|
598
|
-
initialContent: opts.initialContent,
|
|
599
949
|
maxTokens: opts.maxTokens,
|
|
600
|
-
readonly: opts.readonly,
|
|
601
950
|
provider
|
|
602
951
|
};
|
|
603
952
|
});
|
|
@@ -743,13 +1092,352 @@ var Session = class Session {
|
|
|
743
1092
|
if (!this.storage.searchMessages) throw new Error("Session provider does not support search");
|
|
744
1093
|
return this.storage.searchMessages(query, options?.limit ?? 20);
|
|
745
1094
|
}
|
|
746
|
-
/** Returns
|
|
1095
|
+
/** Returns set_context and load_context tools. */
|
|
747
1096
|
async tools() {
|
|
748
1097
|
this._ensureReady();
|
|
749
1098
|
return this.context.tools();
|
|
750
1099
|
}
|
|
751
1100
|
};
|
|
752
1101
|
//#endregion
|
|
753
|
-
|
|
1102
|
+
//#region src/experimental/memory/session/manager.ts
|
|
1103
|
+
var SessionManager = class SessionManager {
|
|
1104
|
+
constructor(agent, options = {}) {
|
|
1105
|
+
this._maxContextMessages = 100;
|
|
1106
|
+
this._pending = [];
|
|
1107
|
+
this._sessions = /* @__PURE__ */ new Map();
|
|
1108
|
+
this._tableReady = false;
|
|
1109
|
+
this._ready = false;
|
|
1110
|
+
this.agent = agent;
|
|
1111
|
+
this._maxContextMessages = options.maxContextMessages ?? 100;
|
|
1112
|
+
this._ready = true;
|
|
1113
|
+
this._ensureTable();
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Chainable SessionManager creation with auto-wired context for all sessions.
|
|
1117
|
+
*
|
|
1118
|
+
* @example
|
|
1119
|
+
* ```ts
|
|
1120
|
+
* const manager = SessionManager.create(this)
|
|
1121
|
+
* .withContext("soul", { provider: { get: async () => "You are helpful." } })
|
|
1122
|
+
* .withContext("memory", { description: "Learned facts", maxTokens: 1100 })
|
|
1123
|
+
* .withCachedPrompt()
|
|
1124
|
+
* .maxContextMessages(50);
|
|
1125
|
+
*
|
|
1126
|
+
* // Each getSession(id) auto-creates namespaced providers:
|
|
1127
|
+
* // memory key: "memory_<sessionId>"
|
|
1128
|
+
* // prompt key: "_system_prompt_<sessionId>"
|
|
1129
|
+
* const session = manager.getSession("chat-123");
|
|
1130
|
+
* ```
|
|
1131
|
+
*/
|
|
1132
|
+
static create(agent) {
|
|
1133
|
+
const mgr = Object.create(SessionManager.prototype);
|
|
1134
|
+
mgr.agent = agent;
|
|
1135
|
+
mgr._maxContextMessages = 100;
|
|
1136
|
+
mgr._pending = [];
|
|
1137
|
+
mgr._compactionFn = null;
|
|
1138
|
+
mgr._tokenThreshold = void 0;
|
|
1139
|
+
mgr._sessions = /* @__PURE__ */ new Map();
|
|
1140
|
+
mgr._tableReady = false;
|
|
1141
|
+
mgr._ready = false;
|
|
1142
|
+
return mgr;
|
|
1143
|
+
}
|
|
1144
|
+
withContext(label, options) {
|
|
1145
|
+
this._pending.push({
|
|
1146
|
+
label,
|
|
1147
|
+
options: options ?? {}
|
|
1148
|
+
});
|
|
1149
|
+
return this;
|
|
1150
|
+
}
|
|
1151
|
+
withCachedPrompt(provider) {
|
|
1152
|
+
this._cachedPrompt = provider ?? true;
|
|
1153
|
+
return this;
|
|
1154
|
+
}
|
|
1155
|
+
maxContextMessages(count) {
|
|
1156
|
+
this._maxContextMessages = count;
|
|
1157
|
+
return this;
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Register a compaction function propagated to all sessions.
|
|
1161
|
+
* Called by `Session.compact()` to compress message history.
|
|
1162
|
+
*/
|
|
1163
|
+
onCompaction(fn) {
|
|
1164
|
+
this._compactionFn = fn;
|
|
1165
|
+
return this;
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Auto-compact when estimated token count exceeds the threshold.
|
|
1169
|
+
* Propagated to all sessions. Requires `onCompaction()`.
|
|
1170
|
+
*/
|
|
1171
|
+
compactAfter(tokenThreshold) {
|
|
1172
|
+
this._tokenThreshold = tokenThreshold;
|
|
1173
|
+
return this;
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Add a searchable context block that searches conversation history
|
|
1177
|
+
* across all sessions managed by this manager.
|
|
1178
|
+
*
|
|
1179
|
+
* The model can use `search_context` to find relevant messages from
|
|
1180
|
+
* any session. The block is readonly (no `set`).
|
|
1181
|
+
*
|
|
1182
|
+
* @example
|
|
1183
|
+
* ```ts
|
|
1184
|
+
* SessionManager.create(this)
|
|
1185
|
+
* .withContext("memory", { maxTokens: 1100 })
|
|
1186
|
+
* .withSearchableHistory("history")
|
|
1187
|
+
* .withCachedPrompt();
|
|
1188
|
+
* ```
|
|
1189
|
+
*/
|
|
1190
|
+
withSearchableHistory(label) {
|
|
1191
|
+
this._historyLabel = label;
|
|
1192
|
+
return this;
|
|
1193
|
+
}
|
|
1194
|
+
_ensureReady() {
|
|
1195
|
+
if (this._ready) return;
|
|
1196
|
+
this._ready = true;
|
|
1197
|
+
this._ensureTable();
|
|
1198
|
+
}
|
|
1199
|
+
_ensureTable() {
|
|
1200
|
+
if (this._tableReady) return;
|
|
1201
|
+
this.agent.sql`
|
|
1202
|
+
CREATE TABLE IF NOT EXISTS assistant_sessions (
|
|
1203
|
+
id TEXT PRIMARY KEY,
|
|
1204
|
+
name TEXT NOT NULL,
|
|
1205
|
+
parent_session_id TEXT,
|
|
1206
|
+
model TEXT,
|
|
1207
|
+
source TEXT,
|
|
1208
|
+
input_tokens INTEGER DEFAULT 0,
|
|
1209
|
+
output_tokens INTEGER DEFAULT 0,
|
|
1210
|
+
estimated_cost REAL DEFAULT 0,
|
|
1211
|
+
end_reason TEXT,
|
|
1212
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
1213
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
1214
|
+
)
|
|
1215
|
+
`;
|
|
1216
|
+
this.agent.sql`
|
|
1217
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS assistant_fts
|
|
1218
|
+
USING fts5(id UNINDEXED, session_id UNINDEXED, role UNINDEXED, content, tokenize='porter unicode61')
|
|
1219
|
+
`;
|
|
1220
|
+
this._tableReady = true;
|
|
1221
|
+
}
|
|
1222
|
+
_createHistoryProvider() {
|
|
1223
|
+
const mgr = this;
|
|
1224
|
+
return {
|
|
1225
|
+
async get() {
|
|
1226
|
+
const sessions = mgr.list();
|
|
1227
|
+
if (sessions.length === 0) return null;
|
|
1228
|
+
return `${sessions.length} session${sessions.length === 1 ? "" : "s"} available for search.`;
|
|
1229
|
+
},
|
|
1230
|
+
async search(query) {
|
|
1231
|
+
const results = mgr.search(query, { limit: 10 });
|
|
1232
|
+
if (results.length === 0) return null;
|
|
1233
|
+
return results.map((r) => `[${r.role}] ${r.content}`).join("\n---\n");
|
|
1234
|
+
}
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
/** Get or create the Session instance for a session ID. */
|
|
1238
|
+
getSession(sessionId) {
|
|
1239
|
+
this._ensureReady();
|
|
1240
|
+
let session = this._sessions.get(sessionId);
|
|
1241
|
+
if (!session) {
|
|
1242
|
+
const s = Session.create(this.agent).forSession(sessionId);
|
|
1243
|
+
for (const { label, options } of this._pending) s.withContext(label, options);
|
|
1244
|
+
if (this._cachedPrompt === true) s.withCachedPrompt();
|
|
1245
|
+
else if (this._cachedPrompt) s.withCachedPrompt(this._cachedPrompt);
|
|
1246
|
+
if (this._historyLabel) s.withContext(this._historyLabel, {
|
|
1247
|
+
description: "Cross-session conversation history",
|
|
1248
|
+
provider: this._createHistoryProvider()
|
|
1249
|
+
});
|
|
1250
|
+
if (this._compactionFn) s.onCompaction(this._compactionFn);
|
|
1251
|
+
if (this._tokenThreshold != null) s.compactAfter(this._tokenThreshold);
|
|
1252
|
+
session = s;
|
|
1253
|
+
this._sessions.set(sessionId, session);
|
|
1254
|
+
}
|
|
1255
|
+
return session;
|
|
1256
|
+
}
|
|
1257
|
+
create(name, opts) {
|
|
1258
|
+
this._ensureReady();
|
|
1259
|
+
const id = crypto.randomUUID();
|
|
1260
|
+
this.agent.sql`
|
|
1261
|
+
INSERT INTO assistant_sessions (id, name, parent_session_id, model, source)
|
|
1262
|
+
VALUES (${id}, ${name}, ${opts?.parentSessionId ?? null}, ${opts?.model ?? null}, ${opts?.source ?? null})
|
|
1263
|
+
`;
|
|
1264
|
+
return this.get(id);
|
|
1265
|
+
}
|
|
1266
|
+
get(sessionId) {
|
|
1267
|
+
this._ensureReady();
|
|
1268
|
+
return this.agent.sql`
|
|
1269
|
+
SELECT * FROM assistant_sessions WHERE id = ${sessionId}
|
|
1270
|
+
`[0] ?? null;
|
|
1271
|
+
}
|
|
1272
|
+
list() {
|
|
1273
|
+
this._ensureReady();
|
|
1274
|
+
return this.agent.sql`
|
|
1275
|
+
SELECT * FROM assistant_sessions ORDER BY updated_at DESC
|
|
1276
|
+
`;
|
|
1277
|
+
}
|
|
1278
|
+
delete(sessionId) {
|
|
1279
|
+
this.getSession(sessionId).clearMessages();
|
|
1280
|
+
this.agent.sql`DELETE FROM assistant_sessions WHERE id = ${sessionId}`;
|
|
1281
|
+
this._sessions.delete(sessionId);
|
|
1282
|
+
}
|
|
1283
|
+
rename(sessionId, name) {
|
|
1284
|
+
this._ensureReady();
|
|
1285
|
+
this.agent.sql`
|
|
1286
|
+
UPDATE assistant_sessions SET name = ${name}, updated_at = CURRENT_TIMESTAMP
|
|
1287
|
+
WHERE id = ${sessionId}
|
|
1288
|
+
`;
|
|
1289
|
+
}
|
|
1290
|
+
async append(sessionId, message, parentId) {
|
|
1291
|
+
await this.getSession(sessionId).appendMessage(message, parentId);
|
|
1292
|
+
this._touch(sessionId);
|
|
1293
|
+
return message.id;
|
|
1294
|
+
}
|
|
1295
|
+
async upsert(sessionId, message, parentId) {
|
|
1296
|
+
const session = this.getSession(sessionId);
|
|
1297
|
+
if (session.getMessage(message.id)) session.updateMessage(message);
|
|
1298
|
+
else await session.appendMessage(message, parentId);
|
|
1299
|
+
this._touch(sessionId);
|
|
1300
|
+
return message.id;
|
|
1301
|
+
}
|
|
1302
|
+
async appendAll(sessionId, messages, parentId) {
|
|
1303
|
+
const session = this.getSession(sessionId);
|
|
1304
|
+
let lastParent = parentId ?? null;
|
|
1305
|
+
for (const msg of messages) {
|
|
1306
|
+
await session.appendMessage(msg, lastParent);
|
|
1307
|
+
lastParent = msg.id;
|
|
1308
|
+
}
|
|
1309
|
+
this._touch(sessionId);
|
|
1310
|
+
return lastParent;
|
|
1311
|
+
}
|
|
1312
|
+
getHistory(sessionId, leafId) {
|
|
1313
|
+
return this.getSession(sessionId).getHistory(leafId);
|
|
1314
|
+
}
|
|
1315
|
+
getMessageCount(sessionId) {
|
|
1316
|
+
return this.getSession(sessionId).getPathLength();
|
|
1317
|
+
}
|
|
1318
|
+
clearMessages(sessionId) {
|
|
1319
|
+
this.getSession(sessionId).clearMessages();
|
|
1320
|
+
this._touch(sessionId);
|
|
1321
|
+
}
|
|
1322
|
+
deleteMessages(sessionId, messageIds) {
|
|
1323
|
+
this.getSession(sessionId).deleteMessages(messageIds);
|
|
1324
|
+
this._touch(sessionId);
|
|
1325
|
+
}
|
|
1326
|
+
getBranches(sessionId, messageId) {
|
|
1327
|
+
return this.getSession(sessionId).getBranches(messageId);
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Fork a session at a specific message, creating a new session
|
|
1331
|
+
* with the history up to that point copied over.
|
|
1332
|
+
*/
|
|
1333
|
+
async fork(sessionId, atMessageId, newName) {
|
|
1334
|
+
const info = this.create(newName, { parentSessionId: sessionId });
|
|
1335
|
+
const history = this.getSession(sessionId).getHistory(atMessageId);
|
|
1336
|
+
const newSession = this.getSession(info.id);
|
|
1337
|
+
let parentId = null;
|
|
1338
|
+
for (const msg of history) {
|
|
1339
|
+
const newId = crypto.randomUUID();
|
|
1340
|
+
const copy = {
|
|
1341
|
+
...msg,
|
|
1342
|
+
id: newId
|
|
1343
|
+
};
|
|
1344
|
+
await newSession.appendMessage(copy, parentId);
|
|
1345
|
+
parentId = newId;
|
|
1346
|
+
}
|
|
1347
|
+
this._touch(info.id);
|
|
1348
|
+
return info;
|
|
1349
|
+
}
|
|
1350
|
+
needsCompaction(sessionId) {
|
|
1351
|
+
return this.getSession(sessionId).getPathLength() > this._maxContextMessages;
|
|
1352
|
+
}
|
|
1353
|
+
addCompaction(sessionId, summary, fromId, toId) {
|
|
1354
|
+
return this.getSession(sessionId).addCompaction(summary, fromId, toId);
|
|
1355
|
+
}
|
|
1356
|
+
getCompactions(sessionId) {
|
|
1357
|
+
return this.getSession(sessionId).getCompactions();
|
|
1358
|
+
}
|
|
1359
|
+
async compactAndSplit(sessionId, summary, newName) {
|
|
1360
|
+
const old = this.get(sessionId);
|
|
1361
|
+
this.agent.sql`
|
|
1362
|
+
UPDATE assistant_sessions SET end_reason = 'compaction', updated_at = CURRENT_TIMESTAMP
|
|
1363
|
+
WHERE id = ${sessionId}
|
|
1364
|
+
`;
|
|
1365
|
+
const info = this.create(newName ?? old?.name ?? "Compacted", {
|
|
1366
|
+
parentSessionId: sessionId,
|
|
1367
|
+
model: old?.model ?? void 0,
|
|
1368
|
+
source: old?.source ?? void 0
|
|
1369
|
+
});
|
|
1370
|
+
await this.append(info.id, {
|
|
1371
|
+
id: crypto.randomUUID(),
|
|
1372
|
+
role: "assistant",
|
|
1373
|
+
parts: [{
|
|
1374
|
+
type: "text",
|
|
1375
|
+
text: `[Context from previous session]\n\n${summary}`
|
|
1376
|
+
}]
|
|
1377
|
+
});
|
|
1378
|
+
return info;
|
|
1379
|
+
}
|
|
1380
|
+
addUsage(sessionId, inputTokens, outputTokens, cost) {
|
|
1381
|
+
this._ensureReady();
|
|
1382
|
+
this.agent.sql`
|
|
1383
|
+
UPDATE assistant_sessions SET
|
|
1384
|
+
input_tokens = input_tokens + ${inputTokens},
|
|
1385
|
+
output_tokens = output_tokens + ${outputTokens},
|
|
1386
|
+
estimated_cost = estimated_cost + ${cost},
|
|
1387
|
+
updated_at = CURRENT_TIMESTAMP
|
|
1388
|
+
WHERE id = ${sessionId}
|
|
1389
|
+
`;
|
|
1390
|
+
}
|
|
1391
|
+
search(query, options) {
|
|
1392
|
+
this._ensureReady();
|
|
1393
|
+
const limit = options?.limit ?? 20;
|
|
1394
|
+
const sanitized = query.split(/\s+/).filter(Boolean).map((w) => `"${w.replace(/"/g, "\"\"")}"`).join(" ");
|
|
1395
|
+
if (!sanitized) return [];
|
|
1396
|
+
try {
|
|
1397
|
+
return this.agent.sql`
|
|
1398
|
+
SELECT id, role, content FROM assistant_fts
|
|
1399
|
+
WHERE assistant_fts MATCH ${sanitized}
|
|
1400
|
+
ORDER BY rank LIMIT ${limit}
|
|
1401
|
+
`.map((r) => ({
|
|
1402
|
+
id: r.id,
|
|
1403
|
+
role: r.role,
|
|
1404
|
+
content: r.content,
|
|
1405
|
+
createdAt: ""
|
|
1406
|
+
}));
|
|
1407
|
+
} catch {
|
|
1408
|
+
return [];
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
tools() {
|
|
1412
|
+
return { session_search: {
|
|
1413
|
+
description: "Search past conversations for relevant context. Searches across all sessions.",
|
|
1414
|
+
inputSchema: jsonSchema({
|
|
1415
|
+
type: "object",
|
|
1416
|
+
properties: { query: {
|
|
1417
|
+
type: "string",
|
|
1418
|
+
description: "Search query"
|
|
1419
|
+
} },
|
|
1420
|
+
required: ["query"]
|
|
1421
|
+
}),
|
|
1422
|
+
execute: async ({ query }) => {
|
|
1423
|
+
try {
|
|
1424
|
+
const results = this.search(query, { limit: 10 });
|
|
1425
|
+
if (results.length === 0) return "No results found.";
|
|
1426
|
+
return results.map((r) => `[${r.role}] ${r.content}`).join("\n---\n");
|
|
1427
|
+
} catch (err) {
|
|
1428
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
} };
|
|
1432
|
+
}
|
|
1433
|
+
_touch(sessionId) {
|
|
1434
|
+
this.agent.sql`
|
|
1435
|
+
UPDATE assistant_sessions SET updated_at = CURRENT_TIMESTAMP
|
|
1436
|
+
WHERE id = ${sessionId}
|
|
1437
|
+
`;
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
//#endregion
|
|
1441
|
+
export { AgentContextProvider, AgentSearchProvider, AgentSessionProvider, R2SkillProvider, Session, SessionManager, isSearchProvider, isSkillProvider, isWritableProvider };
|
|
754
1442
|
|
|
755
1443
|
//# sourceMappingURL=index.js.map
|