agents 0.5.1 → 0.7.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/agent-DnmmRjyv.d.ts +231 -0
- package/dist/{client-connection-CGMuV62J.js → client-connection-D3Wcd6Q6.js} +154 -23
- package/dist/client-connection-D3Wcd6Q6.js.map +1 -0
- package/dist/{client-storage-D633wI1S.d.ts → client-storage-tusTuoSF.d.ts} +262 -13
- package/dist/experimental/forever.d.ts +7 -18
- package/dist/experimental/forever.js +4 -38
- package/dist/experimental/forever.js.map +1 -1
- package/dist/experimental/memory/session/index.d.ts +251 -0
- package/dist/experimental/memory/session/index.js +385 -0
- package/dist/experimental/memory/session/index.js.map +1 -0
- package/dist/index.d.ts +84 -22
- package/dist/index.js +324 -250
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +51 -7
- package/dist/mcp/client.js +157 -32
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/do-oauth-client-provider.js +1 -1
- package/dist/mcp/do-oauth-client-provider.js.map +1 -1
- package/dist/mcp/index.d.ts +27 -165
- package/dist/mcp/index.js +50 -10
- package/dist/mcp/index.js.map +1 -1
- package/dist/observability/index.d.ts +72 -6
- package/dist/observability/index.js +61 -16
- package/dist/observability/index.js.map +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/workflows.d.ts +15 -1
- package/dist/workflows.js +36 -3
- package/dist/workflows.js.map +1 -1
- package/package.json +16 -11
- package/dist/agent-B4_kEsdK.d.ts +0 -60
- package/dist/client-connection-CGMuV62J.js.map +0 -1
- package/dist/mcp-DA0kDE7K.d.ts +0 -61
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { UIMessage } from "ai";
|
|
2
|
+
|
|
3
|
+
//#region src/experimental/memory/session/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Options for querying messages
|
|
6
|
+
*/
|
|
7
|
+
interface MessageQueryOptions {
|
|
8
|
+
/** Maximum number of messages to return */
|
|
9
|
+
limit?: number;
|
|
10
|
+
/** Number of messages to skip */
|
|
11
|
+
offset?: number;
|
|
12
|
+
/** Only return messages created before this timestamp */
|
|
13
|
+
before?: Date;
|
|
14
|
+
/** Only return messages created after this timestamp */
|
|
15
|
+
after?: Date;
|
|
16
|
+
/** Filter by role */
|
|
17
|
+
role?: "user" | "assistant" | "system";
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Granular microCompaction rules.
|
|
21
|
+
* Each rule can be true (use default), false (disable), or a number (custom threshold).
|
|
22
|
+
*/
|
|
23
|
+
interface MicroCompactionRules {
|
|
24
|
+
/**
|
|
25
|
+
* Truncate tool outputs over this size (in chars).
|
|
26
|
+
* @default 30000 chars
|
|
27
|
+
*/
|
|
28
|
+
truncateToolOutputs?: boolean | number;
|
|
29
|
+
/**
|
|
30
|
+
* Truncate text parts over this size in older messages (in chars).
|
|
31
|
+
* @default 10000 chars
|
|
32
|
+
*/
|
|
33
|
+
truncateText?: boolean | number;
|
|
34
|
+
/**
|
|
35
|
+
* Number of recent messages to keep intact (not truncated).
|
|
36
|
+
* @default 4
|
|
37
|
+
*/
|
|
38
|
+
keepRecent?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Compaction function - user implements this to decide how to compact messages.
|
|
42
|
+
* Could summarize with LLM, truncate, filter, or anything else.
|
|
43
|
+
*
|
|
44
|
+
* @param messages Current messages in the session
|
|
45
|
+
* @returns New messages to replace the current ones
|
|
46
|
+
*/
|
|
47
|
+
type CompactFunction = (messages: UIMessage[]) => Promise<UIMessage[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Configuration for full compaction (LLM summarization)
|
|
50
|
+
*/
|
|
51
|
+
interface CompactionConfig {
|
|
52
|
+
/**
|
|
53
|
+
* Token threshold for automatic compaction.
|
|
54
|
+
* When estimated tokens exceed this, compact() is called automatically on append().
|
|
55
|
+
* If not set, auto-compaction is disabled (you can still call compact() manually).
|
|
56
|
+
*/
|
|
57
|
+
tokenThreshold?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Function to compact messages.
|
|
60
|
+
* Receives current messages as stored, returns new messages.
|
|
61
|
+
*/
|
|
62
|
+
fn: CompactFunction;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Result of compaction operation
|
|
66
|
+
*/
|
|
67
|
+
interface CompactResult {
|
|
68
|
+
/** Whether compaction succeeded */
|
|
69
|
+
success: boolean;
|
|
70
|
+
/** Error message if compaction failed */
|
|
71
|
+
error?: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Options for creating a session provider
|
|
75
|
+
*/
|
|
76
|
+
interface SessionProviderOptions {
|
|
77
|
+
/**
|
|
78
|
+
* Lightweight compaction that doesn't require LLM calls.
|
|
79
|
+
* Truncates tool outputs and long text in older messages.
|
|
80
|
+
*
|
|
81
|
+
* Runs automatically on every `append()` — older messages (beyond `keepRecent`)
|
|
82
|
+
* are truncated in storage.
|
|
83
|
+
* This is a destructive operation: original content is permanently replaced.
|
|
84
|
+
* `getMessages()` returns stored content as-is (already compacted).
|
|
85
|
+
*
|
|
86
|
+
* - `true` - enable with default rules
|
|
87
|
+
* - `false` - disable
|
|
88
|
+
* - `{ ... }` - enable with custom rules
|
|
89
|
+
*
|
|
90
|
+
* @default true
|
|
91
|
+
*/
|
|
92
|
+
microCompaction?: boolean | MicroCompactionRules;
|
|
93
|
+
/**
|
|
94
|
+
* Full compaction with custom function (typically LLM summarization).
|
|
95
|
+
*/
|
|
96
|
+
compaction?: CompactionConfig;
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/experimental/memory/session/provider.d.ts
|
|
100
|
+
/**
|
|
101
|
+
* Session storage provider interface.
|
|
102
|
+
*
|
|
103
|
+
* Implement this interface to create custom session storage backends.
|
|
104
|
+
* Providers handle CRUD only — compaction is orchestrated by the Session wrapper.
|
|
105
|
+
*/
|
|
106
|
+
interface SessionProvider {
|
|
107
|
+
/**
|
|
108
|
+
* Get messages with optional filtering
|
|
109
|
+
*/
|
|
110
|
+
getMessages(options?: MessageQueryOptions): UIMessage[];
|
|
111
|
+
/**
|
|
112
|
+
* Get a single message by ID
|
|
113
|
+
*/
|
|
114
|
+
getMessage(id: string): UIMessage | null;
|
|
115
|
+
/**
|
|
116
|
+
* Get the last N messages (most recent)
|
|
117
|
+
*/
|
|
118
|
+
getLastMessages(n: number): UIMessage[];
|
|
119
|
+
/**
|
|
120
|
+
* Append one or more messages to storage.
|
|
121
|
+
*/
|
|
122
|
+
appendMessages(messages: UIMessage | UIMessage[]): Promise<void>;
|
|
123
|
+
/**
|
|
124
|
+
* Update an existing message
|
|
125
|
+
*/
|
|
126
|
+
updateMessage(message: UIMessage): void;
|
|
127
|
+
/**
|
|
128
|
+
* Delete messages by ID
|
|
129
|
+
*/
|
|
130
|
+
deleteMessages(messageIds: string[]): void;
|
|
131
|
+
/**
|
|
132
|
+
* Clear all messages
|
|
133
|
+
*/
|
|
134
|
+
clearMessages(): void;
|
|
135
|
+
/**
|
|
136
|
+
* Fetch messages outside the recent window (for microCompaction).
|
|
137
|
+
* Returns all messages except the most recent `keepRecent`.
|
|
138
|
+
*/
|
|
139
|
+
getOlderMessages(keepRecent: number): UIMessage[];
|
|
140
|
+
/**
|
|
141
|
+
* Bulk replace all messages (used by compact).
|
|
142
|
+
* Clears existing messages and inserts the new ones,
|
|
143
|
+
* preserving original created_at timestamps where possible.
|
|
144
|
+
*/
|
|
145
|
+
replaceMessages(messages: UIMessage[]): Promise<void>;
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/experimental/memory/session/session.d.ts
|
|
149
|
+
declare class Session {
|
|
150
|
+
private storage;
|
|
151
|
+
private microCompactionRules;
|
|
152
|
+
private compactionConfig;
|
|
153
|
+
constructor(storage: SessionProvider, options?: SessionProviderOptions);
|
|
154
|
+
getMessages(options?: MessageQueryOptions): UIMessage[];
|
|
155
|
+
getMessage(id: string): UIMessage | null;
|
|
156
|
+
getLastMessages(n: number): UIMessage[];
|
|
157
|
+
append(messages: UIMessage | UIMessage[]): Promise<void>;
|
|
158
|
+
updateMessage(message: UIMessage): void;
|
|
159
|
+
deleteMessages(messageIds: string[]): void;
|
|
160
|
+
clearMessages(): void;
|
|
161
|
+
compact(): Promise<CompactResult>;
|
|
162
|
+
/**
|
|
163
|
+
* Pre-check for auto-compaction using token estimate heuristic.
|
|
164
|
+
*/
|
|
165
|
+
private shouldAutoCompact;
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/experimental/memory/session/providers/agent.d.ts
|
|
169
|
+
/**
|
|
170
|
+
* Interface for objects that provide a sql tagged template method.
|
|
171
|
+
* This matches the Agent class's sql method signature.
|
|
172
|
+
*/
|
|
173
|
+
interface SqlProvider {
|
|
174
|
+
sql<T = Record<string, string | number | boolean | null>>(strings: TemplateStringsArray, ...values: (string | number | boolean | null)[]): T[];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Session provider that wraps an Agent's SQLite storage.
|
|
178
|
+
* Provides pure CRUD — compaction is handled by the Session wrapper.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```typescript
|
|
182
|
+
* import { Session, AgentSessionProvider } from "agents/experimental/memory/session";
|
|
183
|
+
*
|
|
184
|
+
* // In your Agent class:
|
|
185
|
+
* session = new Session(new AgentSessionProvider(this));
|
|
186
|
+
*
|
|
187
|
+
* // With compaction options:
|
|
188
|
+
* session = new Session(new AgentSessionProvider(this), {
|
|
189
|
+
* microCompaction: { truncateToolOutputs: 2000, keepRecent: 10 },
|
|
190
|
+
* compaction: { tokenThreshold: 20000, fn: summarize }
|
|
191
|
+
* });
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
declare class AgentSessionProvider implements SessionProvider {
|
|
195
|
+
private agent;
|
|
196
|
+
private initialized;
|
|
197
|
+
constructor(agent: SqlProvider);
|
|
198
|
+
/**
|
|
199
|
+
* Ensure the messages table exists
|
|
200
|
+
*/
|
|
201
|
+
private ensureTable;
|
|
202
|
+
/**
|
|
203
|
+
* Get all messages in AI SDK format
|
|
204
|
+
*/
|
|
205
|
+
getMessages(options?: MessageQueryOptions): UIMessage[];
|
|
206
|
+
/**
|
|
207
|
+
* Append one or more messages to storage.
|
|
208
|
+
*/
|
|
209
|
+
appendMessages(messages: UIMessage | UIMessage[]): Promise<void>;
|
|
210
|
+
/**
|
|
211
|
+
* Update an existing message
|
|
212
|
+
*/
|
|
213
|
+
updateMessage(message: UIMessage): void;
|
|
214
|
+
/**
|
|
215
|
+
* Delete messages by their IDs
|
|
216
|
+
*/
|
|
217
|
+
deleteMessages(messageIds: string[]): void;
|
|
218
|
+
/**
|
|
219
|
+
* Clear all messages from the session
|
|
220
|
+
*/
|
|
221
|
+
clearMessages(): void;
|
|
222
|
+
/**
|
|
223
|
+
* Get a single message by ID
|
|
224
|
+
*/
|
|
225
|
+
getMessage(id: string): UIMessage | null;
|
|
226
|
+
/**
|
|
227
|
+
* Get the last N messages (most recent)
|
|
228
|
+
*/
|
|
229
|
+
getLastMessages(n: number): UIMessage[];
|
|
230
|
+
/**
|
|
231
|
+
* Fetch messages outside the recent window (for microCompaction).
|
|
232
|
+
* Returns all messages except the most recent `keepRecent`.
|
|
233
|
+
*/
|
|
234
|
+
getOlderMessages(keepRecent: number): UIMessage[];
|
|
235
|
+
/**
|
|
236
|
+
* Bulk replace all messages.
|
|
237
|
+
* Preserves original created_at timestamps for surviving messages.
|
|
238
|
+
*/
|
|
239
|
+
replaceMessages(messages: UIMessage[]): Promise<void>;
|
|
240
|
+
/**
|
|
241
|
+
* Validate message structure
|
|
242
|
+
*/
|
|
243
|
+
private isValidMessage;
|
|
244
|
+
/**
|
|
245
|
+
* Parse message rows from SQL results into UIMessages.
|
|
246
|
+
*/
|
|
247
|
+
private parseRows;
|
|
248
|
+
}
|
|
249
|
+
//#endregion
|
|
250
|
+
export { AgentSessionProvider, type CompactFunction, type CompactResult, type CompactionConfig, type MessageQueryOptions, type MicroCompactionRules, Session, type SessionProvider, type SessionProviderOptions, type SqlProvider };
|
|
251
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -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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"}
|