@sixb/sqlite 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/dist/action-run-storage.d.ts +29 -0
- package/dist/action-run-storage.d.ts.map +1 -0
- package/dist/agents/agent-storage.d.ts +25 -0
- package/dist/agents/agent-storage.d.ts.map +1 -0
- package/dist/agents/index.d.ts +6 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/messages.d.ts +14 -0
- package/dist/agents/messages.d.ts.map +1 -0
- package/dist/agents/rows.d.ts +77 -0
- package/dist/agents/rows.d.ts.map +1 -0
- package/dist/agents/runs.d.ts +26 -0
- package/dist/agents/runs.d.ts.map +1 -0
- package/dist/agents/threads.d.ts +14 -0
- package/dist/agents/threads.d.ts.map +1 -0
- package/dist/auth-storage/access-tokens.d.ts +33 -0
- package/dist/auth-storage/access-tokens.d.ts.map +1 -0
- package/dist/auth-storage/auth-storage.d.ts +46 -0
- package/dist/auth-storage/auth-storage.d.ts.map +1 -0
- package/dist/auth-storage/group-memberships.d.ts +21 -0
- package/dist/auth-storage/group-memberships.d.ts.map +1 -0
- package/dist/auth-storage/identities.d.ts +17 -0
- package/dist/auth-storage/identities.d.ts.map +1 -0
- package/dist/auth-storage/index.d.ts +3 -0
- package/dist/auth-storage/index.d.ts.map +1 -0
- package/dist/auth-storage/invitations.d.ts +28 -0
- package/dist/auth-storage/invitations.d.ts.map +1 -0
- package/dist/auth-storage/magic-links.d.ts +28 -0
- package/dist/auth-storage/magic-links.d.ts.map +1 -0
- package/dist/auth-storage/oidc-attempts.d.ts +18 -0
- package/dist/auth-storage/oidc-attempts.d.ts.map +1 -0
- package/dist/auth-storage/rows.d.ts +134 -0
- package/dist/auth-storage/rows.d.ts.map +1 -0
- package/dist/auth-storage/service-account-group-memberships.d.ts +17 -0
- package/dist/auth-storage/service-account-group-memberships.d.ts.map +1 -0
- package/dist/auth-storage/service-accounts.d.ts +14 -0
- package/dist/auth-storage/service-accounts.d.ts.map +1 -0
- package/dist/auth-storage/sessions.d.ts +55 -0
- package/dist/auth-storage/sessions.d.ts.map +1 -0
- package/dist/auth-storage/shared.d.ts +131 -0
- package/dist/auth-storage/shared.d.ts.map +1 -0
- package/dist/auth-storage/users.d.ts +19 -0
- package/dist/auth-storage/users.d.ts.map +1 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10204 -0
- package/dist/index.js.map +54 -0
- package/dist/latest-run-query.d.ts +22 -0
- package/dist/latest-run-query.d.ts.map +1 -0
- package/dist/migrations.d.ts +18 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/object-query-compiler.d.ts +44 -0
- package/dist/object-query-compiler.d.ts.map +1 -0
- package/dist/object-storage.d.ts +93 -0
- package/dist/object-storage.d.ts.map +1 -0
- package/dist/ontology-storage/commits.d.ts +14 -0
- package/dist/ontology-storage/commits.d.ts.map +1 -0
- package/dist/ontology-storage/index.d.ts +22 -0
- package/dist/ontology-storage/index.d.ts.map +1 -0
- package/dist/ontology-storage/materialization-session.d.ts +49 -0
- package/dist/ontology-storage/materialization-session.d.ts.map +1 -0
- package/dist/ontology-storage/materialization-state.d.ts +63 -0
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -0
- package/dist/ontology-storage/materialization-writer.d.ts +15 -0
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -0
- package/dist/ontology-storage/materializations.d.ts +32 -0
- package/dist/ontology-storage/materializations.d.ts.map +1 -0
- package/dist/ontology-storage/outbox.d.ts +14 -0
- package/dist/ontology-storage/outbox.d.ts.map +1 -0
- package/dist/ontology-storage/shared.d.ts +91 -0
- package/dist/ontology-storage/shared.d.ts.map +1 -0
- package/dist/ontology-storage/sources.d.ts +26 -0
- package/dist/ontology-storage/sources.d.ts.map +1 -0
- package/dist/pipeline-run-storage.d.ts +26 -0
- package/dist/pipeline-run-storage.d.ts.map +1 -0
- package/dist/projection-run-storage.d.ts +32 -0
- package/dist/projection-run-storage.d.ts.map +1 -0
- package/dist/rules-storage.d.ts +33 -0
- package/dist/rules-storage.d.ts.map +1 -0
- package/dist/run-list-query.d.ts +27 -0
- package/dist/run-list-query.d.ts.map +1 -0
- package/dist/storage-errors.d.ts +2 -0
- package/dist/storage-errors.d.ts.map +1 -0
- package/dist/sync-run-storage.d.ts +23 -0
- package/dist/sync-run-storage.d.ts.map +1 -0
- package/dist/timeseries-storage.d.ts +41 -0
- package/dist/timeseries-storage.d.ts.map +1 -0
- package/dist/transactions.d.ts +15 -0
- package/dist/transactions.d.ts.map +1 -0
- package/dist/tsconfig.json +5 -0
- package/dist/validation.d.ts +5 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/webhook-delivery-storage.d.ts +27 -0
- package/dist/webhook-delivery-storage.d.ts.map +1 -0
- package/dist/webhook-run-storage.d.ts +22 -0
- package/dist/webhook-run-storage.d.ts.map +1 -0
- package/dist/workflow-intervention-storage.d.ts +26 -0
- package/dist/workflow-intervention-storage.d.ts.map +1 -0
- package/dist/workflow-run-storage.d.ts +63 -0
- package/dist/workflow-run-storage.d.ts.map +1 -0
- package/package.json +58 -0
- package/src/action-run-storage.ts +761 -0
- package/src/agents/agent-storage.ts +46 -0
- package/src/agents/index.ts +5 -0
- package/src/agents/messages.ts +162 -0
- package/src/agents/rows.ts +205 -0
- package/src/agents/runs.ts +389 -0
- package/src/agents/threads.ts +133 -0
- package/src/auth-storage/access-tokens.ts +289 -0
- package/src/auth-storage/auth-storage.ts +784 -0
- package/src/auth-storage/group-memberships.ts +52 -0
- package/src/auth-storage/identities.ts +98 -0
- package/src/auth-storage/index.ts +2 -0
- package/src/auth-storage/invitations.ts +258 -0
- package/src/auth-storage/magic-links.ts +149 -0
- package/src/auth-storage/oidc-attempts.ts +109 -0
- package/src/auth-storage/rows.ts +338 -0
- package/src/auth-storage/service-account-group-memberships.ts +161 -0
- package/src/auth-storage/service-accounts.ts +168 -0
- package/src/auth-storage/sessions.ts +230 -0
- package/src/auth-storage/shared.ts +707 -0
- package/src/auth-storage/users.ts +226 -0
- package/src/index.ts +292 -0
- package/src/latest-run-query.ts +49 -0
- package/src/migrations/001-initial-schema.sql +1068 -0
- package/src/migrations.ts +245 -0
- package/src/object-query-compiler.ts +1198 -0
- package/src/object-storage.ts +605 -0
- package/src/ontology-storage/commits.ts +127 -0
- package/src/ontology-storage/index.ts +35 -0
- package/src/ontology-storage/materialization-session.ts +393 -0
- package/src/ontology-storage/materialization-state.ts +901 -0
- package/src/ontology-storage/materialization-writer.ts +394 -0
- package/src/ontology-storage/materializations.ts +838 -0
- package/src/ontology-storage/outbox.ts +237 -0
- package/src/ontology-storage/shared.ts +269 -0
- package/src/ontology-storage/sources.ts +653 -0
- package/src/pipeline-run-storage.ts +506 -0
- package/src/projection-run-storage.ts +592 -0
- package/src/rules-storage.ts +375 -0
- package/src/run-list-query.ts +98 -0
- package/src/sql.d.ts +4 -0
- package/src/storage-errors.ts +3 -0
- package/src/sync-run-storage.ts +299 -0
- package/src/timeseries-storage.ts +212 -0
- package/src/transactions.ts +96 -0
- package/src/validation.ts +30 -0
- package/src/webhook-delivery-storage.ts +272 -0
- package/src/webhook-run-storage.ts +240 -0
- package/src/workflow-intervention-storage.ts +398 -0
- package/src/workflow-run-storage.ts +1148 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
import {
|
|
3
|
+
type AgentRunRecord,
|
|
4
|
+
type AgentRunStore,
|
|
5
|
+
AgentStorageError,
|
|
6
|
+
type ConfirmAgentRunExecutionOwnershipInput,
|
|
7
|
+
type CreateAgentRunInput,
|
|
8
|
+
type FinishAgentRunInput,
|
|
9
|
+
type FinishQueuedAgentRunInput,
|
|
10
|
+
type ListAgentRunsInput,
|
|
11
|
+
type ListAgentRunsResult,
|
|
12
|
+
type ReclaimAgentRunInput,
|
|
13
|
+
type StartAgentRunInput,
|
|
14
|
+
} from "@sixb/core/storage"
|
|
15
|
+
import {
|
|
16
|
+
appendRunListFilters,
|
|
17
|
+
hasEmptyStatuses,
|
|
18
|
+
queryRunList,
|
|
19
|
+
type SqliteValue,
|
|
20
|
+
} from "../run-list-query"
|
|
21
|
+
import { isUniqueConstraintError } from "../storage-errors"
|
|
22
|
+
import { type AgentRunRow, rowToRunRecord } from "./rows"
|
|
23
|
+
|
|
24
|
+
const SQLITE_RUN_ID_BATCH_SIZE = 500
|
|
25
|
+
|
|
26
|
+
export class SqliteAgentRunStore implements AgentRunStore {
|
|
27
|
+
constructor(private readonly db: Database) {}
|
|
28
|
+
|
|
29
|
+
async create(input: CreateAgentRunInput): Promise<AgentRunRecord> {
|
|
30
|
+
const createdAt = input.createdAt ?? new Date()
|
|
31
|
+
|
|
32
|
+
return this.db.transaction(() => {
|
|
33
|
+
const thread = this.db
|
|
34
|
+
.query("SELECT active_run_id FROM agent_threads WHERE project_id = ? AND id = ?")
|
|
35
|
+
.get(input.projectId, input.threadId) as { active_run_id: string | null } | null
|
|
36
|
+
|
|
37
|
+
if (!thread) {
|
|
38
|
+
throw new AgentStorageError(
|
|
39
|
+
"thread_not_found",
|
|
40
|
+
`[SixbSqlite] Agent thread '${input.threadId}' not found for project '${input.projectId}'.`
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (thread.active_run_id !== null) {
|
|
45
|
+
throw new AgentStorageError(
|
|
46
|
+
"active_run_exists",
|
|
47
|
+
`[SixbSqlite] Agent thread '${input.threadId}' already has an active run '${thread.active_run_id}'.`
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
this.db
|
|
53
|
+
.query(
|
|
54
|
+
`
|
|
55
|
+
INSERT INTO agent_runs (
|
|
56
|
+
project_id,
|
|
57
|
+
id,
|
|
58
|
+
thread_id,
|
|
59
|
+
agent_id,
|
|
60
|
+
trigger_message_id,
|
|
61
|
+
requested_by_principal_type,
|
|
62
|
+
requested_by_principal_id,
|
|
63
|
+
status,
|
|
64
|
+
attempt,
|
|
65
|
+
created_at
|
|
66
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, 'queued', 0, ?)
|
|
67
|
+
`
|
|
68
|
+
)
|
|
69
|
+
.run(
|
|
70
|
+
input.projectId,
|
|
71
|
+
input.id,
|
|
72
|
+
input.threadId,
|
|
73
|
+
input.agentId,
|
|
74
|
+
input.triggerMessageId,
|
|
75
|
+
input.requestedByPrincipal.type,
|
|
76
|
+
input.requestedByPrincipal.id,
|
|
77
|
+
createdAt.toISOString()
|
|
78
|
+
)
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (isUniqueConstraintError(error)) {
|
|
81
|
+
throw new AgentStorageError(
|
|
82
|
+
"duplicate_id",
|
|
83
|
+
`[SixbSqlite] Agent run '${input.id}' already exists for project '${input.projectId}'.`
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
throw error
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.db
|
|
91
|
+
.query(
|
|
92
|
+
"UPDATE agent_threads SET active_run_id = ?, updated_at = ? WHERE project_id = ? AND id = ?"
|
|
93
|
+
)
|
|
94
|
+
.run(input.id, createdAt.toISOString(), input.projectId, input.threadId)
|
|
95
|
+
|
|
96
|
+
return this.requireRun(input.projectId, input.id)
|
|
97
|
+
})()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async start(input: StartAgentRunInput): Promise<AgentRunRecord> {
|
|
101
|
+
const startedAt = input.startedAt ?? new Date()
|
|
102
|
+
return this.db.transaction(() => {
|
|
103
|
+
this.requireStatus(input.projectId, input.id, "queued")
|
|
104
|
+
this.db
|
|
105
|
+
.query(
|
|
106
|
+
`
|
|
107
|
+
UPDATE agent_runs
|
|
108
|
+
SET
|
|
109
|
+
status = 'running',
|
|
110
|
+
execution_principal_type = ?,
|
|
111
|
+
execution_principal_id = ?,
|
|
112
|
+
model_id = ?,
|
|
113
|
+
attempt = 1,
|
|
114
|
+
execution_token = ?,
|
|
115
|
+
execution_queue_lease_expires_at = ?,
|
|
116
|
+
started_at = ?
|
|
117
|
+
WHERE project_id = ? AND id = ?
|
|
118
|
+
`
|
|
119
|
+
)
|
|
120
|
+
.run(
|
|
121
|
+
input.executionPrincipal?.type ?? null,
|
|
122
|
+
input.executionPrincipal?.id ?? null,
|
|
123
|
+
input.modelId ?? null,
|
|
124
|
+
input.execution.token,
|
|
125
|
+
input.execution.queueLeaseExpiresAt.toISOString(),
|
|
126
|
+
startedAt.toISOString(),
|
|
127
|
+
input.projectId,
|
|
128
|
+
input.id
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return this.requireRun(input.projectId, input.id)
|
|
132
|
+
})()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async finishQueued(input: FinishQueuedAgentRunInput): Promise<AgentRunRecord> {
|
|
136
|
+
const completedAt = input.completedAt ?? new Date()
|
|
137
|
+
return this.db.transaction(() => {
|
|
138
|
+
const run = this.requireStatus(input.projectId, input.id, "queued")
|
|
139
|
+
this.db
|
|
140
|
+
.query(
|
|
141
|
+
`
|
|
142
|
+
UPDATE agent_runs
|
|
143
|
+
SET status = ?, error = ?, completed_at = ?
|
|
144
|
+
WHERE project_id = ? AND id = ?
|
|
145
|
+
`
|
|
146
|
+
)
|
|
147
|
+
.run(
|
|
148
|
+
input.status,
|
|
149
|
+
input.error ?? null,
|
|
150
|
+
completedAt.toISOString(),
|
|
151
|
+
input.projectId,
|
|
152
|
+
input.id
|
|
153
|
+
)
|
|
154
|
+
this.releaseThread(run, completedAt)
|
|
155
|
+
return this.requireRun(input.projectId, input.id)
|
|
156
|
+
})()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async reclaim(input: ReclaimAgentRunInput): Promise<AgentRunRecord> {
|
|
160
|
+
return this.db.transaction(() => {
|
|
161
|
+
this.requireRunning(input.projectId, input.id)
|
|
162
|
+
this.db
|
|
163
|
+
.query(
|
|
164
|
+
`
|
|
165
|
+
UPDATE agent_runs
|
|
166
|
+
SET
|
|
167
|
+
attempt = attempt + 1,
|
|
168
|
+
execution_token = ?,
|
|
169
|
+
execution_queue_lease_expires_at = ?
|
|
170
|
+
WHERE project_id = ? AND id = ?
|
|
171
|
+
`
|
|
172
|
+
)
|
|
173
|
+
.run(
|
|
174
|
+
input.execution.token,
|
|
175
|
+
input.execution.queueLeaseExpiresAt.toISOString(),
|
|
176
|
+
input.projectId,
|
|
177
|
+
input.id
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return this.requireRun(input.projectId, input.id)
|
|
181
|
+
})()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async confirmExecutionOwnership(
|
|
185
|
+
input: ConfirmAgentRunExecutionOwnershipInput
|
|
186
|
+
): Promise<AgentRunRecord> {
|
|
187
|
+
return this.db.transaction(() => {
|
|
188
|
+
const run = this.requireRunning(input.projectId, input.id)
|
|
189
|
+
if (run.execution_token !== input.executionToken) {
|
|
190
|
+
throw new AgentStorageError(
|
|
191
|
+
"execution_lost",
|
|
192
|
+
`[SixbSqlite] Execution token is no longer current on agent run '${input.id}'.`
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
this.db
|
|
197
|
+
.query(
|
|
198
|
+
`
|
|
199
|
+
UPDATE agent_runs
|
|
200
|
+
SET execution_queue_lease_expires_at = MAX(execution_queue_lease_expires_at, ?)
|
|
201
|
+
WHERE project_id = ? AND id = ?
|
|
202
|
+
`
|
|
203
|
+
)
|
|
204
|
+
.run(input.queueLeaseExpiresAt.toISOString(), input.projectId, input.id)
|
|
205
|
+
return this.requireRun(input.projectId, input.id)
|
|
206
|
+
})()
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async finish(input: FinishAgentRunInput): Promise<AgentRunRecord> {
|
|
210
|
+
const completedAt = input.completedAt ?? new Date()
|
|
211
|
+
const errorValue = input.status === "succeeded" ? null : (input.error ?? null)
|
|
212
|
+
|
|
213
|
+
return this.db.transaction(() => {
|
|
214
|
+
const run = this.requireRunning(input.projectId, input.id)
|
|
215
|
+
|
|
216
|
+
if (run.execution_token !== input.executionToken) {
|
|
217
|
+
throw new AgentStorageError(
|
|
218
|
+
"execution_lost",
|
|
219
|
+
`[SixbSqlite] Execution token is no longer current on agent run '${input.id}'.`
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.db
|
|
224
|
+
.query(
|
|
225
|
+
`
|
|
226
|
+
UPDATE agent_runs
|
|
227
|
+
SET
|
|
228
|
+
status = ?,
|
|
229
|
+
model_id = COALESCE(?, model_id),
|
|
230
|
+
finish_reason = ?,
|
|
231
|
+
usage_input_tokens = ?,
|
|
232
|
+
usage_output_tokens = ?,
|
|
233
|
+
usage_total_tokens = ?,
|
|
234
|
+
usage_reasoning_tokens = ?,
|
|
235
|
+
usage_cached_input_tokens = ?,
|
|
236
|
+
error = ?,
|
|
237
|
+
diagnostics = ?,
|
|
238
|
+
execution_token = NULL,
|
|
239
|
+
execution_queue_lease_expires_at = NULL,
|
|
240
|
+
completed_at = ?
|
|
241
|
+
WHERE project_id = ? AND id = ?
|
|
242
|
+
`
|
|
243
|
+
)
|
|
244
|
+
.run(
|
|
245
|
+
input.status,
|
|
246
|
+
input.modelId ?? null,
|
|
247
|
+
input.finishReason ?? null,
|
|
248
|
+
input.usage?.inputTokens ?? null,
|
|
249
|
+
input.usage?.outputTokens ?? null,
|
|
250
|
+
input.usage?.totalTokens ?? null,
|
|
251
|
+
input.usage?.reasoningTokens ?? null,
|
|
252
|
+
input.usage?.cachedInputTokens ?? null,
|
|
253
|
+
errorValue,
|
|
254
|
+
input.diagnostics === undefined ? null : JSON.stringify(input.diagnostics),
|
|
255
|
+
completedAt.toISOString(),
|
|
256
|
+
input.projectId,
|
|
257
|
+
input.id
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
this.releaseThread(run, completedAt)
|
|
261
|
+
|
|
262
|
+
return this.requireRun(input.projectId, input.id)
|
|
263
|
+
})()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async getById(params: { projectId: string; id: string }): Promise<AgentRunRecord | null> {
|
|
267
|
+
const row = this.db
|
|
268
|
+
.query("SELECT * FROM agent_runs WHERE project_id = ? AND id = ?")
|
|
269
|
+
.get(params.projectId, params.id) as AgentRunRow | null
|
|
270
|
+
|
|
271
|
+
return row ? rowToRunRecord(row) : null
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async getByIds(params: {
|
|
275
|
+
projectId: string
|
|
276
|
+
ids: readonly string[]
|
|
277
|
+
}): Promise<readonly AgentRunRecord[]> {
|
|
278
|
+
const ids = [...new Set(params.ids)]
|
|
279
|
+
if (ids.length === 0) {
|
|
280
|
+
return []
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const rows: AgentRunRow[] = []
|
|
284
|
+
for (let offset = 0; offset < ids.length; offset += SQLITE_RUN_ID_BATCH_SIZE) {
|
|
285
|
+
const batch = ids.slice(offset, offset + SQLITE_RUN_ID_BATCH_SIZE)
|
|
286
|
+
const placeholders = batch.map(() => "?").join(", ")
|
|
287
|
+
rows.push(
|
|
288
|
+
...(this.db
|
|
289
|
+
.query(`SELECT * FROM agent_runs WHERE project_id = ? AND id IN (${placeholders})`)
|
|
290
|
+
.all(params.projectId, ...batch) as AgentRunRow[])
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
const byId = new Map(rows.map((row) => [row.id, rowToRunRecord(row)]))
|
|
294
|
+
return ids.flatMap((id) => {
|
|
295
|
+
const run = byId.get(id)
|
|
296
|
+
return run ? [run] : []
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async list(input: ListAgentRunsInput): Promise<ListAgentRunsResult> {
|
|
301
|
+
if (hasEmptyStatuses(input)) {
|
|
302
|
+
return { runs: [], hasMore: false, total: 0 }
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const whereClauses = ["project_id = ?"]
|
|
306
|
+
const args: SqliteValue[] = [input.projectId]
|
|
307
|
+
|
|
308
|
+
if (input.threadId) {
|
|
309
|
+
whereClauses.push("thread_id = ?")
|
|
310
|
+
args.push(input.threadId)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (input.agentId) {
|
|
314
|
+
whereClauses.push("agent_id = ?")
|
|
315
|
+
args.push(input.agentId)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
appendRunListFilters(whereClauses, args, input, "COALESCE(started_at, created_at)")
|
|
319
|
+
|
|
320
|
+
const { rows, total, hasMore } = queryRunList<AgentRunRow>({
|
|
321
|
+
db: this.db,
|
|
322
|
+
tableName: "agent_runs",
|
|
323
|
+
whereClauses,
|
|
324
|
+
args,
|
|
325
|
+
order: input.order,
|
|
326
|
+
limit: input.limit,
|
|
327
|
+
offset: input.offset,
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
return { runs: rows.map(rowToRunRecord), hasMore, total }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private requireRunning(projectId: string, id: string): AgentRunRow {
|
|
334
|
+
return this.requireStatus(projectId, id, "running")
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private requireStatus(
|
|
338
|
+
projectId: string,
|
|
339
|
+
id: string,
|
|
340
|
+
status: AgentRunRecord["status"]
|
|
341
|
+
): AgentRunRow {
|
|
342
|
+
const row = this.db
|
|
343
|
+
.query("SELECT * FROM agent_runs WHERE project_id = ? AND id = ?")
|
|
344
|
+
.get(projectId, id) as AgentRunRow | null
|
|
345
|
+
|
|
346
|
+
if (!row) {
|
|
347
|
+
throw new AgentStorageError(
|
|
348
|
+
"run_not_found",
|
|
349
|
+
`[SixbSqlite] Agent run '${id}' not found for project '${projectId}'.`
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (row.status !== status) {
|
|
354
|
+
throw new AgentStorageError(
|
|
355
|
+
"invalid_state",
|
|
356
|
+
`[SixbSqlite] Agent run '${id}' is not ${status} (status '${row.status}').`
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return row
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private releaseThread(run: AgentRunRow, completedAt: Date): void {
|
|
364
|
+
this.db
|
|
365
|
+
.query(
|
|
366
|
+
`
|
|
367
|
+
UPDATE agent_threads
|
|
368
|
+
SET active_run_id = NULL, updated_at = ?
|
|
369
|
+
WHERE project_id = ? AND id = ? AND active_run_id = ?
|
|
370
|
+
`
|
|
371
|
+
)
|
|
372
|
+
.run(completedAt.toISOString(), run.project_id, run.thread_id, run.id)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private requireRun(projectId: string, id: string): AgentRunRecord {
|
|
376
|
+
const row = this.db
|
|
377
|
+
.query("SELECT * FROM agent_runs WHERE project_id = ? AND id = ?")
|
|
378
|
+
.get(projectId, id) as AgentRunRow | null
|
|
379
|
+
|
|
380
|
+
if (!row) {
|
|
381
|
+
throw new AgentStorageError(
|
|
382
|
+
"run_not_found",
|
|
383
|
+
`[SixbSqlite] Agent run '${id}' not found for project '${projectId}'.`
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return rowToRunRecord(row)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
import {
|
|
3
|
+
AgentStorageError,
|
|
4
|
+
type AgentThreadRecord,
|
|
5
|
+
type AgentThreadStore,
|
|
6
|
+
type CreateAgentThreadInput,
|
|
7
|
+
type ListAgentThreadsInput,
|
|
8
|
+
type ListAgentThreadsResult,
|
|
9
|
+
} from "@sixb/core/storage"
|
|
10
|
+
import type { SqliteValue } from "../run-list-query"
|
|
11
|
+
import { isUniqueConstraintError } from "../storage-errors"
|
|
12
|
+
import { type AgentThreadRow, queryAgentList, rowToThreadRecord } from "./rows"
|
|
13
|
+
|
|
14
|
+
export class SqliteAgentThreadStore implements AgentThreadStore {
|
|
15
|
+
constructor(private readonly db: Database) {}
|
|
16
|
+
|
|
17
|
+
async create(input: CreateAgentThreadInput): Promise<AgentThreadRecord> {
|
|
18
|
+
const createdAt = input.createdAt ?? new Date()
|
|
19
|
+
const updatedAt = input.updatedAt ?? createdAt
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
this.db
|
|
23
|
+
.query(
|
|
24
|
+
`
|
|
25
|
+
INSERT INTO agent_threads (
|
|
26
|
+
project_id,
|
|
27
|
+
id,
|
|
28
|
+
agent_id,
|
|
29
|
+
owner_principal_type,
|
|
30
|
+
owner_principal_id,
|
|
31
|
+
title,
|
|
32
|
+
status,
|
|
33
|
+
active_run_id,
|
|
34
|
+
last_message_at,
|
|
35
|
+
message_count,
|
|
36
|
+
created_at,
|
|
37
|
+
updated_at
|
|
38
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, NULL, NULL, 0, ?, ?)
|
|
39
|
+
`
|
|
40
|
+
)
|
|
41
|
+
.run(
|
|
42
|
+
input.projectId,
|
|
43
|
+
input.id,
|
|
44
|
+
input.agentId,
|
|
45
|
+
input.ownerPrincipal.type,
|
|
46
|
+
input.ownerPrincipal.id,
|
|
47
|
+
input.title ?? null,
|
|
48
|
+
input.status ?? "active",
|
|
49
|
+
createdAt.toISOString(),
|
|
50
|
+
updatedAt.toISOString()
|
|
51
|
+
)
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (isUniqueConstraintError(error)) {
|
|
54
|
+
throw new AgentStorageError(
|
|
55
|
+
"duplicate_id",
|
|
56
|
+
`[SixbSqlite] Agent thread '${input.id}' already exists for project '${input.projectId}'.`
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
throw error
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return this.requireThread(input.projectId, input.id)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async getById(params: { projectId: string; id: string }): Promise<AgentThreadRecord | null> {
|
|
67
|
+
const row = this.db
|
|
68
|
+
.query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?")
|
|
69
|
+
.get(params.projectId, params.id) as AgentThreadRow | null
|
|
70
|
+
|
|
71
|
+
return row ? rowToThreadRecord(row) : null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async list(input: ListAgentThreadsInput): Promise<ListAgentThreadsResult> {
|
|
75
|
+
if (
|
|
76
|
+
(input.statuses !== undefined && input.statuses.length === 0) ||
|
|
77
|
+
(input.agentIds !== undefined && input.agentIds.length === 0)
|
|
78
|
+
) {
|
|
79
|
+
return { threads: [], hasMore: false, total: 0 }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const whereClauses = ["project_id = ?"]
|
|
83
|
+
const args: SqliteValue[] = [input.projectId]
|
|
84
|
+
|
|
85
|
+
if (input.agentId) {
|
|
86
|
+
whereClauses.push("agent_id = ?")
|
|
87
|
+
args.push(input.agentId)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (input.agentIds) {
|
|
91
|
+
whereClauses.push(`agent_id IN (${input.agentIds.map(() => "?").join(", ")})`)
|
|
92
|
+
args.push(...input.agentIds)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (input.statuses) {
|
|
96
|
+
whereClauses.push(`status IN (${input.statuses.map(() => "?").join(", ")})`)
|
|
97
|
+
args.push(...input.statuses)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (input.ownerPrincipal) {
|
|
101
|
+
whereClauses.push("owner_principal_type = ? AND owner_principal_id = ?")
|
|
102
|
+
args.push(input.ownerPrincipal.type, input.ownerPrincipal.id)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const dir = input.order === "asc" ? "ASC" : "DESC"
|
|
106
|
+
const { rows, total, hasMore } = queryAgentList<AgentThreadRow>({
|
|
107
|
+
db: this.db,
|
|
108
|
+
table: "agent_threads",
|
|
109
|
+
whereClauses,
|
|
110
|
+
args,
|
|
111
|
+
orderBy: `COALESCE(last_message_at, created_at) ${dir}, id ${dir}`,
|
|
112
|
+
limit: input.limit,
|
|
113
|
+
offset: input.offset,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
return { threads: rows.map(rowToThreadRecord), hasMore, total }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private requireThread(projectId: string, id: string): AgentThreadRecord {
|
|
120
|
+
const row = this.db
|
|
121
|
+
.query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?")
|
|
122
|
+
.get(projectId, id) as AgentThreadRow | null
|
|
123
|
+
|
|
124
|
+
if (!row) {
|
|
125
|
+
throw new AgentStorageError(
|
|
126
|
+
"invalid_state",
|
|
127
|
+
`[SixbSqlite] Failed to load agent thread '${id}' for project '${projectId}'.`
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return rowToThreadRecord(row)
|
|
132
|
+
}
|
|
133
|
+
}
|