agents 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,385 @@
1
+ //#region src/experimental/memory/utils/compaction.ts
2
+ /** Default thresholds for microCompaction rules (in chars) */
3
+ const DEFAULTS = {
4
+ truncateToolOutputs: 3e4,
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
+ }
31
+ /**
32
+ * Truncate oversized parts in a single message.
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).
71
+ *
72
+ * No keepRecent logic — the caller decides which messages to pass.
73
+ */
74
+ function microCompact(messages, rules) {
75
+ return messages.map((msg) => truncateMessageParts(msg, rules));
76
+ }
77
+
78
+ //#endregion
79
+ //#region src/experimental/memory/utils/tokens.ts
80
+ /** Approximate characters per token for English text */
81
+ const CHARS_PER_TOKEN = 4;
82
+ /** Approximate token multiplier per whitespace-separated word */
83
+ const WORDS_TOKEN_MULTIPLIER = 1.3;
84
+ /** Approximate overhead tokens per message (role, framing) */
85
+ const TOKENS_PER_MESSAGE = 4;
86
+ /**
87
+ * Estimate token count for a string using a hybrid heuristic.
88
+ *
89
+ * Takes the max of two estimates:
90
+ * - Character-based: `length / 4` — better for dense content (JSON, code, URLs)
91
+ * - Word-based: `words * 1.3` — better for natural language prose
92
+ *
93
+ * This is a heuristic. Do not use where exact counts are required.
94
+ */
95
+ function estimateStringTokens(text) {
96
+ if (!text) return 0;
97
+ const charEstimate = text.length / CHARS_PER_TOKEN;
98
+ const wordEstimate = text.split(/\s+/).filter(Boolean).length * WORDS_TOKEN_MULTIPLIER;
99
+ return Math.ceil(Math.max(charEstimate, wordEstimate));
100
+ }
101
+ /**
102
+ * Estimate total token count for an array of UIMessages.
103
+ *
104
+ * Walks each message's parts (text, tool invocations, tool results)
105
+ * and applies per-message overhead.
106
+ *
107
+ * This is a heuristic. Do not use where exact counts are required.
108
+ */
109
+ function estimateMessageTokens(messages) {
110
+ let tokens = 0;
111
+ for (const msg of messages) {
112
+ tokens += TOKENS_PER_MESSAGE;
113
+ for (const part of msg.parts) if (part.type === "text") tokens += estimateStringTokens(part.text);
114
+ else if (part.type.startsWith("tool-") || part.type === "dynamic-tool") {
115
+ const toolPart = part;
116
+ if (toolPart.input) tokens += estimateStringTokens(JSON.stringify(toolPart.input));
117
+ if (toolPart.output) tokens += estimateStringTokens(JSON.stringify(toolPart.output));
118
+ }
119
+ }
120
+ return tokens;
121
+ }
122
+
123
+ //#endregion
124
+ //#region src/experimental/memory/session/session.ts
125
+ var Session = class {
126
+ constructor(storage, options) {
127
+ this.storage = storage;
128
+ this.microCompactionRules = parseMicroCompactionRules(options?.microCompaction ?? true);
129
+ this.compactionConfig = options?.compaction ?? null;
130
+ }
131
+ getMessages(options) {
132
+ return this.storage.getMessages(options);
133
+ }
134
+ getMessage(id) {
135
+ return this.storage.getMessage(id);
136
+ }
137
+ getLastMessages(n) {
138
+ return this.storage.getLastMessages(n);
139
+ }
140
+ async append(messages) {
141
+ await this.storage.appendMessages(messages);
142
+ if (this.shouldAutoCompact()) {
143
+ if ((await this.compact()).success) return;
144
+ }
145
+ if (this.microCompactionRules) {
146
+ const rules = this.microCompactionRules;
147
+ const older = this.storage.getOlderMessages(rules.keepRecent);
148
+ if (older.length > 0) {
149
+ const compacted = microCompact(older, rules);
150
+ for (let i = 0; i < older.length; i++) if (compacted[i] !== older[i]) this.storage.updateMessage(compacted[i]);
151
+ }
152
+ }
153
+ }
154
+ updateMessage(message) {
155
+ this.storage.updateMessage(message);
156
+ }
157
+ deleteMessages(messageIds) {
158
+ this.storage.deleteMessages(messageIds);
159
+ }
160
+ clearMessages() {
161
+ this.storage.clearMessages();
162
+ }
163
+ async compact() {
164
+ const messages = this.storage.getMessages();
165
+ if (messages.length === 0) return { success: true };
166
+ try {
167
+ let result = messages;
168
+ if (this.compactionConfig?.fn) result = await this.compactionConfig.fn(result);
169
+ await this.storage.replaceMessages(result);
170
+ return { success: true };
171
+ } catch (err) {
172
+ return {
173
+ success: false,
174
+ error: err instanceof Error ? err.message : String(err)
175
+ };
176
+ }
177
+ }
178
+ /**
179
+ * Pre-check for auto-compaction using token estimate heuristic.
180
+ */
181
+ shouldAutoCompact() {
182
+ if (!this.compactionConfig?.tokenThreshold) return false;
183
+ return estimateMessageTokens(this.storage.getMessages()) > this.compactionConfig.tokenThreshold;
184
+ }
185
+ };
186
+
187
+ //#endregion
188
+ //#region src/experimental/memory/session/providers/agent.ts
189
+ /**
190
+ * Session provider that wraps an Agent's SQLite storage.
191
+ * Provides pure CRUD — compaction is handled by the Session wrapper.
192
+ *
193
+ * @example
194
+ * ```typescript
195
+ * import { Session, AgentSessionProvider } from "agents/experimental/memory/session";
196
+ *
197
+ * // In your Agent class:
198
+ * session = new Session(new AgentSessionProvider(this));
199
+ *
200
+ * // With compaction options:
201
+ * session = new Session(new AgentSessionProvider(this), {
202
+ * microCompaction: { truncateToolOutputs: 2000, keepRecent: 10 },
203
+ * compaction: { tokenThreshold: 20000, fn: summarize }
204
+ * });
205
+ * ```
206
+ */
207
+ var AgentSessionProvider = class {
208
+ constructor(agent) {
209
+ this.initialized = false;
210
+ this.agent = agent;
211
+ }
212
+ /**
213
+ * Ensure the messages table exists
214
+ */
215
+ ensureTable() {
216
+ if (this.initialized) return;
217
+ this.agent.sql`
218
+ CREATE TABLE IF NOT EXISTS cf_agents_session_messages (
219
+ id TEXT PRIMARY KEY,
220
+ message TEXT NOT NULL,
221
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
222
+ )
223
+ `;
224
+ this.initialized = true;
225
+ }
226
+ /**
227
+ * Get all messages in AI SDK format
228
+ */
229
+ getMessages(options) {
230
+ this.ensureTable();
231
+ if (options?.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 0)) throw new Error("limit must be a non-negative integer");
232
+ if (options?.offset !== void 0 && (!Number.isInteger(options.offset) || options.offset < 0)) throw new Error("offset must be a non-negative integer");
233
+ const role = options?.role ?? null;
234
+ const before = options?.before?.toISOString() ?? null;
235
+ const after = options?.after?.toISOString() ?? null;
236
+ const limit = options?.limit ?? -1;
237
+ const offset = options?.offset ?? 0;
238
+ const rows = this.agent.sql`
239
+ SELECT id, message, created_at FROM cf_agents_session_messages
240
+ WHERE (${role} IS NULL OR json_extract(message, '$.role') = ${role})
241
+ AND (${before} IS NULL OR created_at < ${before})
242
+ AND (${after} IS NULL OR created_at > ${after})
243
+ ORDER BY created_at ASC, rowid ASC
244
+ LIMIT ${limit} OFFSET ${offset}
245
+ `;
246
+ return this.parseRows(rows);
247
+ }
248
+ /**
249
+ * Append one or more messages to storage.
250
+ */
251
+ async appendMessages(messages) {
252
+ this.ensureTable();
253
+ const messageArray = Array.isArray(messages) ? messages : [messages];
254
+ const now = (/* @__PURE__ */ new Date()).toISOString();
255
+ for (const message of messageArray) {
256
+ const json = JSON.stringify(message);
257
+ this.agent.sql`
258
+ INSERT INTO cf_agents_session_messages (id, message, created_at)
259
+ VALUES (${message.id}, ${json}, ${now})
260
+ ON CONFLICT(id) DO UPDATE SET message = excluded.message
261
+ `;
262
+ }
263
+ }
264
+ /**
265
+ * Update an existing message
266
+ */
267
+ updateMessage(message) {
268
+ this.ensureTable();
269
+ const json = JSON.stringify(message);
270
+ this.agent.sql`
271
+ UPDATE cf_agents_session_messages
272
+ SET message = ${json}
273
+ WHERE id = ${message.id}
274
+ `;
275
+ }
276
+ /**
277
+ * Delete messages by their IDs
278
+ */
279
+ deleteMessages(messageIds) {
280
+ this.ensureTable();
281
+ for (const id of messageIds) this.agent.sql`DELETE FROM cf_agents_session_messages WHERE id = ${id}`;
282
+ }
283
+ /**
284
+ * Clear all messages from the session
285
+ */
286
+ clearMessages() {
287
+ this.ensureTable();
288
+ this.agent.sql`DELETE FROM cf_agents_session_messages`;
289
+ }
290
+ /**
291
+ * Get a single message by ID
292
+ */
293
+ getMessage(id) {
294
+ this.ensureTable();
295
+ const rows = this.agent.sql`
296
+ SELECT message FROM cf_agents_session_messages WHERE id = ${id}
297
+ `;
298
+ if (rows.length === 0) return null;
299
+ try {
300
+ const parsed = JSON.parse(rows[0].message);
301
+ return this.isValidMessage(parsed) ? parsed : null;
302
+ } catch {
303
+ return null;
304
+ }
305
+ }
306
+ /**
307
+ * Get the last N messages (most recent)
308
+ */
309
+ getLastMessages(n) {
310
+ this.ensureTable();
311
+ const rows = this.agent.sql`
312
+ SELECT message FROM cf_agents_session_messages
313
+ ORDER BY created_at DESC, rowid DESC
314
+ LIMIT ${n}
315
+ `;
316
+ return this.parseRows([...rows].reverse());
317
+ }
318
+ /**
319
+ * Fetch messages outside the recent window (for microCompaction).
320
+ * Returns all messages except the most recent `keepRecent`.
321
+ */
322
+ getOlderMessages(keepRecent) {
323
+ this.ensureTable();
324
+ const rows = this.agent.sql`
325
+ SELECT id, message FROM cf_agents_session_messages
326
+ WHERE rowid NOT IN (
327
+ SELECT rowid FROM cf_agents_session_messages
328
+ ORDER BY created_at DESC, rowid DESC
329
+ LIMIT ${keepRecent}
330
+ )
331
+ `;
332
+ return this.parseRows(rows);
333
+ }
334
+ /**
335
+ * Bulk replace all messages.
336
+ * Preserves original created_at timestamps for surviving messages.
337
+ */
338
+ async replaceMessages(messages) {
339
+ this.ensureTable();
340
+ const existingRows = this.agent.sql`
341
+ SELECT id, created_at FROM cf_agents_session_messages
342
+ `;
343
+ const timestampMap = /* @__PURE__ */ new Map();
344
+ for (const row of existingRows) timestampMap.set(row.id, row.created_at);
345
+ this.agent.sql`DELETE FROM cf_agents_session_messages`;
346
+ const now = (/* @__PURE__ */ new Date()).toISOString();
347
+ for (const message of messages) {
348
+ const json = JSON.stringify(message);
349
+ const created_at = timestampMap.get(message.id) ?? now;
350
+ this.agent.sql`
351
+ INSERT INTO cf_agents_session_messages (id, message, created_at)
352
+ VALUES (${message.id}, ${json}, ${created_at})
353
+ ON CONFLICT(id) DO UPDATE SET message = excluded.message
354
+ `;
355
+ }
356
+ }
357
+ /**
358
+ * Validate message structure
359
+ */
360
+ isValidMessage(msg) {
361
+ if (typeof msg !== "object" || msg === null) return false;
362
+ const m = msg;
363
+ if (typeof m.id !== "string" || m.id.length === 0) return false;
364
+ if (m.role !== "user" && m.role !== "assistant" && m.role !== "system") return false;
365
+ if (!Array.isArray(m.parts)) return false;
366
+ return true;
367
+ }
368
+ /**
369
+ * Parse message rows from SQL results into UIMessages.
370
+ */
371
+ parseRows(rows) {
372
+ const messages = [];
373
+ for (const row of rows) try {
374
+ const parsed = JSON.parse(row.message);
375
+ if (this.isValidMessage(parsed)) messages.push(parsed);
376
+ } catch {
377
+ if (row.id) console.warn(`[AgentSessionProvider] Skipping malformed message ${row.id}`);
378
+ }
379
+ return messages;
380
+ }
381
+ };
382
+
383
+ //#endregion
384
+ export { AgentSessionProvider, Session };
385
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/experimental/memory/utils/compaction.ts","../../../../src/experimental/memory/utils/tokens.ts","../../../../src/experimental/memory/session/session.ts","../../../../src/experimental/memory/session/providers/agent.ts"],"sourcesContent":["/**\n * MicroCompaction Utilities\n *\n * Internal pure functions for lightweight compaction (no LLM).\n * Not exported to users — used by the Session wrapper.\n */\n\nimport type { UIMessage } from \"ai\";\nimport type { MicroCompactionRules } from \"../session/types\";\n\n/** Default thresholds for microCompaction rules (in chars) */\nexport const DEFAULTS = {\n truncateToolOutputs: 30000,\n truncateText: 10000,\n keepRecent: 4\n};\n\n/** Resolved microCompaction rules with actual numeric thresholds */\nexport interface ResolvedMicroCompactionRules {\n truncateToolOutputs: number | false;\n truncateText: number | false;\n keepRecent: number;\n}\n\n/**\n * Parse microCompaction config into resolved rules.\n * Returns null if disabled.\n */\nexport function parseMicroCompactionRules(\n config: boolean | MicroCompactionRules\n): ResolvedMicroCompactionRules | null {\n if (config === false) return null;\n\n if (config === true) {\n return {\n truncateToolOutputs: DEFAULTS.truncateToolOutputs,\n truncateText: DEFAULTS.truncateText,\n keepRecent: DEFAULTS.keepRecent\n };\n }\n\n // Custom rules object — validate numeric values\n const keepRecent = config.keepRecent ?? DEFAULTS.keepRecent;\n if (!Number.isInteger(keepRecent) || keepRecent < 0) {\n throw new Error(\"keepRecent must be a non-negative integer\");\n }\n\n const truncateToolOutputs =\n config.truncateToolOutputs === false\n ? false\n : config.truncateToolOutputs === true ||\n config.truncateToolOutputs === undefined\n ? DEFAULTS.truncateToolOutputs\n : config.truncateToolOutputs;\n if (typeof truncateToolOutputs === \"number\" && truncateToolOutputs <= 0) {\n throw new Error(\"truncateToolOutputs must be a positive number\");\n }\n\n const truncateText =\n config.truncateText === false\n ? false\n : config.truncateText === true || config.truncateText === undefined\n ? DEFAULTS.truncateText\n : config.truncateText;\n if (typeof truncateText === \"number\" && truncateText <= 0) {\n throw new Error(\"truncateText must be a positive number\");\n }\n\n return { truncateToolOutputs, truncateText, keepRecent };\n}\n\n/**\n * Truncate oversized parts in a single message.\n * Returns the same reference if nothing changed (allows callers to skip no-op updates).\n */\nfunction truncateMessageParts(\n msg: UIMessage,\n rules: ResolvedMicroCompactionRules\n): UIMessage {\n let changed = false;\n\n const compactedParts = msg.parts.map((part) => {\n // Truncate tool outputs\n if (\n rules.truncateToolOutputs !== false &&\n (part.type.startsWith(\"tool-\") || part.type === \"dynamic-tool\") &&\n \"output\" in part\n ) {\n const toolPart = part as { output?: unknown };\n if (toolPart.output !== undefined) {\n const outputJson = JSON.stringify(toolPart.output);\n if (outputJson.length > rules.truncateToolOutputs) {\n changed = true;\n return {\n ...part,\n output: `[Truncated ${outputJson.length} bytes] ${outputJson.slice(0, 500)}...`\n };\n }\n }\n }\n\n // Truncate long text parts\n if (\n rules.truncateText !== false &&\n part.type === \"text\" &&\n \"text\" in part\n ) {\n const textPart = part as { type: \"text\"; text: string };\n if (textPart.text.length > rules.truncateText) {\n changed = true;\n return {\n ...part,\n text: `${textPart.text.slice(0, rules.truncateText)}... [truncated ${textPart.text.length} chars]`\n };\n }\n }\n\n return part;\n });\n\n return changed ? ({ ...msg, parts: compactedParts } as UIMessage) : msg;\n}\n\n/**\n * Apply microCompaction to an array of messages.\n * Returns same reference for unchanged messages (enables skip-update optimization).\n *\n * No keepRecent logic — the caller decides which messages to pass.\n */\nexport function microCompact(\n messages: UIMessage[],\n rules: ResolvedMicroCompactionRules\n): UIMessage[] {\n return messages.map((msg) => truncateMessageParts(msg, rules));\n}\n","/**\n * Token Estimation Utilities\n *\n * IMPORTANT: These are heuristic estimates, not actual tokenizer counts.\n *\n * We intentionally avoid real tokenizers (e.g. tiktoken, sentencepiece) because:\n * - A single tiktoken instance costs ~80-120MB of heap\n * - Cloudflare Workers have tight memory limits (128MB)\n * - For compaction thresholds, a conservative estimate is sufficient\n *\n * The hybrid approach (max of character-based and word-based estimates) handles\n * both dense token content (JSON, code) and natural language reasonably well.\n *\n * Calibration notes:\n * - Character-based: ~4 chars per token (conservative, from OpenAI guidance)\n * - Word-based: ~1.3 tokens per word (empirical, from Mastra's memory system)\n * - Per-message overhead: ~4 tokens for role/framing (empirical)\n *\n * These ratios are tuned for English. CJK, emoji-heavy, or highly technical\n * content may have different ratios. The conservative estimates help ensure\n * compaction triggers before context windows are actually exceeded.\n */\n\nimport type { UIMessage } from \"ai\";\n\n/** Approximate characters per token for English text */\nexport const CHARS_PER_TOKEN = 4;\n\n/** Approximate token multiplier per whitespace-separated word */\nexport const WORDS_TOKEN_MULTIPLIER = 1.3;\n\n/** Approximate overhead tokens per message (role, framing) */\nexport const TOKENS_PER_MESSAGE = 4;\n\n/**\n * Estimate token count for a string using a hybrid heuristic.\n *\n * Takes the max of two estimates:\n * - Character-based: `length / 4` — better for dense content (JSON, code, URLs)\n * - Word-based: `words * 1.3` — better for natural language prose\n *\n * This is a heuristic. Do not use where exact counts are required.\n */\nexport function estimateStringTokens(text: string): number {\n if (!text) return 0;\n const charEstimate = text.length / CHARS_PER_TOKEN;\n const wordEstimate =\n text.split(/\\s+/).filter(Boolean).length * WORDS_TOKEN_MULTIPLIER;\n return Math.ceil(Math.max(charEstimate, wordEstimate));\n}\n\n/**\n * Estimate total token count for an array of UIMessages.\n *\n * Walks each message's parts (text, tool invocations, tool results)\n * and applies per-message overhead.\n *\n * This is a heuristic. Do not use where exact counts are required.\n */\nexport function estimateMessageTokens(messages: UIMessage[]): number {\n let tokens = 0;\n for (const msg of messages) {\n tokens += TOKENS_PER_MESSAGE;\n for (const part of msg.parts) {\n if (part.type === \"text\") {\n tokens += estimateStringTokens(\n (part as { type: \"text\"; text: string }).text\n );\n } else if (\n part.type.startsWith(\"tool-\") ||\n part.type === \"dynamic-tool\"\n ) {\n const toolPart = part as { input?: unknown; output?: unknown };\n if (toolPart.input) {\n tokens += estimateStringTokens(JSON.stringify(toolPart.input));\n }\n if (toolPart.output) {\n tokens += estimateStringTokens(JSON.stringify(toolPart.output));\n }\n }\n }\n }\n return tokens;\n}\n","/**\n * Session — top-level API for conversation history with compaction.\n *\n * Wraps any SessionProvider (pure storage) and orchestrates compaction:\n * - microCompaction on every append() — cheap, no LLM\n * - full compaction when token threshold exceeded — user-supplied fn\n */\n\nimport type { UIMessage } from \"ai\";\nimport type { SessionProvider } from \"./provider\";\nimport type {\n MessageQueryOptions,\n SessionProviderOptions,\n CompactResult\n} from \"./types\";\nimport {\n parseMicroCompactionRules,\n microCompact,\n type ResolvedMicroCompactionRules\n} from \"../utils/compaction\";\nimport { estimateMessageTokens } from \"../utils/tokens\";\n\nexport class Session {\n private storage: SessionProvider;\n private microCompactionRules: ResolvedMicroCompactionRules | null;\n private compactionConfig: SessionProviderOptions[\"compaction\"] | null;\n\n constructor(storage: SessionProvider, options?: SessionProviderOptions) {\n this.storage = storage;\n\n const mc = options?.microCompaction ?? true;\n this.microCompactionRules = parseMicroCompactionRules(mc);\n this.compactionConfig = options?.compaction ?? null;\n }\n\n // ── Read (delegated to storage) ────────────────────────────────────\n\n getMessages(options?: MessageQueryOptions): UIMessage[] {\n return this.storage.getMessages(options);\n }\n\n getMessage(id: string): UIMessage | null {\n return this.storage.getMessage(id);\n }\n\n getLastMessages(n: number): UIMessage[] {\n return this.storage.getLastMessages(n);\n }\n\n // ── Write (delegated + compaction) ─────────────────────────────────\n\n async append(messages: UIMessage | UIMessage[]): Promise<void> {\n // 1. Storage inserts\n await this.storage.appendMessages(messages);\n\n // 2. Full compaction if token threshold exceeded — runs instead of microCompaction\n if (this.shouldAutoCompact()) {\n const result = await this.compact();\n if (result.success) return;\n // Fall through to microCompaction if full compaction failed\n }\n\n // 3. MicroCompaction on older messages (only if no full compaction)\n if (this.microCompactionRules) {\n const rules = this.microCompactionRules;\n const older = this.storage.getOlderMessages(rules.keepRecent);\n\n if (older.length > 0) {\n const compacted = microCompact(older, rules);\n for (let i = 0; i < older.length; i++) {\n if (compacted[i] !== older[i]) {\n this.storage.updateMessage(compacted[i]);\n }\n }\n }\n }\n }\n\n updateMessage(message: UIMessage): void {\n this.storage.updateMessage(message);\n }\n\n deleteMessages(messageIds: string[]): void {\n this.storage.deleteMessages(messageIds);\n }\n\n clearMessages(): void {\n this.storage.clearMessages();\n }\n\n // ── Compaction ─────────────────────────────────────────────────────\n\n async compact(): Promise<CompactResult> {\n const messages = this.storage.getMessages();\n\n if (messages.length === 0) {\n return { success: true };\n }\n\n try {\n let result = messages;\n\n if (this.compactionConfig?.fn) {\n result = await this.compactionConfig.fn(result);\n }\n\n await this.storage.replaceMessages(result);\n\n return { success: true };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err)\n };\n }\n }\n\n /**\n * Pre-check for auto-compaction using token estimate heuristic.\n */\n private shouldAutoCompact(): boolean {\n if (!this.compactionConfig?.tokenThreshold) return false;\n\n const messages = this.storage.getMessages();\n const approxTokens = estimateMessageTokens(messages);\n return approxTokens > this.compactionConfig.tokenThreshold;\n }\n}\n","/**\n * Agent Session Provider\n *\n * Pure storage provider that uses the Agent's DO SQLite storage.\n * Compaction is orchestrated by the Session wrapper, not here.\n */\n\nimport type { UIMessage } from \"ai\";\nimport type { SessionProvider } from \"../provider\";\nimport type { MessageQueryOptions } from \"../types\";\n\n/**\n * Interface for objects that provide a sql tagged template method.\n * This matches the Agent class's sql method signature.\n */\nexport interface SqlProvider {\n sql<T = Record<string, string | number | boolean | null>>(\n strings: TemplateStringsArray,\n ...values: (string | number | boolean | null)[]\n ): T[];\n}\n\n/**\n * Session provider that wraps an Agent's SQLite storage.\n * Provides pure CRUD — compaction is handled by the Session wrapper.\n *\n * @example\n * ```typescript\n * import { Session, AgentSessionProvider } from \"agents/experimental/memory/session\";\n *\n * // In your Agent class:\n * session = new Session(new AgentSessionProvider(this));\n *\n * // With compaction options:\n * session = new Session(new AgentSessionProvider(this), {\n * microCompaction: { truncateToolOutputs: 2000, keepRecent: 10 },\n * compaction: { tokenThreshold: 20000, fn: summarize }\n * });\n * ```\n */\nexport class AgentSessionProvider implements SessionProvider {\n private agent: SqlProvider;\n private initialized = false;\n\n constructor(agent: SqlProvider) {\n this.agent = agent;\n }\n\n /**\n * Ensure the messages table exists\n */\n private ensureTable(): void {\n if (this.initialized) return;\n\n this.agent.sql`\n CREATE TABLE IF NOT EXISTS cf_agents_session_messages (\n id TEXT PRIMARY KEY,\n message TEXT NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n )\n `;\n this.initialized = true;\n }\n\n /**\n * Get all messages in AI SDK format\n */\n getMessages(options?: MessageQueryOptions): UIMessage[] {\n this.ensureTable();\n\n if (\n options?.limit !== undefined &&\n (!Number.isInteger(options.limit) || options.limit < 0)\n ) {\n throw new Error(\"limit must be a non-negative integer\");\n }\n if (\n options?.offset !== undefined &&\n (!Number.isInteger(options.offset) || options.offset < 0)\n ) {\n throw new Error(\"offset must be a non-negative integer\");\n }\n\n type Row = { id: string; message: string; created_at: string };\n const role = options?.role ?? null;\n const before = options?.before?.toISOString() ?? null;\n const after = options?.after?.toISOString() ?? null;\n const limit = options?.limit ?? -1;\n const offset = options?.offset ?? 0;\n\n const rows = this.agent.sql<Row>`\n SELECT id, message, created_at FROM cf_agents_session_messages\n WHERE (${role} IS NULL OR json_extract(message, '$.role') = ${role})\n AND (${before} IS NULL OR created_at < ${before})\n AND (${after} IS NULL OR created_at > ${after})\n ORDER BY created_at ASC, rowid ASC\n LIMIT ${limit} OFFSET ${offset}\n `;\n\n return this.parseRows(rows);\n }\n\n /**\n * Append one or more messages to storage.\n */\n async appendMessages(messages: UIMessage | UIMessage[]): Promise<void> {\n this.ensureTable();\n\n const messageArray = Array.isArray(messages) ? messages : [messages];\n const now = new Date().toISOString();\n\n for (const message of messageArray) {\n const json = JSON.stringify(message);\n this.agent.sql`\n INSERT INTO cf_agents_session_messages (id, message, created_at)\n VALUES (${message.id}, ${json}, ${now})\n ON CONFLICT(id) DO UPDATE SET message = excluded.message\n `;\n }\n }\n\n /**\n * Update an existing message\n */\n updateMessage(message: UIMessage): void {\n this.ensureTable();\n\n const json = JSON.stringify(message);\n this.agent.sql`\n UPDATE cf_agents_session_messages\n SET message = ${json}\n WHERE id = ${message.id}\n `;\n }\n\n /**\n * Delete messages by their IDs\n */\n deleteMessages(messageIds: string[]): void {\n this.ensureTable();\n\n for (const id of messageIds) {\n this.agent.sql`DELETE FROM cf_agents_session_messages WHERE id = ${id}`;\n }\n }\n\n /**\n * Clear all messages from the session\n */\n clearMessages(): void {\n this.ensureTable();\n this.agent.sql`DELETE FROM cf_agents_session_messages`;\n }\n\n /**\n * Get a single message by ID\n */\n getMessage(id: string): UIMessage | null {\n this.ensureTable();\n\n const rows = this.agent.sql<{ message: string }>`\n SELECT message FROM cf_agents_session_messages WHERE id = ${id}\n `;\n\n if (rows.length === 0) return null;\n\n try {\n const parsed = JSON.parse(rows[0].message);\n return this.isValidMessage(parsed) ? parsed : null;\n } catch {\n return null;\n }\n }\n\n /**\n * Get the last N messages (most recent)\n */\n getLastMessages(n: number): UIMessage[] {\n this.ensureTable();\n\n const rows = this.agent.sql<{ message: string }>`\n SELECT message FROM cf_agents_session_messages\n ORDER BY created_at DESC, rowid DESC\n LIMIT ${n}\n `;\n\n return this.parseRows([...rows].reverse());\n }\n\n /**\n * Fetch messages outside the recent window (for microCompaction).\n * Returns all messages except the most recent `keepRecent`.\n */\n getOlderMessages(keepRecent: number): UIMessage[] {\n this.ensureTable();\n\n type Row = { id: string; message: string };\n const rows = this.agent.sql<Row>`\n SELECT id, message FROM cf_agents_session_messages\n WHERE rowid NOT IN (\n SELECT rowid FROM cf_agents_session_messages\n ORDER BY created_at DESC, rowid DESC\n LIMIT ${keepRecent}\n )\n `;\n\n return this.parseRows(rows);\n }\n\n /**\n * Bulk replace all messages.\n * Preserves original created_at timestamps for surviving messages.\n */\n async replaceMessages(messages: UIMessage[]): Promise<void> {\n this.ensureTable();\n\n // Build timestamp map from existing messages before clearing\n type Row = { id: string; created_at: string };\n const existingRows = this.agent.sql<Row>`\n SELECT id, created_at FROM cf_agents_session_messages\n `;\n const timestampMap = new Map<string, string>();\n for (const row of existingRows) {\n timestampMap.set(row.id, row.created_at);\n }\n\n // Durable Objects auto-coalesces all synchronous SQL writes within\n // a single I/O gate into one atomic batch — no explicit transaction needed.\n this.agent.sql`DELETE FROM cf_agents_session_messages`;\n\n const now = new Date().toISOString();\n for (const message of messages) {\n const json = JSON.stringify(message);\n const created_at = timestampMap.get(message.id) ?? now;\n this.agent.sql`\n INSERT INTO cf_agents_session_messages (id, message, created_at)\n VALUES (${message.id}, ${json}, ${created_at})\n ON CONFLICT(id) DO UPDATE SET message = excluded.message\n `;\n }\n }\n\n /**\n * Validate message structure\n */\n private isValidMessage(msg: unknown): msg is UIMessage {\n if (typeof msg !== \"object\" || msg === null) return false;\n const m = msg as Record<string, unknown>;\n\n if (typeof m.id !== \"string\" || m.id.length === 0) return false;\n if (m.role !== \"user\" && m.role !== \"assistant\" && m.role !== \"system\") {\n return false;\n }\n if (!Array.isArray(m.parts)) return false;\n\n return true;\n }\n\n /**\n * Parse message rows from SQL results into UIMessages.\n */\n private parseRows(rows: { id?: string; message: string }[]): UIMessage[] {\n const messages: UIMessage[] = [];\n for (const row of rows) {\n try {\n const parsed = JSON.parse(row.message);\n if (this.isValidMessage(parsed)) {\n messages.push(parsed);\n }\n } catch {\n if (row.id) {\n console.warn(\n `[AgentSessionProvider] Skipping malformed message ${row.id}`\n );\n }\n }\n }\n return messages;\n }\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ import { RetryOptions } from "./retries.js";
7
7
  import {
8
8
  r as MCPConnectionState,
9
9
  w as TransportType
10
- } from "./client-storage-D5nDLKMW.js";
10
+ } from "./client-storage-yDVwzgfF.js";
11
11
  import {
12
12
  AgentMcpOAuthProvider,
13
13
  AgentsOAuthProvider,
@@ -23,7 +23,7 @@ import {
23
23
  WorkflowPage,
24
24
  WorkflowQueryCriteria
25
25
  } from "./workflow-types.js";
26
- import { Observability } from "./observability/index.js";
26
+ import { Observability, ObservabilityEvent } from "./observability/index.js";
27
27
  import { MessageType } from "./types.js";
28
28
  import {
29
29
  Connection,
@@ -321,6 +321,14 @@ declare class Agent<
321
321
  * The observability implementation to use for the Agent
322
322
  */
323
323
  observability?: Observability;
324
+ /**
325
+ * Emit an observability event with auto-generated timestamp.
326
+ * @internal
327
+ */
328
+ protected _emit(
329
+ type: ObservabilityEvent["type"],
330
+ payload?: Record<string, unknown>
331
+ ): void;
324
332
  /**
325
333
  * Execute SQL queries against the Agent's database
326
334
  * @template T Type of the returned rows
@@ -632,6 +640,53 @@ declare class Agent<
632
640
  * @returns true if the task was cancelled, false if the task was not found
633
641
  */
634
642
  cancelSchedule(id: string): Promise<boolean>;
643
+ /**
644
+ * Keep the Durable Object alive via alarm heartbeats.
645
+ * Returns a disposer function that stops the heartbeat when called.
646
+ *
647
+ * Use this when you have long-running work and need to prevent the
648
+ * DO from going idle (eviction after ~70-140s of inactivity).
649
+ * The heartbeat fires every 30 seconds via the scheduling system.
650
+ *
651
+ * @experimental This API may change between releases.
652
+ *
653
+ * @example
654
+ * ```ts
655
+ * const dispose = await this.keepAlive();
656
+ * try {
657
+ * // ... long-running work ...
658
+ * } finally {
659
+ * dispose();
660
+ * }
661
+ * ```
662
+ */
663
+ keepAlive(): Promise<() => void>;
664
+ /**
665
+ * Run an async function while keeping the Durable Object alive.
666
+ * The heartbeat is automatically stopped when the function completes
667
+ * (whether it succeeds or throws).
668
+ *
669
+ * This is the recommended way to use keepAlive — it guarantees cleanup
670
+ * so you cannot forget to dispose the heartbeat.
671
+ *
672
+ * @experimental This API may change between releases.
673
+ *
674
+ * @example
675
+ * ```ts
676
+ * const result = await this.keepAliveWhile(async () => {
677
+ * const data = await longRunningComputation();
678
+ * return data;
679
+ * });
680
+ * ```
681
+ */
682
+ keepAliveWhile<T>(fn: () => Promise<T>): Promise<T>;
683
+ /**
684
+ * Internal no-op callback invoked by the keepAlive heartbeat schedule.
685
+ * Its only purpose is to keep the DO alive — the alarm machinery
686
+ * handles the rest.
687
+ * @internal
688
+ */
689
+ _cf_keepAliveHeartbeat(): Promise<void>;
635
690
  private _scheduleNextAlarm;
636
691
  /**
637
692
  * Method called when an alarm fires.