@cloudflare/think 0.0.0 → 0.0.2

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.
@@ -0,0 +1,507 @@
1
+ //#region src/session/storage.ts
2
+ var SessionStorage = class {
3
+ constructor(sql, exec) {
4
+ this.sql = sql;
5
+ this.exec = exec ?? null;
6
+ this._initSchema();
7
+ }
8
+ _initSchema() {
9
+ this.sql`
10
+ CREATE TABLE IF NOT EXISTS assistant_sessions (
11
+ id TEXT PRIMARY KEY,
12
+ name TEXT NOT NULL,
13
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
14
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
15
+ )
16
+ `;
17
+ this.sql`
18
+ CREATE TABLE IF NOT EXISTS assistant_messages (
19
+ id TEXT PRIMARY KEY,
20
+ session_id TEXT NOT NULL,
21
+ parent_id TEXT,
22
+ role TEXT NOT NULL,
23
+ content TEXT NOT NULL,
24
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
25
+ FOREIGN KEY (session_id) REFERENCES assistant_sessions(id)
26
+ )
27
+ `;
28
+ this.sql`
29
+ CREATE INDEX IF NOT EXISTS idx_assistant_messages_session
30
+ ON assistant_messages(session_id, created_at)
31
+ `;
32
+ this.sql`
33
+ CREATE INDEX IF NOT EXISTS idx_assistant_messages_parent
34
+ ON assistant_messages(parent_id)
35
+ `;
36
+ this.sql`
37
+ CREATE TABLE IF NOT EXISTS assistant_compactions (
38
+ id TEXT PRIMARY KEY,
39
+ session_id TEXT NOT NULL,
40
+ summary TEXT NOT NULL,
41
+ from_message_id TEXT NOT NULL,
42
+ to_message_id TEXT NOT NULL,
43
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
44
+ FOREIGN KEY (session_id) REFERENCES assistant_sessions(id)
45
+ )
46
+ `;
47
+ }
48
+ createSession(id, name) {
49
+ this.sql`
50
+ INSERT INTO assistant_sessions (id, name)
51
+ VALUES (${id}, ${name})
52
+ `;
53
+ return this.sql`
54
+ SELECT * FROM assistant_sessions WHERE id = ${id}
55
+ `[0];
56
+ }
57
+ getSession(id) {
58
+ return this.sql`
59
+ SELECT * FROM assistant_sessions WHERE id = ${id}
60
+ `[0] ?? null;
61
+ }
62
+ listSessions() {
63
+ return this.sql`
64
+ SELECT * FROM assistant_sessions ORDER BY updated_at DESC
65
+ `;
66
+ }
67
+ updateSessionTimestamp(id) {
68
+ this.sql`
69
+ UPDATE assistant_sessions SET updated_at = CURRENT_TIMESTAMP WHERE id = ${id}
70
+ `;
71
+ }
72
+ deleteSession(id) {
73
+ this.sql`DELETE FROM assistant_compactions WHERE session_id = ${id}`;
74
+ this.sql`DELETE FROM assistant_messages WHERE session_id = ${id}`;
75
+ this.sql`DELETE FROM assistant_sessions WHERE id = ${id}`;
76
+ }
77
+ /**
78
+ * Delete all messages and compactions for a session without
79
+ * deleting the session itself. Resets updated_at.
80
+ */
81
+ clearSessionMessages(id) {
82
+ this.sql`DELETE FROM assistant_compactions WHERE session_id = ${id}`;
83
+ this.sql`DELETE FROM assistant_messages WHERE session_id = ${id}`;
84
+ this.updateSessionTimestamp(id);
85
+ }
86
+ renameSession(id, name) {
87
+ this.sql`
88
+ UPDATE assistant_sessions SET name = ${name}, updated_at = CURRENT_TIMESTAMP
89
+ WHERE id = ${id}
90
+ `;
91
+ }
92
+ /**
93
+ * Insert a message. Uses INSERT OR IGNORE so appending the same
94
+ * message ID twice is a safe no-op (idempotent).
95
+ */
96
+ appendMessage(id, sessionId, parentId, message) {
97
+ const content = JSON.stringify(message);
98
+ this.sql`
99
+ INSERT OR IGNORE INTO assistant_messages (id, session_id, parent_id, role, content)
100
+ VALUES (${id}, ${sessionId}, ${parentId}, ${message.role}, ${content})
101
+ `;
102
+ this.updateSessionTimestamp(sessionId);
103
+ return this.sql`
104
+ SELECT * FROM assistant_messages WHERE id = ${id}
105
+ `[0];
106
+ }
107
+ /**
108
+ * Insert or update a message. Uses INSERT ... ON CONFLICT to update
109
+ * the content if the message already exists (same id). This enables
110
+ * incremental persistence — first call inserts, subsequent calls update.
111
+ */
112
+ upsertMessage(id, sessionId, parentId, message) {
113
+ const content = JSON.stringify(message);
114
+ this.sql`
115
+ INSERT INTO assistant_messages (id, session_id, parent_id, role, content)
116
+ VALUES (${id}, ${sessionId}, ${parentId}, ${message.role}, ${content})
117
+ ON CONFLICT(id) DO UPDATE SET content = ${content}
118
+ `;
119
+ this.updateSessionTimestamp(sessionId);
120
+ return this.sql`
121
+ SELECT * FROM assistant_messages WHERE id = ${id}
122
+ `[0];
123
+ }
124
+ /**
125
+ * Delete a single message by ID.
126
+ * In a tree structure, children of the deleted message retain their
127
+ * parent_id (now pointing to a missing row), which naturally truncates
128
+ * the path when walking via the recursive CTE.
129
+ */
130
+ deleteMessage(id) {
131
+ this.sql`DELETE FROM assistant_messages WHERE id = ${id}`;
132
+ }
133
+ /**
134
+ * Delete multiple messages by ID in a single query.
135
+ */
136
+ deleteMessages(ids) {
137
+ if (ids.length === 0) return;
138
+ if (this.exec) {
139
+ const placeholders = ids.map(() => "?").join(", ");
140
+ this.exec(`DELETE FROM assistant_messages WHERE id IN (${placeholders})`, ...ids);
141
+ } else for (const id of ids) this.sql`DELETE FROM assistant_messages WHERE id = ${id}`;
142
+ }
143
+ getMessage(id) {
144
+ return this.sql`
145
+ SELECT * FROM assistant_messages WHERE id = ${id}
146
+ `[0] ?? null;
147
+ }
148
+ /**
149
+ * Get all messages for a session, ordered by creation time.
150
+ * This returns the flat list — use getMessagePath for a branch path.
151
+ */
152
+ getSessionMessages(sessionId) {
153
+ return this.sql`
154
+ SELECT * FROM assistant_messages
155
+ WHERE session_id = ${sessionId}
156
+ ORDER BY created_at ASC
157
+ `;
158
+ }
159
+ /**
160
+ * Walk from a leaf message back to the root via a recursive CTE,
161
+ * returning messages in chronological order (root first).
162
+ * Uses a depth counter for ordering since created_at may be
163
+ * identical for messages inserted in quick succession.
164
+ */
165
+ getMessagePath(leafId) {
166
+ return this.sql`
167
+ WITH RECURSIVE path AS (
168
+ SELECT *, 0 as depth FROM assistant_messages WHERE id = ${leafId}
169
+ UNION ALL
170
+ SELECT m.*, p.depth + 1 FROM assistant_messages m
171
+ JOIN path p ON m.id = p.parent_id
172
+ )
173
+ SELECT id, session_id, parent_id, role, content, created_at
174
+ FROM path ORDER BY depth DESC
175
+ `;
176
+ }
177
+ /**
178
+ * Count the number of messages on the path from root to leaf.
179
+ * Used by needsCompaction to avoid loading full message content.
180
+ */
181
+ getPathLength(leafId) {
182
+ return this.sql`
183
+ WITH RECURSIVE path AS (
184
+ SELECT id, parent_id FROM assistant_messages WHERE id = ${leafId}
185
+ UNION ALL
186
+ SELECT m.id, m.parent_id FROM assistant_messages m
187
+ JOIN path p ON m.id = p.parent_id
188
+ )
189
+ SELECT COUNT(*) as count FROM path
190
+ `[0]?.count ?? 0;
191
+ }
192
+ /**
193
+ * Get children of a message (for branch exploration).
194
+ */
195
+ getChildren(parentId) {
196
+ return this.sql`
197
+ SELECT * FROM assistant_messages
198
+ WHERE parent_id = ${parentId}
199
+ ORDER BY created_at ASC
200
+ `;
201
+ }
202
+ /**
203
+ * Get the latest leaf message in a session (most recent message
204
+ * that has no children). Used to find the "current" position.
205
+ */
206
+ getLatestLeaf(sessionId) {
207
+ return this.sql`
208
+ SELECT m.* FROM assistant_messages m
209
+ LEFT JOIN assistant_messages c ON c.parent_id = m.id
210
+ WHERE m.session_id = ${sessionId} AND c.id IS NULL
211
+ ORDER BY m.created_at DESC
212
+ LIMIT 1
213
+ `[0] ?? null;
214
+ }
215
+ /**
216
+ * Count all messages in a session (across all branches).
217
+ */
218
+ getMessageCount(sessionId) {
219
+ return this.sql`
220
+ SELECT COUNT(*) as count FROM assistant_messages
221
+ WHERE session_id = ${sessionId}
222
+ `[0]?.count ?? 0;
223
+ }
224
+ addCompaction(id, sessionId, summary, fromMessageId, toMessageId) {
225
+ this.sql`
226
+ INSERT INTO assistant_compactions (id, session_id, summary, from_message_id, to_message_id)
227
+ VALUES (${id}, ${sessionId}, ${summary}, ${fromMessageId}, ${toMessageId})
228
+ `;
229
+ return this.sql`
230
+ SELECT * FROM assistant_compactions WHERE id = ${id}
231
+ `[0];
232
+ }
233
+ getCompactions(sessionId) {
234
+ return this.sql`
235
+ SELECT * FROM assistant_compactions
236
+ WHERE session_id = ${sessionId}
237
+ ORDER BY created_at ASC
238
+ `;
239
+ }
240
+ /**
241
+ * Parse a stored message's content field back into a UIMessage.
242
+ */
243
+ parseMessage(stored) {
244
+ return JSON.parse(stored.content);
245
+ }
246
+ };
247
+ //#endregion
248
+ //#region src/session/index.ts
249
+ const DEFAULT_MAX_CHARS = 3e4;
250
+ const ELLIPSIS = "\n\n... [truncated] ...\n\n";
251
+ /**
252
+ * Truncate from the head (keep the end of the content).
253
+ */
254
+ function truncateHead(text, maxChars = DEFAULT_MAX_CHARS) {
255
+ if (text.length <= maxChars) return text;
256
+ const keep = maxChars - 23;
257
+ if (keep <= 0) return text.slice(-maxChars);
258
+ return ELLIPSIS + text.slice(-keep);
259
+ }
260
+ /**
261
+ * Truncate from the tail (keep the start of the content).
262
+ */
263
+ function truncateTail(text, maxChars = DEFAULT_MAX_CHARS) {
264
+ if (text.length <= maxChars) return text;
265
+ const keep = maxChars - 23;
266
+ if (keep <= 0) return text.slice(0, maxChars);
267
+ return text.slice(0, keep) + ELLIPSIS;
268
+ }
269
+ /**
270
+ * Truncate by line count (keep the first N lines).
271
+ */
272
+ function truncateLines(text, maxLines = 200) {
273
+ const lines = text.split("\n");
274
+ if (lines.length <= maxLines) return text;
275
+ return lines.slice(0, maxLines).join("\n") + `\n\n... [${lines.length - maxLines} more lines truncated] ...`;
276
+ }
277
+ /**
278
+ * Truncate from both ends, keeping the start and end.
279
+ */
280
+ function truncateMiddle(text, maxChars = DEFAULT_MAX_CHARS) {
281
+ if (text.length <= maxChars) return text;
282
+ const halfKeep = Math.floor((maxChars - 23) / 2);
283
+ if (halfKeep <= 0) return text.slice(0, maxChars);
284
+ return text.slice(0, halfKeep) + ELLIPSIS + text.slice(-halfKeep);
285
+ }
286
+ /**
287
+ * Smart truncation for tool output.
288
+ */
289
+ function truncateToolOutput(output, options = {}) {
290
+ const { maxChars = DEFAULT_MAX_CHARS, maxLines = 500, strategy = "tail" } = options;
291
+ let result = truncateLines(output, maxLines);
292
+ if (result.length > maxChars) switch (strategy) {
293
+ case "head":
294
+ result = truncateHead(result, maxChars);
295
+ break;
296
+ case "middle":
297
+ result = truncateMiddle(result, maxChars);
298
+ break;
299
+ default:
300
+ result = truncateTail(result, maxChars);
301
+ break;
302
+ }
303
+ return result;
304
+ }
305
+ var SessionManager = class {
306
+ constructor(agent, options = {}) {
307
+ this._storage = new SessionStorage(agent.sql.bind(agent), options.exec);
308
+ this._options = {
309
+ maxContextMessages: 100,
310
+ ...options
311
+ };
312
+ }
313
+ /**
314
+ * Create a new session with a name.
315
+ */
316
+ create(name) {
317
+ return this._storage.createSession(crypto.randomUUID(), name);
318
+ }
319
+ /**
320
+ * Get a session by ID.
321
+ */
322
+ get(sessionId) {
323
+ return this._storage.getSession(sessionId);
324
+ }
325
+ /**
326
+ * List all sessions, most recently updated first.
327
+ */
328
+ list() {
329
+ return this._storage.listSessions();
330
+ }
331
+ /**
332
+ * Delete a session and all its messages and compactions.
333
+ */
334
+ delete(sessionId) {
335
+ this._storage.deleteSession(sessionId);
336
+ }
337
+ /**
338
+ * Clear all messages and compactions for a session without
339
+ * deleting the session itself.
340
+ */
341
+ clearMessages(sessionId) {
342
+ this._storage.clearSessionMessages(sessionId);
343
+ }
344
+ /**
345
+ * Rename a session.
346
+ */
347
+ rename(sessionId, name) {
348
+ this._storage.renameSession(sessionId, name);
349
+ }
350
+ /**
351
+ * Append a message to a session. If parentId is not provided,
352
+ * the message is appended after the latest leaf.
353
+ *
354
+ * Idempotent — appending the same message.id twice is a no-op.
355
+ *
356
+ * Returns the stored message ID.
357
+ */
358
+ append(sessionId, message, parentId) {
359
+ const resolvedParent = parentId ?? this._storage.getLatestLeaf(sessionId)?.id ?? null;
360
+ const id = message.id || crypto.randomUUID();
361
+ this._storage.appendMessage(id, sessionId, resolvedParent, message);
362
+ return id;
363
+ }
364
+ /**
365
+ * Insert or update a message. First call inserts, subsequent calls
366
+ * update the content. Enables incremental persistence.
367
+ *
368
+ * Idempotent on insert, content-updating on subsequent calls.
369
+ */
370
+ upsert(sessionId, message, parentId) {
371
+ const resolvedParent = parentId ?? this._storage.getLatestLeaf(sessionId)?.id ?? null;
372
+ const id = message.id || crypto.randomUUID();
373
+ this._storage.upsertMessage(id, sessionId, resolvedParent, message);
374
+ return id;
375
+ }
376
+ /**
377
+ * Delete a single message by ID.
378
+ * Children of the deleted message naturally become path roots
379
+ * (their parent_id points to a missing row, truncating the CTE walk).
380
+ */
381
+ deleteMessage(messageId) {
382
+ this._storage.deleteMessage(messageId);
383
+ }
384
+ /**
385
+ * Delete multiple messages by ID.
386
+ */
387
+ deleteMessages(messageIds) {
388
+ this._storage.deleteMessages(messageIds);
389
+ }
390
+ /**
391
+ * Append multiple messages in sequence (each parented to the previous).
392
+ * Returns the ID of the last appended message.
393
+ */
394
+ appendAll(sessionId, messages, parentId) {
395
+ let lastId = parentId ?? null;
396
+ for (const msg of messages) {
397
+ const resolvedParent = lastId ?? this._storage.getLatestLeaf(sessionId)?.id ?? null;
398
+ const id = msg.id || crypto.randomUUID();
399
+ this._storage.appendMessage(id, sessionId, resolvedParent, msg);
400
+ lastId = id;
401
+ }
402
+ return lastId;
403
+ }
404
+ /**
405
+ * Get the conversation history for a session as UIMessage[].
406
+ *
407
+ * If leafId is provided, returns the path from root to that leaf
408
+ * (a specific branch). Otherwise returns the path to the most
409
+ * recent leaf (the "current" branch).
410
+ *
411
+ * If compactions exist, older messages covered by a compaction
412
+ * are replaced with a system message containing the summary.
413
+ */
414
+ getHistory(sessionId, leafId) {
415
+ const leaf = leafId ? this._storage.getMessage(leafId) : this._storage.getLatestLeaf(sessionId);
416
+ if (!leaf) return [];
417
+ const storedPath = this._storage.getMessagePath(leaf.id);
418
+ const compactions = this._storage.getCompactions(sessionId);
419
+ if (compactions.length === 0) return storedPath.map((m) => this._storage.parseMessage(m));
420
+ return this._applyCompactions(storedPath, compactions);
421
+ }
422
+ /**
423
+ * Get the total message count for a session (across all branches).
424
+ */
425
+ getMessageCount(sessionId) {
426
+ return this._storage.getMessageCount(sessionId);
427
+ }
428
+ /**
429
+ * Check if the session's current branch needs compaction.
430
+ * Uses a count-only query — does not load message content.
431
+ */
432
+ needsCompaction(sessionId) {
433
+ const leaf = this._storage.getLatestLeaf(sessionId);
434
+ if (!leaf) return false;
435
+ return this._storage.getPathLength(leaf.id) > (this._options.maxContextMessages ?? 100);
436
+ }
437
+ /**
438
+ * Get the children of a message (branches from that point).
439
+ */
440
+ getBranches(messageId) {
441
+ return this._storage.getChildren(messageId).map((m) => this._storage.parseMessage(m));
442
+ }
443
+ /**
444
+ * Fork a session at a specific message, creating a new session
445
+ * with the history up to that point copied over.
446
+ */
447
+ fork(atMessageId, newName) {
448
+ const newSession = this.create(newName);
449
+ const path = this._storage.getMessagePath(atMessageId);
450
+ let parentId = null;
451
+ for (const stored of path) {
452
+ const msg = this._storage.parseMessage(stored);
453
+ const newId = crypto.randomUUID();
454
+ this._storage.appendMessage(newId, newSession.id, parentId, msg);
455
+ parentId = newId;
456
+ }
457
+ return newSession;
458
+ }
459
+ /**
460
+ * Add a compaction record. The summary replaces messages from
461
+ * fromMessageId to toMessageId in context assembly.
462
+ *
463
+ * Typically called after using an LLM to summarize older messages.
464
+ */
465
+ addCompaction(sessionId, summary, fromMessageId, toMessageId) {
466
+ return this._storage.addCompaction(crypto.randomUUID(), sessionId, summary, fromMessageId, toMessageId);
467
+ }
468
+ /**
469
+ * Get all compaction records for a session.
470
+ */
471
+ getCompactions(sessionId) {
472
+ return this._storage.getCompactions(sessionId);
473
+ }
474
+ _applyCompactions(path, compactions) {
475
+ const pathIds = path.map((m) => m.id);
476
+ const result = [];
477
+ let i = 0;
478
+ while (i < path.length) {
479
+ const compaction = compactions.find((c) => c.from_message_id === pathIds[i]);
480
+ if (compaction) {
481
+ const endIdx = pathIds.indexOf(compaction.to_message_id);
482
+ if (endIdx >= i) {
483
+ result.push({
484
+ id: `compaction_${compaction.id}`,
485
+ role: "system",
486
+ parts: [{
487
+ type: "text",
488
+ text: `[Previous conversation summary]\n${compaction.summary}`
489
+ }]
490
+ });
491
+ i = endIdx + 1;
492
+ } else {
493
+ result.push(JSON.parse(path[i].content));
494
+ i++;
495
+ }
496
+ } else {
497
+ result.push(JSON.parse(path[i].content));
498
+ i++;
499
+ }
500
+ }
501
+ return result;
502
+ }
503
+ };
504
+ //#endregion
505
+ export { truncateTail as a, truncateMiddle as i, truncateHead as n, truncateToolOutput as o, truncateLines as r, SessionManager as t };
506
+
507
+ //# sourceMappingURL=session-C6ZU_1zM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-C6ZU_1zM.js","names":[],"sources":["../src/session/storage.ts","../src/session/index.ts"],"sourcesContent":["/**\n * Session storage layer backed by DO SQLite.\n *\n * Schema:\n * assistant_sessions — named conversation roots\n * assistant_messages — append-only message log with parent_id for branching\n * assistant_compactions — summaries that replace older messages in context assembly\n *\n * All queries use the Agent's `this.sql` tagged template.\n */\nimport type { UIMessage } from \"ai\";\n\n// ── Types ──────────────────────────────────────────────────────────\n\nexport interface Session {\n id: string;\n name: string;\n created_at: string;\n updated_at: string;\n}\n\nexport interface StoredMessage {\n id: string;\n session_id: string;\n parent_id: string | null;\n role: string;\n content: string; // JSON-serialized UIMessage\n created_at: string;\n}\n\nexport interface Compaction {\n id: string;\n session_id: string;\n summary: string;\n from_message_id: string;\n to_message_id: string;\n created_at: string;\n}\n\n// Mirrors Agent.sql — kept structural to avoid importing the 4k-line Agent class.\ntype SqlFn = (\n strings: TemplateStringsArray,\n ...values: (string | number | boolean | null)[]\n) => Array<Record<string, unknown>>;\n\n/** Raw SQL exec function — allows dynamic queries with parameter arrays. */\ntype SqlExecFn = (\n query: string,\n ...values: (string | number | boolean | null)[]\n) => void;\n\n// ── Storage class ──────────────────────────────────────────────────────\n\nexport class SessionStorage {\n private exec: SqlExecFn | null;\n\n constructor(\n private sql: SqlFn,\n exec?: SqlExecFn\n ) {\n this.exec = exec ?? null;\n this._initSchema();\n }\n\n private _initSchema() {\n this.sql`\n CREATE TABLE IF NOT EXISTS assistant_sessions (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP\n )\n `;\n\n this.sql`\n CREATE TABLE IF NOT EXISTS assistant_messages (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL,\n parent_id TEXT,\n role TEXT NOT NULL,\n content TEXT NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (session_id) REFERENCES assistant_sessions(id)\n )\n `;\n\n this.sql`\n CREATE INDEX IF NOT EXISTS idx_assistant_messages_session\n ON assistant_messages(session_id, created_at)\n `;\n\n this.sql`\n CREATE INDEX IF NOT EXISTS idx_assistant_messages_parent\n ON assistant_messages(parent_id)\n `;\n\n this.sql`\n CREATE TABLE IF NOT EXISTS assistant_compactions (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL,\n summary TEXT NOT NULL,\n from_message_id TEXT NOT NULL,\n to_message_id TEXT NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (session_id) REFERENCES assistant_sessions(id)\n )\n `;\n }\n\n // ── Sessions ───────────────────────────────────────────────────\n\n createSession(id: string, name: string): Session {\n this.sql`\n INSERT INTO assistant_sessions (id, name)\n VALUES (${id}, ${name})\n `;\n const rows = this.sql`\n SELECT * FROM assistant_sessions WHERE id = ${id}\n ` as unknown as Session[];\n return rows[0];\n }\n\n getSession(id: string): Session | null {\n const rows = this.sql`\n SELECT * FROM assistant_sessions WHERE id = ${id}\n ` as unknown as Session[];\n return rows[0] ?? null;\n }\n\n listSessions(): Session[] {\n return this.sql`\n SELECT * FROM assistant_sessions ORDER BY updated_at DESC\n ` as unknown as Session[];\n }\n\n updateSessionTimestamp(id: string): void {\n this.sql`\n UPDATE assistant_sessions SET updated_at = CURRENT_TIMESTAMP WHERE id = ${id}\n `;\n }\n\n deleteSession(id: string): void {\n this.sql`DELETE FROM assistant_compactions WHERE session_id = ${id}`;\n this.sql`DELETE FROM assistant_messages WHERE session_id = ${id}`;\n this.sql`DELETE FROM assistant_sessions WHERE id = ${id}`;\n }\n\n /**\n * Delete all messages and compactions for a session without\n * deleting the session itself. Resets updated_at.\n */\n clearSessionMessages(id: string): void {\n this.sql`DELETE FROM assistant_compactions WHERE session_id = ${id}`;\n this.sql`DELETE FROM assistant_messages WHERE session_id = ${id}`;\n this.updateSessionTimestamp(id);\n }\n\n renameSession(id: string, name: string): void {\n this.sql`\n UPDATE assistant_sessions SET name = ${name}, updated_at = CURRENT_TIMESTAMP\n WHERE id = ${id}\n `;\n }\n\n // ── Messages ───────────────────────────────────────────────────\n\n /**\n * Insert a message. Uses INSERT OR IGNORE so appending the same\n * message ID twice is a safe no-op (idempotent).\n */\n appendMessage(\n id: string,\n sessionId: string,\n parentId: string | null,\n message: UIMessage\n ): StoredMessage {\n const content = JSON.stringify(message);\n this.sql`\n INSERT OR IGNORE INTO assistant_messages (id, session_id, parent_id, role, content)\n VALUES (${id}, ${sessionId}, ${parentId}, ${message.role}, ${content})\n `;\n this.updateSessionTimestamp(sessionId);\n const rows = this.sql`\n SELECT * FROM assistant_messages WHERE id = ${id}\n ` as unknown as StoredMessage[];\n return rows[0];\n }\n\n /**\n * Insert or update a message. Uses INSERT ... ON CONFLICT to update\n * the content if the message already exists (same id). This enables\n * incremental persistence — first call inserts, subsequent calls update.\n */\n upsertMessage(\n id: string,\n sessionId: string,\n parentId: string | null,\n message: UIMessage\n ): StoredMessage {\n const content = JSON.stringify(message);\n this.sql`\n INSERT INTO assistant_messages (id, session_id, parent_id, role, content)\n VALUES (${id}, ${sessionId}, ${parentId}, ${message.role}, ${content})\n ON CONFLICT(id) DO UPDATE SET content = ${content}\n `;\n this.updateSessionTimestamp(sessionId);\n const rows = this.sql`\n SELECT * FROM assistant_messages WHERE id = ${id}\n ` as unknown as StoredMessage[];\n return rows[0];\n }\n\n /**\n * Delete a single message by ID.\n * In a tree structure, children of the deleted message retain their\n * parent_id (now pointing to a missing row), which naturally truncates\n * the path when walking via the recursive CTE.\n */\n deleteMessage(id: string): void {\n this.sql`DELETE FROM assistant_messages WHERE id = ${id}`;\n }\n\n /**\n * Delete multiple messages by ID in a single query.\n */\n deleteMessages(ids: string[]): void {\n if (ids.length === 0) return;\n if (this.exec) {\n const placeholders = ids.map(() => \"?\").join(\", \");\n this.exec(\n `DELETE FROM assistant_messages WHERE id IN (${placeholders})`,\n ...ids\n );\n } else {\n for (const id of ids) {\n this.sql`DELETE FROM assistant_messages WHERE id = ${id}`;\n }\n }\n }\n\n getMessage(id: string): StoredMessage | null {\n const rows = this.sql`\n SELECT * FROM assistant_messages WHERE id = ${id}\n ` as unknown as StoredMessage[];\n return rows[0] ?? null;\n }\n\n /**\n * Get all messages for a session, ordered by creation time.\n * This returns the flat list — use getMessagePath for a branch path.\n */\n getSessionMessages(sessionId: string): StoredMessage[] {\n return this.sql`\n SELECT * FROM assistant_messages\n WHERE session_id = ${sessionId}\n ORDER BY created_at ASC\n ` as unknown as StoredMessage[];\n }\n\n /**\n * Walk from a leaf message back to the root via a recursive CTE,\n * returning messages in chronological order (root first).\n * Uses a depth counter for ordering since created_at may be\n * identical for messages inserted in quick succession.\n */\n getMessagePath(leafId: string): StoredMessage[] {\n return this.sql`\n WITH RECURSIVE path AS (\n SELECT *, 0 as depth FROM assistant_messages WHERE id = ${leafId}\n UNION ALL\n SELECT m.*, p.depth + 1 FROM assistant_messages m\n JOIN path p ON m.id = p.parent_id\n )\n SELECT id, session_id, parent_id, role, content, created_at\n FROM path ORDER BY depth DESC\n ` as unknown as StoredMessage[];\n }\n\n /**\n * Count the number of messages on the path from root to leaf.\n * Used by needsCompaction to avoid loading full message content.\n */\n getPathLength(leafId: string): number {\n const rows = this.sql`\n WITH RECURSIVE path AS (\n SELECT id, parent_id FROM assistant_messages WHERE id = ${leafId}\n UNION ALL\n SELECT m.id, m.parent_id FROM assistant_messages m\n JOIN path p ON m.id = p.parent_id\n )\n SELECT COUNT(*) as count FROM path\n ` as unknown as Array<{ count: number }>;\n return rows[0]?.count ?? 0;\n }\n\n /**\n * Get children of a message (for branch exploration).\n */\n getChildren(parentId: string): StoredMessage[] {\n return this.sql`\n SELECT * FROM assistant_messages\n WHERE parent_id = ${parentId}\n ORDER BY created_at ASC\n ` as unknown as StoredMessage[];\n }\n\n /**\n * Get the latest leaf message in a session (most recent message\n * that has no children). Used to find the \"current\" position.\n */\n getLatestLeaf(sessionId: string): StoredMessage | null {\n const rows = this.sql`\n SELECT m.* FROM assistant_messages m\n LEFT JOIN assistant_messages c ON c.parent_id = m.id\n WHERE m.session_id = ${sessionId} AND c.id IS NULL\n ORDER BY m.created_at DESC\n LIMIT 1\n ` as unknown as StoredMessage[];\n return rows[0] ?? null;\n }\n\n /**\n * Count all messages in a session (across all branches).\n */\n getMessageCount(sessionId: string): number {\n const rows = this.sql`\n SELECT COUNT(*) as count FROM assistant_messages\n WHERE session_id = ${sessionId}\n ` as unknown as Array<{ count: number }>;\n return rows[0]?.count ?? 0;\n }\n\n // ── Compactions ────────────────────────────────────────────────\n\n addCompaction(\n id: string,\n sessionId: string,\n summary: string,\n fromMessageId: string,\n toMessageId: string\n ): Compaction {\n this.sql`\n INSERT INTO assistant_compactions (id, session_id, summary, from_message_id, to_message_id)\n VALUES (${id}, ${sessionId}, ${summary}, ${fromMessageId}, ${toMessageId})\n `;\n const rows = this.sql`\n SELECT * FROM assistant_compactions WHERE id = ${id}\n ` as unknown as Compaction[];\n return rows[0];\n }\n\n getCompactions(sessionId: string): Compaction[] {\n return this.sql`\n SELECT * FROM assistant_compactions\n WHERE session_id = ${sessionId}\n ORDER BY created_at ASC\n ` as unknown as Compaction[];\n }\n\n /**\n * Parse a stored message's content field back into a UIMessage.\n */\n parseMessage(stored: StoredMessage): UIMessage {\n return JSON.parse(stored.content) as UIMessage;\n }\n}\n","/**\n * SessionManager — persistent conversation state with branching and compaction.\n *\n * Provides:\n * - Multiple named sessions (conversations)\n * - Tree-structured messages (parent_id for branching)\n * - History retrieval following a branch path\n * - Compaction (summarize old messages to save context)\n * - Compatible with AI SDK's UIMessage type\n *\n * Usage:\n * const sessions = new SessionManager(agent);\n * const session = sessions.create(\"my-chat\");\n * sessions.append(session.id, { id: \"msg1\", role: \"user\", parts: [...] });\n * const history = sessions.getHistory(session.id); // UIMessage[]\n */\nimport type { UIMessage } from \"ai\";\nimport { SessionStorage } from \"./storage\";\nimport type { Session, Compaction } from \"./storage\";\n\nexport type { Session, Compaction } from \"./storage\";\n\n// ── Truncation utilities ──────────────────────────────────────────\n\nconst DEFAULT_MAX_CHARS = 30_000;\nconst ELLIPSIS = \"\\n\\n... [truncated] ...\\n\\n\";\n\n/**\n * Truncate from the head (keep the end of the content).\n */\nexport function truncateHead(\n text: string,\n maxChars: number = DEFAULT_MAX_CHARS\n): string {\n if (text.length <= maxChars) return text;\n const keep = maxChars - ELLIPSIS.length;\n if (keep <= 0) return text.slice(-maxChars);\n return ELLIPSIS + text.slice(-keep);\n}\n\n/**\n * Truncate from the tail (keep the start of the content).\n */\nexport function truncateTail(\n text: string,\n maxChars: number = DEFAULT_MAX_CHARS\n): string {\n if (text.length <= maxChars) return text;\n const keep = maxChars - ELLIPSIS.length;\n if (keep <= 0) return text.slice(0, maxChars);\n return text.slice(0, keep) + ELLIPSIS;\n}\n\n/**\n * Truncate by line count (keep the first N lines).\n */\nexport function truncateLines(text: string, maxLines: number = 200): string {\n const lines = text.split(\"\\n\");\n if (lines.length <= maxLines) return text;\n const kept = lines.slice(0, maxLines).join(\"\\n\");\n const omitted = lines.length - maxLines;\n return kept + `\\n\\n... [${omitted} more lines truncated] ...`;\n}\n\n/**\n * Truncate from both ends, keeping the start and end.\n */\nexport function truncateMiddle(\n text: string,\n maxChars: number = DEFAULT_MAX_CHARS\n): string {\n if (text.length <= maxChars) return text;\n const halfKeep = Math.floor((maxChars - ELLIPSIS.length) / 2);\n if (halfKeep <= 0) return text.slice(0, maxChars);\n return text.slice(0, halfKeep) + ELLIPSIS + text.slice(-halfKeep);\n}\n\n/**\n * Smart truncation for tool output.\n */\nexport function truncateToolOutput(\n output: string,\n options: {\n maxChars?: number;\n maxLines?: number;\n strategy?: \"head\" | \"tail\" | \"middle\";\n } = {}\n): string {\n const {\n maxChars = DEFAULT_MAX_CHARS,\n maxLines = 500,\n strategy = \"tail\"\n } = options;\n\n let result = truncateLines(output, maxLines);\n\n if (result.length > maxChars) {\n switch (strategy) {\n case \"head\":\n result = truncateHead(result, maxChars);\n break;\n case \"middle\":\n result = truncateMiddle(result, maxChars);\n break;\n case \"tail\":\n default:\n result = truncateTail(result, maxChars);\n break;\n }\n }\n\n return result;\n}\n\n// Mirrors Agent.sql — kept structural to avoid importing the 4k-line Agent class.\ninterface AgentLike {\n sql: (\n strings: TemplateStringsArray,\n ...values: (string | number | boolean | null)[]\n ) => Array<Record<string, unknown>>;\n}\n\nexport interface SessionManagerOptions {\n /**\n * Maximum number of messages on the current branch before\n * needsCompaction() returns true. Default: 100.\n */\n maxContextMessages?: number;\n\n /**\n * Raw SQL exec function for batch operations (e.g. DELETE ... WHERE id IN (...)).\n * When provided, batch deletes use a single query instead of N individual ones.\n *\n * Typically: `(query, ...values) => { agent.ctx.storage.sql.exec(query, ...values); }`\n */\n exec?: (\n query: string,\n ...values: (string | number | boolean | null)[]\n ) => void;\n}\n\nexport class SessionManager {\n private _storage: SessionStorage;\n private _options: SessionManagerOptions;\n\n constructor(agent: AgentLike, options: SessionManagerOptions = {}) {\n this._storage = new SessionStorage(agent.sql.bind(agent), options.exec);\n this._options = {\n maxContextMessages: 100,\n ...options\n };\n }\n\n // ── Session lifecycle ──────────────────────────────────────────\n\n /**\n * Create a new session with a name.\n */\n create(name: string): Session {\n return this._storage.createSession(crypto.randomUUID(), name);\n }\n\n /**\n * Get a session by ID.\n */\n get(sessionId: string): Session | null {\n return this._storage.getSession(sessionId);\n }\n\n /**\n * List all sessions, most recently updated first.\n */\n list(): Session[] {\n return this._storage.listSessions();\n }\n\n /**\n * Delete a session and all its messages and compactions.\n */\n delete(sessionId: string): void {\n this._storage.deleteSession(sessionId);\n }\n\n /**\n * Clear all messages and compactions for a session without\n * deleting the session itself.\n */\n clearMessages(sessionId: string): void {\n this._storage.clearSessionMessages(sessionId);\n }\n\n /**\n * Rename a session.\n */\n rename(sessionId: string, name: string): void {\n this._storage.renameSession(sessionId, name);\n }\n\n // ── Messages ───────────────────────────────────────────────────\n\n /**\n * Append a message to a session. If parentId is not provided,\n * the message is appended after the latest leaf.\n *\n * Idempotent — appending the same message.id twice is a no-op.\n *\n * Returns the stored message ID.\n */\n append(sessionId: string, message: UIMessage, parentId?: string): string {\n const resolvedParent =\n parentId ?? this._storage.getLatestLeaf(sessionId)?.id ?? null;\n\n const id = message.id || crypto.randomUUID();\n this._storage.appendMessage(id, sessionId, resolvedParent, message);\n return id;\n }\n\n /**\n * Insert or update a message. First call inserts, subsequent calls\n * update the content. Enables incremental persistence.\n *\n * Idempotent on insert, content-updating on subsequent calls.\n */\n upsert(sessionId: string, message: UIMessage, parentId?: string): string {\n const resolvedParent =\n parentId ?? this._storage.getLatestLeaf(sessionId)?.id ?? null;\n const id = message.id || crypto.randomUUID();\n this._storage.upsertMessage(id, sessionId, resolvedParent, message);\n return id;\n }\n\n /**\n * Delete a single message by ID.\n * Children of the deleted message naturally become path roots\n * (their parent_id points to a missing row, truncating the CTE walk).\n */\n deleteMessage(messageId: string): void {\n this._storage.deleteMessage(messageId);\n }\n\n /**\n * Delete multiple messages by ID.\n */\n deleteMessages(messageIds: string[]): void {\n this._storage.deleteMessages(messageIds);\n }\n\n /**\n * Append multiple messages in sequence (each parented to the previous).\n * Returns the ID of the last appended message.\n */\n appendAll(\n sessionId: string,\n messages: UIMessage[],\n parentId?: string\n ): string | null {\n let lastId = parentId ?? null;\n for (const msg of messages) {\n const resolvedParent =\n lastId ?? this._storage.getLatestLeaf(sessionId)?.id ?? null;\n const id = msg.id || crypto.randomUUID();\n this._storage.appendMessage(id, sessionId, resolvedParent, msg);\n lastId = id;\n }\n return lastId;\n }\n\n /**\n * Get the conversation history for a session as UIMessage[].\n *\n * If leafId is provided, returns the path from root to that leaf\n * (a specific branch). Otherwise returns the path to the most\n * recent leaf (the \"current\" branch).\n *\n * If compactions exist, older messages covered by a compaction\n * are replaced with a system message containing the summary.\n */\n getHistory(sessionId: string, leafId?: string): UIMessage[] {\n const leaf = leafId\n ? this._storage.getMessage(leafId)\n : this._storage.getLatestLeaf(sessionId);\n\n if (!leaf) return [];\n\n const storedPath = this._storage.getMessagePath(leaf.id);\n const compactions = this._storage.getCompactions(sessionId);\n\n if (compactions.length === 0) {\n return storedPath.map((m) => this._storage.parseMessage(m));\n }\n\n return this._applyCompactions(storedPath, compactions);\n }\n\n /**\n * Get the total message count for a session (across all branches).\n */\n getMessageCount(sessionId: string): number {\n return this._storage.getMessageCount(sessionId);\n }\n\n /**\n * Check if the session's current branch needs compaction.\n * Uses a count-only query — does not load message content.\n */\n needsCompaction(sessionId: string): boolean {\n const leaf = this._storage.getLatestLeaf(sessionId);\n if (!leaf) return false;\n const pathLen = this._storage.getPathLength(leaf.id);\n return pathLen > (this._options.maxContextMessages ?? 100);\n }\n\n // ── Branching ──────────────────────────────────────────────────\n\n /**\n * Get the children of a message (branches from that point).\n */\n getBranches(messageId: string): UIMessage[] {\n const children = this._storage.getChildren(messageId);\n return children.map((m) => this._storage.parseMessage(m));\n }\n\n /**\n * Fork a session at a specific message, creating a new session\n * with the history up to that point copied over.\n */\n fork(atMessageId: string, newName: string): Session {\n const newSession = this.create(newName);\n const path = this._storage.getMessagePath(atMessageId);\n\n let parentId: string | null = null;\n for (const stored of path) {\n const msg = this._storage.parseMessage(stored);\n const newId = crypto.randomUUID();\n this._storage.appendMessage(newId, newSession.id, parentId, msg);\n parentId = newId;\n }\n\n return newSession;\n }\n\n // ── Compaction ─────────────────────────────────────────────────\n\n /**\n * Add a compaction record. The summary replaces messages from\n * fromMessageId to toMessageId in context assembly.\n *\n * Typically called after using an LLM to summarize older messages.\n */\n addCompaction(\n sessionId: string,\n summary: string,\n fromMessageId: string,\n toMessageId: string\n ): Compaction {\n return this._storage.addCompaction(\n crypto.randomUUID(),\n sessionId,\n summary,\n fromMessageId,\n toMessageId\n );\n }\n\n /**\n * Get all compaction records for a session.\n */\n getCompactions(sessionId: string): Compaction[] {\n return this._storage.getCompactions(sessionId);\n }\n\n // ── Internal ───────────────────────────────────────────────────\n\n private _applyCompactions(\n path: Array<{ id: string; content: string }>,\n compactions: Compaction[]\n ): UIMessage[] {\n const pathIds = path.map((m) => m.id);\n const result: UIMessage[] = [];\n let i = 0;\n\n while (i < path.length) {\n // Check if any compaction starts at this message\n const compaction = compactions.find(\n (c) => c.from_message_id === pathIds[i]\n );\n\n if (compaction) {\n // Only apply if the compaction's end is also on this path\n const endIdx = pathIds.indexOf(compaction.to_message_id);\n if (endIdx >= i) {\n result.push({\n id: `compaction_${compaction.id}`,\n role: \"system\",\n parts: [\n {\n type: \"text\",\n text: `[Previous conversation summary]\\n${compaction.summary}`\n }\n ]\n });\n i = endIdx + 1;\n } else {\n // Compaction doesn't span this path — skip it, emit message as-is\n result.push(JSON.parse(path[i].content) as UIMessage);\n i++;\n }\n } else {\n result.push(JSON.parse(path[i].content) as UIMessage);\n i++;\n }\n }\n\n return result;\n }\n}\n"],"mappings":"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"}