agents 0.13.1 → 0.13.3

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,453 @@
1
+ import { Agent, getCurrentAgent } from "../index.js";
2
+ //#region src/chat-sdk/agent.ts
3
+ const NEXT_CLEANUP_AT_KEY = "next_cleanup_at";
4
+ const CLEANUP_SCHEDULE_ID_KEY = "cleanup_schedule_id";
5
+ var ChatSdkStateAgent = class extends Agent {
6
+ onStart() {
7
+ this.migrate();
8
+ this.scheduleNextCleanup();
9
+ }
10
+ subscribe(threadId) {
11
+ this.sql`
12
+ INSERT OR IGNORE INTO chat_sdk_state_subscriptions (thread_id)
13
+ VALUES (${threadId})
14
+ `;
15
+ }
16
+ unsubscribe(threadId) {
17
+ this.sql`
18
+ DELETE FROM chat_sdk_state_subscriptions
19
+ WHERE thread_id = ${threadId}
20
+ `;
21
+ }
22
+ isSubscribed(threadId) {
23
+ return this.sql`
24
+ SELECT 1 as found
25
+ FROM chat_sdk_state_subscriptions
26
+ WHERE thread_id = ${threadId}
27
+ LIMIT 1
28
+ `.length > 0;
29
+ }
30
+ async acquireLock(threadId, ttlMs) {
31
+ const result = this.ctx.storage.transactionSync(() => {
32
+ const now = Date.now();
33
+ this.ctx.storage.sql.exec("DELETE FROM chat_sdk_state_locks WHERE thread_id = ? AND expires_at <= ?", threadId, now);
34
+ if (this.ctx.storage.sql.exec("SELECT 1 FROM chat_sdk_state_locks WHERE thread_id = ? LIMIT 1", threadId).toArray().length > 0) return null;
35
+ const token = crypto.randomUUID();
36
+ const expiresAt = now + ttlMs;
37
+ this.ctx.storage.sql.exec("INSERT INTO chat_sdk_state_locks (thread_id, token, expires_at) VALUES (?, ?, ?)", threadId, token, expiresAt);
38
+ return {
39
+ threadId,
40
+ token,
41
+ expiresAt
42
+ };
43
+ });
44
+ await this.scheduleCleanupForExpiry(result?.expiresAt ?? null);
45
+ return result;
46
+ }
47
+ releaseLock(threadId, token) {
48
+ this.sql`
49
+ DELETE FROM chat_sdk_state_locks
50
+ WHERE thread_id = ${threadId} AND token = ${token}
51
+ `;
52
+ }
53
+ async extendLock(threadId, token, ttlMs) {
54
+ const result = this.ctx.storage.transactionSync(() => {
55
+ const now = Date.now();
56
+ return this.ctx.storage.sql.exec(`UPDATE chat_sdk_state_locks SET expires_at = ?
57
+ WHERE thread_id = ? AND token = ? AND expires_at > ?
58
+ RETURNING thread_id`, now + ttlMs, threadId, token, now).toArray().length > 0;
59
+ });
60
+ if (result) await this.scheduleCleanupForExpiry(Date.now() + ttlMs);
61
+ return result;
62
+ }
63
+ forceReleaseLock(threadId) {
64
+ this.sql`
65
+ DELETE FROM chat_sdk_state_locks
66
+ WHERE thread_id = ${threadId}
67
+ `;
68
+ }
69
+ async enqueue(threadId, value, maxSize) {
70
+ const parsed = parseQueueEntry(value);
71
+ const count = this.ctx.storage.transactionSync(() => {
72
+ this.ctx.storage.sql.exec("INSERT INTO chat_sdk_state_queue (thread_id, value, enqueued_at, expires_at) VALUES (?, ?, ?, ?)", threadId, value, parsed.enqueuedAt, parsed.expiresAt);
73
+ this.ctx.storage.sql.exec(`DELETE FROM chat_sdk_state_queue WHERE thread_id = ? AND id NOT IN (
74
+ SELECT id FROM chat_sdk_state_queue
75
+ WHERE thread_id = ?
76
+ ORDER BY id DESC
77
+ LIMIT ?
78
+ )`, threadId, threadId, maxSize);
79
+ return this.ctx.storage.sql.exec("SELECT COUNT(*) as count FROM chat_sdk_state_queue WHERE thread_id = ?", threadId).one().count;
80
+ });
81
+ await this.scheduleCleanupForExpiry(parsed.expiresAt);
82
+ return count;
83
+ }
84
+ popQueue(threadId) {
85
+ return this.ctx.storage.transactionSync(() => {
86
+ const now = Date.now();
87
+ this.ctx.storage.sql.exec("DELETE FROM chat_sdk_state_queue WHERE thread_id = ? AND expires_at <= ?", threadId, now);
88
+ const row = this.ctx.storage.sql.exec("SELECT id, value FROM chat_sdk_state_queue WHERE thread_id = ? ORDER BY id ASC LIMIT 1", threadId).toArray()[0];
89
+ if (!row) return null;
90
+ this.ctx.storage.sql.exec("DELETE FROM chat_sdk_state_queue WHERE id = ?", row.id);
91
+ return row.value;
92
+ });
93
+ }
94
+ queueDepth(threadId) {
95
+ return this.sql`
96
+ SELECT COUNT(*) as count
97
+ FROM chat_sdk_state_queue
98
+ WHERE thread_id = ${threadId} AND expires_at > ${Date.now()}
99
+ `[0]?.count ?? 0;
100
+ }
101
+ async listAppend(key, value, maxLength, ttlMs) {
102
+ const expiresAt = ttlMs && ttlMs > 0 ? Date.now() + ttlMs : null;
103
+ this.ctx.storage.transactionSync(() => {
104
+ this.ctx.storage.sql.exec("INSERT INTO chat_sdk_state_lists (key, value, expires_at) VALUES (?, ?, ?)", key, value, expiresAt);
105
+ if (expiresAt !== null) this.ctx.storage.sql.exec("UPDATE chat_sdk_state_lists SET expires_at = ? WHERE key = ?", expiresAt, key);
106
+ if (maxLength != null && maxLength > 0) this.ctx.storage.sql.exec(`DELETE FROM chat_sdk_state_lists WHERE key = ? AND id NOT IN (
107
+ SELECT id FROM chat_sdk_state_lists
108
+ WHERE key = ?
109
+ ORDER BY id DESC
110
+ LIMIT ?
111
+ )`, key, key, maxLength);
112
+ });
113
+ await this.scheduleCleanupForExpiry(expiresAt);
114
+ }
115
+ listGet(key) {
116
+ const now = Date.now();
117
+ this.sql`
118
+ DELETE FROM chat_sdk_state_lists
119
+ WHERE key = ${key}
120
+ AND expires_at IS NOT NULL
121
+ AND expires_at <= ${now}
122
+ `;
123
+ return this.sql`
124
+ SELECT value
125
+ FROM chat_sdk_state_lists
126
+ WHERE key = ${key}
127
+ ORDER BY id ASC
128
+ `.map((row) => row.value);
129
+ }
130
+ cacheGet(key) {
131
+ return this.readCacheValue(key, Date.now());
132
+ }
133
+ async cacheSet(key, value, ttlMs) {
134
+ const expiresAt = ttlMs && ttlMs > 0 ? Date.now() + ttlMs : null;
135
+ this.upsertCacheValue(key, value, expiresAt);
136
+ await this.scheduleCleanupForExpiry(expiresAt);
137
+ }
138
+ async cacheSetIfNotExists(key, value, ttlMs) {
139
+ const now = Date.now();
140
+ const inserted = this.ctx.storage.transactionSync(() => {
141
+ this.ctx.storage.sql.exec("DELETE FROM chat_sdk_state_cache WHERE key = ? AND expires_at IS NOT NULL AND expires_at <= ?", key, now);
142
+ if (this.readCacheValue(key, now) !== null) return false;
143
+ const expiresAt = ttlMs && ttlMs > 0 ? now + ttlMs : null;
144
+ this.upsertCacheValue(key, value, expiresAt);
145
+ return true;
146
+ });
147
+ if (inserted) {
148
+ const expiresAt = ttlMs && ttlMs > 0 ? Date.now() + ttlMs : null;
149
+ await this.scheduleCleanupForExpiry(expiresAt);
150
+ }
151
+ return inserted;
152
+ }
153
+ cacheDelete(key) {
154
+ this.sql`
155
+ DELETE FROM chat_sdk_state_cache
156
+ WHERE key = ${key}
157
+ `;
158
+ }
159
+ async cleanupExpired(payload) {
160
+ const current = this.readCleanupMetadata();
161
+ if (payload?.expiresAt !== void 0 && current.nextCleanupAt !== null && payload.expiresAt !== current.nextCleanupAt) return;
162
+ const now = Date.now();
163
+ this.clearCleanupMetadata();
164
+ this.sql`
165
+ DELETE FROM chat_sdk_state_locks
166
+ WHERE expires_at <= ${now}
167
+ `;
168
+ this.sql`
169
+ DELETE FROM chat_sdk_state_cache
170
+ WHERE expires_at IS NOT NULL AND expires_at <= ${now}
171
+ `;
172
+ this.sql`
173
+ DELETE FROM chat_sdk_state_queue
174
+ WHERE expires_at <= ${now}
175
+ `;
176
+ this.sql`
177
+ DELETE FROM chat_sdk_state_lists
178
+ WHERE expires_at IS NOT NULL AND expires_at <= ${now}
179
+ `;
180
+ await this.scheduleNextCleanup();
181
+ }
182
+ migrate() {
183
+ this.sql`
184
+ CREATE TABLE IF NOT EXISTS chat_sdk_state_subscriptions (
185
+ thread_id TEXT PRIMARY KEY
186
+ )
187
+ `;
188
+ this.sql`
189
+ CREATE TABLE IF NOT EXISTS chat_sdk_state_locks (
190
+ thread_id TEXT PRIMARY KEY,
191
+ token TEXT NOT NULL,
192
+ expires_at INTEGER NOT NULL
193
+ )
194
+ `;
195
+ this.sql`
196
+ CREATE TABLE IF NOT EXISTS chat_sdk_state_cache (
197
+ key TEXT PRIMARY KEY,
198
+ value TEXT NOT NULL,
199
+ expires_at INTEGER
200
+ )
201
+ `;
202
+ this.sql`
203
+ CREATE TABLE IF NOT EXISTS chat_sdk_state_queue (
204
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
205
+ thread_id TEXT NOT NULL,
206
+ value TEXT NOT NULL,
207
+ enqueued_at INTEGER NOT NULL,
208
+ expires_at INTEGER NOT NULL
209
+ )
210
+ `;
211
+ this.sql`
212
+ CREATE TABLE IF NOT EXISTS chat_sdk_state_lists (
213
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
214
+ key TEXT NOT NULL,
215
+ value TEXT NOT NULL,
216
+ expires_at INTEGER
217
+ )
218
+ `;
219
+ this.sql`
220
+ CREATE TABLE IF NOT EXISTS chat_sdk_state_metadata (
221
+ key TEXT PRIMARY KEY,
222
+ value TEXT NOT NULL
223
+ )
224
+ `;
225
+ this.sql`
226
+ CREATE INDEX IF NOT EXISTS idx_chat_sdk_state_locks_expires
227
+ ON chat_sdk_state_locks(expires_at)
228
+ `;
229
+ this.sql`
230
+ CREATE INDEX IF NOT EXISTS idx_chat_sdk_state_cache_expires
231
+ ON chat_sdk_state_cache(expires_at)
232
+ WHERE expires_at IS NOT NULL
233
+ `;
234
+ this.sql`
235
+ CREATE INDEX IF NOT EXISTS idx_chat_sdk_state_queue_thread
236
+ ON chat_sdk_state_queue(thread_id, id)
237
+ `;
238
+ this.sql`
239
+ CREATE INDEX IF NOT EXISTS idx_chat_sdk_state_queue_expires
240
+ ON chat_sdk_state_queue(expires_at)
241
+ `;
242
+ this.sql`
243
+ CREATE INDEX IF NOT EXISTS idx_chat_sdk_state_lists_key
244
+ ON chat_sdk_state_lists(key, id)
245
+ `;
246
+ this.sql`
247
+ CREATE INDEX IF NOT EXISTS idx_chat_sdk_state_lists_expires
248
+ ON chat_sdk_state_lists(expires_at)
249
+ WHERE expires_at IS NOT NULL
250
+ `;
251
+ }
252
+ readCacheValue(key, now) {
253
+ return this.sql`
254
+ SELECT value
255
+ FROM chat_sdk_state_cache
256
+ WHERE key = ${key}
257
+ AND (expires_at IS NULL OR expires_at > ${now})
258
+ `[0]?.value ?? null;
259
+ }
260
+ upsertCacheValue(key, value, expiresAt) {
261
+ this.sql`
262
+ INSERT OR REPLACE INTO chat_sdk_state_cache (key, value, expires_at)
263
+ VALUES (${key}, ${value}, ${expiresAt})
264
+ `;
265
+ }
266
+ async scheduleCleanupForExpiry(expiresAt) {
267
+ if (expiresAt === null) return;
268
+ await this.ensureCleanupScheduled(expiresAt);
269
+ }
270
+ async scheduleNextCleanup() {
271
+ const next = this.nextExpiry();
272
+ if (next === null) {
273
+ const current = this.readCleanupMetadata();
274
+ if (current.scheduleId) await this.cancelSchedule(current.scheduleId).catch(() => false);
275
+ this.clearCleanupMetadata();
276
+ return;
277
+ }
278
+ await this.ensureCleanupScheduled(next);
279
+ }
280
+ async ensureCleanupScheduled(expiresAt) {
281
+ const current = this.readCleanupMetadata();
282
+ if (current.nextCleanupAt !== null && current.nextCleanupAt <= expiresAt) return;
283
+ if (current.scheduleId) await this.cancelSchedule(current.scheduleId).catch(() => false);
284
+ const delaySeconds = Math.max(0, Math.ceil((expiresAt - Date.now()) / 1e3));
285
+ const schedule = await this.schedule(delaySeconds, "cleanupExpired", { expiresAt });
286
+ this.writeCleanupMetadata(expiresAt, schedule.id);
287
+ }
288
+ nextExpiry() {
289
+ return this.sql`
290
+ SELECT MIN(expires_at) as expires_at
291
+ FROM (
292
+ SELECT expires_at FROM chat_sdk_state_locks
293
+ UNION ALL
294
+ SELECT expires_at FROM chat_sdk_state_queue
295
+ UNION ALL
296
+ SELECT expires_at FROM chat_sdk_state_cache WHERE expires_at IS NOT NULL
297
+ UNION ALL
298
+ SELECT expires_at FROM chat_sdk_state_lists WHERE expires_at IS NOT NULL
299
+ )
300
+ `[0]?.expires_at ?? null;
301
+ }
302
+ readCleanupMetadata() {
303
+ const rows = this.sql`
304
+ SELECT key, value
305
+ FROM chat_sdk_state_metadata
306
+ WHERE key IN (${NEXT_CLEANUP_AT_KEY}, ${CLEANUP_SCHEDULE_ID_KEY})
307
+ `;
308
+ const values = new Map(rows.map((row) => [row.key, row.value]));
309
+ const nextCleanupAt = Number(values.get(NEXT_CLEANUP_AT_KEY));
310
+ return {
311
+ nextCleanupAt: Number.isFinite(nextCleanupAt) ? nextCleanupAt : null,
312
+ scheduleId: values.get(CLEANUP_SCHEDULE_ID_KEY) ?? null
313
+ };
314
+ }
315
+ writeCleanupMetadata(expiresAt, scheduleId) {
316
+ this.sql`
317
+ INSERT OR REPLACE INTO chat_sdk_state_metadata (key, value)
318
+ VALUES (${NEXT_CLEANUP_AT_KEY}, ${String(expiresAt)})
319
+ `;
320
+ this.sql`
321
+ INSERT OR REPLACE INTO chat_sdk_state_metadata (key, value)
322
+ VALUES (${CLEANUP_SCHEDULE_ID_KEY}, ${scheduleId})
323
+ `;
324
+ }
325
+ clearCleanupMetadata() {
326
+ this.sql`
327
+ DELETE FROM chat_sdk_state_metadata
328
+ WHERE key IN (${NEXT_CLEANUP_AT_KEY}, ${CLEANUP_SCHEDULE_ID_KEY})
329
+ `;
330
+ }
331
+ };
332
+ function parseQueueEntry(value) {
333
+ const raw = JSON.parse(value);
334
+ if (typeof raw.enqueuedAt !== "number" || typeof raw.expiresAt !== "number") throw new Error("ChatSdkStateAgent expected QueueEntry JSON with numeric TTLs");
335
+ return {
336
+ enqueuedAt: raw.enqueuedAt,
337
+ expiresAt: raw.expiresAt
338
+ };
339
+ }
340
+ //#endregion
341
+ //#region src/chat-sdk/adapter.ts
342
+ const THREAD_STATE_PREFIX = "thread-state:";
343
+ const CHANNEL_STATE_PREFIX = "channel-state:";
344
+ const MESSAGE_HISTORY_PREFIX = "msg-history:";
345
+ const TRANSCRIPTS_USER_PREFIX = "transcripts:user:";
346
+ function parseStoredJson(raw, label) {
347
+ try {
348
+ return JSON.parse(raw);
349
+ } catch (error) {
350
+ throw new Error(`ChatSdkStateAdapter expected JSON-encoded ${label}`, { cause: error });
351
+ }
352
+ }
353
+ function defaultThreadShard(threadId) {
354
+ return threadId.split(":").slice(0, 2).join(":") || "default";
355
+ }
356
+ function defaultKeyShard(key, shardThread = defaultThreadShard) {
357
+ for (const prefix of [
358
+ THREAD_STATE_PREFIX,
359
+ CHANNEL_STATE_PREFIX,
360
+ MESSAGE_HISTORY_PREFIX,
361
+ TRANSCRIPTS_USER_PREFIX
362
+ ]) if (key.startsWith(prefix)) return shardThread(key.slice(prefix.length));
363
+ }
364
+ var ChatSdkStateAdapter = class {
365
+ constructor(options = {}) {
366
+ this.connected = false;
367
+ const parent = options.parent ?? getCurrentAgent().agent;
368
+ if (!parent) throw new Error("ChatSdkStateAdapter requires a parent Agent. Pass `parent` or create it inside an Agent context.");
369
+ this.parent = parent;
370
+ this.agentClass = options.agent ?? ChatSdkStateAgent;
371
+ this.defaultName = options.name ?? "default";
372
+ this.keyShard = options.keyShard;
373
+ this.shardKey = options.shardKey ?? defaultThreadShard;
374
+ }
375
+ async connect() {
376
+ this.connected = true;
377
+ }
378
+ async disconnect() {
379
+ this.connected = false;
380
+ }
381
+ async subscribe(threadId) {
382
+ await (await this.stateAgent(threadId)).subscribe(threadId);
383
+ }
384
+ async unsubscribe(threadId) {
385
+ await (await this.stateAgent(threadId)).unsubscribe(threadId);
386
+ }
387
+ async isSubscribed(threadId) {
388
+ return (await this.stateAgent(threadId)).isSubscribed(threadId);
389
+ }
390
+ async acquireLock(threadId, ttlMs) {
391
+ return (await this.stateAgent(threadId)).acquireLock(threadId, ttlMs);
392
+ }
393
+ async releaseLock(lock) {
394
+ await (await this.stateAgent(lock.threadId)).releaseLock(lock.threadId, lock.token);
395
+ }
396
+ async extendLock(lock, ttlMs) {
397
+ return (await this.stateAgent(lock.threadId)).extendLock(lock.threadId, lock.token, ttlMs);
398
+ }
399
+ async forceReleaseLock(threadId) {
400
+ await (await this.stateAgent(threadId)).forceReleaseLock(threadId);
401
+ }
402
+ async enqueue(threadId, entry, maxSize) {
403
+ return (await this.stateAgent(threadId)).enqueue(threadId, JSON.stringify(entry), maxSize);
404
+ }
405
+ async dequeue(threadId) {
406
+ const raw = await (await this.stateAgent(threadId)).popQueue(threadId);
407
+ return raw === null ? null : parseStoredJson(raw, `queue entry for ${threadId}`);
408
+ }
409
+ async queueDepth(threadId) {
410
+ return (await this.stateAgent(threadId)).queueDepth(threadId);
411
+ }
412
+ async appendToList(key, value, options) {
413
+ await (await this.stateAgentForKey(key)).listAppend(key, JSON.stringify(value), options?.maxLength, options?.ttlMs);
414
+ }
415
+ async getList(key) {
416
+ return (await (await this.stateAgentForKey(key)).listGet(key)).map((value) => parseStoredJson(value, `list entry for ${key}`));
417
+ }
418
+ async get(key) {
419
+ const raw = await (await this.stateAgentForKey(key)).cacheGet(key);
420
+ return raw === null ? null : parseStoredJson(raw, `cache key ${key}`);
421
+ }
422
+ async set(key, value, ttlMs) {
423
+ await (await this.stateAgentForKey(key)).cacheSet(key, JSON.stringify(value), ttlMs);
424
+ }
425
+ async setIfNotExists(key, value, ttlMs) {
426
+ return (await this.stateAgentForKey(key)).cacheSetIfNotExists(key, JSON.stringify(value), ttlMs);
427
+ }
428
+ async delete(key) {
429
+ await (await this.stateAgentForKey(key)).cacheDelete(key);
430
+ }
431
+ async stateAgent(threadId) {
432
+ this.ensureConnected();
433
+ const name = threadId ? this.shardKey(threadId) : this.defaultName;
434
+ return this.parent.subAgent(this.agentClass, name);
435
+ }
436
+ async stateAgentForKey(key) {
437
+ this.ensureConnected();
438
+ const name = this.keyShard?.(key) ?? defaultKeyShard(key, this.shardKey) ?? this.defaultName;
439
+ return this.parent.subAgent(this.agentClass, name);
440
+ }
441
+ ensureConnected() {
442
+ if (!this.connected) throw new Error("ChatSdkStateAdapter is not connected");
443
+ }
444
+ };
445
+ //#endregion
446
+ //#region src/chat-sdk/index.ts
447
+ function createChatSdkState(options = {}) {
448
+ return new ChatSdkStateAdapter(options);
449
+ }
450
+ //#endregion
451
+ export { ChatSdkStateAdapter, ChatSdkStateAgent, createChatSdkState, defaultKeyShard, defaultThreadShard };
452
+
453
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/chat-sdk/agent.ts","../../src/chat-sdk/adapter.ts","../../src/chat-sdk/index.ts"],"sourcesContent":["import { Agent } from \"../index\";\n\ninterface StoredLock {\n threadId: string;\n token: string;\n expiresAt: number;\n}\n\ninterface StoredQueueEntry {\n enqueuedAt: number;\n expiresAt: number;\n}\n\nconst NEXT_CLEANUP_AT_KEY = \"next_cleanup_at\";\nconst CLEANUP_SCHEDULE_ID_KEY = \"cleanup_schedule_id\";\n\nexport class ChatSdkStateAgent extends Agent {\n onStart(): void {\n this.migrate();\n void this.scheduleNextCleanup();\n }\n\n subscribe(threadId: string): void {\n this.sql`\n INSERT OR IGNORE INTO chat_sdk_state_subscriptions (thread_id)\n VALUES (${threadId})\n `;\n }\n\n unsubscribe(threadId: string): void {\n this.sql`\n DELETE FROM chat_sdk_state_subscriptions\n WHERE thread_id = ${threadId}\n `;\n }\n\n isSubscribed(threadId: string): boolean {\n const rows = this.sql<{ found: number }>`\n SELECT 1 as found\n FROM chat_sdk_state_subscriptions\n WHERE thread_id = ${threadId}\n LIMIT 1\n `;\n return rows.length > 0;\n }\n\n async acquireLock(\n threadId: string,\n ttlMs: number\n ): Promise<StoredLock | null> {\n const result = this.ctx.storage.transactionSync(() => {\n const now = Date.now();\n\n this.ctx.storage.sql.exec(\n \"DELETE FROM chat_sdk_state_locks WHERE thread_id = ? AND expires_at <= ?\",\n threadId,\n now\n );\n\n const existing = this.ctx.storage.sql\n .exec(\n \"SELECT 1 FROM chat_sdk_state_locks WHERE thread_id = ? LIMIT 1\",\n threadId\n )\n .toArray();\n if (existing.length > 0) {\n return null;\n }\n\n const token = crypto.randomUUID();\n const expiresAt = now + ttlMs;\n\n this.ctx.storage.sql.exec(\n \"INSERT INTO chat_sdk_state_locks (thread_id, token, expires_at) VALUES (?, ?, ?)\",\n threadId,\n token,\n expiresAt\n );\n\n return { threadId, token, expiresAt };\n });\n\n await this.scheduleCleanupForExpiry(result?.expiresAt ?? null);\n return result;\n }\n\n releaseLock(threadId: string, token: string): void {\n this.sql`\n DELETE FROM chat_sdk_state_locks\n WHERE thread_id = ${threadId} AND token = ${token}\n `;\n }\n\n async extendLock(\n threadId: string,\n token: string,\n ttlMs: number\n ): Promise<boolean> {\n const result = this.ctx.storage.transactionSync(() => {\n const now = Date.now();\n const rows = this.ctx.storage.sql\n .exec<{ thread_id: string }>(\n `UPDATE chat_sdk_state_locks SET expires_at = ?\n WHERE thread_id = ? AND token = ? AND expires_at > ?\n RETURNING thread_id`,\n now + ttlMs,\n threadId,\n token,\n now\n )\n .toArray();\n return rows.length > 0;\n });\n if (result) {\n await this.scheduleCleanupForExpiry(Date.now() + ttlMs);\n }\n return result;\n }\n\n forceReleaseLock(threadId: string): void {\n this.sql`\n DELETE FROM chat_sdk_state_locks\n WHERE thread_id = ${threadId}\n `;\n }\n\n async enqueue(\n threadId: string,\n value: string,\n maxSize: number\n ): Promise<number> {\n const parsed = parseQueueEntry(value);\n\n const count = this.ctx.storage.transactionSync(() => {\n this.ctx.storage.sql.exec(\n \"INSERT INTO chat_sdk_state_queue (thread_id, value, enqueued_at, expires_at) VALUES (?, ?, ?, ?)\",\n threadId,\n value,\n parsed.enqueuedAt,\n parsed.expiresAt\n );\n\n this.ctx.storage.sql.exec(\n `DELETE FROM chat_sdk_state_queue WHERE thread_id = ? AND id NOT IN (\n SELECT id FROM chat_sdk_state_queue\n WHERE thread_id = ?\n ORDER BY id DESC\n LIMIT ?\n )`,\n threadId,\n threadId,\n maxSize\n );\n\n const row = this.ctx.storage.sql\n .exec<{ count: number }>(\n \"SELECT COUNT(*) as count FROM chat_sdk_state_queue WHERE thread_id = ?\",\n threadId\n )\n .one();\n return row.count;\n });\n await this.scheduleCleanupForExpiry(parsed.expiresAt);\n return count;\n }\n\n popQueue(threadId: string): string | null {\n return this.ctx.storage.transactionSync(() => {\n const now = Date.now();\n\n this.ctx.storage.sql.exec(\n \"DELETE FROM chat_sdk_state_queue WHERE thread_id = ? AND expires_at <= ?\",\n threadId,\n now\n );\n\n const rows = this.ctx.storage.sql\n .exec<{ id: number; value: string }>(\n \"SELECT id, value FROM chat_sdk_state_queue WHERE thread_id = ? ORDER BY id ASC LIMIT 1\",\n threadId\n )\n .toArray();\n const row = rows[0];\n if (!row) {\n return null;\n }\n\n this.ctx.storage.sql.exec(\n \"DELETE FROM chat_sdk_state_queue WHERE id = ?\",\n row.id\n );\n return row.value;\n });\n }\n\n queueDepth(threadId: string): number {\n const rows = this.sql<{ count: number }>`\n SELECT COUNT(*) as count\n FROM chat_sdk_state_queue\n WHERE thread_id = ${threadId} AND expires_at > ${Date.now()}\n `;\n return rows[0]?.count ?? 0;\n }\n\n async listAppend(\n key: string,\n value: string,\n maxLength?: number,\n ttlMs?: number\n ): Promise<void> {\n const expiresAt = ttlMs && ttlMs > 0 ? Date.now() + ttlMs : null;\n\n this.ctx.storage.transactionSync(() => {\n this.ctx.storage.sql.exec(\n \"INSERT INTO chat_sdk_state_lists (key, value, expires_at) VALUES (?, ?, ?)\",\n key,\n value,\n expiresAt\n );\n\n if (expiresAt !== null) {\n // Chat SDK history lists use a list-level TTL: any append refreshes the\n // expiry for the whole logical list, not only the new row.\n this.ctx.storage.sql.exec(\n \"UPDATE chat_sdk_state_lists SET expires_at = ? WHERE key = ?\",\n expiresAt,\n key\n );\n }\n\n if (maxLength != null && maxLength > 0) {\n this.ctx.storage.sql.exec(\n `DELETE FROM chat_sdk_state_lists WHERE key = ? AND id NOT IN (\n SELECT id FROM chat_sdk_state_lists\n WHERE key = ?\n ORDER BY id DESC\n LIMIT ?\n )`,\n key,\n key,\n maxLength\n );\n }\n });\n await this.scheduleCleanupForExpiry(expiresAt);\n }\n\n listGet(key: string): string[] {\n const now = Date.now();\n\n this.sql`\n DELETE FROM chat_sdk_state_lists\n WHERE key = ${key}\n AND expires_at IS NOT NULL\n AND expires_at <= ${now}\n `;\n\n return this.sql<{ value: string }>`\n SELECT value\n FROM chat_sdk_state_lists\n WHERE key = ${key}\n ORDER BY id ASC\n `.map((row) => row.value);\n }\n\n cacheGet(key: string): string | null {\n return this.readCacheValue(key, Date.now());\n }\n\n async cacheSet(key: string, value: string, ttlMs?: number): Promise<void> {\n const expiresAt = ttlMs && ttlMs > 0 ? Date.now() + ttlMs : null;\n this.upsertCacheValue(key, value, expiresAt);\n await this.scheduleCleanupForExpiry(expiresAt);\n }\n\n async cacheSetIfNotExists(\n key: string,\n value: string,\n ttlMs?: number\n ): Promise<boolean> {\n const now = Date.now();\n\n const inserted = this.ctx.storage.transactionSync(() => {\n this.ctx.storage.sql.exec(\n \"DELETE FROM chat_sdk_state_cache WHERE key = ? AND expires_at IS NOT NULL AND expires_at <= ?\",\n key,\n now\n );\n\n if (this.readCacheValue(key, now) !== null) {\n return false;\n }\n\n const expiresAt = ttlMs && ttlMs > 0 ? now + ttlMs : null;\n this.upsertCacheValue(key, value, expiresAt);\n return true;\n });\n if (inserted) {\n const expiresAt = ttlMs && ttlMs > 0 ? Date.now() + ttlMs : null;\n await this.scheduleCleanupForExpiry(expiresAt);\n }\n return inserted;\n }\n\n cacheDelete(key: string): void {\n this.sql`\n DELETE FROM chat_sdk_state_cache\n WHERE key = ${key}\n `;\n }\n\n async cleanupExpired(payload?: { expiresAt?: number }): Promise<void> {\n const current = this.readCleanupMetadata();\n if (\n payload?.expiresAt !== undefined &&\n current.nextCleanupAt !== null &&\n payload.expiresAt !== current.nextCleanupAt\n ) {\n return;\n }\n\n const now = Date.now();\n this.clearCleanupMetadata();\n\n this.sql`\n DELETE FROM chat_sdk_state_locks\n WHERE expires_at <= ${now}\n `;\n this.sql`\n DELETE FROM chat_sdk_state_cache\n WHERE expires_at IS NOT NULL AND expires_at <= ${now}\n `;\n this.sql`\n DELETE FROM chat_sdk_state_queue\n WHERE expires_at <= ${now}\n `;\n this.sql`\n DELETE FROM chat_sdk_state_lists\n WHERE expires_at IS NOT NULL AND expires_at <= ${now}\n `;\n await this.scheduleNextCleanup();\n }\n\n private migrate(): void {\n this.sql`\n CREATE TABLE IF NOT EXISTS chat_sdk_state_subscriptions (\n thread_id TEXT PRIMARY KEY\n )\n `;\n\n this.sql`\n CREATE TABLE IF NOT EXISTS chat_sdk_state_locks (\n thread_id TEXT PRIMARY KEY,\n token TEXT NOT NULL,\n expires_at INTEGER NOT NULL\n )\n `;\n\n this.sql`\n CREATE TABLE IF NOT EXISTS chat_sdk_state_cache (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n expires_at INTEGER\n )\n `;\n\n this.sql`\n CREATE TABLE IF NOT EXISTS chat_sdk_state_queue (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n thread_id TEXT NOT NULL,\n value TEXT NOT NULL,\n enqueued_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL\n )\n `;\n\n this.sql`\n CREATE TABLE IF NOT EXISTS chat_sdk_state_lists (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n key TEXT NOT NULL,\n value TEXT NOT NULL,\n expires_at INTEGER\n )\n `;\n\n this.sql`\n CREATE TABLE IF NOT EXISTS chat_sdk_state_metadata (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n )\n `;\n\n this.sql`\n CREATE INDEX IF NOT EXISTS idx_chat_sdk_state_locks_expires\n ON chat_sdk_state_locks(expires_at)\n `;\n this.sql`\n CREATE INDEX IF NOT EXISTS idx_chat_sdk_state_cache_expires\n ON chat_sdk_state_cache(expires_at)\n WHERE expires_at IS NOT NULL\n `;\n this.sql`\n CREATE INDEX IF NOT EXISTS idx_chat_sdk_state_queue_thread\n ON chat_sdk_state_queue(thread_id, id)\n `;\n this.sql`\n CREATE INDEX IF NOT EXISTS idx_chat_sdk_state_queue_expires\n ON chat_sdk_state_queue(expires_at)\n `;\n this.sql`\n CREATE INDEX IF NOT EXISTS idx_chat_sdk_state_lists_key\n ON chat_sdk_state_lists(key, id)\n `;\n this.sql`\n CREATE INDEX IF NOT EXISTS idx_chat_sdk_state_lists_expires\n ON chat_sdk_state_lists(expires_at)\n WHERE expires_at IS NOT NULL\n `;\n }\n\n private readCacheValue(key: string, now: number): string | null {\n const rows = this.sql<{ value: string }>`\n SELECT value\n FROM chat_sdk_state_cache\n WHERE key = ${key}\n AND (expires_at IS NULL OR expires_at > ${now})\n `;\n return rows[0]?.value ?? null;\n }\n\n private upsertCacheValue(\n key: string,\n value: string,\n expiresAt: number | null\n ): void {\n this.sql`\n INSERT OR REPLACE INTO chat_sdk_state_cache (key, value, expires_at)\n VALUES (${key}, ${value}, ${expiresAt})\n `;\n }\n\n private async scheduleCleanupForExpiry(\n expiresAt: number | null\n ): Promise<void> {\n if (expiresAt === null) {\n return;\n }\n\n await this.ensureCleanupScheduled(expiresAt);\n }\n\n private async scheduleNextCleanup(): Promise<void> {\n const next = this.nextExpiry();\n if (next === null) {\n const current = this.readCleanupMetadata();\n if (current.scheduleId) {\n await this.cancelSchedule(current.scheduleId).catch(() => false);\n }\n this.clearCleanupMetadata();\n return;\n }\n\n await this.ensureCleanupScheduled(next);\n }\n\n private async ensureCleanupScheduled(expiresAt: number): Promise<void> {\n const current = this.readCleanupMetadata();\n if (current.nextCleanupAt !== null && current.nextCleanupAt <= expiresAt) {\n return;\n }\n\n if (current.scheduleId) {\n await this.cancelSchedule(current.scheduleId).catch(() => false);\n }\n\n const delaySeconds = Math.max(\n 0,\n Math.ceil((expiresAt - Date.now()) / 1000)\n );\n const schedule = await this.schedule(delaySeconds, \"cleanupExpired\", {\n expiresAt\n });\n this.writeCleanupMetadata(expiresAt, schedule.id);\n }\n\n private nextExpiry(): number | null {\n const rows = this.sql<{ expires_at: number | null }>`\n SELECT MIN(expires_at) as expires_at\n FROM (\n SELECT expires_at FROM chat_sdk_state_locks\n UNION ALL\n SELECT expires_at FROM chat_sdk_state_queue\n UNION ALL\n SELECT expires_at FROM chat_sdk_state_cache WHERE expires_at IS NOT NULL\n UNION ALL\n SELECT expires_at FROM chat_sdk_state_lists WHERE expires_at IS NOT NULL\n )\n `;\n return rows[0]?.expires_at ?? null;\n }\n\n private readCleanupMetadata(): {\n nextCleanupAt: number | null;\n scheduleId: string | null;\n } {\n const rows = this.sql<{ key: string; value: string }>`\n SELECT key, value\n FROM chat_sdk_state_metadata\n WHERE key IN (${NEXT_CLEANUP_AT_KEY}, ${CLEANUP_SCHEDULE_ID_KEY})\n `;\n const values = new Map(rows.map((row) => [row.key, row.value]));\n const nextCleanupAt = Number(values.get(NEXT_CLEANUP_AT_KEY));\n return {\n nextCleanupAt: Number.isFinite(nextCleanupAt) ? nextCleanupAt : null,\n scheduleId: values.get(CLEANUP_SCHEDULE_ID_KEY) ?? null\n };\n }\n\n private writeCleanupMetadata(expiresAt: number, scheduleId: string): void {\n this.sql`\n INSERT OR REPLACE INTO chat_sdk_state_metadata (key, value)\n VALUES (${NEXT_CLEANUP_AT_KEY}, ${String(expiresAt)})\n `;\n this.sql`\n INSERT OR REPLACE INTO chat_sdk_state_metadata (key, value)\n VALUES (${CLEANUP_SCHEDULE_ID_KEY}, ${scheduleId})\n `;\n }\n\n private clearCleanupMetadata(): void {\n this.sql`\n DELETE FROM chat_sdk_state_metadata\n WHERE key IN (${NEXT_CLEANUP_AT_KEY}, ${CLEANUP_SCHEDULE_ID_KEY})\n `;\n }\n}\n\nfunction parseQueueEntry(value: string): StoredQueueEntry {\n const raw = JSON.parse(value) as Record<string, unknown>;\n if (typeof raw.enqueuedAt !== \"number\" || typeof raw.expiresAt !== \"number\") {\n throw new Error(\n \"ChatSdkStateAgent expected QueueEntry JSON with numeric TTLs\"\n );\n }\n\n return {\n enqueuedAt: raw.enqueuedAt,\n expiresAt: raw.expiresAt\n };\n}\n","import type {\n Lock as ChatSdkLock,\n QueueEntry as ChatSdkQueueEntry,\n StateAdapter as ChatSdkStateAdapterInterface\n} from \"chat\";\nimport {\n getCurrentAgent,\n type SubAgentClass,\n type SubAgentStub\n} from \"../index\";\nimport { ChatSdkStateAgent } from \"./agent\";\nimport type { ChatSdkStateAdapterOptions } from \"./types\";\n\nconst THREAD_STATE_PREFIX = \"thread-state:\";\nconst CHANNEL_STATE_PREFIX = \"channel-state:\";\nconst MESSAGE_HISTORY_PREFIX = \"msg-history:\";\nconst TRANSCRIPTS_USER_PREFIX = \"transcripts:user:\";\n\nfunction parseStoredJson<T>(raw: string, label: string): T {\n try {\n return JSON.parse(raw) as T;\n } catch (error) {\n throw new Error(`ChatSdkStateAdapter expected JSON-encoded ${label}`, {\n cause: error\n });\n }\n}\n\nexport function defaultThreadShard(threadId: string): string {\n return threadId.split(\":\").slice(0, 2).join(\":\") || \"default\";\n}\n\nexport function defaultKeyShard(\n key: string,\n shardThread: (threadId: string) => string = defaultThreadShard\n): string | undefined {\n for (const prefix of [\n THREAD_STATE_PREFIX,\n CHANNEL_STATE_PREFIX,\n MESSAGE_HISTORY_PREFIX,\n TRANSCRIPTS_USER_PREFIX\n ]) {\n if (key.startsWith(prefix)) {\n return shardThread(key.slice(prefix.length));\n }\n }\n\n return undefined;\n}\n\nexport class ChatSdkStateAdapter implements ChatSdkStateAdapterInterface {\n private readonly parent: NonNullable<ChatSdkStateAdapterOptions[\"parent\"]>;\n private readonly agentClass: SubAgentClass<ChatSdkStateAgent>;\n private readonly defaultName: string;\n private readonly keyShard?: (key: string) => string | undefined;\n private readonly shardKey: (threadId: string) => string;\n private connected = false;\n\n constructor(options: ChatSdkStateAdapterOptions = {}) {\n const parent = options.parent ?? getCurrentAgent().agent;\n if (!parent) {\n throw new Error(\n \"ChatSdkStateAdapter requires a parent Agent. Pass `parent` or create it inside an Agent context.\"\n );\n }\n\n this.parent = parent;\n this.agentClass = options.agent ?? ChatSdkStateAgent;\n this.defaultName = options.name ?? \"default\";\n this.keyShard = options.keyShard;\n this.shardKey = options.shardKey ?? defaultThreadShard;\n }\n\n async connect(): Promise<void> {\n this.connected = true;\n }\n\n async disconnect(): Promise<void> {\n this.connected = false;\n }\n\n async subscribe(threadId: string): Promise<void> {\n await (await this.stateAgent(threadId)).subscribe(threadId);\n }\n\n async unsubscribe(threadId: string): Promise<void> {\n await (await this.stateAgent(threadId)).unsubscribe(threadId);\n }\n\n async isSubscribed(threadId: string): Promise<boolean> {\n return (await this.stateAgent(threadId)).isSubscribed(threadId);\n }\n\n async acquireLock(\n threadId: string,\n ttlMs: number\n ): Promise<ChatSdkLock | null> {\n return (await this.stateAgent(threadId)).acquireLock(threadId, ttlMs);\n }\n\n async releaseLock(lock: ChatSdkLock): Promise<void> {\n await (\n await this.stateAgent(lock.threadId)\n ).releaseLock(lock.threadId, lock.token);\n }\n\n async extendLock(lock: ChatSdkLock, ttlMs: number): Promise<boolean> {\n return (await this.stateAgent(lock.threadId)).extendLock(\n lock.threadId,\n lock.token,\n ttlMs\n );\n }\n\n async forceReleaseLock(threadId: string): Promise<void> {\n await (await this.stateAgent(threadId)).forceReleaseLock(threadId);\n }\n\n async enqueue(\n threadId: string,\n entry: ChatSdkQueueEntry,\n maxSize: number\n ): Promise<number> {\n return (await this.stateAgent(threadId)).enqueue(\n threadId,\n JSON.stringify(entry),\n maxSize\n );\n }\n\n async dequeue(threadId: string): Promise<ChatSdkQueueEntry | null> {\n const raw = await (await this.stateAgent(threadId)).popQueue(threadId);\n return raw === null\n ? null\n : parseStoredJson<ChatSdkQueueEntry>(raw, `queue entry for ${threadId}`);\n }\n\n async queueDepth(threadId: string): Promise<number> {\n return (await this.stateAgent(threadId)).queueDepth(threadId);\n }\n\n async appendToList(\n key: string,\n value: unknown,\n options?: { maxLength?: number; ttlMs?: number }\n ): Promise<void> {\n await (\n await this.stateAgentForKey(key)\n ).listAppend(\n key,\n JSON.stringify(value),\n options?.maxLength,\n options?.ttlMs\n );\n }\n\n async getList<T = unknown>(key: string): Promise<T[]> {\n const raw = await (await this.stateAgentForKey(key)).listGet(key);\n return raw.map((value) =>\n parseStoredJson<T>(value, `list entry for ${key}`)\n );\n }\n\n async get<T = unknown>(key: string): Promise<T | null> {\n const raw = await (await this.stateAgentForKey(key)).cacheGet(key);\n return raw === null ? null : parseStoredJson<T>(raw, `cache key ${key}`);\n }\n\n async set<T = unknown>(key: string, value: T, ttlMs?: number): Promise<void> {\n await (\n await this.stateAgentForKey(key)\n ).cacheSet(key, JSON.stringify(value), ttlMs);\n }\n\n async setIfNotExists<T = unknown>(\n key: string,\n value: T,\n ttlMs?: number\n ): Promise<boolean> {\n return (await this.stateAgentForKey(key)).cacheSetIfNotExists(\n key,\n JSON.stringify(value),\n ttlMs\n );\n }\n\n async delete(key: string): Promise<void> {\n await (await this.stateAgentForKey(key)).cacheDelete(key);\n }\n\n private async stateAgent(\n threadId?: string\n ): Promise<SubAgentStub<ChatSdkStateAgent>> {\n this.ensureConnected();\n const name = threadId ? this.shardKey(threadId) : this.defaultName;\n return this.parent.subAgent(this.agentClass, name);\n }\n\n private async stateAgentForKey(\n key: string\n ): Promise<SubAgentStub<ChatSdkStateAgent>> {\n this.ensureConnected();\n const name =\n this.keyShard?.(key) ??\n defaultKeyShard(key, this.shardKey) ??\n this.defaultName;\n return this.parent.subAgent(this.agentClass, name);\n }\n\n private ensureConnected(): void {\n if (!this.connected) {\n throw new Error(\"ChatSdkStateAdapter is not connected\");\n }\n }\n}\n","export {\n ChatSdkStateAdapter,\n defaultKeyShard,\n defaultThreadShard\n} from \"./adapter\";\nexport { ChatSdkStateAgent } from \"./agent\";\nexport type { ChatSdkStateParent, ChatSdkStateAdapterOptions } from \"./types\";\n\nimport { ChatSdkStateAdapter } from \"./adapter\";\nimport type { ChatSdkStateAdapterOptions } from \"./types\";\n\nexport function createChatSdkState(\n options: ChatSdkStateAdapterOptions = {}\n): ChatSdkStateAdapter {\n return new ChatSdkStateAdapter(options);\n}\n"],"mappings":"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"}
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { v as Agent } from "./agent-tool-types-Dn9n-3SI.js";
1
+ import { v as Agent } from "./agent-tool-types-l98LCbBl.js";
2
2
  import {
3
3
  ClientParameters,
4
4
  Method,
@@ -8,6 +8,7 @@ import { ToolSet } from "ai";
8
8
  interface SessionMessagePart {
9
9
  type: string;
10
10
  text?: string;
11
+ reasoning?: string;
11
12
  toolCallId?: string;
12
13
  toolName?: string;
13
14
  input?: unknown;
@@ -15,6 +16,28 @@ interface SessionMessagePart {
15
16
  state?: string;
16
17
  result?: unknown;
17
18
  }
19
+ interface SessionTokenCounterInput {
20
+ /** Messages returned by `session.getHistory()` for the active branch. */
21
+ messages: SessionMessage[];
22
+ /** Frozen system prompt managed by the Session context system. */
23
+ systemPrompt: string;
24
+ /** Loaded context blocks that were used to build `systemPrompt`. */
25
+ contextBlocks: ContextBlock[];
26
+ }
27
+ type SessionTokenCounter = (
28
+ input: SessionTokenCounterInput
29
+ ) => number | Promise<number>;
30
+ interface CompactAfterOptions {
31
+ /**
32
+ * Override the token estimate used by auto-compaction and status broadcasts.
33
+ *
34
+ * The default is a Workers-safe heuristic over message parts plus the
35
+ * Session-managed frozen system prompt. Callers that have model-reported
36
+ * usage or a tokenizer can provide a more precise counter here.
37
+ */
38
+ tokenCounter?: SessionTokenCounter;
39
+ }
40
+ type CompactionErrorHandler = (error: unknown) => void | Promise<void>;
18
41
  /**
19
42
  * Minimal message shape used by Session internals.
20
43
  * Vercel AI SDK's `UIMessage` is structurally compatible — you can pass
@@ -34,6 +57,10 @@ interface SessionOptions {
34
57
  context?: ContextConfig[];
35
58
  /** Provider for persisting the frozen system prompt. */
36
59
  promptStore?: WritableContextProvider;
60
+ /** Custom token counter for auto-compaction/status estimates. */
61
+ tokenCounter?: SessionTokenCounter;
62
+ /** Called when automatic compaction fails after a threshold trigger. */
63
+ onCompactionError?: CompactionErrorHandler;
37
64
  }
38
65
  //#endregion
39
66
  //#region src/experimental/memory/session/provider.d.ts
@@ -432,11 +459,14 @@ declare function createCompactFunction(
432
459
  ): (messages: SessionMessage[]) => Promise<CompactResult | null>;
433
460
  //#endregion
434
461
  export {
435
- SessionOptions as A,
462
+ SessionMessage as A,
436
463
  AgentSessionProvider as C,
437
464
  StoredCompaction as D,
438
465
  SessionProvider as E,
439
- SessionMessage as O,
466
+ SessionOptions as M,
467
+ SessionTokenCounter as N,
468
+ CompactAfterOptions as O,
469
+ SessionTokenCounterInput as P,
440
470
  isSearchProvider as S,
441
471
  SearchResult as T,
442
472
  R2SkillProvider as _,
@@ -448,7 +478,8 @@ export {
448
478
  isWritableProvider as g,
449
479
  WritableContextProvider as h,
450
480
  alignBoundaryBackward as i,
451
- SessionMessagePart as k,
481
+ SessionMessagePart as j,
482
+ CompactionErrorHandler as k,
452
483
  findTailCutByTokens as l,
453
484
  ContextProvider as m,
454
485
  CompactOptions as n,
@@ -463,4 +494,4 @@ export {
463
494
  SearchProvider as x,
464
495
  isSkillProvider as y
465
496
  };
466
- //# sourceMappingURL=compaction-helpers-DAe-xiVY.d.ts.map
497
+ //# sourceMappingURL=compaction-helpers-B-pG5J22.d.ts.map
@@ -20,10 +20,19 @@ function estimateStringTokens(text) {
20
20
  const wordEstimate = text.split(/\s+/).filter(Boolean).length * WORDS_TOKEN_MULTIPLIER;
21
21
  return Math.ceil(Math.max(charEstimate, wordEstimate));
22
22
  }
23
+ function estimateUnknownTokens(value) {
24
+ if (value === null || value === void 0) return 0;
25
+ if (typeof value === "string") return estimateStringTokens(value);
26
+ try {
27
+ return estimateStringTokens(JSON.stringify(value));
28
+ } catch {
29
+ return estimateStringTokens(String(value));
30
+ }
31
+ }
23
32
  /**
24
33
  * Estimate total token count for an array of UIMessages.
25
34
  *
26
- * Walks each message's parts (text, tool invocations, tool results)
35
+ * Walks each message's parts (text, reasoning, tool invocations, tool results)
27
36
  * and applies per-message overhead.
28
37
  *
29
38
  * This is a heuristic. Do not use where exact counts are required.
@@ -32,12 +41,12 @@ function estimateMessageTokens(messages) {
32
41
  let tokens = 0;
33
42
  for (const msg of messages) {
34
43
  tokens += 4;
35
- for (const part of msg.parts) if (part.type === "text") tokens += estimateStringTokens(part.text);
44
+ for (const part of msg.parts) if (part.type === "text" || part.type === "reasoning") tokens += estimateUnknownTokens(part.text ?? part.reasoning);
36
45
  else if (part.type.startsWith("tool-") || part.type === "dynamic-tool") {
37
- const toolPart = part;
38
- if (toolPart.input) tokens += estimateStringTokens(JSON.stringify(toolPart.input));
39
- if (toolPart.output) tokens += estimateStringTokens(JSON.stringify(toolPart.output));
40
- }
46
+ tokens += estimateUnknownTokens(part.input);
47
+ tokens += estimateUnknownTokens(part.output ?? part.result);
48
+ } else if (part.text !== void 0) tokens += estimateUnknownTokens(part.text);
49
+ else if (part.result !== void 0) tokens += estimateUnknownTokens(part.result);
41
50
  }
42
51
  return tokens;
43
52
  }
@@ -309,4 +318,4 @@ function createCompactFunction(opts) {
309
318
  //#endregion
310
319
  export { computeSummaryBudget as a, isCompactionMessage as c, TOKENS_PER_MESSAGE as d, WORDS_TOKEN_MULTIPLIER as f, buildSummaryPrompt as i, sanitizeToolPairs as l, estimateStringTokens as m, alignBoundaryBackward as n, createCompactFunction as o, estimateMessageTokens as p, alignBoundaryForward as r, findTailCutByTokens as s, COMPACTION_PREFIX as t, CHARS_PER_TOKEN as u };
311
320
 
312
- //# sourceMappingURL=compaction-helpers-DvcZnvQ1.js.map
321
+ //# sourceMappingURL=compaction-helpers-fJyf8j4m.js.map