@sixb/pg 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +7 -6
  2. package/dist/agents/agent-storage.d.ts +7 -4
  3. package/dist/agents/agent-storage.d.ts.map +1 -1
  4. package/dist/agents/checkpoints.d.ts +12 -0
  5. package/dist/agents/checkpoints.d.ts.map +1 -0
  6. package/dist/agents/index.d.ts +1 -0
  7. package/dist/agents/index.d.ts.map +1 -1
  8. package/dist/agents/messages.d.ts +2 -1
  9. package/dist/agents/messages.d.ts.map +1 -1
  10. package/dist/agents/rows.d.ts +22 -12
  11. package/dist/agents/rows.d.ts.map +1 -1
  12. package/dist/agents/runs.d.ts +3 -2
  13. package/dist/agents/runs.d.ts.map +1 -1
  14. package/dist/auth-storage/service-accounts.d.ts.map +1 -1
  15. package/dist/auth-storage/shared.d.ts +0 -4
  16. package/dist/auth-storage/shared.d.ts.map +1 -1
  17. package/dist/connector-connection-storage.d.ts +9 -0
  18. package/dist/connector-connection-storage.d.ts.map +1 -0
  19. package/dist/index.d.ts +9 -5
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +5010 -1837
  22. package/dist/index.js.map +31 -26
  23. package/dist/latest-run-query.d.ts.map +1 -1
  24. package/dist/migrations.d.ts.map +1 -1
  25. package/dist/ontology-storage/materialization-session.d.ts.map +1 -1
  26. package/dist/ontology-storage/materialization-state.d.ts +0 -2
  27. package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
  28. package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
  29. package/dist/ontology-storage/materializations.d.ts.map +1 -1
  30. package/dist/ontology-storage/outbox.d.ts.map +1 -1
  31. package/dist/ontology-storage/shared.d.ts +3 -6
  32. package/dist/ontology-storage/shared.d.ts.map +1 -1
  33. package/dist/pg-action-run-storage.d.ts +3 -2
  34. package/dist/pg-action-run-storage.d.ts.map +1 -1
  35. package/dist/pg-ai-cost-storage.d.ts +12 -0
  36. package/dist/pg-ai-cost-storage.d.ts.map +1 -0
  37. package/dist/pg-ai-usage-storage.d.ts +16 -0
  38. package/dist/pg-ai-usage-storage.d.ts.map +1 -0
  39. package/dist/pg-execution-storage.d.ts +14 -0
  40. package/dist/pg-execution-storage.d.ts.map +1 -0
  41. package/dist/pg-pipeline-run-storage.d.ts +5 -2
  42. package/dist/pg-pipeline-run-storage.d.ts.map +1 -1
  43. package/dist/pg-projection-run-storage.d.ts +5 -2
  44. package/dist/pg-projection-run-storage.d.ts.map +1 -1
  45. package/dist/pg-sync-run-storage.d.ts +5 -2
  46. package/dist/pg-sync-run-storage.d.ts.map +1 -1
  47. package/dist/pg-webhook-run-storage.d.ts +5 -2
  48. package/dist/pg-webhook-run-storage.d.ts.map +1 -1
  49. package/dist/pg-workflow-run-storage.d.ts +5 -3
  50. package/dist/pg-workflow-run-storage.d.ts.map +1 -1
  51. package/dist/run-list-query.d.ts +1 -1
  52. package/dist/run-list-query.d.ts.map +1 -1
  53. package/dist/testing.d.ts +6 -0
  54. package/dist/testing.d.ts.map +1 -0
  55. package/package.json +3 -3
  56. package/src/agents/agent-storage.ts +9 -5
  57. package/src/agents/checkpoints.ts +175 -0
  58. package/src/agents/index.ts +1 -0
  59. package/src/agents/messages.ts +71 -13
  60. package/src/agents/rows.ts +57 -43
  61. package/src/agents/runs.ts +29 -15
  62. package/src/auth-storage/service-accounts.ts +14 -15
  63. package/src/auth-storage/shared.ts +0 -8
  64. package/src/connector-connection-storage.ts +760 -0
  65. package/src/index.ts +38 -15
  66. package/src/latest-run-query.ts +7 -1
  67. package/src/migrations/004-executions.sql +140 -0
  68. package/src/migrations/005-workflow-executions.sql +10 -0
  69. package/src/migrations/006-narrow-ontology-source-root-index.sql +4 -0
  70. package/src/migrations/007-split-overrides.sql +153 -0
  71. package/src/migrations/008-action-executions.sql +7 -0
  72. package/src/migrations/009-agent-executions.sql +21 -0
  73. package/src/migrations/010-ai-usage-accounting-foundation.sql +76 -0
  74. package/src/migrations/011-sync-failure-record.sql +23 -0
  75. package/src/migrations/012-pipeline-failure-record.sql +47 -0
  76. package/src/migrations/013-workflow-failure-record.sql +45 -0
  77. package/src/migrations/014-agent-failure-record.sql +56 -0
  78. package/src/migrations/015-projection-failure-record.sql +20 -0
  79. package/src/migrations/016-webhook-run-failure-record.sql +19 -0
  80. package/src/migrations/017-action-failure-record.sql +65 -0
  81. package/src/migrations/018-ontology-outbox-failure-record.sql +29 -0
  82. package/src/migrations/019-webhook-delivery-failure-record.sql +27 -0
  83. package/src/migrations/020-drop-run-usage-projections.sql +8 -0
  84. package/src/migrations/021-sync-pipeline-executions.sql +50 -0
  85. package/src/migrations/022-projection-executions.sql +115 -0
  86. package/src/migrations/023-webhook-executions.sql +30 -0
  87. package/src/migrations/024-ontology-commit-executions.sql +12 -0
  88. package/src/migrations/025-connector-connections.sql +231 -0
  89. package/src/migrations/026-ai-cost-accounting.sql +53 -0
  90. package/src/migrations/027-agent-context-checkpoints.sql +34 -0
  91. package/src/migrations/028-object-override-edit-times.sql +3 -0
  92. package/src/migrations.ts +82 -0
  93. package/src/ontology-storage/materialization-session.ts +25 -28
  94. package/src/ontology-storage/materialization-state.ts +161 -43
  95. package/src/ontology-storage/materialization-writer.ts +149 -76
  96. package/src/ontology-storage/materializations.ts +3 -1
  97. package/src/ontology-storage/outbox.ts +9 -2
  98. package/src/ontology-storage/shared.ts +10 -20
  99. package/src/pg-action-run-storage.ts +43 -67
  100. package/src/pg-ai-cost-storage.ts +707 -0
  101. package/src/pg-ai-usage-storage.ts +290 -0
  102. package/src/pg-execution-storage.ts +147 -0
  103. package/src/pg-pipeline-run-storage.ts +107 -41
  104. package/src/pg-projection-run-storage.ts +159 -91
  105. package/src/pg-sync-run-storage.ts +100 -28
  106. package/src/pg-webhook-run-storage.ts +89 -57
  107. package/src/pg-workflow-run-storage.ts +97 -130
  108. package/src/run-list-query.ts +9 -4
  109. package/src/testing.ts +18 -0
  110. package/dist/pg-webhook-delivery-storage.d.ts +0 -17
  111. package/dist/pg-webhook-delivery-storage.d.ts.map +0 -1
  112. package/src/pg-webhook-delivery-storage.ts +0 -180
@@ -0,0 +1,175 @@
1
+ import {
2
+ agentContextCheckpointMatchesCreateInput,
3
+ assertAgentContextCheckpointAnchors,
4
+ assertAgentContextCheckpointAuthority,
5
+ assertAgentContextCheckpointReplayState,
6
+ assertCreateAgentContextCheckpointInput,
7
+ } from "@sixb/core/internal/agent-run-storage-provider"
8
+ import {
9
+ type AgentContextCheckpointRecord,
10
+ type AgentContextCheckpointStore,
11
+ AgentStorageError,
12
+ type CreateAgentContextCheckpointInput,
13
+ } from "@sixb/core/storage"
14
+ import { isUniqueViolation } from "../storage-errors"
15
+ import { type PgStoreClient, runPgTransaction } from "../transactions"
16
+ import {
17
+ type AgentContextCheckpointRow,
18
+ type AgentMessageRow,
19
+ type AgentRunRow,
20
+ type AgentThreadRow,
21
+ rowToContextCheckpointRecord,
22
+ rowToMessageRecord,
23
+ rowToRunRecord,
24
+ rowToThreadRecord,
25
+ } from "./rows"
26
+
27
+ export class PgAgentContextCheckpointStore implements AgentContextCheckpointStore {
28
+ constructor(private readonly sql: PgStoreClient) {}
29
+
30
+ async create(input: CreateAgentContextCheckpointInput): Promise<AgentContextCheckpointRecord> {
31
+ assertCreateAgentContextCheckpointInput(input, "SixbPg")
32
+
33
+ try {
34
+ return await runPgTransaction(this.sql, async (tx) => {
35
+ // Match run finalization's lock order (run, then thread) to avoid a cross-operation deadlock.
36
+ const [runRow] = await tx<AgentRunRow[]>`
37
+ SELECT * FROM agent_runs
38
+ WHERE project_id = ${input.projectId} AND id = ${input.createdByRunId}
39
+ FOR UPDATE
40
+ `
41
+ const [threadRow] = await tx<AgentThreadRow[]>`
42
+ SELECT * FROM agent_threads
43
+ WHERE project_id = ${input.projectId} AND id = ${input.threadId}
44
+ FOR UPDATE
45
+ `
46
+ assertAgentContextCheckpointAuthority({
47
+ create: input,
48
+ run: runRow ? rowToRunRecord(runRow) : null,
49
+ thread: threadRow ? rowToThreadRecord(threadRow) : null,
50
+ prefix: "SixbPg",
51
+ })
52
+
53
+ const [latestRow] = await tx<AgentContextCheckpointRow[]>`
54
+ SELECT * FROM agent_context_checkpoints
55
+ WHERE project_id = ${input.projectId} AND thread_id = ${input.threadId}
56
+ ORDER BY summarized_through_seq DESC, created_at DESC, id DESC
57
+ LIMIT 1
58
+ `
59
+ const latest = latestRow ? rowToContextCheckpointRecord(latestRow) : null
60
+ const [headRow] = await tx<{ seq: number | string }[]>`
61
+ SELECT COALESCE(MAX(seq), 0) AS seq FROM agent_messages
62
+ WHERE project_id = ${input.projectId} AND thread_id = ${input.threadId}
63
+ `
64
+ const headSeq = Number(headRow?.seq ?? 0)
65
+
66
+ const [existingForRunRow] = await tx<AgentContextCheckpointRow[]>`
67
+ SELECT * FROM agent_context_checkpoints
68
+ WHERE project_id = ${input.projectId} AND created_by_run_id = ${input.createdByRunId}
69
+ `
70
+ if (existingForRunRow) {
71
+ const record = rowToContextCheckpointRecord(existingForRunRow)
72
+ if (agentContextCheckpointMatchesCreateInput(record, input)) {
73
+ assertAgentContextCheckpointReplayState({
74
+ create: input,
75
+ existing: record,
76
+ latest,
77
+ headSeq,
78
+ prefix: "SixbPg",
79
+ })
80
+ return record
81
+ }
82
+ throw new AgentStorageError(
83
+ "invalid_state",
84
+ `[SixbPg] Agent run '${input.createdByRunId}' already created a different context checkpoint.`
85
+ )
86
+ }
87
+
88
+ const [duplicateId] = await tx<{ present: number }[]>`
89
+ SELECT 1 AS present FROM agent_context_checkpoints
90
+ WHERE project_id = ${input.projectId} AND id = ${input.id}
91
+ `
92
+ if (duplicateId) {
93
+ throw new AgentStorageError(
94
+ "duplicate_id",
95
+ `[SixbPg] Agent context checkpoint '${input.id}' already exists for project '${input.projectId}'.`
96
+ )
97
+ }
98
+
99
+ const [firstRetainedRow] = await tx<AgentMessageRow[]>`
100
+ SELECT * FROM agent_messages
101
+ WHERE
102
+ project_id = ${input.projectId}
103
+ AND thread_id = ${input.threadId}
104
+ AND seq > ${input.summarizedThroughSeq}
105
+ ORDER BY seq ASC
106
+ LIMIT 1
107
+ `
108
+ assertAgentContextCheckpointAnchors({
109
+ create: input,
110
+ latest,
111
+ headSeq,
112
+ firstRetained: firstRetainedRow ? rowToMessageRecord(firstRetainedRow) : null,
113
+ prefix: "SixbPg",
114
+ })
115
+
116
+ const [row] = await tx<AgentContextCheckpointRow[]>`
117
+ INSERT INTO agent_context_checkpoints (
118
+ project_id,
119
+ id,
120
+ thread_id,
121
+ created_by_run_id,
122
+ previous_checkpoint_id,
123
+ reason,
124
+ summary,
125
+ summary_format_version,
126
+ summarized_through_seq,
127
+ observed_head_seq,
128
+ estimated_input_tokens_before,
129
+ estimated_input_tokens_after,
130
+ summary_model_id,
131
+ created_at
132
+ ) VALUES (
133
+ ${input.projectId},
134
+ ${input.id},
135
+ ${input.threadId},
136
+ ${input.createdByRunId},
137
+ ${input.expectedPreviousCheckpointId},
138
+ ${input.reason},
139
+ ${input.summary},
140
+ ${input.summaryFormatVersion},
141
+ ${input.summarizedThroughSeq},
142
+ ${input.observedHeadSeq},
143
+ ${input.estimatedInputTokensBefore},
144
+ ${input.estimatedInputTokensAfter},
145
+ ${input.summaryModelId},
146
+ ${input.createdAt ?? new Date()}
147
+ )
148
+ RETURNING *
149
+ `
150
+ return rowToContextCheckpointRecord(row)
151
+ })
152
+ } catch (error) {
153
+ if (isUniqueViolation(error)) {
154
+ throw new AgentStorageError(
155
+ "duplicate_id",
156
+ `[SixbPg] Agent context checkpoint '${input.id}' already exists for project '${input.projectId}'.`
157
+ )
158
+ }
159
+ throw error
160
+ }
161
+ }
162
+
163
+ async getLatest(input: {
164
+ readonly projectId: string
165
+ readonly threadId: string
166
+ }): Promise<AgentContextCheckpointRecord | null> {
167
+ const [row] = await this.sql<AgentContextCheckpointRow[]>`
168
+ SELECT * FROM agent_context_checkpoints
169
+ WHERE project_id = ${input.projectId} AND thread_id = ${input.threadId}
170
+ ORDER BY summarized_through_seq DESC, created_at DESC, id DESC
171
+ LIMIT 1
172
+ `
173
+ return row ? rowToContextCheckpointRecord(row) : null
174
+ }
175
+ }
@@ -1,5 +1,6 @@
1
1
  export type { PgAgentStorageOptions } from "./agent-storage"
2
2
  export { PgAgentStorage } from "./agent-storage"
3
+ export { PgAgentContextCheckpointStore } from "./checkpoints"
3
4
  export { PgAgentMessageStore } from "./messages"
4
5
  export { PgAgentRunStore } from "./runs"
5
6
  export { PgAgentThreadStore } from "./threads"
@@ -4,6 +4,7 @@ import {
4
4
  type AgentMessageStore,
5
5
  AgentStorageError,
6
6
  type AppendAgentMessageInput,
7
+ type DeleteAgentMessagesByRunInput,
7
8
  type ListAgentMessagesInput,
8
9
  type ListAgentMessagesResult,
9
10
  } from "@sixb/core/storage"
@@ -20,6 +21,23 @@ export class PgAgentMessageStore implements AgentMessageStore {
20
21
 
21
22
  try {
22
23
  return await runPgTransaction(this.sql, async (tx) => {
24
+ // Assistant finalization appends and finishes in one outer transaction. Lock its run first
25
+ // so every operation that needs both rows follows the same run -> thread order.
26
+ if (input.runId !== null) {
27
+ const [run] = await tx<{ id: string }[]>`
28
+ SELECT id FROM agent_runs
29
+ WHERE project_id = ${input.projectId} AND id = ${input.runId}
30
+ FOR UPDATE
31
+ `
32
+
33
+ if (!run) {
34
+ throw new AgentStorageError(
35
+ "run_not_found",
36
+ `[SixbPg] Agent run '${input.runId}' not found for project '${input.projectId}'.`
37
+ )
38
+ }
39
+ }
40
+
23
41
  const [thread] = await tx<{ id: string }[]>`
24
42
  SELECT id FROM agent_threads
25
43
  WHERE project_id = ${input.projectId} AND id = ${input.threadId}
@@ -33,19 +51,6 @@ export class PgAgentMessageStore implements AgentMessageStore {
33
51
  )
34
52
  }
35
53
 
36
- if (input.runId !== null) {
37
- const [run] = await tx<{ id: string }[]>`
38
- SELECT id FROM agent_runs WHERE project_id = ${input.projectId} AND id = ${input.runId}
39
- `
40
-
41
- if (!run) {
42
- throw new AgentStorageError(
43
- "run_not_found",
44
- `[SixbPg] Agent run '${input.runId}' not found for project '${input.projectId}'.`
45
- )
46
- }
47
- }
48
-
49
54
  const [seqRow] = await tx<{ next: number | string }[]>`
50
55
  SELECT COALESCE(MAX(seq), 0) + 1 AS next FROM agent_messages
51
56
  WHERE project_id = ${input.projectId} AND thread_id = ${input.threadId}
@@ -104,6 +109,54 @@ export class PgAgentMessageStore implements AgentMessageStore {
104
109
  }
105
110
  }
106
111
 
112
+ async deleteByRunId(input: DeleteAgentMessagesByRunInput): Promise<number> {
113
+ return runPgTransaction(this.sql, async (tx) => {
114
+ const [thread] = await tx<{ id: string }[]>`
115
+ SELECT id FROM agent_threads
116
+ WHERE project_id = ${input.projectId} AND id = ${input.threadId}
117
+ FOR UPDATE
118
+ `
119
+
120
+ if (!thread) {
121
+ throw new AgentStorageError(
122
+ "thread_not_found",
123
+ `[SixbPg] Agent thread '${input.threadId}' not found for project '${input.projectId}'.`
124
+ )
125
+ }
126
+
127
+ const deleted = await tx<{ id: string }[]>`
128
+ DELETE FROM agent_messages
129
+ WHERE project_id = ${input.projectId}
130
+ AND thread_id = ${input.threadId}
131
+ AND run_id = ${input.runId}
132
+ RETURNING id
133
+ `
134
+ if (deleted.length === 0) {
135
+ return 0
136
+ }
137
+
138
+ const updatedAt = new Date()
139
+ await tx`
140
+ UPDATE agent_threads
141
+ SET
142
+ message_count = (
143
+ SELECT COUNT(*) FROM agent_messages
144
+ WHERE project_id = ${input.projectId} AND thread_id = ${input.threadId}
145
+ ),
146
+ last_message_at = (
147
+ SELECT created_at FROM agent_messages
148
+ WHERE project_id = ${input.projectId} AND thread_id = ${input.threadId}
149
+ ORDER BY seq DESC
150
+ LIMIT 1
151
+ ),
152
+ updated_at = ${updatedAt}
153
+ WHERE project_id = ${input.projectId} AND id = ${input.threadId}
154
+ `
155
+
156
+ return deleted.length
157
+ })
158
+ }
159
+
107
160
  async getById(params: { projectId: string; id: string }): Promise<AgentMessageRecord | null> {
108
161
  const [row] = await this.sql<AgentMessageRow[]>`
109
162
  SELECT * FROM agent_messages WHERE project_id = ${params.projectId} AND id = ${params.id}
@@ -121,6 +174,11 @@ export class PgAgentMessageStore implements AgentMessageStore {
121
174
  const params: SqlParameter[] = [input.projectId, input.threadId]
122
175
  let index = 3
123
176
 
177
+ if (input.afterSeq !== undefined) {
178
+ whereClauses.push(`seq > $${index++}`)
179
+ params.push(input.afterSeq)
180
+ }
181
+
124
182
  if (input.roles) {
125
183
  const placeholders = input.roles.map(() => `$${index++}`)
126
184
  whereClauses.push(`role IN (${placeholders.join(", ")})`)
@@ -1,9 +1,11 @@
1
- import { type AgentMessagePart, type Principal, SYSTEM_PRINCIPAL } from "@sixb/core"
1
+ import type { AgentMessagePart, JsonValue, Principal } from "@sixb/core"
2
+ import { parseSixbFailure } from "@sixb/core/internal/errors"
2
3
  import {
4
+ AGENT_RUN_FAILURE_CODES,
5
+ type AgentContextCheckpointRecord,
3
6
  type AgentMessageRecord,
4
7
  type AgentRunDiagnostic,
5
8
  type AgentRunRecord,
6
- type AgentRunUsage,
7
9
  type AgentThreadRecord,
8
10
  coerceAgentRunFinishReason,
9
11
  } from "@sixb/core/storage"
@@ -30,22 +32,15 @@ export interface AgentThreadRow {
30
32
  export interface AgentRunRow {
31
33
  project_id: string
32
34
  id: string
35
+ execution_id: string
33
36
  thread_id: string
34
37
  agent_id: string
35
38
  trigger_message_id: string
36
- requested_by_principal_type: Principal["type"] | null
37
- requested_by_principal_id: string | null
38
- execution_principal_type: "serviceAccount" | null
39
- execution_principal_id: string | null
39
+ requester_group_ids: string[] | string
40
40
  status: AgentRunRecord["status"]
41
41
  model_id: string | null
42
42
  finish_reason: string | null
43
- usage_input_tokens: number | string | null
44
- usage_output_tokens: number | string | null
45
- usage_total_tokens: number | string | null
46
- usage_reasoning_tokens: number | string | null
47
- usage_cached_input_tokens: number | string | null
48
- error: string | null
43
+ error: JsonValue | null
49
44
  diagnostics: AgentRunDiagnostic[] | string | null
50
45
  attempt: number | string
51
46
  execution_token: string | null
@@ -71,6 +66,23 @@ export interface AgentMessageRow {
71
66
  completed_at: Date | string | null
72
67
  }
73
68
 
69
+ export interface AgentContextCheckpointRow {
70
+ project_id: string
71
+ id: string
72
+ thread_id: string
73
+ created_by_run_id: string
74
+ previous_checkpoint_id: string | null
75
+ reason: AgentContextCheckpointRecord["reason"]
76
+ summary: string
77
+ summary_format_version: number | string
78
+ summarized_through_seq: number | string
79
+ observed_head_seq: number | string
80
+ estimated_input_tokens_before: number | string
81
+ estimated_input_tokens_after: number | string
82
+ summary_model_id: string
83
+ created_at: Date | string
84
+ }
85
+
74
86
  // ── row → record mappers ────────────────────────────────────────────────────────────────────────
75
87
 
76
88
  export function rowToThreadRecord(row: AgentThreadRow): AgentThreadRecord {
@@ -93,21 +105,18 @@ export function rowToRunRecord(row: AgentRunRow): AgentRunRecord {
93
105
  return {
94
106
  id: row.id,
95
107
  projectId: row.project_id,
108
+ executionId: row.execution_id,
96
109
  threadId: row.thread_id,
97
110
  agentId: row.agent_id,
98
111
  triggerMessageId: row.trigger_message_id,
99
- requestedByPrincipal:
100
- principalFromColumns(row.requested_by_principal_type, row.requested_by_principal_id) ??
101
- SYSTEM_PRINCIPAL,
102
- executionPrincipal: serviceAccountPrincipalFromColumns(
103
- row.execution_principal_type,
104
- row.execution_principal_id
105
- ),
112
+ requesterGroupIds:
113
+ typeof row.requester_group_ids === "string"
114
+ ? (JSON.parse(row.requester_group_ids) as string[])
115
+ : row.requester_group_ids,
106
116
  status: row.status,
107
117
  modelId: row.model_id ?? undefined,
108
118
  finishReason: coerceAgentRunFinishReason(row.finish_reason),
109
- usage: rowToUsage(row),
110
- error: row.error ?? undefined,
119
+ error: row.error === null ? undefined : parseSixbFailure(row.error, AGENT_RUN_FAILURE_CODES),
111
120
  diagnostics: normalizeDiagnostics(row.diagnostics),
112
121
  attempt: Number(row.attempt),
113
122
  execution:
@@ -143,6 +152,33 @@ export function rowToMessageRecord(row: AgentMessageRow): AgentMessageRecord {
143
152
  }
144
153
  }
145
154
 
155
+ export function rowToContextCheckpointRecord(
156
+ row: AgentContextCheckpointRow
157
+ ): AgentContextCheckpointRecord {
158
+ const summaryFormatVersion = Number(row.summary_format_version)
159
+ if (summaryFormatVersion !== 1) {
160
+ throw new Error(
161
+ `[SixbPg] Unsupported agent context checkpoint summary format version '${summaryFormatVersion}'.`
162
+ )
163
+ }
164
+ return {
165
+ id: row.id,
166
+ projectId: row.project_id,
167
+ threadId: row.thread_id,
168
+ createdByRunId: row.created_by_run_id,
169
+ previousCheckpointId: row.previous_checkpoint_id ?? undefined,
170
+ reason: row.reason,
171
+ summary: row.summary,
172
+ summaryFormatVersion: 1,
173
+ summarizedThroughSeq: Number(row.summarized_through_seq),
174
+ observedHeadSeq: Number(row.observed_head_seq),
175
+ estimatedInputTokensBefore: Number(row.estimated_input_tokens_before),
176
+ estimatedInputTokensAfter: Number(row.estimated_input_tokens_after),
177
+ summaryModelId: row.summary_model_id,
178
+ createdAt: new Date(row.created_at),
179
+ }
180
+ }
181
+
146
182
  function principalFromColumns(
147
183
  type: Principal["type"] | null | undefined,
148
184
  id: string | null | undefined
@@ -150,28 +186,6 @@ function principalFromColumns(
150
186
  return type && id ? { type, id } : undefined
151
187
  }
152
188
 
153
- function serviceAccountPrincipalFromColumns(
154
- type: "serviceAccount" | null | undefined,
155
- id: string | null | undefined
156
- ): Extract<Principal, { readonly type: "serviceAccount" }> | undefined {
157
- return type === "serviceAccount" && id ? { type, id } : undefined
158
- }
159
-
160
- function rowToUsage(row: AgentRunRow): AgentRunUsage | undefined {
161
- const usage: AgentRunUsage = {
162
- ...(row.usage_input_tokens === null ? {} : { inputTokens: Number(row.usage_input_tokens) }),
163
- ...(row.usage_output_tokens === null ? {} : { outputTokens: Number(row.usage_output_tokens) }),
164
- ...(row.usage_total_tokens === null ? {} : { totalTokens: Number(row.usage_total_tokens) }),
165
- ...(row.usage_reasoning_tokens === null
166
- ? {}
167
- : { reasoningTokens: Number(row.usage_reasoning_tokens) }),
168
- ...(row.usage_cached_input_tokens === null
169
- ? {}
170
- : { cachedInputTokens: Number(row.usage_cached_input_tokens) }),
171
- }
172
- return Object.keys(usage).length === 0 ? undefined : usage
173
- }
174
-
175
189
  function normalizeMetadata(value: unknown): AgentMessageRecord["metadata"] {
176
190
  if (value === null || value === undefined) {
177
191
  return undefined
@@ -1,9 +1,14 @@
1
+ import { assertAgentRunExecution } from "@sixb/core/internal/agent-run-storage-provider"
2
+ import { normalizeRequesterGroupIds } from "@sixb/core/internal/auth"
3
+ import { serializeSixbFailure } from "@sixb/core/internal/errors"
1
4
  import {
5
+ AGENT_RUN_FAILURE_CODES,
2
6
  type AgentRunRecord,
3
7
  type AgentRunStore,
4
8
  AgentStorageError,
5
9
  type ConfirmAgentRunExecutionOwnershipInput,
6
10
  type CreateAgentRunInput,
11
+ type ExecutionStorage,
7
12
  type FinishAgentRunInput,
8
13
  type FinishQueuedAgentRunInput,
9
14
  type ListAgentRunsInput,
@@ -20,10 +25,20 @@ import { type AgentRunRow, rowToRunRecord } from "./rows"
20
25
  const PG_RUN_ID_BATCH_SIZE = 10_000
21
26
 
22
27
  export class PgAgentRunStore implements AgentRunStore {
23
- constructor(private readonly sql: PgStoreClient) {}
28
+ constructor(
29
+ private readonly sql: PgStoreClient,
30
+ private readonly executions: ExecutionStorage
31
+ ) {}
24
32
 
25
33
  async create(input: CreateAgentRunInput): Promise<AgentRunRecord> {
26
34
  const createdAt = input.createdAt ?? new Date()
35
+ await assertAgentRunExecution({
36
+ executions: this.executions,
37
+ projectId: input.projectId,
38
+ executionId: input.executionId,
39
+ runId: input.id,
40
+ agentId: input.agentId,
41
+ })
27
42
 
28
43
  try {
29
44
  return await runPgTransaction(this.sql, async (tx) => {
@@ -51,22 +66,22 @@ export class PgAgentRunStore implements AgentRunStore {
51
66
  INSERT INTO agent_runs (
52
67
  project_id,
53
68
  id,
69
+ execution_id,
54
70
  thread_id,
55
71
  agent_id,
56
72
  trigger_message_id,
57
- requested_by_principal_type,
58
- requested_by_principal_id,
73
+ requester_group_ids,
59
74
  status,
60
75
  attempt,
61
76
  created_at
62
77
  ) VALUES (
63
78
  ${input.projectId},
64
79
  ${input.id},
80
+ ${input.executionId},
65
81
  ${input.threadId},
66
82
  ${input.agentId},
67
83
  ${input.triggerMessageId},
68
- ${input.requestedByPrincipal.type},
69
- ${input.requestedByPrincipal.id},
84
+ ${JSON.stringify(normalizeRequesterGroupIds(input.requesterGroupIds))}::text::jsonb,
70
85
  ${"queued"},
71
86
  ${0},
72
87
  ${createdAt}
@@ -101,8 +116,6 @@ export class PgAgentRunStore implements AgentRunStore {
101
116
  UPDATE agent_runs
102
117
  SET
103
118
  status = ${"running"},
104
- execution_principal_type = ${input.executionPrincipal?.type ?? null},
105
- execution_principal_id = ${input.executionPrincipal?.id ?? null},
106
119
  model_id = ${input.modelId ?? null},
107
120
  attempt = ${1},
108
121
  execution_token = ${input.execution.token},
@@ -121,7 +134,10 @@ export class PgAgentRunStore implements AgentRunStore {
121
134
  const run = await this.lockStatus(tx, input.projectId, input.id, "queued")
122
135
  const [row] = await tx<AgentRunRow[]>`
123
136
  UPDATE agent_runs
124
- SET status = ${input.status}, error = ${input.error ?? null}, completed_at = ${completedAt}
137
+ SET
138
+ status = ${input.status},
139
+ error = ${input.error === undefined ? null : serializeSixbFailure(input.error, AGENT_RUN_FAILURE_CODES)}::text::jsonb,
140
+ completed_at = ${completedAt}
125
141
  WHERE project_id = ${input.projectId} AND id = ${input.id}
126
142
  RETURNING *
127
143
  `
@@ -174,7 +190,10 @@ export class PgAgentRunStore implements AgentRunStore {
174
190
 
175
191
  async finish(input: FinishAgentRunInput): Promise<AgentRunRecord> {
176
192
  const completedAt = input.completedAt ?? new Date()
177
- const errorValue = input.status === "succeeded" ? null : (input.error ?? null)
193
+ const errorValue =
194
+ input.status === "succeeded" || input.error === undefined
195
+ ? null
196
+ : serializeSixbFailure(input.error, AGENT_RUN_FAILURE_CODES)
178
197
 
179
198
  return runPgTransaction(this.sql, async (tx) => {
180
199
  const run = await this.lockRunning(tx, input.projectId, input.id)
@@ -192,12 +211,7 @@ export class PgAgentRunStore implements AgentRunStore {
192
211
  status = ${input.status},
193
212
  model_id = COALESCE(${input.modelId ?? null}, model_id),
194
213
  finish_reason = ${input.finishReason ?? null},
195
- usage_input_tokens = ${input.usage?.inputTokens ?? null},
196
- usage_output_tokens = ${input.usage?.outputTokens ?? null},
197
- usage_total_tokens = ${input.usage?.totalTokens ?? null},
198
- usage_reasoning_tokens = ${input.usage?.reasoningTokens ?? null},
199
- usage_cached_input_tokens = ${input.usage?.cachedInputTokens ?? null},
200
- error = ${errorValue},
214
+ error = ${errorValue}::text::jsonb,
201
215
  diagnostics = ${input.diagnostics === undefined ? null : JSON.stringify(input.diagnostics)}::text::jsonb,
202
216
  execution_token = ${null},
203
217
  execution_queue_lease_expires_at = ${null},
@@ -85,28 +85,27 @@ export class PgAuthServiceAccountStore implements AuthServiceAccountStore {
85
85
  }
86
86
 
87
87
  async update(input: UpdateAuthServiceAccountInput): Promise<ServiceAccountRecord> {
88
- const existing = await this.getById(input)
89
- if (!existing) {
90
- throw new AuthStorageError(
91
- "missing_service_account",
92
- `[Sixb] Service account '${input.id}' not found for project '${input.projectId}'.`
93
- )
94
- }
95
-
88
+ const id = assertNonEmpty(input.id, "Service account id")
89
+ const projectId = assertNonEmpty(input.projectId, "Project id")
96
90
  const updatedAt = dateOrNow(input.updatedAt)
97
91
  const name =
98
- input.name === undefined ? existing.name : assertNonEmpty(input.name, "Service account name")
99
- const description = input.description === undefined ? existing.description : input.description
92
+ input.name === undefined ? null : assertNonEmpty(input.name, "Service account name")
100
93
  const [row] = await this.sql<PgAuthServiceAccountRow[]>`
101
94
  UPDATE auth_service_accounts
102
- SET name = ${name},
103
- description = ${description ?? null},
104
- status = ${input.status ?? existing.status},
95
+ SET name = COALESCE(${name}, name),
96
+ description = COALESCE(${input.description ?? null}, description),
97
+ status = COALESCE(${input.status ?? null}, status),
105
98
  updated_at = ${updatedAt}
106
- WHERE project_id = ${input.projectId}
107
- AND id = ${input.id}
99
+ WHERE project_id = ${projectId}
100
+ AND id = ${id}
108
101
  RETURNING *
109
102
  `
103
+ if (!row) {
104
+ throw new AuthStorageError(
105
+ "missing_service_account",
106
+ `[Sixb] Service account '${id}' not found for project '${projectId}'.`
107
+ )
108
+ }
110
109
 
111
110
  return rowToServiceAccountRecord(row)
112
111
  }
@@ -128,14 +128,6 @@ export async function getUserRowByEmail(
128
128
  return row ?? null
129
129
  }
130
130
 
131
- export async function getUserByEmail(
132
- sql: SQLClient,
133
- params: { readonly projectId: string; readonly email: string }
134
- ): Promise<UserRecord | null> {
135
- const row = await getUserRowByEmail(sql, params)
136
- return row ? rowToUserRecord(row) : null
137
- }
138
-
139
131
  export async function requireUserById(
140
132
  sql: SQLClient,
141
133
  params: { readonly projectId: string; readonly id: string }