@ash-ai/server 0.0.2 → 0.0.4

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.
Files changed (171) hide show
  1. package/dist/__tests__/attachments.test.d.ts +2 -0
  2. package/dist/__tests__/attachments.test.d.ts.map +1 -0
  3. package/dist/__tests__/attachments.test.js +57 -0
  4. package/dist/__tests__/attachments.test.js.map +1 -0
  5. package/dist/__tests__/bundle.test.d.ts +2 -0
  6. package/dist/__tests__/bundle.test.d.ts.map +1 -0
  7. package/dist/__tests__/bundle.test.js +55 -0
  8. package/dist/__tests__/bundle.test.js.map +1 -0
  9. package/dist/__tests__/coordinator.test.d.ts +2 -0
  10. package/dist/__tests__/coordinator.test.d.ts.map +1 -0
  11. package/dist/__tests__/coordinator.test.js +283 -0
  12. package/dist/__tests__/coordinator.test.js.map +1 -0
  13. package/dist/__tests__/crypto.test.d.ts +2 -0
  14. package/dist/__tests__/crypto.test.d.ts.map +1 -0
  15. package/dist/__tests__/crypto.test.js +45 -0
  16. package/dist/__tests__/crypto.test.js.map +1 -0
  17. package/dist/__tests__/file-store.test.d.ts +2 -0
  18. package/dist/__tests__/file-store.test.d.ts.map +1 -0
  19. package/dist/__tests__/file-store.test.js +105 -0
  20. package/dist/__tests__/file-store.test.js.map +1 -0
  21. package/dist/__tests__/files.test.js +3 -3
  22. package/dist/__tests__/files.test.js.map +1 -1
  23. package/dist/__tests__/openapi.test.js +6 -3
  24. package/dist/__tests__/openapi.test.js.map +1 -1
  25. package/dist/__tests__/queue.test.d.ts +2 -0
  26. package/dist/__tests__/queue.test.d.ts.map +1 -0
  27. package/dist/__tests__/queue.test.js +151 -0
  28. package/dist/__tests__/queue.test.js.map +1 -0
  29. package/dist/__tests__/usage.test.d.ts +2 -0
  30. package/dist/__tests__/usage.test.d.ts.map +1 -0
  31. package/dist/__tests__/usage.test.js +74 -0
  32. package/dist/__tests__/usage.test.js.map +1 -0
  33. package/dist/crypto.d.ts +8 -0
  34. package/dist/crypto.d.ts.map +1 -0
  35. package/dist/crypto.js +29 -0
  36. package/dist/crypto.js.map +1 -0
  37. package/dist/db/drizzle-db.d.ts +128 -0
  38. package/dist/db/drizzle-db.d.ts.map +1 -0
  39. package/dist/db/drizzle-db.js +789 -0
  40. package/dist/db/drizzle-db.js.map +1 -0
  41. package/dist/db/index.d.ts +161 -3
  42. package/dist/db/index.d.ts.map +1 -1
  43. package/dist/db/index.js +164 -8
  44. package/dist/db/index.js.map +1 -1
  45. package/dist/db/schema.pg.d.ts +1625 -0
  46. package/dist/db/schema.pg.d.ts.map +1 -0
  47. package/dist/db/schema.pg.js +150 -0
  48. package/dist/db/schema.pg.js.map +1 -0
  49. package/dist/db/schema.sqlite.d.ts +1781 -0
  50. package/dist/db/schema.sqlite.d.ts.map +1 -0
  51. package/dist/db/schema.sqlite.js +150 -0
  52. package/dist/db/schema.sqlite.js.map +1 -0
  53. package/dist/index.js +18 -1
  54. package/dist/index.js.map +1 -1
  55. package/dist/queue/processor.d.ts +51 -0
  56. package/dist/queue/processor.d.ts.map +1 -0
  57. package/dist/queue/processor.js +98 -0
  58. package/dist/queue/processor.js.map +1 -0
  59. package/dist/routes/attachments.d.ts +3 -0
  60. package/dist/routes/attachments.d.ts.map +1 -0
  61. package/dist/routes/attachments.js +168 -0
  62. package/dist/routes/attachments.js.map +1 -0
  63. package/dist/routes/credentials.d.ts +11 -0
  64. package/dist/routes/credentials.d.ts.map +1 -0
  65. package/dist/routes/credentials.js +120 -0
  66. package/dist/routes/credentials.js.map +1 -0
  67. package/dist/routes/files.js +5 -5
  68. package/dist/routes/files.js.map +1 -1
  69. package/dist/routes/health.d.ts.map +1 -1
  70. package/dist/routes/health.js +9 -1
  71. package/dist/routes/health.js.map +1 -1
  72. package/dist/routes/queue.d.ts +3 -0
  73. package/dist/routes/queue.d.ts.map +1 -0
  74. package/dist/routes/queue.js +144 -0
  75. package/dist/routes/queue.js.map +1 -0
  76. package/dist/routes/runners.d.ts +5 -0
  77. package/dist/routes/runners.d.ts.map +1 -1
  78. package/dist/routes/runners.js +42 -5
  79. package/dist/routes/runners.js.map +1 -1
  80. package/dist/routes/sessions.d.ts +2 -1
  81. package/dist/routes/sessions.d.ts.map +1 -1
  82. package/dist/routes/sessions.js +236 -11
  83. package/dist/routes/sessions.js.map +1 -1
  84. package/dist/routes/usage.d.ts +3 -0
  85. package/dist/routes/usage.d.ts.map +1 -0
  86. package/dist/routes/usage.js +64 -0
  87. package/dist/routes/usage.js.map +1 -0
  88. package/dist/routes/workspace.d.ts +4 -0
  89. package/dist/routes/workspace.d.ts.map +1 -0
  90. package/dist/routes/workspace.js +123 -0
  91. package/dist/routes/workspace.js.map +1 -0
  92. package/dist/runner/coordinator.d.ts +77 -9
  93. package/dist/runner/coordinator.d.ts.map +1 -1
  94. package/dist/runner/coordinator.js +163 -89
  95. package/dist/runner/coordinator.js.map +1 -1
  96. package/dist/runner/local-backend.d.ts +1 -0
  97. package/dist/runner/local-backend.d.ts.map +1 -1
  98. package/dist/runner/local-backend.js +7 -0
  99. package/dist/runner/local-backend.js.map +1 -1
  100. package/dist/runner/remote-backend.d.ts +2 -0
  101. package/dist/runner/remote-backend.d.ts.map +1 -1
  102. package/dist/runner/remote-backend.js +7 -0
  103. package/dist/runner/remote-backend.js.map +1 -1
  104. package/dist/runner/runner-client.d.ts +4 -0
  105. package/dist/runner/runner-client.d.ts.map +1 -1
  106. package/dist/runner/runner-client.js +12 -0
  107. package/dist/runner/runner-client.js.map +1 -1
  108. package/dist/runner/types.d.ts +4 -0
  109. package/dist/runner/types.d.ts.map +1 -1
  110. package/dist/schemas.d.ts.map +1 -1
  111. package/dist/schemas.js +115 -1
  112. package/dist/schemas.js.map +1 -1
  113. package/dist/telemetry/exporter.d.ts +16 -0
  114. package/dist/telemetry/exporter.d.ts.map +1 -0
  115. package/dist/telemetry/exporter.js +89 -0
  116. package/dist/telemetry/exporter.js.map +1 -0
  117. package/dist/usage/extractor.d.ts +18 -0
  118. package/dist/usage/extractor.d.ts.map +1 -0
  119. package/dist/usage/extractor.js +48 -0
  120. package/dist/usage/extractor.js.map +1 -0
  121. package/drizzle/pg/0000_thick_loners.sql +75 -0
  122. package/drizzle/pg/0001_rare_lester.sql +13 -0
  123. package/drizzle/pg/0002_short_shinko_yamashiro.sql +1 -0
  124. package/drizzle/pg/0003_remarkable_mastermind.sql +14 -0
  125. package/drizzle/pg/0004_warm_reaper.sql +18 -0
  126. package/drizzle/pg/0005_overconfident_mole_man.sql +14 -0
  127. package/drizzle/pg/0006_third_shiva.sql +13 -0
  128. package/drizzle/pg/0007_keen_shockwave.sql +2 -0
  129. package/drizzle/pg/meta/0000_snapshot.json +648 -0
  130. package/drizzle/pg/meta/0001_snapshot.json +743 -0
  131. package/drizzle/pg/meta/0002_snapshot.json +749 -0
  132. package/drizzle/pg/meta/0003_snapshot.json +841 -0
  133. package/drizzle/pg/meta/0004_snapshot.json +974 -0
  134. package/drizzle/pg/meta/0005_snapshot.json +1079 -0
  135. package/drizzle/pg/meta/0006_snapshot.json +1193 -0
  136. package/drizzle/pg/meta/0007_snapshot.json +1199 -0
  137. package/drizzle/pg/meta/_journal.json +62 -0
  138. package/drizzle/sqlite/0000_massive_kinsey_walden.sql +75 -0
  139. package/drizzle/sqlite/0001_quiet_phantom_reporter.sql +13 -0
  140. package/drizzle/sqlite/0002_broad_sheva_callister.sql +1 -0
  141. package/drizzle/sqlite/0003_thankful_agent_brand.sql +14 -0
  142. package/drizzle/sqlite/0004_productive_wolverine.sql +18 -0
  143. package/drizzle/sqlite/0005_chilly_carlie_cooper.sql +14 -0
  144. package/drizzle/sqlite/0006_workable_starfox.sql +13 -0
  145. package/drizzle/sqlite/0007_quick_hemingway.sql +19 -0
  146. package/drizzle/sqlite/meta/0000_snapshot.json +503 -0
  147. package/drizzle/sqlite/meta/0001_snapshot.json +587 -0
  148. package/drizzle/sqlite/meta/0002_snapshot.json +594 -0
  149. package/drizzle/sqlite/meta/0003_snapshot.json +685 -0
  150. package/drizzle/sqlite/meta/0004_snapshot.json +807 -0
  151. package/drizzle/sqlite/meta/0005_snapshot.json +897 -0
  152. package/drizzle/sqlite/meta/0006_snapshot.json +981 -0
  153. package/drizzle/sqlite/meta/0007_snapshot.json +988 -0
  154. package/drizzle/sqlite/meta/_journal.json +62 -0
  155. package/package.json +10 -5
  156. package/dist/__tests__/schema.test.d.ts +0 -2
  157. package/dist/__tests__/schema.test.d.ts.map +0 -1
  158. package/dist/__tests__/schema.test.js +0 -31
  159. package/dist/__tests__/schema.test.js.map +0 -1
  160. package/dist/db/dump-schema.d.ts +0 -10
  161. package/dist/db/dump-schema.d.ts.map +0 -1
  162. package/dist/db/dump-schema.js +0 -64
  163. package/dist/db/dump-schema.js.map +0 -1
  164. package/dist/db/pg.d.ts +0 -35
  165. package/dist/db/pg.d.ts.map +0 -1
  166. package/dist/db/pg.js +0 -272
  167. package/dist/db/pg.js.map +0 -1
  168. package/dist/db/sqlite.d.ts +0 -34
  169. package/dist/db/sqlite.d.ts.map +0 -1
  170. package/dist/db/sqlite.js +0 -296
  171. package/dist/db/sqlite.js.map +0 -1
@@ -0,0 +1,789 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { eq, and, sql, gt, lt, ne, asc, desc, inArray } from 'drizzle-orm';
3
+ /**
4
+ * Unified Db implementation backed by Drizzle ORM.
5
+ * Works with both SQLite (better-sqlite3) and PostgreSQL (pg) drivers.
6
+ */
7
+ export class DrizzleDb {
8
+ drizzle;
9
+ schema;
10
+ dialect;
11
+ closeFn;
12
+ constructor(drizzle, // DrizzleSQLiteDatabase | DrizzleNodePgDatabase
13
+ schema, dialect, closeFn) {
14
+ this.drizzle = drizzle;
15
+ this.schema = schema;
16
+ this.dialect = dialect;
17
+ this.closeFn = closeFn;
18
+ }
19
+ // -- Runners ----------------------------------------------------------------
20
+ async upsertRunner(id, host, port, maxSandboxes) {
21
+ const { runners } = this.schema;
22
+ const now = new Date().toISOString();
23
+ await this.drizzle
24
+ .insert(runners)
25
+ .values({ id, host, port, maxSandboxes, activeCount: 0, warmingCount: 0, lastHeartbeatAt: now, registeredAt: now })
26
+ .onConflictDoUpdate({
27
+ target: runners.id,
28
+ set: { host, port, maxSandboxes, lastHeartbeatAt: now },
29
+ });
30
+ return { id, host, port, maxSandboxes, activeCount: 0, warmingCount: 0, lastHeartbeatAt: now, registeredAt: now };
31
+ }
32
+ async heartbeatRunner(id, activeCount, warmingCount) {
33
+ const { runners } = this.schema;
34
+ const now = new Date().toISOString();
35
+ await this.drizzle
36
+ .update(runners)
37
+ .set({ activeCount, warmingCount, lastHeartbeatAt: now })
38
+ .where(eq(runners.id, id));
39
+ }
40
+ async getRunner(id) {
41
+ const { runners } = this.schema;
42
+ const rows = await this.drizzle
43
+ .select()
44
+ .from(runners)
45
+ .where(eq(runners.id, id))
46
+ .limit(1);
47
+ if (rows.length === 0)
48
+ return null;
49
+ const r = rows[0];
50
+ return { id: r.id, host: r.host, port: r.port, maxSandboxes: r.maxSandboxes, activeCount: r.activeCount, warmingCount: r.warmingCount, lastHeartbeatAt: r.lastHeartbeatAt, registeredAt: r.registeredAt };
51
+ }
52
+ async listHealthyRunners(cutoffIso) {
53
+ const { runners } = this.schema;
54
+ const rows = await this.drizzle
55
+ .select()
56
+ .from(runners)
57
+ .where(gt(runners.lastHeartbeatAt, cutoffIso));
58
+ return rows.map((r) => ({
59
+ id: r.id, host: r.host, port: r.port, maxSandboxes: r.maxSandboxes,
60
+ activeCount: r.activeCount, warmingCount: r.warmingCount,
61
+ lastHeartbeatAt: r.lastHeartbeatAt, registeredAt: r.registeredAt,
62
+ }));
63
+ }
64
+ async listDeadRunners(cutoffIso) {
65
+ const { runners } = this.schema;
66
+ const rows = await this.drizzle
67
+ .select()
68
+ .from(runners)
69
+ .where(sql `${runners.lastHeartbeatAt} <= ${cutoffIso}`);
70
+ return rows.map((r) => ({
71
+ id: r.id, host: r.host, port: r.port, maxSandboxes: r.maxSandboxes,
72
+ activeCount: r.activeCount, warmingCount: r.warmingCount,
73
+ lastHeartbeatAt: r.lastHeartbeatAt, registeredAt: r.registeredAt,
74
+ }));
75
+ }
76
+ async selectBestRunner(cutoffIso) {
77
+ const { runners } = this.schema;
78
+ const available = sql `${runners.maxSandboxes} - ${runners.activeCount} - ${runners.warmingCount}`;
79
+ const rows = await this.drizzle
80
+ .select()
81
+ .from(runners)
82
+ .where(and(gt(runners.lastHeartbeatAt, cutoffIso), gt(available, 0)))
83
+ .orderBy(desc(available))
84
+ .limit(1);
85
+ if (rows.length === 0)
86
+ return null;
87
+ const r = rows[0];
88
+ return { id: r.id, host: r.host, port: r.port, maxSandboxes: r.maxSandboxes, activeCount: r.activeCount, warmingCount: r.warmingCount, lastHeartbeatAt: r.lastHeartbeatAt, registeredAt: r.registeredAt };
89
+ }
90
+ async deleteRunner(id) {
91
+ const { runners } = this.schema;
92
+ await this.drizzle
93
+ .delete(runners)
94
+ .where(eq(runners.id, id));
95
+ }
96
+ async listAllRunners() {
97
+ const { runners } = this.schema;
98
+ const rows = await this.drizzle
99
+ .select()
100
+ .from(runners)
101
+ .orderBy(desc(sql `${runners.maxSandboxes} - ${runners.activeCount} - ${runners.warmingCount}`));
102
+ return rows.map((r) => ({
103
+ id: r.id, host: r.host, port: r.port, maxSandboxes: r.maxSandboxes,
104
+ activeCount: r.activeCount, warmingCount: r.warmingCount,
105
+ lastHeartbeatAt: r.lastHeartbeatAt, registeredAt: r.registeredAt,
106
+ }));
107
+ }
108
+ // -- Agents -----------------------------------------------------------------
109
+ async upsertAgent(name, path, tenantId = 'default') {
110
+ const { agents } = this.schema;
111
+ const now = new Date().toISOString();
112
+ // Check for existing agent to get id and version
113
+ const existing = await this.drizzle
114
+ .select({ id: agents.id, version: agents.version })
115
+ .from(agents)
116
+ .where(and(eq(agents.tenantId, tenantId), eq(agents.name, name)))
117
+ .limit(1);
118
+ const version = existing.length > 0 ? existing[0].version + 1 : 1;
119
+ const id = existing.length > 0 ? existing[0].id : randomUUID();
120
+ await this.drizzle
121
+ .insert(agents)
122
+ .values({ id, tenantId, name, version, path, createdAt: now, updatedAt: now })
123
+ .onConflictDoUpdate({
124
+ target: [agents.tenantId, agents.name],
125
+ set: { version, path, updatedAt: now },
126
+ });
127
+ return { id, name, tenantId, version, path, createdAt: now, updatedAt: now };
128
+ }
129
+ async getAgent(name, tenantId = 'default') {
130
+ const { agents } = this.schema;
131
+ const rows = await this.drizzle
132
+ .select()
133
+ .from(agents)
134
+ .where(and(eq(agents.tenantId, tenantId), eq(agents.name, name)))
135
+ .limit(1);
136
+ if (rows.length === 0)
137
+ return null;
138
+ const r = rows[0];
139
+ return { id: r.id, name: r.name, tenantId: r.tenantId, version: r.version, path: r.path, createdAt: r.createdAt, updatedAt: r.updatedAt };
140
+ }
141
+ async listAgents(tenantId = 'default') {
142
+ const { agents } = this.schema;
143
+ const rows = await this.drizzle
144
+ .select()
145
+ .from(agents)
146
+ .where(eq(agents.tenantId, tenantId))
147
+ .orderBy(asc(agents.name));
148
+ return rows.map((r) => ({ id: r.id, name: r.name, tenantId: r.tenantId, version: r.version, path: r.path, createdAt: r.createdAt, updatedAt: r.updatedAt }));
149
+ }
150
+ async deleteAgent(name, tenantId = 'default') {
151
+ const { agents, sessions } = this.schema;
152
+ // Delete related sessions first
153
+ await this.drizzle
154
+ .delete(sessions)
155
+ .where(and(eq(sessions.agentName, name), eq(sessions.tenantId, tenantId)));
156
+ const result = await this.drizzle
157
+ .delete(agents)
158
+ .where(and(eq(agents.name, name), eq(agents.tenantId, tenantId)));
159
+ // Drizzle returns { changes } for sqlite and { rowCount } for pg
160
+ return (result.changes ?? result.rowCount ?? 0) > 0;
161
+ }
162
+ // -- Sessions ---------------------------------------------------------------
163
+ async insertSession(id, agentName, sandboxId, tenantId = 'default', parentSessionId) {
164
+ const { sessions } = this.schema;
165
+ const now = new Date().toISOString();
166
+ await this.drizzle
167
+ .insert(sessions)
168
+ .values({ id, tenantId, agentName, sandboxId, status: 'starting', parentSessionId: parentSessionId ?? null, createdAt: now, lastActiveAt: now });
169
+ return { id, tenantId, agentName, sandboxId, status: 'starting', parentSessionId: parentSessionId ?? null, createdAt: now, lastActiveAt: now };
170
+ }
171
+ async insertForkedSession(id, parentSession, sandboxId) {
172
+ const { sessions, messages } = this.schema;
173
+ const now = new Date().toISOString();
174
+ const tenantId = parentSession.tenantId ?? 'default';
175
+ // 1. Create new session linked to parent
176
+ await this.drizzle
177
+ .insert(sessions)
178
+ .values({ id, tenantId, agentName: parentSession.agentName, sandboxId, status: 'paused', parentSessionId: parentSession.id, createdAt: now, lastActiveAt: now });
179
+ // 2. Copy all messages from parent session with new IDs and session reference
180
+ const parentMessages = await this.drizzle
181
+ .select()
182
+ .from(messages)
183
+ .where(and(eq(messages.tenantId, tenantId), eq(messages.sessionId, parentSession.id)))
184
+ .orderBy(asc(messages.sequence));
185
+ if (parentMessages.length > 0) {
186
+ const copied = parentMessages.map((m) => ({
187
+ id: randomUUID(),
188
+ tenantId,
189
+ sessionId: id,
190
+ role: m.role,
191
+ content: m.content,
192
+ sequence: m.sequence,
193
+ createdAt: m.createdAt,
194
+ }));
195
+ await this.drizzle.insert(messages).values(copied);
196
+ }
197
+ return { id, tenantId, agentName: parentSession.agentName, sandboxId, status: 'paused', parentSessionId: parentSession.id, createdAt: now, lastActiveAt: now };
198
+ }
199
+ async updateSessionStatus(id, status) {
200
+ const { sessions } = this.schema;
201
+ const now = new Date().toISOString();
202
+ await this.drizzle
203
+ .update(sessions)
204
+ .set({ status, lastActiveAt: now })
205
+ .where(eq(sessions.id, id));
206
+ }
207
+ async updateSessionSandbox(id, sandboxId) {
208
+ const { sessions } = this.schema;
209
+ const now = new Date().toISOString();
210
+ await this.drizzle
211
+ .update(sessions)
212
+ .set({ sandboxId, lastActiveAt: now })
213
+ .where(eq(sessions.id, id));
214
+ }
215
+ async updateSessionRunner(id, runnerId) {
216
+ const { sessions } = this.schema;
217
+ const now = new Date().toISOString();
218
+ await this.drizzle
219
+ .update(sessions)
220
+ .set({ runnerId, lastActiveAt: now })
221
+ .where(eq(sessions.id, id));
222
+ }
223
+ async getSession(id) {
224
+ const { sessions } = this.schema;
225
+ const rows = await this.drizzle
226
+ .select()
227
+ .from(sessions)
228
+ .where(eq(sessions.id, id))
229
+ .limit(1);
230
+ if (rows.length === 0)
231
+ return null;
232
+ const r = rows[0];
233
+ return { id: r.id, tenantId: r.tenantId, agentName: r.agentName, sandboxId: r.sandboxId, status: r.status, runnerId: r.runnerId ?? null, parentSessionId: r.parentSessionId ?? null, createdAt: r.createdAt, lastActiveAt: r.lastActiveAt };
234
+ }
235
+ async listSessions(tenantId = 'default', agent) {
236
+ const { sessions } = this.schema;
237
+ const condition = agent
238
+ ? and(eq(sessions.tenantId, tenantId), eq(sessions.agentName, agent))
239
+ : eq(sessions.tenantId, tenantId);
240
+ const rows = await this.drizzle
241
+ .select()
242
+ .from(sessions)
243
+ .where(condition)
244
+ .orderBy(desc(sessions.createdAt));
245
+ return rows.map((r) => ({
246
+ id: r.id, tenantId: r.tenantId, agentName: r.agentName, sandboxId: r.sandboxId,
247
+ status: r.status, runnerId: r.runnerId ?? null, parentSessionId: r.parentSessionId ?? null, createdAt: r.createdAt, lastActiveAt: r.lastActiveAt,
248
+ }));
249
+ }
250
+ async listSessionsByRunner(runnerId) {
251
+ const { sessions } = this.schema;
252
+ const rows = await this.drizzle
253
+ .select()
254
+ .from(sessions)
255
+ .where(eq(sessions.runnerId, runnerId))
256
+ .orderBy(desc(sessions.createdAt));
257
+ return rows.map((r) => ({
258
+ id: r.id, tenantId: r.tenantId, agentName: r.agentName, sandboxId: r.sandboxId,
259
+ status: r.status, runnerId: r.runnerId ?? null, parentSessionId: r.parentSessionId ?? null, createdAt: r.createdAt, lastActiveAt: r.lastActiveAt,
260
+ }));
261
+ }
262
+ async bulkPauseSessionsByRunner(runnerId) {
263
+ const { sessions } = this.schema;
264
+ const now = new Date().toISOString();
265
+ const result = await this.drizzle
266
+ .update(sessions)
267
+ .set({ status: 'paused', lastActiveAt: now })
268
+ .where(and(eq(sessions.runnerId, runnerId), inArray(sessions.status, ['active', 'starting'])));
269
+ return result.changes ?? result.rowCount ?? 0;
270
+ }
271
+ async touchSession(id) {
272
+ const { sessions } = this.schema;
273
+ const now = new Date().toISOString();
274
+ await this.drizzle
275
+ .update(sessions)
276
+ .set({ lastActiveAt: now })
277
+ .where(eq(sessions.id, id));
278
+ }
279
+ // -- Sandboxes --------------------------------------------------------------
280
+ async insertSandbox(id, agentName, workspaceDir, sessionId, tenantId = 'default') {
281
+ const { sandboxes } = this.schema;
282
+ const now = new Date().toISOString();
283
+ await this.drizzle
284
+ .insert(sandboxes)
285
+ .values({ id, tenantId, agentName, workspaceDir, sessionId: sessionId ?? null, state: 'warming', createdAt: now, lastUsedAt: now });
286
+ }
287
+ async updateSandboxState(id, state) {
288
+ const { sandboxes } = this.schema;
289
+ await this.drizzle
290
+ .update(sandboxes)
291
+ .set({ state })
292
+ .where(eq(sandboxes.id, id));
293
+ }
294
+ async updateSandboxSession(id, sessionId) {
295
+ const { sandboxes } = this.schema;
296
+ await this.drizzle
297
+ .update(sandboxes)
298
+ .set({ sessionId })
299
+ .where(eq(sandboxes.id, id));
300
+ }
301
+ async touchSandbox(id) {
302
+ const { sandboxes } = this.schema;
303
+ const now = new Date().toISOString();
304
+ await this.drizzle
305
+ .update(sandboxes)
306
+ .set({ lastUsedAt: now })
307
+ .where(eq(sandboxes.id, id));
308
+ }
309
+ async getSandbox(id) {
310
+ const { sandboxes } = this.schema;
311
+ const rows = await this.drizzle
312
+ .select()
313
+ .from(sandboxes)
314
+ .where(eq(sandboxes.id, id))
315
+ .limit(1);
316
+ if (rows.length === 0)
317
+ return null;
318
+ const r = rows[0];
319
+ return { id: r.id, sessionId: r.sessionId, agentName: r.agentName, state: r.state, workspaceDir: r.workspaceDir, createdAt: r.createdAt, lastUsedAt: r.lastUsedAt };
320
+ }
321
+ async countSandboxes() {
322
+ const { sandboxes } = this.schema;
323
+ const rows = await this.drizzle
324
+ .select({ count: sql `count(*)` })
325
+ .from(sandboxes);
326
+ // SQLite returns number directly, PG returns string
327
+ return typeof rows[0].count === 'string' ? parseInt(rows[0].count, 10) : rows[0].count;
328
+ }
329
+ async getBestEvictionCandidate() {
330
+ const { sandboxes } = this.schema;
331
+ const rows = await this.drizzle
332
+ .select()
333
+ .from(sandboxes)
334
+ .where(inArray(sandboxes.state, ['cold', 'warm', 'waiting']))
335
+ .orderBy(sql `CASE ${sandboxes.state} WHEN 'cold' THEN 0 WHEN 'warm' THEN 1 WHEN 'waiting' THEN 2 END`, asc(sandboxes.lastUsedAt))
336
+ .limit(1);
337
+ if (rows.length === 0)
338
+ return null;
339
+ const r = rows[0];
340
+ return { id: r.id, sessionId: r.sessionId, agentName: r.agentName, state: r.state, workspaceDir: r.workspaceDir, createdAt: r.createdAt, lastUsedAt: r.lastUsedAt };
341
+ }
342
+ async getIdleSandboxes(olderThan) {
343
+ const { sandboxes } = this.schema;
344
+ const rows = await this.drizzle
345
+ .select()
346
+ .from(sandboxes)
347
+ .where(and(eq(sandboxes.state, 'waiting'), lt(sandboxes.lastUsedAt, olderThan)))
348
+ .orderBy(asc(sandboxes.lastUsedAt));
349
+ return rows.map((r) => ({
350
+ id: r.id, sessionId: r.sessionId, agentName: r.agentName,
351
+ state: r.state, workspaceDir: r.workspaceDir, createdAt: r.createdAt, lastUsedAt: r.lastUsedAt,
352
+ }));
353
+ }
354
+ async deleteSandbox(id) {
355
+ const { sandboxes } = this.schema;
356
+ await this.drizzle
357
+ .delete(sandboxes)
358
+ .where(eq(sandboxes.id, id));
359
+ }
360
+ async markAllSandboxesCold() {
361
+ const { sandboxes } = this.schema;
362
+ const result = await this.drizzle
363
+ .update(sandboxes)
364
+ .set({ state: 'cold' })
365
+ .where(ne(sandboxes.state, 'cold'));
366
+ return result.changes ?? result.rowCount ?? 0;
367
+ }
368
+ // -- Messages ---------------------------------------------------------------
369
+ async insertMessage(sessionId, role, content, tenantId = 'default') {
370
+ const { messages } = this.schema;
371
+ const id = randomUUID();
372
+ const now = new Date().toISOString();
373
+ if (this.dialect === 'pg') {
374
+ // PG: atomic subquery for sequence assignment (no TOCTOU race)
375
+ const rows = await this.drizzle.execute(sql `INSERT INTO messages (id, tenant_id, session_id, role, content, sequence, created_at)
376
+ VALUES (${id}, ${tenantId}, ${sessionId}, ${role}, ${content},
377
+ COALESCE((SELECT MAX(sequence) FROM messages WHERE tenant_id = ${tenantId} AND session_id = ${sessionId}), 0) + 1,
378
+ ${now})
379
+ RETURNING sequence`);
380
+ return { id, sessionId, tenantId, role, content, sequence: rows.rows[0].sequence, createdAt: now };
381
+ }
382
+ // SQLite: synchronous driver, no race condition risk
383
+ const last = await this.drizzle
384
+ .select({ maxSeq: sql `MAX(${messages.sequence})` })
385
+ .from(messages)
386
+ .where(and(eq(messages.tenantId, tenantId), eq(messages.sessionId, sessionId)));
387
+ const sequence = (last[0]?.maxSeq ?? 0) + 1;
388
+ await this.drizzle
389
+ .insert(messages)
390
+ .values({ id, tenantId, sessionId, role, content, sequence, createdAt: now });
391
+ return { id, sessionId, tenantId, role, content, sequence, createdAt: now };
392
+ }
393
+ async listMessages(sessionId, tenantId = 'default', opts) {
394
+ const { messages } = this.schema;
395
+ const limit = opts?.limit ?? 100;
396
+ const afterSeq = opts?.afterSequence ?? 0;
397
+ const rows = await this.drizzle
398
+ .select()
399
+ .from(messages)
400
+ .where(and(eq(messages.tenantId, tenantId), eq(messages.sessionId, sessionId), gt(messages.sequence, afterSeq)))
401
+ .orderBy(asc(messages.sequence))
402
+ .limit(limit);
403
+ return rows.map((r) => ({
404
+ id: r.id, sessionId: r.sessionId, tenantId: r.tenantId,
405
+ role: r.role, content: r.content, sequence: r.sequence, createdAt: r.createdAt,
406
+ }));
407
+ }
408
+ // -- Session Events ---------------------------------------------------------
409
+ async insertSessionEvent(sessionId, type, data, tenantId = 'default') {
410
+ const { sessionEvents } = this.schema;
411
+ const id = randomUUID();
412
+ const now = new Date().toISOString();
413
+ if (this.dialect === 'pg') {
414
+ const rows = await this.drizzle.execute(sql `INSERT INTO session_events (id, tenant_id, session_id, type, data, sequence, created_at)
415
+ VALUES (${id}, ${tenantId}, ${sessionId}, ${type}, ${data},
416
+ COALESCE((SELECT MAX(sequence) FROM session_events WHERE tenant_id = ${tenantId} AND session_id = ${sessionId}), 0) + 1,
417
+ ${now})
418
+ RETURNING sequence`);
419
+ return { id, sessionId, tenantId, type, data, sequence: rows.rows[0].sequence, createdAt: now };
420
+ }
421
+ // SQLite
422
+ const last = await this.drizzle
423
+ .select({ maxSeq: sql `MAX(${sessionEvents.sequence})` })
424
+ .from(sessionEvents)
425
+ .where(and(eq(sessionEvents.tenantId, tenantId), eq(sessionEvents.sessionId, sessionId)));
426
+ const sequence = (last[0]?.maxSeq ?? 0) + 1;
427
+ await this.drizzle
428
+ .insert(sessionEvents)
429
+ .values({ id, tenantId, sessionId, type, data, sequence, createdAt: now });
430
+ return { id, sessionId, tenantId, type, data, sequence, createdAt: now };
431
+ }
432
+ async insertSessionEvents(events) {
433
+ if (events.length === 0)
434
+ return [];
435
+ if (events.length === 1)
436
+ return [await this.insertSessionEvent(events[0].sessionId, events[0].type, events[0].data, events[0].tenantId)];
437
+ const { sessionEvents } = this.schema;
438
+ const results = [];
439
+ if (this.dialect === 'pg') {
440
+ // PG: use a transaction with raw SQL for atomic sequence assignment
441
+ await this.drizzle.transaction(async (tx) => {
442
+ for (const ev of events) {
443
+ const id = randomUUID();
444
+ const now = new Date().toISOString();
445
+ const tenantId = ev.tenantId ?? 'default';
446
+ const rows = await tx.execute(sql `INSERT INTO session_events (id, tenant_id, session_id, type, data, sequence, created_at)
447
+ VALUES (${id}, ${tenantId}, ${ev.sessionId}, ${ev.type}, ${ev.data},
448
+ COALESCE((SELECT MAX(sequence) FROM session_events WHERE tenant_id = ${tenantId} AND session_id = ${ev.sessionId}), 0) + 1,
449
+ ${now})
450
+ RETURNING sequence`);
451
+ results.push({ id, sessionId: ev.sessionId, tenantId, type: ev.type, data: ev.data, sequence: rows.rows[0].sequence, createdAt: now });
452
+ }
453
+ });
454
+ return results;
455
+ }
456
+ // SQLite: transaction with sequential sequence computation
457
+ return this.drizzle.transaction((tx) => {
458
+ const groups = new Map();
459
+ for (const ev of events) {
460
+ const tenantId = ev.tenantId ?? 'default';
461
+ const key = `${tenantId}:${ev.sessionId}`;
462
+ if (!groups.has(key)) {
463
+ const last = tx
464
+ .select({ maxSeq: sql `MAX(${sessionEvents.sequence})` })
465
+ .from(sessionEvents)
466
+ .where(and(eq(sessionEvents.tenantId, tenantId), eq(sessionEvents.sessionId, ev.sessionId)))
467
+ .get();
468
+ groups.set(key, last?.maxSeq ?? 0);
469
+ }
470
+ const sequence = groups.get(key) + 1;
471
+ groups.set(key, sequence);
472
+ const id = randomUUID();
473
+ const now = new Date().toISOString();
474
+ tx.insert(sessionEvents)
475
+ .values({ id, tenantId, sessionId: ev.sessionId, type: ev.type, data: ev.data, sequence, createdAt: now })
476
+ .run();
477
+ results.push({ id, sessionId: ev.sessionId, tenantId, type: ev.type, data: ev.data, sequence, createdAt: now });
478
+ }
479
+ return results;
480
+ });
481
+ }
482
+ async listSessionEvents(sessionId, tenantId = 'default', opts) {
483
+ const { sessionEvents } = this.schema;
484
+ const limit = opts?.limit ?? 200;
485
+ const afterSeq = opts?.afterSequence ?? 0;
486
+ const conditions = [
487
+ eq(sessionEvents.tenantId, tenantId),
488
+ eq(sessionEvents.sessionId, sessionId),
489
+ gt(sessionEvents.sequence, afterSeq),
490
+ ];
491
+ if (opts?.type) {
492
+ conditions.push(eq(sessionEvents.type, opts.type));
493
+ }
494
+ const rows = await this.drizzle
495
+ .select()
496
+ .from(sessionEvents)
497
+ .where(and(...conditions))
498
+ .orderBy(asc(sessionEvents.sequence))
499
+ .limit(limit);
500
+ return rows.map((r) => ({
501
+ id: r.id, sessionId: r.sessionId, tenantId: r.tenantId,
502
+ type: r.type, data: r.data, sequence: r.sequence, createdAt: r.createdAt,
503
+ }));
504
+ }
505
+ // -- API Keys ---------------------------------------------------------------
506
+ async getApiKeyByHash(keyHash) {
507
+ const { apiKeys } = this.schema;
508
+ const rows = await this.drizzle
509
+ .select()
510
+ .from(apiKeys)
511
+ .where(eq(apiKeys.keyHash, keyHash))
512
+ .limit(1);
513
+ if (rows.length === 0)
514
+ return null;
515
+ const r = rows[0];
516
+ return { id: r.id, tenantId: r.tenantId, keyHash: r.keyHash, label: r.label, createdAt: r.createdAt };
517
+ }
518
+ async insertApiKey(id, tenantId, keyHash, label) {
519
+ const { apiKeys } = this.schema;
520
+ const now = new Date().toISOString();
521
+ await this.drizzle
522
+ .insert(apiKeys)
523
+ .values({ id, tenantId, keyHash, label, createdAt: now });
524
+ return { id, tenantId, keyHash, label, createdAt: now };
525
+ }
526
+ async listApiKeysByTenant(tenantId) {
527
+ const { apiKeys } = this.schema;
528
+ const rows = await this.drizzle
529
+ .select()
530
+ .from(apiKeys)
531
+ .where(eq(apiKeys.tenantId, tenantId))
532
+ .orderBy(desc(apiKeys.createdAt));
533
+ return rows.map((r) => ({ id: r.id, tenantId: r.tenantId, keyHash: r.keyHash, label: r.label, createdAt: r.createdAt }));
534
+ }
535
+ async deleteApiKey(id) {
536
+ const { apiKeys } = this.schema;
537
+ const result = await this.drizzle
538
+ .delete(apiKeys)
539
+ .where(eq(apiKeys.id, id));
540
+ return (result.changes ?? result.rowCount ?? 0) > 0;
541
+ }
542
+ // -- Credentials ------------------------------------------------------------
543
+ async insertCredential(id, tenantId, type, encryptedKey, iv, authTag, label) {
544
+ const { credentials } = this.schema;
545
+ const now = new Date().toISOString();
546
+ await this.drizzle
547
+ .insert(credentials)
548
+ .values({ id, tenantId, type, encryptedKey, iv, authTag, label, active: 1, createdAt: now, lastUsedAt: null });
549
+ return { id, tenantId, type: type, label, active: true, createdAt: now, lastUsedAt: null };
550
+ }
551
+ async getCredential(id) {
552
+ const { credentials } = this.schema;
553
+ const rows = await this.drizzle
554
+ .select()
555
+ .from(credentials)
556
+ .where(eq(credentials.id, id))
557
+ .limit(1);
558
+ if (rows.length === 0)
559
+ return null;
560
+ const r = rows[0];
561
+ return { id: r.id, tenantId: r.tenantId, type: r.type, encryptedKey: r.encryptedKey, iv: r.iv, authTag: r.authTag, label: r.label, active: r.active === 1, createdAt: r.createdAt, lastUsedAt: r.lastUsedAt };
562
+ }
563
+ async listCredentials(tenantId) {
564
+ const { credentials } = this.schema;
565
+ const rows = await this.drizzle
566
+ .select()
567
+ .from(credentials)
568
+ .where(eq(credentials.tenantId, tenantId))
569
+ .orderBy(desc(credentials.createdAt));
570
+ return rows.map((r) => ({
571
+ id: r.id, tenantId: r.tenantId, type: r.type, label: r.label, active: r.active === 1, createdAt: r.createdAt, lastUsedAt: r.lastUsedAt,
572
+ }));
573
+ }
574
+ async deleteCredential(id) {
575
+ const { credentials } = this.schema;
576
+ const result = await this.drizzle
577
+ .delete(credentials)
578
+ .where(eq(credentials.id, id));
579
+ return (result.changes ?? result.rowCount ?? 0) > 0;
580
+ }
581
+ async touchCredentialUsed(id) {
582
+ const { credentials } = this.schema;
583
+ const now = new Date().toISOString();
584
+ await this.drizzle
585
+ .update(credentials)
586
+ .set({ lastUsedAt: now })
587
+ .where(eq(credentials.id, id));
588
+ }
589
+ // -- Queue ------------------------------------------------------------------
590
+ async insertQueueItem(id, tenantId, agentName, prompt, sessionId, priority, maxRetries) {
591
+ const { queueItems } = this.schema;
592
+ const now = new Date().toISOString();
593
+ const item = { id, tenantId, sessionId: sessionId ?? null, agentName, prompt, status: 'pending', priority: priority ?? 0, retryCount: 0, maxRetries: maxRetries ?? 3, error: null, createdAt: now, startedAt: null, completedAt: null };
594
+ await this.drizzle.insert(queueItems).values(item);
595
+ return item;
596
+ }
597
+ async getQueueItem(id) {
598
+ const { queueItems } = this.schema;
599
+ const rows = await this.drizzle.select().from(queueItems).where(eq(queueItems.id, id)).limit(1);
600
+ if (rows.length === 0)
601
+ return null;
602
+ return rows[0];
603
+ }
604
+ async getNextPendingQueueItem(tenantId) {
605
+ const { queueItems } = this.schema;
606
+ const now = new Date().toISOString();
607
+ const conditions = [
608
+ eq(queueItems.status, 'pending'),
609
+ // Only return items that are eligible (no retryAfter or retryAfter has passed)
610
+ sql `(${queueItems.retryAfter} IS NULL OR ${queueItems.retryAfter} <= ${now})`,
611
+ ];
612
+ if (tenantId)
613
+ conditions.push(eq(queueItems.tenantId, tenantId));
614
+ const rows = await this.drizzle.select().from(queueItems).where(and(...conditions)).orderBy(desc(queueItems.priority), asc(queueItems.createdAt)).limit(1);
615
+ if (rows.length === 0)
616
+ return null;
617
+ return rows[0];
618
+ }
619
+ /**
620
+ * Atomically claim a queue item by setting status to 'processing'
621
+ * only if it is still 'pending'. Returns true if the claim succeeded.
622
+ */
623
+ async claimQueueItem(id) {
624
+ const { queueItems } = this.schema;
625
+ const now = new Date().toISOString();
626
+ const result = await this.drizzle
627
+ .update(queueItems)
628
+ .set({ status: 'processing', startedAt: now })
629
+ .where(and(eq(queueItems.id, id), eq(queueItems.status, 'pending')));
630
+ return (result.changes ?? result.rowCount ?? 0) > 0;
631
+ }
632
+ async updateQueueItemStatus(id, status, error) {
633
+ const { queueItems } = this.schema;
634
+ const now = new Date().toISOString();
635
+ const set = { status };
636
+ if (status === 'processing')
637
+ set.startedAt = now;
638
+ if (status === 'completed' || status === 'failed')
639
+ set.completedAt = now;
640
+ if (error !== undefined)
641
+ set.error = error;
642
+ await this.drizzle.update(queueItems).set(set).where(eq(queueItems.id, id));
643
+ }
644
+ async incrementQueueItemRetry(id, retryAfter) {
645
+ const { queueItems } = this.schema;
646
+ const set = { retryCount: sql `${queueItems.retryCount} + 1` };
647
+ if (retryAfter)
648
+ set.retryAfter = retryAfter;
649
+ await this.drizzle
650
+ .update(queueItems)
651
+ .set(set)
652
+ .where(eq(queueItems.id, id));
653
+ }
654
+ async listQueueItems(tenantId, status, limit = 50) {
655
+ const { queueItems } = this.schema;
656
+ const conditions = [eq(queueItems.tenantId, tenantId)];
657
+ if (status)
658
+ conditions.push(eq(queueItems.status, status));
659
+ const rows = await this.drizzle.select().from(queueItems).where(and(...conditions)).orderBy(desc(queueItems.createdAt)).limit(limit);
660
+ return rows;
661
+ }
662
+ async getQueueStats(tenantId) {
663
+ const { queueItems } = this.schema;
664
+ const rows = await this.drizzle
665
+ .select({ status: queueItems.status, count: sql `count(*)` })
666
+ .from(queueItems)
667
+ .where(eq(queueItems.tenantId, tenantId))
668
+ .groupBy(queueItems.status);
669
+ const stats = { pending: 0, processing: 0, completed: 0, failed: 0, cancelled: 0 };
670
+ for (const r of rows) {
671
+ const s = r.status;
672
+ if (s in stats)
673
+ stats[s] = Number(r.count);
674
+ }
675
+ return stats;
676
+ }
677
+ // -- Attachments -------------------------------------------------------------
678
+ async insertAttachment(id, tenantId, messageId, sessionId, filename, mimeType, size, storagePath) {
679
+ const { attachments } = this.schema;
680
+ const now = new Date().toISOString();
681
+ const row = { id, tenantId, messageId, sessionId, filename, mimeType, size, storagePath, createdAt: now };
682
+ await this.drizzle.insert(attachments).values(row);
683
+ return row;
684
+ }
685
+ async getAttachment(id) {
686
+ const { attachments } = this.schema;
687
+ const rows = await this.drizzle.select().from(attachments).where(eq(attachments.id, id)).limit(1);
688
+ if (rows.length === 0)
689
+ return null;
690
+ return rows[0];
691
+ }
692
+ async listAttachmentsByMessage(messageId, tenantId) {
693
+ const { attachments } = this.schema;
694
+ const conditions = [eq(attachments.messageId, messageId)];
695
+ if (tenantId)
696
+ conditions.push(eq(attachments.tenantId, tenantId));
697
+ const rows = await this.drizzle.select().from(attachments).where(and(...conditions)).orderBy(asc(attachments.createdAt));
698
+ return rows;
699
+ }
700
+ async listAttachmentsBySession(sessionId, tenantId) {
701
+ const { attachments } = this.schema;
702
+ const conditions = [eq(attachments.sessionId, sessionId)];
703
+ if (tenantId)
704
+ conditions.push(eq(attachments.tenantId, tenantId));
705
+ const rows = await this.drizzle.select().from(attachments).where(and(...conditions)).orderBy(asc(attachments.createdAt));
706
+ return rows;
707
+ }
708
+ async deleteAttachment(id) {
709
+ const { attachments } = this.schema;
710
+ const result = await this.drizzle.delete(attachments).where(eq(attachments.id, id));
711
+ return (result?.rowsAffected ?? result?.changes ?? 0) > 0;
712
+ }
713
+ // -- Usage ------------------------------------------------------------------
714
+ async insertUsageEvent(id, tenantId, sessionId, agentName, eventType, value) {
715
+ const { usageEvents } = this.schema;
716
+ const now = new Date().toISOString();
717
+ const row = { id, tenantId, sessionId, agentName, eventType, value, createdAt: now };
718
+ await this.drizzle.insert(usageEvents).values(row);
719
+ return row;
720
+ }
721
+ async insertUsageEvents(events) {
722
+ if (events.length === 0)
723
+ return;
724
+ const { usageEvents } = this.schema;
725
+ const now = new Date().toISOString();
726
+ await this.drizzle.insert(usageEvents).values(events.map(e => ({ ...e, createdAt: now })));
727
+ }
728
+ async listUsageEvents(tenantId, opts) {
729
+ const { usageEvents } = this.schema;
730
+ const conditions = [eq(usageEvents.tenantId, tenantId)];
731
+ if (opts?.sessionId)
732
+ conditions.push(eq(usageEvents.sessionId, opts.sessionId));
733
+ if (opts?.agentName)
734
+ conditions.push(eq(usageEvents.agentName, opts.agentName));
735
+ if (opts?.after)
736
+ conditions.push(sql `${usageEvents.createdAt} >= ${opts.after}`);
737
+ if (opts?.before)
738
+ conditions.push(sql `${usageEvents.createdAt} <= ${opts.before}`);
739
+ const limit = opts?.limit ?? 100;
740
+ const rows = await this.drizzle.select().from(usageEvents).where(and(...conditions)).orderBy(desc(usageEvents.createdAt)).limit(limit);
741
+ return rows;
742
+ }
743
+ async getUsageStats(tenantId, opts) {
744
+ const { usageEvents } = this.schema;
745
+ const conditions = [eq(usageEvents.tenantId, tenantId)];
746
+ if (opts?.sessionId)
747
+ conditions.push(eq(usageEvents.sessionId, opts.sessionId));
748
+ if (opts?.agentName)
749
+ conditions.push(eq(usageEvents.agentName, opts.agentName));
750
+ if (opts?.after)
751
+ conditions.push(sql `${usageEvents.createdAt} >= ${opts.after}`);
752
+ if (opts?.before)
753
+ conditions.push(sql `${usageEvents.createdAt} <= ${opts.before}`);
754
+ const rows = await this.drizzle
755
+ .select({ eventType: usageEvents.eventType, total: sql `sum(${usageEvents.value})` })
756
+ .from(usageEvents)
757
+ .where(and(...conditions))
758
+ .groupBy(usageEvents.eventType);
759
+ const stats = {
760
+ totalInputTokens: 0,
761
+ totalOutputTokens: 0,
762
+ totalCacheCreationTokens: 0,
763
+ totalCacheReadTokens: 0,
764
+ totalToolCalls: 0,
765
+ totalMessages: 0,
766
+ totalComputeSeconds: 0,
767
+ };
768
+ const map = {
769
+ input_tokens: 'totalInputTokens',
770
+ output_tokens: 'totalOutputTokens',
771
+ cache_creation_tokens: 'totalCacheCreationTokens',
772
+ cache_read_tokens: 'totalCacheReadTokens',
773
+ tool_call: 'totalToolCalls',
774
+ message: 'totalMessages',
775
+ compute_seconds: 'totalComputeSeconds',
776
+ };
777
+ for (const r of rows) {
778
+ const key = map[r.eventType];
779
+ if (key)
780
+ stats[key] = Number(r.total);
781
+ }
782
+ return stats;
783
+ }
784
+ // -- Lifecycle --------------------------------------------------------------
785
+ async close() {
786
+ await this.closeFn();
787
+ }
788
+ }
789
+ //# sourceMappingURL=drizzle-db.js.map