@sixb/sqlite 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/agent-storage.d.ts +8 -2
- package/dist/agents/agent-storage.d.ts.map +1 -1
- package/dist/agents/approvals.d.ts +51 -0
- package/dist/agents/approvals.d.ts.map +1 -0
- package/dist/agents/checkpoints.d.ts +1 -0
- package/dist/agents/checkpoints.d.ts.map +1 -1
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/memory.d.ts +21 -0
- package/dist/agents/memory.d.ts.map +1 -0
- package/dist/agents/messages.d.ts.map +1 -1
- package/dist/agents/rows.d.ts +15 -1
- package/dist/agents/rows.d.ts.map +1 -1
- package/dist/agents/runs.d.ts +14 -1
- package/dist/agents/runs.d.ts.map +1 -1
- package/dist/agents/settings.d.ts +29 -0
- package/dist/agents/settings.d.ts.map +1 -0
- package/dist/agents/threads.d.ts +12 -1
- package/dist/agents/threads.d.ts.map +1 -1
- package/dist/ai-cost-groups.d.ts.map +1 -1
- package/dist/ai-cost-storage.d.ts.map +1 -1
- package/dist/ai-usage-storage.d.ts +4 -1
- package/dist/ai-usage-storage.d.ts.map +1 -1
- package/dist/artifact-storage.d.ts +10 -1
- package/dist/artifact-storage.d.ts.map +1 -1
- package/dist/auth-storage/auth-storage.d.ts +3 -2
- package/dist/auth-storage/auth-storage.d.ts.map +1 -1
- package/dist/auth-storage/users.d.ts +7 -3
- package/dist/auth-storage/users.d.ts.map +1 -1
- package/dist/execution-storage.d.ts +2 -1
- package/dist/execution-storage.d.ts.map +1 -1
- package/dist/index.js +3348 -1515
- package/dist/index.js.map +32 -26
- package/dist/migrations.d.ts +0 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/objects/conditional-reader.d.ts +8 -0
- package/dist/objects/conditional-reader.d.ts.map +1 -0
- package/dist/objects/query-compiler.d.ts +26 -1
- package/dist/objects/query-compiler.d.ts.map +1 -1
- package/dist/objects/read-scope.d.ts +10 -1
- package/dist/objects/read-scope.d.ts.map +1 -1
- package/dist/objects/reader.d.ts +2 -0
- package/dist/objects/reader.d.ts.map +1 -1
- package/dist/objects/scoped-reader.d.ts +15 -0
- package/dist/objects/scoped-reader.d.ts.map +1 -0
- package/dist/objects/selected-reader.d.ts.map +1 -1
- package/dist/objects/storage.d.ts +6 -1
- package/dist/objects/storage.d.ts.map +1 -1
- package/dist/ontology-storage/index.d.ts +2 -0
- package/dist/ontology-storage/index.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-writer.d.ts +2 -0
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/unique-values.d.ts +42 -0
- package/dist/ontology-storage/unique-values.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/agents/agent-storage.ts +11 -2
- package/src/agents/approvals.ts +456 -0
- package/src/agents/checkpoints.ts +9 -4
- package/src/agents/index.ts +1 -0
- package/src/agents/memory.ts +195 -0
- package/src/agents/messages.ts +21 -18
- package/src/agents/rows.ts +46 -1
- package/src/agents/runs.ts +202 -21
- package/src/agents/settings.ts +112 -0
- package/src/agents/threads.ts +385 -1
- package/src/ai-cost-groups.ts +11 -1
- package/src/ai-cost-storage.ts +54 -12
- package/src/ai-usage-storage.ts +79 -2
- package/src/artifact-storage.ts +206 -9
- package/src/auth-storage/auth-storage.ts +58 -26
- package/src/auth-storage/users.ts +106 -60
- package/src/execution-storage.ts +39 -0
- package/src/migrations/067-artifact-kinds.sql +32 -0
- package/src/migrations/068-artifact-state.sql +15 -0
- package/src/migrations/069-agent-thread-participants.sql +16 -0
- package/src/migrations/070-forked-context-checkpoints.sql +45 -0
- package/src/migrations/071-unique-values.sql +22 -0
- package/src/migrations/072-generated-media-usage.sql +32 -0
- package/src/migrations/073-model-call-files.sql +11 -0
- package/src/migrations/074-agent-memory.sql +32 -0
- package/src/migrations/075-artifact-threads.sql +21 -0
- package/src/migrations/076-agent-approvals.sql +59 -0
- package/src/migrations/077-agent-approval-resume.sql +20 -0
- package/src/migrations/078-subagent-threads.sql +241 -0
- package/src/migrations/079-subagent-questions.sql +6 -0
- package/src/migrations.ts +75 -1
- package/src/objects/README.md +7 -0
- package/src/objects/conditional-reader.ts +27 -0
- package/src/objects/query-compiler.ts +36 -6
- package/src/objects/read-scope.ts +140 -23
- package/src/objects/reader.ts +47 -14
- package/src/objects/scoped-reader.ts +103 -0
- package/src/objects/selected-reader.ts +12 -75
- package/src/objects/storage.ts +16 -0
- package/src/ontology-storage/index.ts +3 -0
- package/src/ontology-storage/materialization-writer.ts +14 -0
- package/src/ontology-storage/materializations.ts +16 -1
- package/src/ontology-storage/unique-values.ts +187 -0
- package/src/transactions.ts +1 -1
package/src/agents/threads.ts
CHANGED
|
@@ -1,24 +1,40 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite"
|
|
2
2
|
import {
|
|
3
|
+
assertConversationThreadRow,
|
|
4
|
+
participantsToAdd,
|
|
5
|
+
planAgentThreadFork,
|
|
6
|
+
remapArtifactIds,
|
|
3
7
|
snapshotAgentThreadSandboxParams,
|
|
4
8
|
transitionAgentThreadSandbox,
|
|
5
9
|
} from "@sixb/core/internal/agent-run-storage-provider"
|
|
6
10
|
import {
|
|
11
|
+
type AddAgentThreadParticipantsInput,
|
|
12
|
+
type AgentContextCheckpointRecord,
|
|
7
13
|
AgentStorageError,
|
|
14
|
+
type AgentThreadParticipantRecord,
|
|
8
15
|
type AgentThreadRecord,
|
|
9
16
|
type AgentThreadSandboxState,
|
|
10
17
|
type AgentThreadStore,
|
|
11
18
|
type CreateAgentThreadInput,
|
|
19
|
+
type ForkAgentThreadInput,
|
|
20
|
+
type ListAgentThreadChildrenInput,
|
|
21
|
+
type ListAgentThreadParticipantsInput,
|
|
12
22
|
type ListAgentThreadsInput,
|
|
13
23
|
type ListAgentThreadsResult,
|
|
24
|
+
type RemoveAgentThreadParticipantInput,
|
|
14
25
|
type TransitionAgentThreadSandboxInput,
|
|
15
26
|
} from "@sixb/core/storage"
|
|
16
27
|
import type { SqliteValue } from "../run-list-query"
|
|
17
28
|
import { isUniqueConstraintError } from "../storage-errors"
|
|
18
29
|
import {
|
|
30
|
+
type AgentContextCheckpointRow,
|
|
31
|
+
type AgentMessageRow,
|
|
19
32
|
type AgentRunRow,
|
|
33
|
+
type AgentThreadParticipantRow,
|
|
20
34
|
type AgentThreadRow,
|
|
21
35
|
queryAgentList,
|
|
36
|
+
rowToContextCheckpointRecord,
|
|
37
|
+
rowToParticipantRecord,
|
|
22
38
|
rowToRunRecord,
|
|
23
39
|
rowToThreadRecord,
|
|
24
40
|
} from "./rows"
|
|
@@ -100,6 +116,130 @@ export class SqliteAgentThreadStore implements AgentThreadStore {
|
|
|
100
116
|
return this.requireThread(input.projectId, input.id)
|
|
101
117
|
}
|
|
102
118
|
|
|
119
|
+
async fork(input: ForkAgentThreadInput): Promise<AgentThreadRecord> {
|
|
120
|
+
return this.db
|
|
121
|
+
.transaction(() => {
|
|
122
|
+
const source = this.db
|
|
123
|
+
.query(
|
|
124
|
+
"SELECT id, kind, active_run_id FROM agent_threads WHERE project_id = ? AND id = ?"
|
|
125
|
+
)
|
|
126
|
+
.get(input.projectId, input.sourceThreadId) as Pick<
|
|
127
|
+
AgentThreadRow,
|
|
128
|
+
"id" | "kind" | "active_run_id"
|
|
129
|
+
> | null
|
|
130
|
+
|
|
131
|
+
if (!source) {
|
|
132
|
+
throw new AgentStorageError(
|
|
133
|
+
"thread_not_found",
|
|
134
|
+
`[SixbSqlite] Agent thread '${input.sourceThreadId}' not found for project '${input.projectId}'.`
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
assertConversationThreadRow(source, "can be forked", "SixbSqlite")
|
|
138
|
+
|
|
139
|
+
const activeRun = source.active_run_id === null ? null : this.findActiveRun(input, source)
|
|
140
|
+
|
|
141
|
+
// Only what the plan needs: the parts are copied by the database, not through here. A
|
|
142
|
+
// message of approval outcomes only is not counted (`countsAsThreadMessage`).
|
|
143
|
+
const messages = (
|
|
144
|
+
this.db
|
|
145
|
+
.query(
|
|
146
|
+
`
|
|
147
|
+
SELECT id, seq,
|
|
148
|
+
NOT (
|
|
149
|
+
json_array_length(parts) > 0 AND NOT EXISTS (
|
|
150
|
+
SELECT 1 FROM json_each(agent_messages.parts)
|
|
151
|
+
WHERE json_extract(value, '$.type') <> 'approval-outcome'
|
|
152
|
+
)
|
|
153
|
+
) AS counted
|
|
154
|
+
FROM agent_messages WHERE project_id = ? AND thread_id = ? ORDER BY seq ASC
|
|
155
|
+
`
|
|
156
|
+
)
|
|
157
|
+
.all(input.projectId, source.id) as (Pick<AgentMessageRow, "id" | "seq"> & {
|
|
158
|
+
counted: number
|
|
159
|
+
})[]
|
|
160
|
+
).map((row) => ({ id: row.id, seq: row.seq, counted: row.counted === 1 }))
|
|
161
|
+
|
|
162
|
+
const checkpoints = this.db
|
|
163
|
+
.query(
|
|
164
|
+
`
|
|
165
|
+
SELECT * FROM agent_context_checkpoints
|
|
166
|
+
WHERE project_id = ? AND thread_id = ?
|
|
167
|
+
ORDER BY summarized_through_seq DESC, created_at DESC, id DESC
|
|
168
|
+
`
|
|
169
|
+
)
|
|
170
|
+
.all(input.projectId, source.id) as AgentContextCheckpointRow[]
|
|
171
|
+
|
|
172
|
+
const plan = planAgentThreadFork(
|
|
173
|
+
input,
|
|
174
|
+
{
|
|
175
|
+
...(activeRun?.trigger_message_id
|
|
176
|
+
? { triggerMessageId: activeRun.trigger_message_id }
|
|
177
|
+
: {}),
|
|
178
|
+
messages,
|
|
179
|
+
checkpoints: checkpoints.map(rowToContextCheckpointRecord),
|
|
180
|
+
},
|
|
181
|
+
"SixbSqlite"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
this.insertForkedThread(plan.thread)
|
|
185
|
+
|
|
186
|
+
this.db
|
|
187
|
+
.query(
|
|
188
|
+
`
|
|
189
|
+
INSERT INTO agent_messages (
|
|
190
|
+
project_id,
|
|
191
|
+
id,
|
|
192
|
+
thread_id,
|
|
193
|
+
run_id,
|
|
194
|
+
role,
|
|
195
|
+
author_principal_type,
|
|
196
|
+
author_principal_id,
|
|
197
|
+
seq,
|
|
198
|
+
parts,
|
|
199
|
+
metadata,
|
|
200
|
+
content_version,
|
|
201
|
+
created_at,
|
|
202
|
+
completed_at
|
|
203
|
+
)
|
|
204
|
+
SELECT
|
|
205
|
+
message.project_id,
|
|
206
|
+
json_extract(copy.value, '$[1]'),
|
|
207
|
+
?,
|
|
208
|
+
message.run_id,
|
|
209
|
+
message.role,
|
|
210
|
+
message.author_principal_type,
|
|
211
|
+
message.author_principal_id,
|
|
212
|
+
message.seq,
|
|
213
|
+
message.parts,
|
|
214
|
+
message.metadata,
|
|
215
|
+
message.content_version,
|
|
216
|
+
message.created_at,
|
|
217
|
+
message.completed_at
|
|
218
|
+
FROM json_each(?) AS copy
|
|
219
|
+
JOIN agent_messages AS message
|
|
220
|
+
ON message.project_id = ? AND message.id = json_extract(copy.value, '$[0]')
|
|
221
|
+
`
|
|
222
|
+
)
|
|
223
|
+
.run(
|
|
224
|
+
plan.thread.id,
|
|
225
|
+
JSON.stringify(plan.messages.map((copy) => [copy.sourceId, copy.id])),
|
|
226
|
+
input.projectId
|
|
227
|
+
)
|
|
228
|
+
this.remapCopiedParts(
|
|
229
|
+
input.projectId,
|
|
230
|
+
plan.messages.map((copy) => copy.id),
|
|
231
|
+
input.artifactCopies
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if (plan.checkpoint) {
|
|
235
|
+
this.insertCopiedCheckpoint(plan.checkpoint)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return this.requireThread(input.projectId, input.id)
|
|
239
|
+
})
|
|
240
|
+
.immediate()
|
|
241
|
+
}
|
|
242
|
+
|
|
103
243
|
async getById(params: { projectId: string; id: string }): Promise<AgentThreadRecord | null> {
|
|
104
244
|
const row = this.db
|
|
105
245
|
.query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?")
|
|
@@ -135,12 +275,36 @@ export class SqliteAgentThreadStore implements AgentThreadStore {
|
|
|
135
275
|
})
|
|
136
276
|
}
|
|
137
277
|
|
|
278
|
+
async listChildren(input: ListAgentThreadChildrenInput): Promise<readonly AgentThreadRecord[]> {
|
|
279
|
+
// By `idx_agent_threads_children`, which holds sub-agents' threads only.
|
|
280
|
+
const turn = input.parentRunId === undefined ? "" : "AND parent_run_id = ?"
|
|
281
|
+
const rows = this.db
|
|
282
|
+
.query(
|
|
283
|
+
`
|
|
284
|
+
SELECT *
|
|
285
|
+
FROM agent_threads
|
|
286
|
+
WHERE project_id = ? AND parent_thread_id = ? AND kind = 'subagent' ${turn}
|
|
287
|
+
ORDER BY created_at ASC, id ASC
|
|
288
|
+
LIMIT ? OFFSET ?
|
|
289
|
+
`
|
|
290
|
+
)
|
|
291
|
+
.all(
|
|
292
|
+
input.projectId,
|
|
293
|
+
input.parentThreadId,
|
|
294
|
+
...(input.parentRunId === undefined ? [] : [input.parentRunId]),
|
|
295
|
+
input.limit ?? 100,
|
|
296
|
+
input.offset ?? 0
|
|
297
|
+
) as AgentThreadRow[]
|
|
298
|
+
return rows.map(rowToThreadRecord)
|
|
299
|
+
}
|
|
300
|
+
|
|
138
301
|
async list(input: ListAgentThreadsInput): Promise<ListAgentThreadsResult> {
|
|
139
302
|
if (input.statuses !== undefined && input.statuses.length === 0) {
|
|
140
303
|
return { threads: [], hasMore: false, total: 0 }
|
|
141
304
|
}
|
|
142
305
|
|
|
143
|
-
|
|
306
|
+
// A sub-agent's thread is read through its conversation; the list indexes leave it out too.
|
|
307
|
+
const whereClauses = ["project_id = ?", "kind = 'conversation'"]
|
|
144
308
|
const args: SqliteValue[] = [input.projectId]
|
|
145
309
|
|
|
146
310
|
if (input.statuses) {
|
|
@@ -153,6 +317,17 @@ export class SqliteAgentThreadStore implements AgentThreadStore {
|
|
|
153
317
|
args.push(input.ownerPrincipal.type, input.ownerPrincipal.id)
|
|
154
318
|
}
|
|
155
319
|
|
|
320
|
+
if (input.participantPrincipal) {
|
|
321
|
+
whereClauses.push(`EXISTS (
|
|
322
|
+
SELECT 1 FROM agent_thread_participants participant
|
|
323
|
+
WHERE participant.project_id = agent_threads.project_id
|
|
324
|
+
AND participant.thread_id = agent_threads.id
|
|
325
|
+
AND participant.principal_type = ?
|
|
326
|
+
AND participant.principal_id = ?
|
|
327
|
+
)`)
|
|
328
|
+
args.push(input.participantPrincipal.type, input.participantPrincipal.id)
|
|
329
|
+
}
|
|
330
|
+
|
|
156
331
|
const dir = input.order === "asc" ? "ASC" : "DESC"
|
|
157
332
|
const { rows, total, hasMore } = queryAgentList<AgentThreadRow>({
|
|
158
333
|
db: this.db,
|
|
@@ -167,6 +342,215 @@ export class SqliteAgentThreadStore implements AgentThreadStore {
|
|
|
167
342
|
return { threads: rows.map(rowToThreadRecord), hasMore, total }
|
|
168
343
|
}
|
|
169
344
|
|
|
345
|
+
async addParticipants(
|
|
346
|
+
input: AddAgentThreadParticipantsInput
|
|
347
|
+
): Promise<readonly AgentThreadParticipantRecord[]> {
|
|
348
|
+
return this.db
|
|
349
|
+
.transaction(() => {
|
|
350
|
+
const thread = this.db
|
|
351
|
+
.query("SELECT id, kind FROM agent_threads WHERE project_id = ? AND id = ?")
|
|
352
|
+
.get(input.projectId, input.threadId) as Pick<AgentThreadRow, "id" | "kind"> | null
|
|
353
|
+
if (!thread) {
|
|
354
|
+
throw new AgentStorageError(
|
|
355
|
+
"thread_not_found",
|
|
356
|
+
`[SixbSqlite] Agent thread '${input.threadId}' not found for project '${input.projectId}'.`
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
assertConversationThreadRow(thread, "has participants", "SixbSqlite")
|
|
360
|
+
|
|
361
|
+
const existing = this.selectParticipants(input.projectId, [input.threadId])
|
|
362
|
+
const added = participantsToAdd(input, existing, "SixbSqlite")
|
|
363
|
+
const addedAt = (input.addedAt ?? new Date()).toISOString()
|
|
364
|
+
|
|
365
|
+
const insert = this.db.query(`
|
|
366
|
+
INSERT INTO agent_thread_participants (
|
|
367
|
+
project_id,
|
|
368
|
+
thread_id,
|
|
369
|
+
principal_type,
|
|
370
|
+
principal_id,
|
|
371
|
+
added_at,
|
|
372
|
+
added_by_type,
|
|
373
|
+
added_by_id
|
|
374
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
375
|
+
`)
|
|
376
|
+
for (const principal of added) {
|
|
377
|
+
insert.run(
|
|
378
|
+
input.projectId,
|
|
379
|
+
input.threadId,
|
|
380
|
+
principal.type,
|
|
381
|
+
principal.id,
|
|
382
|
+
addedAt,
|
|
383
|
+
input.addedBy.type,
|
|
384
|
+
input.addedBy.id
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return added.length === 0
|
|
389
|
+
? existing
|
|
390
|
+
: this.selectParticipants(input.projectId, [input.threadId])
|
|
391
|
+
})
|
|
392
|
+
.immediate()
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async removeParticipant(input: RemoveAgentThreadParticipantInput): Promise<boolean> {
|
|
396
|
+
const result = this.db
|
|
397
|
+
.query(`
|
|
398
|
+
DELETE FROM agent_thread_participants
|
|
399
|
+
WHERE project_id = ? AND thread_id = ? AND principal_type = ? AND principal_id = ?
|
|
400
|
+
`)
|
|
401
|
+
.run(input.projectId, input.threadId, input.principal.type, input.principal.id)
|
|
402
|
+
|
|
403
|
+
return result.changes > 0
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async listParticipants(
|
|
407
|
+
input: ListAgentThreadParticipantsInput
|
|
408
|
+
): Promise<readonly AgentThreadParticipantRecord[]> {
|
|
409
|
+
return this.selectParticipants(input.projectId, [...new Set(input.threadIds)])
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
private selectParticipants(
|
|
413
|
+
projectId: string,
|
|
414
|
+
threadIds: readonly string[]
|
|
415
|
+
): AgentThreadParticipantRecord[] {
|
|
416
|
+
if (threadIds.length === 0) {
|
|
417
|
+
return []
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const rows = this.db
|
|
421
|
+
.query(`
|
|
422
|
+
SELECT *
|
|
423
|
+
FROM agent_thread_participants
|
|
424
|
+
WHERE project_id = ?
|
|
425
|
+
AND thread_id IN (SELECT value FROM json_each(?))
|
|
426
|
+
ORDER BY added_at, principal_id, thread_id
|
|
427
|
+
`)
|
|
428
|
+
.all(projectId, JSON.stringify(threadIds)) as AgentThreadParticipantRow[]
|
|
429
|
+
|
|
430
|
+
return rows.map(rowToParticipantRecord)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private findActiveRun(
|
|
434
|
+
input: ForkAgentThreadInput,
|
|
435
|
+
source: Pick<AgentThreadRow, "active_run_id">
|
|
436
|
+
): Pick<AgentRunRow, "trigger_message_id"> | null {
|
|
437
|
+
return this.db
|
|
438
|
+
.query("SELECT trigger_message_id FROM agent_runs WHERE project_id = ? AND id = ?")
|
|
439
|
+
.get(input.projectId, source.active_run_id) as Pick<AgentRunRow, "trigger_message_id"> | null
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private insertForkedThread(thread: AgentThreadRecord): void {
|
|
443
|
+
try {
|
|
444
|
+
this.db
|
|
445
|
+
.query(
|
|
446
|
+
`
|
|
447
|
+
INSERT INTO agent_threads (
|
|
448
|
+
project_id,
|
|
449
|
+
id,
|
|
450
|
+
owner_principal_type,
|
|
451
|
+
owner_principal_id,
|
|
452
|
+
title,
|
|
453
|
+
sandbox_params,
|
|
454
|
+
sandbox_state,
|
|
455
|
+
status,
|
|
456
|
+
active_run_id,
|
|
457
|
+
last_message_at,
|
|
458
|
+
message_count,
|
|
459
|
+
created_at,
|
|
460
|
+
updated_at
|
|
461
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?)
|
|
462
|
+
`
|
|
463
|
+
)
|
|
464
|
+
.run(
|
|
465
|
+
thread.projectId,
|
|
466
|
+
thread.id,
|
|
467
|
+
thread.ownerPrincipal.type,
|
|
468
|
+
thread.ownerPrincipal.id,
|
|
469
|
+
thread.title ?? null,
|
|
470
|
+
thread.sandboxParams === undefined ? null : JSON.stringify(thread.sandboxParams),
|
|
471
|
+
thread.sandboxState === undefined ? null : JSON.stringify(thread.sandboxState),
|
|
472
|
+
thread.status,
|
|
473
|
+
thread.lastMessageAt?.toISOString() ?? null,
|
|
474
|
+
thread.messageCount,
|
|
475
|
+
thread.createdAt.toISOString(),
|
|
476
|
+
thread.updatedAt.toISOString()
|
|
477
|
+
)
|
|
478
|
+
} catch (error) {
|
|
479
|
+
if (isUniqueConstraintError(error)) {
|
|
480
|
+
throw new AgentStorageError(
|
|
481
|
+
"duplicate_id",
|
|
482
|
+
`[SixbSqlite] Agent thread '${thread.id}' already exists for project '${thread.projectId}'.`
|
|
483
|
+
)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
throw error
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private insertCopiedCheckpoint(checkpoint: AgentContextCheckpointRecord): void {
|
|
491
|
+
this.db
|
|
492
|
+
.query(
|
|
493
|
+
`
|
|
494
|
+
INSERT INTO agent_context_checkpoints (
|
|
495
|
+
project_id,
|
|
496
|
+
id,
|
|
497
|
+
thread_id,
|
|
498
|
+
created_by_run_id,
|
|
499
|
+
reason,
|
|
500
|
+
summary,
|
|
501
|
+
summary_format_version,
|
|
502
|
+
summarized_through_seq,
|
|
503
|
+
observed_head_seq,
|
|
504
|
+
estimated_input_tokens_before,
|
|
505
|
+
estimated_input_tokens_after,
|
|
506
|
+
summary_model_id,
|
|
507
|
+
created_at
|
|
508
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
509
|
+
`
|
|
510
|
+
)
|
|
511
|
+
.run(
|
|
512
|
+
checkpoint.projectId,
|
|
513
|
+
checkpoint.id,
|
|
514
|
+
checkpoint.threadId,
|
|
515
|
+
checkpoint.createdByRunId,
|
|
516
|
+
checkpoint.reason,
|
|
517
|
+
checkpoint.summary,
|
|
518
|
+
checkpoint.summaryFormatVersion,
|
|
519
|
+
checkpoint.summarizedThroughSeq,
|
|
520
|
+
checkpoint.observedHeadSeq,
|
|
521
|
+
checkpoint.estimatedInputTokensBefore,
|
|
522
|
+
checkpoint.estimatedInputTokensAfter,
|
|
523
|
+
checkpoint.summaryModelId,
|
|
524
|
+
checkpoint.createdAt.toISOString()
|
|
525
|
+
)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** The copied messages name the fork's copies of the source's artifacts. */
|
|
529
|
+
private remapCopiedParts(
|
|
530
|
+
projectId: string,
|
|
531
|
+
messageIds: readonly string[],
|
|
532
|
+
copies: Readonly<Record<string, string>> | undefined
|
|
533
|
+
): void {
|
|
534
|
+
if (!copies || Object.keys(copies).length === 0 || messageIds.length === 0) return
|
|
535
|
+
|
|
536
|
+
const rows = this.db
|
|
537
|
+
.query(
|
|
538
|
+
`SELECT id, parts FROM agent_messages
|
|
539
|
+
WHERE project_id = ? AND id IN (SELECT value FROM json_each(?))`
|
|
540
|
+
)
|
|
541
|
+
.all(projectId, JSON.stringify(messageIds)) as {
|
|
542
|
+
readonly id: string
|
|
543
|
+
readonly parts: string
|
|
544
|
+
}[]
|
|
545
|
+
const update = this.db.query(
|
|
546
|
+
"UPDATE agent_messages SET parts = ? WHERE project_id = ? AND id = ?"
|
|
547
|
+
)
|
|
548
|
+
for (const row of rows) {
|
|
549
|
+
const parts = remapArtifactIds(row.parts, copies)
|
|
550
|
+
if (parts !== row.parts) update.run(parts, projectId, row.id)
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
170
554
|
private requireThread(projectId: string, id: string): AgentThreadRecord {
|
|
171
555
|
const row = this.db
|
|
172
556
|
.query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?")
|
package/src/ai-cost-groups.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
aiModelCallGroupFragmentFromRow,
|
|
4
4
|
buildAiModelCallGroupsFromFragments,
|
|
5
5
|
normalizeAiModelCallAccountingQuery,
|
|
6
|
+
TOKENLESS_MODEL_KINDS,
|
|
6
7
|
} from "@sixb/core/internal/ai-cost-storage-provider"
|
|
7
8
|
import type { ListAiModelCallGroupsInput, ListAiModelCallGroupsResult } from "@sixb/core/storage"
|
|
8
9
|
import type { SqliteStoreConnection } from "./transactions"
|
|
@@ -32,11 +33,15 @@ export async function listAiModelCallGroups(
|
|
|
32
33
|
return { items, total, hasMore: query.offset + items.length < total }
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
// Fixed kind names, never user input.
|
|
37
|
+
const TOKENLESS_KINDS = TOKENLESS_MODEL_KINDS.map((kind) => `'${kind}'`).join(", ")
|
|
38
|
+
|
|
35
39
|
const FILTERED_SQL = `
|
|
36
40
|
WITH filtered AS (
|
|
37
41
|
SELECT COALESCE(root_agent.execution_id, usage.execution_id) AS root_execution_id,
|
|
38
42
|
usage.execution_id, usage.provider_id, usage.requested_model_id, usage.occurred_at,
|
|
39
|
-
usage.total_tokens,
|
|
43
|
+
usage.total_tokens, usage.model_kind, usage.output_images, usage.input_characters,
|
|
44
|
+
usage.audio_duration_ms,
|
|
40
45
|
execution.executor_kind, execution.executor_id, execution.authority_primitive_id AS primitive_id,
|
|
41
46
|
COALESCE(cost.status, 'unvalued') AS valuation_status,
|
|
42
47
|
cost.currency AS amount_currency, cost.amount_nanos,
|
|
@@ -82,7 +87,12 @@ SELECT filtered.root_execution_id, execution_id, provider_id, requested_model_id
|
|
|
82
87
|
MIN(occurred_at) AS first_call_at, MAX(occurred_at) AS last_call_at,
|
|
83
88
|
CAST(COUNT(*) AS TEXT) AS call_count,
|
|
84
89
|
CAST(COUNT(total_tokens) AS TEXT) AS token_count,
|
|
90
|
+
CAST(COUNT(CASE WHEN total_tokens IS NULL AND model_kind IN (${TOKENLESS_KINDS}) THEN 1 END) AS TEXT)
|
|
91
|
+
AS tokenless_count,
|
|
85
92
|
CAST(SUM(total_tokens) AS TEXT) AS total_tokens,
|
|
93
|
+
CAST(SUM(output_images) AS TEXT) AS output_images,
|
|
94
|
+
CAST(SUM(input_characters) AS TEXT) AS input_characters,
|
|
95
|
+
CAST(SUM(audio_duration_ms) AS TEXT) AS audio_duration_ms,
|
|
86
96
|
valuation_status, amount_currency,
|
|
87
97
|
CAST(SUM(amount_nanos / 1000000000000) AS TEXT) AS amount_high,
|
|
88
98
|
CAST(SUM((amount_nanos / 1000000) % 1000000) AS TEXT) AS amount_middle,
|
package/src/ai-cost-storage.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type AiAccountingAggregateFragment,
|
|
3
3
|
type AiAccountingRecordSetItem,
|
|
4
|
+
aiAccountingModelKind,
|
|
4
5
|
aiModelCallCostDetails,
|
|
5
6
|
aiModelCallCostMatchesUsage,
|
|
6
7
|
buildAiAccountingOverviewFromFragments,
|
|
@@ -238,6 +239,7 @@ const SQLITE_ACCOUNTING_OVERVIEW_SQL = `
|
|
|
238
239
|
WITH classified AS (
|
|
239
240
|
SELECT usage.provider_id,
|
|
240
241
|
usage.requested_model_id,
|
|
242
|
+
usage.model_kind,
|
|
241
243
|
usage.occurred_at,
|
|
242
244
|
usage.input_tokens,
|
|
243
245
|
usage.output_tokens,
|
|
@@ -247,6 +249,10 @@ const SQLITE_ACCOUNTING_OVERVIEW_SQL = `
|
|
|
247
249
|
usage.text_output_tokens,
|
|
248
250
|
usage.reasoning_output_tokens,
|
|
249
251
|
usage.audio_duration_ms,
|
|
252
|
+
usage.image_output_tokens,
|
|
253
|
+
usage.audio_output_tokens,
|
|
254
|
+
usage.output_images,
|
|
255
|
+
usage.input_characters,
|
|
250
256
|
usage.reporting_status,
|
|
251
257
|
CASE
|
|
252
258
|
WHEN direct_agent.kind = 'conversation' THEN 'agent'
|
|
@@ -300,6 +306,8 @@ const SQLITE_ACCOUNTING_OVERVIEW_SQL = `
|
|
|
300
306
|
UNION ALL
|
|
301
307
|
SELECT 'model', provider_id, requested_model_id, NULL, NULL, classified.* FROM classified
|
|
302
308
|
UNION ALL
|
|
309
|
+
SELECT 'kind', model_kind, NULL, NULL, NULL, classified.* FROM classified
|
|
310
|
+
UNION ALL
|
|
303
311
|
SELECT 'agent', attribution_agent_kind, attribution_workflow_id, attribution_agent_step_id, NULL, classified.*
|
|
304
312
|
FROM classified WHERE attribution_agent_kind IS NOT NULL
|
|
305
313
|
UNION ALL
|
|
@@ -332,7 +340,15 @@ const SQLITE_ACCOUNTING_OVERVIEW_SQL = `
|
|
|
332
340
|
CAST(COUNT(reasoning_output_tokens) AS TEXT) AS reasoning_output_tokens_count,
|
|
333
341
|
CAST(SUM(reasoning_output_tokens) AS TEXT) AS reasoning_output_tokens_sum,
|
|
334
342
|
CAST(COUNT(audio_duration_ms) AS TEXT) AS audio_duration_ms_count,
|
|
343
|
+
CAST(COUNT(image_output_tokens) AS TEXT) AS image_output_tokens_count,
|
|
344
|
+
CAST(COUNT(audio_output_tokens) AS TEXT) AS audio_output_tokens_count,
|
|
345
|
+
CAST(COUNT(output_images) AS TEXT) AS output_images_count,
|
|
346
|
+
CAST(COUNT(input_characters) AS TEXT) AS input_characters_count,
|
|
335
347
|
CAST(SUM(audio_duration_ms) AS TEXT) AS audio_duration_ms_sum,
|
|
348
|
+
CAST(SUM(image_output_tokens) AS TEXT) AS image_output_tokens_sum,
|
|
349
|
+
CAST(SUM(audio_output_tokens) AS TEXT) AS audio_output_tokens_sum,
|
|
350
|
+
CAST(SUM(output_images) AS TEXT) AS output_images_sum,
|
|
351
|
+
CAST(SUM(input_characters) AS TEXT) AS input_characters_sum,
|
|
336
352
|
CAST(SUM(CASE WHEN valuation_status = 'rated' THEN 1 ELSE 0 END) AS TEXT)
|
|
337
353
|
AS rated_call_count,
|
|
338
354
|
CAST(SUM(CASE WHEN valuation_status = 'unpriceable' THEN 1 ELSE 0 END) AS TEXT)
|
|
@@ -398,6 +414,10 @@ interface UsageRow {
|
|
|
398
414
|
readonly text_output_tokens: number | null
|
|
399
415
|
readonly reasoning_output_tokens: number | null
|
|
400
416
|
readonly audio_duration_ms: number | null
|
|
417
|
+
readonly image_output_tokens: number | null
|
|
418
|
+
readonly audio_output_tokens: number | null
|
|
419
|
+
readonly output_images: number | null
|
|
420
|
+
readonly input_characters: number | null
|
|
401
421
|
readonly reporting_status: AiModelCallUsageRecord["usage"]["reportingStatus"]
|
|
402
422
|
readonly raw_usage: string | null
|
|
403
423
|
readonly provider_ids: string | null
|
|
@@ -453,7 +473,15 @@ interface AggregateRow {
|
|
|
453
473
|
readonly reasoning_output_tokens_count: string
|
|
454
474
|
readonly reasoning_output_tokens_sum: string | null
|
|
455
475
|
readonly audio_duration_ms_count: string
|
|
476
|
+
readonly image_output_tokens_count: string
|
|
477
|
+
readonly audio_output_tokens_count: string
|
|
478
|
+
readonly output_images_count: string
|
|
479
|
+
readonly input_characters_count: string
|
|
456
480
|
readonly audio_duration_ms_sum: string | null
|
|
481
|
+
readonly image_output_tokens_sum: string | null
|
|
482
|
+
readonly audio_output_tokens_sum: string | null
|
|
483
|
+
readonly output_images_sum: string | null
|
|
484
|
+
readonly input_characters_sum: string | null
|
|
457
485
|
readonly rated_call_count: string
|
|
458
486
|
readonly unpriceable_call_count: string
|
|
459
487
|
readonly unvalued_call_count: string
|
|
@@ -477,18 +505,20 @@ function aggregateFragmentFromRow(row: AggregateRow): AiAccountingAggregateFragm
|
|
|
477
505
|
const dimensionKeys =
|
|
478
506
|
row.dimension === "model"
|
|
479
507
|
? { providerId: required(row.key_1), modelId: required(row.key_2) }
|
|
480
|
-
: row.dimension === "
|
|
481
|
-
?
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
: row.dimension === "
|
|
490
|
-
? {
|
|
491
|
-
:
|
|
508
|
+
: row.dimension === "kind"
|
|
509
|
+
? withModelKind(aiAccountingModelKind(row.key_1))
|
|
510
|
+
: row.dimension === "agent"
|
|
511
|
+
? {
|
|
512
|
+
agentKind: required(row.key_1) as "agent" | "workflowAgent",
|
|
513
|
+
...(row.key_1 === "workflowAgent"
|
|
514
|
+
? { workflowId: required(row.key_2), agentStepId: required(row.key_3) }
|
|
515
|
+
: {}),
|
|
516
|
+
}
|
|
517
|
+
: row.dimension === "workflow"
|
|
518
|
+
? { workflowId: required(row.key_1) }
|
|
519
|
+
: row.dimension === "series"
|
|
520
|
+
? { start: new Date(required(row.bucket_start)) }
|
|
521
|
+
: {}
|
|
492
522
|
return {
|
|
493
523
|
dimension: row.dimension,
|
|
494
524
|
...dimensionKeys,
|
|
@@ -516,6 +546,10 @@ function aggregateFragmentFromRow(row: AggregateRow): AiAccountingAggregateFragm
|
|
|
516
546
|
row.reasoning_output_tokens_sum
|
|
517
547
|
),
|
|
518
548
|
audioDurationMs: meterFragment(row.audio_duration_ms_count, row.audio_duration_ms_sum),
|
|
549
|
+
imageOutputTokens: meterFragment(row.image_output_tokens_count, row.image_output_tokens_sum),
|
|
550
|
+
audioOutputTokens: meterFragment(row.audio_output_tokens_count, row.audio_output_tokens_sum),
|
|
551
|
+
outputImages: meterFragment(row.output_images_count, row.output_images_sum),
|
|
552
|
+
inputCharacters: meterFragment(row.input_characters_count, row.input_characters_sum),
|
|
519
553
|
},
|
|
520
554
|
costs: {
|
|
521
555
|
...(row.amount_currency === null ||
|
|
@@ -619,6 +653,10 @@ function usageFromRow(row: UsageRow, requesterGroupIds: readonly string[]): AiMo
|
|
|
619
653
|
? {}
|
|
620
654
|
: { reasoningOutputTokens: row.reasoning_output_tokens }),
|
|
621
655
|
...(row.audio_duration_ms === null ? {} : { audioDurationMs: row.audio_duration_ms }),
|
|
656
|
+
...(row.image_output_tokens === null ? {} : { imageOutputTokens: row.image_output_tokens }),
|
|
657
|
+
...(row.audio_output_tokens === null ? {} : { audioOutputTokens: row.audio_output_tokens }),
|
|
658
|
+
...(row.output_images === null ? {} : { outputImages: row.output_images }),
|
|
659
|
+
...(row.input_characters === null ? {} : { inputCharacters: row.input_characters }),
|
|
622
660
|
reportingStatus: row.reporting_status,
|
|
623
661
|
},
|
|
624
662
|
...(row.raw_usage === null
|
|
@@ -807,3 +845,7 @@ function required<T>(value: T | null | undefined): T {
|
|
|
807
845
|
}
|
|
808
846
|
return value
|
|
809
847
|
}
|
|
848
|
+
|
|
849
|
+
function withModelKind(modelKind: AiAccountingAggregateFragment["modelKind"]) {
|
|
850
|
+
return modelKind === undefined ? {} : { modelKind }
|
|
851
|
+
}
|