@sixb/pg 0.1.11 → 0.1.12
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/README.md +34 -0
- package/dist/agents/rows.d.ts +3 -1
- package/dist/agents/rows.d.ts.map +1 -1
- package/dist/agents/threads.d.ts +3 -2
- package/dist/agents/threads.d.ts.map +1 -1
- package/dist/index.js +553 -54
- package/dist/index.js.map +16 -14
- package/dist/migrations.d.ts.map +1 -1
- package/dist/objects/query-compiler.d.ts +8 -1
- package/dist/objects/query-compiler.d.ts.map +1 -1
- package/dist/objects/read-scope.d.ts +1 -0
- package/dist/objects/read-scope.d.ts.map +1 -1
- package/dist/objects/reader.d.ts +1 -0
- package/dist/objects/reader.d.ts.map +1 -1
- package/dist/objects/rows.d.ts +1 -0
- package/dist/objects/rows.d.ts.map +1 -1
- package/dist/objects/storage.d.ts +1 -1
- package/dist/objects/storage.d.ts.map +1 -1
- package/dist/ontology-storage/index.d.ts +4 -0
- package/dist/ontology-storage/index.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts +1 -0
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/vector-indexing.d.ts +20 -0
- package/dist/ontology-storage/vector-indexing.d.ts.map +1 -0
- package/dist/ontology-storage/vectors.d.ts +16 -0
- package/dist/ontology-storage/vectors.d.ts.map +1 -0
- package/dist/pg-ai-cost-storage.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/agents/rows.ts +21 -0
- package/src/agents/threads.ts +47 -2
- package/src/migrations/041-agent-thread-workspaces.sql +2 -0
- package/src/migrations/042-vector-profiles.sql +28 -0
- package/src/migrations/043-vector-indexing.sql +24 -0
- package/src/migrations/044-vector-batching.sql +3 -0
- package/src/migrations/045-agent-thread-sandbox-state.sql +2 -0
- package/src/migrations.ts +14 -0
- package/src/objects/query-compiler.ts +102 -11
- package/src/objects/read-scope.ts +26 -25
- package/src/objects/reader.ts +33 -1
- package/src/objects/rows.ts +5 -1
- package/src/objects/storage.ts +20 -2
- package/src/ontology-storage/index.ts +15 -0
- package/src/ontology-storage/materializations.ts +13 -0
- package/src/ontology-storage/vector-indexing.ts +168 -0
- package/src/ontology-storage/vectors.ts +172 -0
- package/src/pg-ai-cost-storage.ts +4 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { MaterializationConflictError } from "@sixb/core/internal/materialization"
|
|
2
|
+
import type {
|
|
3
|
+
MaterializationSession,
|
|
4
|
+
ObjectVectorState,
|
|
5
|
+
OntologyVectorStorage,
|
|
6
|
+
} from "@sixb/core/storage"
|
|
7
|
+
import type { SQLClient } from "../pg-client"
|
|
8
|
+
import { jsonParameter as json, type PgRootOperation } from "./shared"
|
|
9
|
+
|
|
10
|
+
interface VectorMetadataRow {
|
|
11
|
+
readonly profile: string
|
|
12
|
+
readonly configuration: string
|
|
13
|
+
readonly source: string[]
|
|
14
|
+
readonly source_fingerprint: string
|
|
15
|
+
readonly last_commit_id: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class PgOntologyVectorStorage implements OntologyVectorStorage {
|
|
19
|
+
constructor(
|
|
20
|
+
private readonly sql: SQLClient,
|
|
21
|
+
private readonly runRootOperation: PgRootOperation,
|
|
22
|
+
private readonly assertSession: (
|
|
23
|
+
session: MaterializationSession,
|
|
24
|
+
projectId: string,
|
|
25
|
+
commitId?: string
|
|
26
|
+
) => void
|
|
27
|
+
) {}
|
|
28
|
+
|
|
29
|
+
async list(
|
|
30
|
+
input: Parameters<OntologyVectorStorage["list"]>[0]
|
|
31
|
+
): Promise<readonly ObjectVectorState[]> {
|
|
32
|
+
return this.runRootOperation(async (sql) => {
|
|
33
|
+
const rows = await sql<VectorMetadataRow[]>`
|
|
34
|
+
SELECT profile, configuration, source, source_fingerprint, last_commit_id
|
|
35
|
+
FROM object_vectors
|
|
36
|
+
WHERE project_id = ${input.projectId}
|
|
37
|
+
AND object_type_id = ${input.ref.objectTypeId} AND primary_id = ${input.ref.primaryId}
|
|
38
|
+
ORDER BY profile
|
|
39
|
+
`
|
|
40
|
+
return rows.map((row) => ({
|
|
41
|
+
ref: { ...input.ref },
|
|
42
|
+
profile: row.profile,
|
|
43
|
+
configuration: row.configuration,
|
|
44
|
+
source: row.source,
|
|
45
|
+
sourceFingerprint: row.source_fingerprint,
|
|
46
|
+
lastCommitId: row.last_commit_id,
|
|
47
|
+
}))
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async listBatch(
|
|
52
|
+
input: Parameters<OntologyVectorStorage["listBatch"]>[0]
|
|
53
|
+
): Promise<readonly ObjectVectorState[]> {
|
|
54
|
+
if (!input.refs.length) return []
|
|
55
|
+
return this.runRootOperation(async (sql) => {
|
|
56
|
+
const rows = await sql<
|
|
57
|
+
(VectorMetadataRow & { object_type_id: string; primary_id: string })[]
|
|
58
|
+
>`
|
|
59
|
+
SELECT stored.object_type_id, stored.primary_id, stored.profile, stored.configuration,
|
|
60
|
+
stored.source, stored.source_fingerprint, stored.last_commit_id
|
|
61
|
+
FROM (SELECT DISTINCT ref->>'objectTypeId' AS object_type_id, ref->>'primaryId' AS primary_id
|
|
62
|
+
FROM jsonb_array_elements(${json(sql, input.refs)}::jsonb) AS requested(ref)) AS requested
|
|
63
|
+
JOIN object_vectors AS stored ON stored.project_id = ${input.projectId}
|
|
64
|
+
AND stored.object_type_id = requested.object_type_id AND stored.primary_id = requested.primary_id
|
|
65
|
+
ORDER BY stored.object_type_id, stored.primary_id, stored.profile
|
|
66
|
+
`
|
|
67
|
+
return rows.map((row) => ({
|
|
68
|
+
ref: { objectTypeId: row.object_type_id, primaryId: row.primary_id },
|
|
69
|
+
profile: row.profile,
|
|
70
|
+
configuration: row.configuration,
|
|
71
|
+
source: row.source,
|
|
72
|
+
sourceFingerprint: row.source_fingerprint,
|
|
73
|
+
lastCommitId: row.last_commit_id,
|
|
74
|
+
}))
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async removeBatch(input: Parameters<OntologyVectorStorage["removeBatch"]>[0]): Promise<void> {
|
|
79
|
+
this.assertSession(input.session, input.projectId)
|
|
80
|
+
if (!input.entries.length) return
|
|
81
|
+
const removed = await this.sql`
|
|
82
|
+
DELETE FROM object_vectors AS stored
|
|
83
|
+
USING jsonb_array_elements(${json(this.sql, input.entries)}::jsonb) AS requested(entry)
|
|
84
|
+
WHERE stored.project_id = ${input.projectId}
|
|
85
|
+
AND stored.object_type_id = entry#>>'{ref,objectTypeId}'
|
|
86
|
+
AND stored.primary_id = entry#>>'{ref,primaryId}' AND stored.profile = entry->>'profile'
|
|
87
|
+
AND stored.last_commit_id = entry->>'expectedCommitId'
|
|
88
|
+
RETURNING stored.profile
|
|
89
|
+
`
|
|
90
|
+
if (removed.length !== input.entries.length) throw vectorConflict()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async write(input: Parameters<OntologyVectorStorage["write"]>[0]): Promise<void> {
|
|
94
|
+
await this.writeBatch({ session: input.session, projectId: input.projectId, entries: [input] })
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async writeBatch(input: Parameters<OntologyVectorStorage["writeBatch"]>[0]): Promise<void> {
|
|
98
|
+
const entries = input.entries.map(({ value, expectedCommitId }) => {
|
|
99
|
+
this.assertSession(input.session, input.projectId, value.lastCommitId)
|
|
100
|
+
return {
|
|
101
|
+
object_type_id: value.ref.objectTypeId,
|
|
102
|
+
primary_id: value.ref.primaryId,
|
|
103
|
+
profile: value.profile,
|
|
104
|
+
configuration: value.configuration,
|
|
105
|
+
source: [...value.source],
|
|
106
|
+
source_fingerprint: value.sourceFingerprint,
|
|
107
|
+
embedding: [...value.values],
|
|
108
|
+
last_commit_id: value.lastCommitId,
|
|
109
|
+
expected_commit_id: expectedCommitId,
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
const inserts = entries.filter((entry) => entry.expected_commit_id === null)
|
|
113
|
+
const updates = entries.filter((entry) => entry.expected_commit_id !== null)
|
|
114
|
+
|
|
115
|
+
// Keep insert-if-absent and revision-checked update distinct: an upsert could recreate
|
|
116
|
+
// a deleted vector or overwrite a representation produced after preparation.
|
|
117
|
+
if (inserts.length) {
|
|
118
|
+
const rows = await this.sql`
|
|
119
|
+
INSERT INTO object_vectors (
|
|
120
|
+
project_id, object_type_id, primary_id, profile, configuration,
|
|
121
|
+
source, source_fingerprint, embedding, last_commit_id
|
|
122
|
+
)
|
|
123
|
+
SELECT ${input.projectId}, object_type_id, primary_id, profile, configuration,
|
|
124
|
+
source, source_fingerprint, embedding, last_commit_id
|
|
125
|
+
FROM jsonb_to_recordset(${json(this.sql, inserts)}::jsonb) AS incoming(
|
|
126
|
+
object_type_id text, primary_id text, profile text, configuration text,
|
|
127
|
+
source text[], source_fingerprint text, embedding real[], last_commit_id text
|
|
128
|
+
)
|
|
129
|
+
ORDER BY object_type_id, primary_id, profile
|
|
130
|
+
ON CONFLICT (project_id, object_type_id, primary_id, profile) DO NOTHING
|
|
131
|
+
RETURNING profile`
|
|
132
|
+
if (rows.length !== inserts.length) throw vectorConflict()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (updates.length) {
|
|
136
|
+
const rows = await this.sql`
|
|
137
|
+
UPDATE object_vectors AS stored SET
|
|
138
|
+
configuration = incoming.configuration, source = incoming.source,
|
|
139
|
+
source_fingerprint = incoming.source_fingerprint, embedding = incoming.embedding,
|
|
140
|
+
last_commit_id = incoming.last_commit_id
|
|
141
|
+
FROM jsonb_to_recordset(${json(this.sql, updates)}::jsonb) AS incoming(
|
|
142
|
+
object_type_id text, primary_id text, profile text, configuration text,
|
|
143
|
+
source text[], source_fingerprint text, embedding real[], last_commit_id text,
|
|
144
|
+
expected_commit_id text
|
|
145
|
+
)
|
|
146
|
+
WHERE stored.project_id = ${input.projectId}
|
|
147
|
+
AND stored.object_type_id = incoming.object_type_id AND stored.primary_id = incoming.primary_id
|
|
148
|
+
AND stored.profile = incoming.profile AND stored.last_commit_id = incoming.expected_commit_id
|
|
149
|
+
RETURNING stored.profile`
|
|
150
|
+
if (rows.length !== updates.length) throw vectorConflict()
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async remove(input: Parameters<OntologyVectorStorage["remove"]>[0]): Promise<void> {
|
|
155
|
+
this.assertSession(input.session, input.projectId)
|
|
156
|
+
const rows = await this.sql`
|
|
157
|
+
DELETE FROM object_vectors
|
|
158
|
+
WHERE project_id = ${input.projectId}
|
|
159
|
+
AND object_type_id = ${input.ref.objectTypeId} AND primary_id = ${input.ref.primaryId}
|
|
160
|
+
AND profile = ${input.profile} AND last_commit_id = ${input.expectedCommitId}
|
|
161
|
+
RETURNING profile
|
|
162
|
+
`
|
|
163
|
+
if (rows.length !== 1) throw vectorConflict()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function vectorConflict(): MaterializationConflictError {
|
|
168
|
+
return new MaterializationConflictError(
|
|
169
|
+
"effective-state",
|
|
170
|
+
"Vector changed since it was prepared."
|
|
171
|
+
)
|
|
172
|
+
}
|
|
@@ -188,6 +188,8 @@ export class PgAiCostStorage implements AiCostStorage {
|
|
|
188
188
|
COUNT(*)::text AS model_call_count,
|
|
189
189
|
COUNT(*) FILTER (WHERE reporting_status <> 'unavailable')::text
|
|
190
190
|
AS reported_usage_call_count,
|
|
191
|
+
COUNT(*) FILTER (WHERE input_tokens IS NOT NULL AND output_tokens IS NOT NULL)::text
|
|
192
|
+
AS complete_usage_call_count,
|
|
191
193
|
COUNT(input_tokens)::text AS input_tokens_count,
|
|
192
194
|
SUM(input_tokens)::text AS input_tokens_sum,
|
|
193
195
|
COUNT(output_tokens)::text AS output_tokens_count,
|
|
@@ -379,6 +381,7 @@ interface AggregateRow {
|
|
|
379
381
|
readonly amount_currency: string | null
|
|
380
382
|
readonly model_call_count: string
|
|
381
383
|
readonly reported_usage_call_count: string
|
|
384
|
+
readonly complete_usage_call_count: string
|
|
382
385
|
readonly input_tokens_count: string
|
|
383
386
|
readonly input_tokens_sum: string | null
|
|
384
387
|
readonly output_tokens_count: string
|
|
@@ -455,6 +458,7 @@ function aggregateFragmentFromRow(row: AggregateRow): AiAccountingAggregateFragm
|
|
|
455
458
|
modelCallCount: row.model_call_count,
|
|
456
459
|
usage: {
|
|
457
460
|
reportedCallCount: row.reported_usage_call_count,
|
|
461
|
+
completeCallCount: row.complete_usage_call_count,
|
|
458
462
|
inputTokens: meterFragment(row.input_tokens_count, row.input_tokens_sum),
|
|
459
463
|
outputTokens: meterFragment(row.output_tokens_count, row.output_tokens_sum),
|
|
460
464
|
uncachedInputTokens: meterFragment(
|