@opengeni/db 0.14.3 → 0.16.2
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/{chunk-ELMUCYZF.js → chunk-4RUK5CON.js} +183 -42
- package/dist/chunk-4RUK5CON.js.map +1 -0
- package/dist/chunk-HZD7KVAR.js +4648 -0
- package/dist/chunk-HZD7KVAR.js.map +1 -0
- package/dist/{chunk-Y5WZZVQK.js → chunk-SK7YYHBI.js} +5 -2
- package/dist/chunk-SK7YYHBI.js.map +1 -0
- package/dist/codex-token-resolver.d.ts +64 -0
- package/dist/connection-token-resolver.d.ts +90 -0
- package/dist/environment-crypto.d.ts +11 -0
- package/dist/event-payload-sanitizer.d.ts +32 -0
- package/dist/index.d.ts +6217 -10
- package/dist/index.js +6930 -2708
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +157 -0
- package/dist/memory-domain.d.ts +44 -0
- package/dist/migrate.d.ts +5 -7
- package/dist/migrate.js +1 -1
- package/dist/new-session-drafts.d.ts +49 -0
- package/dist/persistence-errors.d.ts +69 -0
- package/dist/preference-registry-schema.d.ts +1147 -0
- package/dist/preference-registry.d.ts +878 -0
- package/dist/provision-roles.d.ts +4 -6489
- package/dist/provision-roles.js +1 -1
- package/dist/role-relationships.d.ts +35 -0
- package/dist/runtime-posture-cli.d.ts +1 -0
- package/dist/runtime-posture.d.ts +103 -0
- package/dist/schema.d.ts +23533 -3
- package/dist/schema.js +17 -1
- package/dist/session-control.d.ts +331 -0
- package/dist/session-queue-commands.d.ts +186 -0
- package/dist/session-tool-call-settlement.d.ts +32 -0
- package/dist/turn-initiator.d.ts +28 -0
- package/dist/workspace-instruction-policies-schema.d.ts +735 -0
- package/dist/workspace-instruction-policies.d.ts +64 -0
- package/drizzle/0136_unified_session_tool_policy.sql +1 -1
- package/drizzle/0137_preference_registry.sql +1347 -0
- package/drizzle/0138_sandbox_checkpoint_artifacts_and_deadlines.sql +1074 -0
- package/drizzle/0139_codex_provider_artifact_invalidations.sql +51 -0
- package/drizzle/0140_sandbox_restore_and_reaper_fences.sql +276 -0
- package/drizzle/0142_sandbox_archive_capture_gate.sql +80 -0
- package/drizzle/0143_session_codex_compaction_mode.sql +20 -0
- package/drizzle/0144_sandbox_viewer_force_drain_gate.sql +95 -0
- package/drizzle/0145_model_call_facts.sql +96 -0
- package/drizzle/0146_slack_bot_delete_idempotency.sql +105 -0
- package/drizzle/0147_draft_latency_mode.sql +12 -0
- package/drizzle/0148_session_turn_latency_mode.sql +12 -0
- package/package.json +6 -6
- package/src/index.ts +4995 -421
- package/src/insights.ts +837 -0
- package/src/migrate.ts +9 -1
- package/src/new-session-drafts.ts +4 -0
- package/src/preference-registry-schema.ts +170 -0
- package/src/preference-registry.ts +1220 -0
- package/src/provision-roles.ts +95 -22
- package/src/role-relationships.ts +132 -0
- package/src/runtime-posture.ts +28 -26
- package/src/schema.ts +478 -5
- package/src/session-queue-commands.ts +39 -6
- package/dist/chunk-DW24CKCT.js +0 -4046
- package/dist/chunk-DW24CKCT.js.map +0 -1
- package/dist/chunk-ELMUCYZF.js.map +0 -1
- package/dist/chunk-Y5WZZVQK.js.map +0 -1
- package/dist/schema-DhkRhcuQ.d.ts +0 -22666
package/src/insights.ts
ADDED
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
import { and, desc, eq, gte, inArray, lt, sql } from "drizzle-orm";
|
|
2
|
+
import { alias } from "drizzle-orm/pg-core";
|
|
3
|
+
import type { Database } from "./index";
|
|
4
|
+
import { rlsContextForWorkspace, withRlsContext } from "./index";
|
|
5
|
+
import * as schema from "./schema";
|
|
6
|
+
|
|
7
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
8
|
+
|
|
9
|
+
export type InsightsTimeWindow = {
|
|
10
|
+
since: Date;
|
|
11
|
+
until: Date;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type ModelCallFactAggregateRow = {
|
|
15
|
+
provider: string;
|
|
16
|
+
model: string;
|
|
17
|
+
billingPath: string;
|
|
18
|
+
calls: number;
|
|
19
|
+
inputTokens: number;
|
|
20
|
+
outputTokens: number;
|
|
21
|
+
cachedTokens: number;
|
|
22
|
+
cacheWriteTokens: number;
|
|
23
|
+
reasoningTokens: number;
|
|
24
|
+
totalTokens: number;
|
|
25
|
+
pricedCostMicros: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type InsightsDayBucket = {
|
|
29
|
+
day: string;
|
|
30
|
+
modelCostMicros: number;
|
|
31
|
+
warmSeconds: number;
|
|
32
|
+
inputTokens: number;
|
|
33
|
+
cachedTokens: number;
|
|
34
|
+
calls: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type WarmGroupAggregate = {
|
|
38
|
+
groupId: string;
|
|
39
|
+
warmSeconds: number;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type LiveWarmLeaseRow = {
|
|
43
|
+
id: string;
|
|
44
|
+
groupId: string;
|
|
45
|
+
backend: string;
|
|
46
|
+
turnHolders: number;
|
|
47
|
+
viewerHolders: number;
|
|
48
|
+
lastMeterAt: Date | null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type RootSessionDriverRow = {
|
|
52
|
+
rootSessionId: string;
|
|
53
|
+
title: string | null;
|
|
54
|
+
pricedCostMicros: number;
|
|
55
|
+
inputTokens: number;
|
|
56
|
+
cachedTokens: number;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type ScheduleFactAggregate = {
|
|
60
|
+
scheduledTaskId: string;
|
|
61
|
+
pricedCostMicros: number;
|
|
62
|
+
inputTokens: number;
|
|
63
|
+
cachedTokens: number;
|
|
64
|
+
calls: number;
|
|
65
|
+
/** Prefer credits whenever any credits-path call exists in the window. */
|
|
66
|
+
billingPath: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type ModelCallFacetRow = {
|
|
70
|
+
provider: string;
|
|
71
|
+
model: string;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type SessionDepthAggregate = {
|
|
75
|
+
depth: number;
|
|
76
|
+
sessions: number;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type FloorSessionRow = {
|
|
80
|
+
id: string;
|
|
81
|
+
title: string | null;
|
|
82
|
+
status: string;
|
|
83
|
+
directControlState: string;
|
|
84
|
+
nestedAgentDepth: number;
|
|
85
|
+
model: string;
|
|
86
|
+
sandboxBackend: string;
|
|
87
|
+
updatedAt: Date;
|
|
88
|
+
createdAt: Date;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
function dayKeyUtc(value: Date): string {
|
|
92
|
+
return value.toISOString().slice(0, 10);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function sumUsageQuantityInRange(
|
|
96
|
+
db: Database,
|
|
97
|
+
input: {
|
|
98
|
+
workspaceId: string;
|
|
99
|
+
eventType: string;
|
|
100
|
+
since: Date;
|
|
101
|
+
until: Date;
|
|
102
|
+
},
|
|
103
|
+
): Promise<number> {
|
|
104
|
+
const context = await rlsContextForWorkspace(db, input.workspaceId);
|
|
105
|
+
return await withRlsContext(db, context, async (scopedDb) => {
|
|
106
|
+
const [{ total } = { total: 0 }] = await scopedDb
|
|
107
|
+
.select({
|
|
108
|
+
total: sql<number>`coalesce(sum(${schema.usageEvents.quantity}), 0)`,
|
|
109
|
+
})
|
|
110
|
+
.from(schema.usageEvents)
|
|
111
|
+
.where(
|
|
112
|
+
and(
|
|
113
|
+
eq(schema.usageEvents.workspaceId, input.workspaceId),
|
|
114
|
+
eq(schema.usageEvents.eventType, input.eventType),
|
|
115
|
+
gte(schema.usageEvents.occurredAt, input.since),
|
|
116
|
+
lt(schema.usageEvents.occurredAt, input.until),
|
|
117
|
+
),
|
|
118
|
+
);
|
|
119
|
+
return Number(total);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function sumUsageQuantityByDay(
|
|
124
|
+
db: Database,
|
|
125
|
+
input: {
|
|
126
|
+
workspaceId: string;
|
|
127
|
+
eventType: string;
|
|
128
|
+
since: Date;
|
|
129
|
+
until: Date;
|
|
130
|
+
},
|
|
131
|
+
): Promise<Map<string, number>> {
|
|
132
|
+
const context = await rlsContextForWorkspace(db, input.workspaceId);
|
|
133
|
+
return await withRlsContext(db, context, async (scopedDb) => {
|
|
134
|
+
const rows = await scopedDb
|
|
135
|
+
.select({
|
|
136
|
+
day: sql<string>`to_char(date_trunc('day', ${schema.usageEvents.occurredAt} at time zone 'UTC'), 'YYYY-MM-DD')`,
|
|
137
|
+
total: sql<number>`coalesce(sum(${schema.usageEvents.quantity}), 0)`,
|
|
138
|
+
})
|
|
139
|
+
.from(schema.usageEvents)
|
|
140
|
+
.where(
|
|
141
|
+
and(
|
|
142
|
+
eq(schema.usageEvents.workspaceId, input.workspaceId),
|
|
143
|
+
eq(schema.usageEvents.eventType, input.eventType),
|
|
144
|
+
gte(schema.usageEvents.occurredAt, input.since),
|
|
145
|
+
lt(schema.usageEvents.occurredAt, input.until),
|
|
146
|
+
),
|
|
147
|
+
)
|
|
148
|
+
.groupBy(sql`date_trunc('day', ${schema.usageEvents.occurredAt} at time zone 'UTC')`);
|
|
149
|
+
return new Map(rows.map((row) => [row.day, Number(row.total)]));
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function aggregateModelCallFacts(
|
|
154
|
+
db: Database,
|
|
155
|
+
input: {
|
|
156
|
+
workspaceId: string;
|
|
157
|
+
since: Date;
|
|
158
|
+
until: Date;
|
|
159
|
+
provider?: string | null;
|
|
160
|
+
model?: string | null;
|
|
161
|
+
},
|
|
162
|
+
): Promise<ModelCallFactAggregateRow[]> {
|
|
163
|
+
const context = await rlsContextForWorkspace(db, input.workspaceId);
|
|
164
|
+
return await withRlsContext(db, context, async (scopedDb) => {
|
|
165
|
+
const clauses = [
|
|
166
|
+
eq(schema.modelCallFacts.workspaceId, input.workspaceId),
|
|
167
|
+
gte(schema.modelCallFacts.occurredAt, input.since),
|
|
168
|
+
lt(schema.modelCallFacts.occurredAt, input.until),
|
|
169
|
+
...(input.provider ? [eq(schema.modelCallFacts.provider, input.provider)] : []),
|
|
170
|
+
...(input.model ? [eq(schema.modelCallFacts.model, input.model)] : []),
|
|
171
|
+
];
|
|
172
|
+
const rows = await scopedDb
|
|
173
|
+
.select({
|
|
174
|
+
provider: schema.modelCallFacts.provider,
|
|
175
|
+
model: schema.modelCallFacts.model,
|
|
176
|
+
billingPath: schema.modelCallFacts.billingPath,
|
|
177
|
+
calls: sql<number>`count(*)::int`,
|
|
178
|
+
inputTokens: sql<number>`coalesce(sum(${schema.modelCallFacts.inputTokens}), 0)`,
|
|
179
|
+
outputTokens: sql<number>`coalesce(sum(${schema.modelCallFacts.outputTokens}), 0)`,
|
|
180
|
+
cachedTokens: sql<number>`coalesce(sum(${schema.modelCallFacts.cachedTokens}), 0)`,
|
|
181
|
+
cacheWriteTokens: sql<number>`coalesce(sum(${schema.modelCallFacts.cacheWriteTokens}), 0)`,
|
|
182
|
+
reasoningTokens: sql<number>`coalesce(sum(${schema.modelCallFacts.reasoningTokens}), 0)`,
|
|
183
|
+
totalTokens: sql<number>`coalesce(sum(${schema.modelCallFacts.totalTokens}), 0)`,
|
|
184
|
+
pricedCostMicros: sql<number>`coalesce(sum(${schema.modelCallFacts.pricedCostMicros}), 0)`,
|
|
185
|
+
})
|
|
186
|
+
.from(schema.modelCallFacts)
|
|
187
|
+
.where(and(...clauses))
|
|
188
|
+
.groupBy(
|
|
189
|
+
schema.modelCallFacts.provider,
|
|
190
|
+
schema.modelCallFacts.model,
|
|
191
|
+
schema.modelCallFacts.billingPath,
|
|
192
|
+
);
|
|
193
|
+
return rows.map((row) => ({
|
|
194
|
+
provider: row.provider,
|
|
195
|
+
model: row.model,
|
|
196
|
+
billingPath: row.billingPath,
|
|
197
|
+
calls: Number(row.calls),
|
|
198
|
+
inputTokens: Number(row.inputTokens),
|
|
199
|
+
outputTokens: Number(row.outputTokens),
|
|
200
|
+
cachedTokens: Number(row.cachedTokens),
|
|
201
|
+
cacheWriteTokens: Number(row.cacheWriteTokens),
|
|
202
|
+
reasoningTokens: Number(row.reasoningTokens),
|
|
203
|
+
totalTokens: Number(row.totalTokens),
|
|
204
|
+
pricedCostMicros: Number(row.pricedCostMicros),
|
|
205
|
+
}));
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function aggregateModelCallFactsByDay(
|
|
210
|
+
db: Database,
|
|
211
|
+
input: {
|
|
212
|
+
workspaceId: string;
|
|
213
|
+
since: Date;
|
|
214
|
+
until: Date;
|
|
215
|
+
provider?: string | null;
|
|
216
|
+
model?: string | null;
|
|
217
|
+
},
|
|
218
|
+
): Promise<
|
|
219
|
+
Map<string, { costMicros: number; inputTokens: number; cachedTokens: number; calls: number }>
|
|
220
|
+
> {
|
|
221
|
+
const context = await rlsContextForWorkspace(db, input.workspaceId);
|
|
222
|
+
return await withRlsContext(db, context, async (scopedDb) => {
|
|
223
|
+
const clauses = [
|
|
224
|
+
eq(schema.modelCallFacts.workspaceId, input.workspaceId),
|
|
225
|
+
gte(schema.modelCallFacts.occurredAt, input.since),
|
|
226
|
+
lt(schema.modelCallFacts.occurredAt, input.until),
|
|
227
|
+
...(input.provider ? [eq(schema.modelCallFacts.provider, input.provider)] : []),
|
|
228
|
+
...(input.model ? [eq(schema.modelCallFacts.model, input.model)] : []),
|
|
229
|
+
];
|
|
230
|
+
const rows = await scopedDb
|
|
231
|
+
.select({
|
|
232
|
+
day: sql<string>`to_char(date_trunc('day', ${schema.modelCallFacts.occurredAt} at time zone 'UTC'), 'YYYY-MM-DD')`,
|
|
233
|
+
costMicros: sql<number>`coalesce(sum(${schema.modelCallFacts.pricedCostMicros}), 0)`,
|
|
234
|
+
inputTokens: sql<number>`coalesce(sum(${schema.modelCallFacts.inputTokens}), 0)`,
|
|
235
|
+
cachedTokens: sql<number>`coalesce(sum(${schema.modelCallFacts.cachedTokens}), 0)`,
|
|
236
|
+
calls: sql<number>`count(*)::int`,
|
|
237
|
+
})
|
|
238
|
+
.from(schema.modelCallFacts)
|
|
239
|
+
.where(and(...clauses))
|
|
240
|
+
.groupBy(sql`date_trunc('day', ${schema.modelCallFacts.occurredAt} at time zone 'UTC')`);
|
|
241
|
+
return new Map(
|
|
242
|
+
rows.map((row) => [
|
|
243
|
+
row.day,
|
|
244
|
+
{
|
|
245
|
+
costMicros: Number(row.costMicros),
|
|
246
|
+
inputTokens: Number(row.inputTokens),
|
|
247
|
+
cachedTokens: Number(row.cachedTokens),
|
|
248
|
+
calls: Number(row.calls),
|
|
249
|
+
},
|
|
250
|
+
]),
|
|
251
|
+
);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export async function aggregateWarmSecondsByGroup(
|
|
256
|
+
db: Database,
|
|
257
|
+
input: InsightsTimeWindow & { workspaceId: string; limit?: number },
|
|
258
|
+
): Promise<WarmGroupAggregate[]> {
|
|
259
|
+
const context = await rlsContextForWorkspace(db, input.workspaceId);
|
|
260
|
+
const limit = input.limit ?? 24;
|
|
261
|
+
return await withRlsContext(db, context, async (scopedDb) => {
|
|
262
|
+
const rows = await scopedDb
|
|
263
|
+
.select({
|
|
264
|
+
groupId: sql<string>`split_part(${schema.usageEvents.sourceResourceId}, ':', 1)`,
|
|
265
|
+
warmSeconds: sql<number>`coalesce(sum(${schema.usageEvents.quantity}), 0)`,
|
|
266
|
+
})
|
|
267
|
+
.from(schema.usageEvents)
|
|
268
|
+
.where(
|
|
269
|
+
and(
|
|
270
|
+
eq(schema.usageEvents.workspaceId, input.workspaceId),
|
|
271
|
+
eq(schema.usageEvents.eventType, "sandbox.warm_seconds"),
|
|
272
|
+
gte(schema.usageEvents.occurredAt, input.since),
|
|
273
|
+
lt(schema.usageEvents.occurredAt, input.until),
|
|
274
|
+
sql`${schema.usageEvents.sourceResourceId} is not null`,
|
|
275
|
+
),
|
|
276
|
+
)
|
|
277
|
+
.groupBy(sql`split_part(${schema.usageEvents.sourceResourceId}, ':', 1)`)
|
|
278
|
+
.orderBy(sql`coalesce(sum(${schema.usageEvents.quantity}), 0) desc`)
|
|
279
|
+
.limit(Math.max(limit * 4, limit));
|
|
280
|
+
return rows
|
|
281
|
+
.filter((row) => UUID_RE.test(row.groupId))
|
|
282
|
+
.slice(0, limit)
|
|
283
|
+
.map((row) => ({
|
|
284
|
+
groupId: row.groupId,
|
|
285
|
+
warmSeconds: Number(row.warmSeconds),
|
|
286
|
+
}));
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export async function listLiveWarmLeases(
|
|
291
|
+
db: Database,
|
|
292
|
+
workspaceId: string,
|
|
293
|
+
): Promise<LiveWarmLeaseRow[]> {
|
|
294
|
+
const context = await rlsContextForWorkspace(db, workspaceId);
|
|
295
|
+
return await withRlsContext(db, context, async (scopedDb) => {
|
|
296
|
+
const rows = await scopedDb
|
|
297
|
+
.select({
|
|
298
|
+
id: schema.sandboxLeases.id,
|
|
299
|
+
groupId: schema.sandboxLeases.sandboxGroupId,
|
|
300
|
+
backend: schema.sandboxLeases.backend,
|
|
301
|
+
turnHolders: schema.sandboxLeases.turnHolders,
|
|
302
|
+
viewerHolders: schema.sandboxLeases.viewerHolders,
|
|
303
|
+
lastMeterAt: schema.sandboxLeases.lastMeterAt,
|
|
304
|
+
})
|
|
305
|
+
.from(schema.sandboxLeases)
|
|
306
|
+
.where(
|
|
307
|
+
and(
|
|
308
|
+
eq(schema.sandboxLeases.workspaceId, workspaceId),
|
|
309
|
+
// Only ready warm boxes — warming leases are not yet idle/usable compute.
|
|
310
|
+
eq(schema.sandboxLeases.liveness, "warm"),
|
|
311
|
+
),
|
|
312
|
+
)
|
|
313
|
+
.orderBy(desc(schema.sandboxLeases.updatedAt));
|
|
314
|
+
return rows.map((row) => ({
|
|
315
|
+
id: row.id,
|
|
316
|
+
groupId: row.groupId,
|
|
317
|
+
backend: row.backend,
|
|
318
|
+
turnHolders: row.turnHolders,
|
|
319
|
+
viewerHolders: row.viewerHolders,
|
|
320
|
+
lastMeterAt: row.lastMeterAt,
|
|
321
|
+
}));
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export async function countSessionsAttachedToGroups(
|
|
326
|
+
db: Database,
|
|
327
|
+
workspaceId: string,
|
|
328
|
+
groupIds: string[],
|
|
329
|
+
): Promise<Map<string, number>> {
|
|
330
|
+
if (groupIds.length === 0) return new Map();
|
|
331
|
+
const context = await rlsContextForWorkspace(db, workspaceId);
|
|
332
|
+
return await withRlsContext(db, context, async (scopedDb) => {
|
|
333
|
+
const rows = await scopedDb
|
|
334
|
+
.select({
|
|
335
|
+
groupId: schema.sessions.sandboxGroupId,
|
|
336
|
+
n: sql<number>`count(*)::int`,
|
|
337
|
+
})
|
|
338
|
+
.from(schema.sessions)
|
|
339
|
+
.where(
|
|
340
|
+
and(
|
|
341
|
+
eq(schema.sessions.workspaceId, workspaceId),
|
|
342
|
+
inArray(schema.sessions.sandboxGroupId, groupIds),
|
|
343
|
+
),
|
|
344
|
+
)
|
|
345
|
+
.groupBy(schema.sessions.sandboxGroupId);
|
|
346
|
+
return new Map(
|
|
347
|
+
rows
|
|
348
|
+
.filter((row): row is { groupId: string; n: number } => typeof row.groupId === "string")
|
|
349
|
+
.map((row) => [row.groupId, Number(row.n)]),
|
|
350
|
+
);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export async function aggregateRootSessionDrivers(
|
|
355
|
+
db: Database,
|
|
356
|
+
input: InsightsTimeWindow & {
|
|
357
|
+
workspaceId: string;
|
|
358
|
+
provider?: string | null;
|
|
359
|
+
model?: string | null;
|
|
360
|
+
rootSessionIds?: string[];
|
|
361
|
+
limit?: number;
|
|
362
|
+
},
|
|
363
|
+
): Promise<RootSessionDriverRow[]> {
|
|
364
|
+
if (input.rootSessionIds && input.rootSessionIds.length === 0) return [];
|
|
365
|
+
const context = await rlsContextForWorkspace(db, input.workspaceId);
|
|
366
|
+
const childSessions = alias(schema.sessions, "insight_child_sessions");
|
|
367
|
+
const rootSessions = alias(schema.sessions, "insight_root_sessions");
|
|
368
|
+
return await withRlsContext(db, context, async (scopedDb) => {
|
|
369
|
+
const clauses = [
|
|
370
|
+
eq(schema.modelCallFacts.workspaceId, input.workspaceId),
|
|
371
|
+
gte(schema.modelCallFacts.occurredAt, input.since),
|
|
372
|
+
lt(schema.modelCallFacts.occurredAt, input.until),
|
|
373
|
+
...(input.provider ? [eq(schema.modelCallFacts.provider, input.provider)] : []),
|
|
374
|
+
...(input.model ? [eq(schema.modelCallFacts.model, input.model)] : []),
|
|
375
|
+
...(input.rootSessionIds ? [inArray(childSessions.rootSessionId, input.rootSessionIds)] : []),
|
|
376
|
+
];
|
|
377
|
+
const query = scopedDb
|
|
378
|
+
.select({
|
|
379
|
+
rootSessionId: childSessions.rootSessionId,
|
|
380
|
+
title: rootSessions.title,
|
|
381
|
+
pricedCostMicros: sql<number>`coalesce(sum(${schema.modelCallFacts.pricedCostMicros}), 0)`,
|
|
382
|
+
inputTokens: sql<number>`coalesce(sum(${schema.modelCallFacts.inputTokens}), 0)`,
|
|
383
|
+
cachedTokens: sql<number>`coalesce(sum(${schema.modelCallFacts.cachedTokens}), 0)`,
|
|
384
|
+
})
|
|
385
|
+
.from(schema.modelCallFacts)
|
|
386
|
+
.innerJoin(
|
|
387
|
+
childSessions,
|
|
388
|
+
and(
|
|
389
|
+
eq(childSessions.workspaceId, schema.modelCallFacts.workspaceId),
|
|
390
|
+
eq(childSessions.id, schema.modelCallFacts.sessionId),
|
|
391
|
+
),
|
|
392
|
+
)
|
|
393
|
+
.leftJoin(
|
|
394
|
+
rootSessions,
|
|
395
|
+
and(
|
|
396
|
+
eq(rootSessions.workspaceId, childSessions.workspaceId),
|
|
397
|
+
eq(rootSessions.id, childSessions.rootSessionId),
|
|
398
|
+
),
|
|
399
|
+
)
|
|
400
|
+
.where(and(...clauses))
|
|
401
|
+
.groupBy(childSessions.rootSessionId, rootSessions.title)
|
|
402
|
+
.orderBy(sql`coalesce(sum(${schema.modelCallFacts.pricedCostMicros}), 0) desc`);
|
|
403
|
+
const rows = input.rootSessionIds ? await query : await query.limit(input.limit ?? 8);
|
|
404
|
+
return rows.map((row) => ({
|
|
405
|
+
rootSessionId: row.rootSessionId,
|
|
406
|
+
title: row.title,
|
|
407
|
+
pricedCostMicros: Number(row.pricedCostMicros),
|
|
408
|
+
inputTokens: Number(row.inputTokens),
|
|
409
|
+
cachedTokens: Number(row.cachedTokens),
|
|
410
|
+
}));
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export async function listModelCallFacets(
|
|
415
|
+
db: Database,
|
|
416
|
+
input: InsightsTimeWindow & { workspaceId: string },
|
|
417
|
+
): Promise<ModelCallFacetRow[]> {
|
|
418
|
+
const context = await rlsContextForWorkspace(db, input.workspaceId);
|
|
419
|
+
return await withRlsContext(db, context, async (scopedDb) => {
|
|
420
|
+
const rows = await scopedDb
|
|
421
|
+
.selectDistinct({
|
|
422
|
+
provider: schema.modelCallFacts.provider,
|
|
423
|
+
model: schema.modelCallFacts.model,
|
|
424
|
+
})
|
|
425
|
+
.from(schema.modelCallFacts)
|
|
426
|
+
.where(
|
|
427
|
+
and(
|
|
428
|
+
eq(schema.modelCallFacts.workspaceId, input.workspaceId),
|
|
429
|
+
gte(schema.modelCallFacts.occurredAt, input.since),
|
|
430
|
+
lt(schema.modelCallFacts.occurredAt, input.until),
|
|
431
|
+
),
|
|
432
|
+
)
|
|
433
|
+
.orderBy(schema.modelCallFacts.provider, schema.modelCallFacts.model)
|
|
434
|
+
.limit(500);
|
|
435
|
+
return rows;
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export async function aggregateScheduleFacts(
|
|
440
|
+
db: Database,
|
|
441
|
+
input: InsightsTimeWindow & {
|
|
442
|
+
workspaceId: string;
|
|
443
|
+
provider?: string | null;
|
|
444
|
+
model?: string | null;
|
|
445
|
+
},
|
|
446
|
+
): Promise<ScheduleFactAggregate[]> {
|
|
447
|
+
const context = await rlsContextForWorkspace(db, input.workspaceId);
|
|
448
|
+
return await withRlsContext(db, context, async (scopedDb) => {
|
|
449
|
+
const clauses = [
|
|
450
|
+
eq(schema.modelCallFacts.workspaceId, input.workspaceId),
|
|
451
|
+
gte(schema.modelCallFacts.occurredAt, input.since),
|
|
452
|
+
lt(schema.modelCallFacts.occurredAt, input.until),
|
|
453
|
+
sql`${schema.modelCallFacts.scheduledTaskId} is not null`,
|
|
454
|
+
...(input.provider ? [eq(schema.modelCallFacts.provider, input.provider)] : []),
|
|
455
|
+
...(input.model ? [eq(schema.modelCallFacts.model, input.model)] : []),
|
|
456
|
+
];
|
|
457
|
+
const rows = await scopedDb
|
|
458
|
+
.select({
|
|
459
|
+
scheduledTaskId: schema.modelCallFacts.scheduledTaskId,
|
|
460
|
+
pricedCostMicros: sql<number>`coalesce(sum(${schema.modelCallFacts.pricedCostMicros}), 0)`,
|
|
461
|
+
inputTokens: sql<number>`coalesce(sum(${schema.modelCallFacts.inputTokens}), 0)`,
|
|
462
|
+
cachedTokens: sql<number>`coalesce(sum(${schema.modelCallFacts.cachedTokens}), 0)`,
|
|
463
|
+
calls: sql<number>`count(*)::int`,
|
|
464
|
+
billingPath: sql<string>`case
|
|
465
|
+
when bool_or(${schema.modelCallFacts.billingPath} = 'opengeni_credits')
|
|
466
|
+
then 'opengeni_credits'
|
|
467
|
+
else 'external'
|
|
468
|
+
end`,
|
|
469
|
+
})
|
|
470
|
+
.from(schema.modelCallFacts)
|
|
471
|
+
.where(and(...clauses))
|
|
472
|
+
.groupBy(schema.modelCallFacts.scheduledTaskId);
|
|
473
|
+
return rows
|
|
474
|
+
.filter((row): row is typeof row & { scheduledTaskId: string } => row.scheduledTaskId != null)
|
|
475
|
+
.map((row) => ({
|
|
476
|
+
scheduledTaskId: row.scheduledTaskId,
|
|
477
|
+
pricedCostMicros: Number(row.pricedCostMicros),
|
|
478
|
+
inputTokens: Number(row.inputTokens),
|
|
479
|
+
cachedTokens: Number(row.cachedTokens),
|
|
480
|
+
calls: Number(row.calls),
|
|
481
|
+
billingPath: row.billingPath,
|
|
482
|
+
}));
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export async function countScheduledTaskFires(
|
|
487
|
+
db: Database,
|
|
488
|
+
input: InsightsTimeWindow & { workspaceId: string; taskIds: string[] },
|
|
489
|
+
): Promise<Map<string, number>> {
|
|
490
|
+
if (input.taskIds.length === 0) return new Map();
|
|
491
|
+
const context = await rlsContextForWorkspace(db, input.workspaceId);
|
|
492
|
+
return await withRlsContext(db, context, async (scopedDb) => {
|
|
493
|
+
const rows = await scopedDb
|
|
494
|
+
.select({
|
|
495
|
+
taskId: schema.scheduledTaskRuns.taskId,
|
|
496
|
+
n: sql<number>`count(*)::int`,
|
|
497
|
+
})
|
|
498
|
+
.from(schema.scheduledTaskRuns)
|
|
499
|
+
.where(
|
|
500
|
+
and(
|
|
501
|
+
eq(schema.scheduledTaskRuns.workspaceId, input.workspaceId),
|
|
502
|
+
inArray(schema.scheduledTaskRuns.taskId, input.taskIds),
|
|
503
|
+
gte(schema.scheduledTaskRuns.createdAt, input.since),
|
|
504
|
+
lt(schema.scheduledTaskRuns.createdAt, input.until),
|
|
505
|
+
),
|
|
506
|
+
)
|
|
507
|
+
.groupBy(schema.scheduledTaskRuns.taskId);
|
|
508
|
+
return new Map(rows.map((row) => [row.taskId, Number(row.n)]));
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export async function aggregateSessionDepth(
|
|
513
|
+
db: Database,
|
|
514
|
+
workspaceId: string,
|
|
515
|
+
): Promise<{
|
|
516
|
+
buckets: SessionDepthAggregate[];
|
|
517
|
+
sessionsTouched: number;
|
|
518
|
+
rootSessions: number;
|
|
519
|
+
deepestDepth: number;
|
|
520
|
+
deepestSessionTitle: string;
|
|
521
|
+
avgDepth: number;
|
|
522
|
+
goalsActive: number;
|
|
523
|
+
goalsCompleted: number;
|
|
524
|
+
}> {
|
|
525
|
+
const context = await rlsContextForWorkspace(db, workspaceId);
|
|
526
|
+
return await withRlsContext(db, context, async (scopedDb) => {
|
|
527
|
+
const buckets = await scopedDb
|
|
528
|
+
.select({
|
|
529
|
+
depth: schema.sessions.nestedAgentDepth,
|
|
530
|
+
sessions: sql<number>`count(*)::int`,
|
|
531
|
+
})
|
|
532
|
+
.from(schema.sessions)
|
|
533
|
+
.where(eq(schema.sessions.workspaceId, workspaceId))
|
|
534
|
+
.groupBy(schema.sessions.nestedAgentDepth)
|
|
535
|
+
.orderBy(schema.sessions.nestedAgentDepth);
|
|
536
|
+
const [stats] = await scopedDb
|
|
537
|
+
.select({
|
|
538
|
+
sessionsTouched: sql<number>`count(*)::int`,
|
|
539
|
+
rootSessions: sql<number>`count(*) filter (where ${schema.sessions.nestedAgentDepth} = 0)::int`,
|
|
540
|
+
deepestDepth: sql<number>`coalesce(max(${schema.sessions.nestedAgentDepth}), 0)`,
|
|
541
|
+
avgDepth: sql<number>`coalesce(avg(${schema.sessions.nestedAgentDepth}), 0)`,
|
|
542
|
+
})
|
|
543
|
+
.from(schema.sessions)
|
|
544
|
+
.where(eq(schema.sessions.workspaceId, workspaceId));
|
|
545
|
+
const [deepest] = await scopedDb
|
|
546
|
+
.select({
|
|
547
|
+
title: schema.sessions.title,
|
|
548
|
+
depth: schema.sessions.nestedAgentDepth,
|
|
549
|
+
})
|
|
550
|
+
.from(schema.sessions)
|
|
551
|
+
.where(eq(schema.sessions.workspaceId, workspaceId))
|
|
552
|
+
.orderBy(desc(schema.sessions.nestedAgentDepth), desc(schema.sessions.updatedAt))
|
|
553
|
+
.limit(1);
|
|
554
|
+
const [goals] = await scopedDb
|
|
555
|
+
.select({
|
|
556
|
+
active: sql<number>`count(*) filter (where ${schema.sessionGoals.status} = 'active')::int`,
|
|
557
|
+
completed: sql<number>`count(*) filter (where ${schema.sessionGoals.status} = 'completed')::int`,
|
|
558
|
+
})
|
|
559
|
+
.from(schema.sessionGoals)
|
|
560
|
+
.where(eq(schema.sessionGoals.workspaceId, workspaceId));
|
|
561
|
+
return {
|
|
562
|
+
buckets: buckets.map((row) => ({
|
|
563
|
+
depth: row.depth,
|
|
564
|
+
sessions: Number(row.sessions),
|
|
565
|
+
})),
|
|
566
|
+
sessionsTouched: Number(stats?.sessionsTouched ?? 0),
|
|
567
|
+
rootSessions: Number(stats?.rootSessions ?? 0),
|
|
568
|
+
deepestDepth: Number(stats?.deepestDepth ?? 0),
|
|
569
|
+
deepestSessionTitle: deepest?.title?.trim() || "",
|
|
570
|
+
avgDepth: Number(stats?.avgDepth ?? 0),
|
|
571
|
+
goalsActive: Number(goals?.active ?? 0),
|
|
572
|
+
goalsCompleted: Number(goals?.completed ?? 0),
|
|
573
|
+
};
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export async function listFloorSessions(
|
|
578
|
+
db: Database,
|
|
579
|
+
workspaceId: string,
|
|
580
|
+
limit = 24,
|
|
581
|
+
): Promise<FloorSessionRow[]> {
|
|
582
|
+
const context = await rlsContextForWorkspace(db, workspaceId);
|
|
583
|
+
return await withRlsContext(db, context, async (scopedDb) => {
|
|
584
|
+
const rows = await scopedDb
|
|
585
|
+
.select({
|
|
586
|
+
id: schema.sessions.id,
|
|
587
|
+
title: schema.sessions.title,
|
|
588
|
+
status: schema.sessions.status,
|
|
589
|
+
directControlState: schema.sessions.directControlState,
|
|
590
|
+
nestedAgentDepth: schema.sessions.nestedAgentDepth,
|
|
591
|
+
model: schema.sessions.model,
|
|
592
|
+
sandboxBackend: schema.sessions.sandboxBackend,
|
|
593
|
+
updatedAt: schema.sessions.updatedAt,
|
|
594
|
+
createdAt: schema.sessions.createdAt,
|
|
595
|
+
})
|
|
596
|
+
.from(schema.sessions)
|
|
597
|
+
.where(eq(schema.sessions.workspaceId, workspaceId))
|
|
598
|
+
.orderBy(desc(schema.sessions.updatedAt))
|
|
599
|
+
.limit(limit);
|
|
600
|
+
return rows;
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export async function countOnlineMachines(
|
|
605
|
+
db: Database,
|
|
606
|
+
workspaceId: string,
|
|
607
|
+
heartbeatFreshMs: number,
|
|
608
|
+
): Promise<number> {
|
|
609
|
+
const context = await rlsContextForWorkspace(db, workspaceId);
|
|
610
|
+
return await withRlsContext(db, context, async (scopedDb) => {
|
|
611
|
+
const cutoff = new Date(Date.now() - heartbeatFreshMs);
|
|
612
|
+
const [{ n } = { n: 0 }] = await scopedDb
|
|
613
|
+
.select({ n: sql<number>`count(*)::int` })
|
|
614
|
+
.from(schema.enrollments)
|
|
615
|
+
.where(
|
|
616
|
+
and(
|
|
617
|
+
eq(schema.enrollments.workspaceId, workspaceId),
|
|
618
|
+
eq(schema.enrollments.status, "active"),
|
|
619
|
+
gte(schema.enrollments.lastSeenAt, cutoff),
|
|
620
|
+
),
|
|
621
|
+
);
|
|
622
|
+
return Number(n);
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export function enumerateUtcDays(since: Date, until: Date): string[] {
|
|
627
|
+
const days: string[] = [];
|
|
628
|
+
const cursor = new Date(
|
|
629
|
+
Date.UTC(since.getUTCFullYear(), since.getUTCMonth(), since.getUTCDate()),
|
|
630
|
+
);
|
|
631
|
+
const end = until.getTime();
|
|
632
|
+
while (cursor.getTime() < end) {
|
|
633
|
+
days.push(dayKeyUtc(cursor));
|
|
634
|
+
cursor.setUTCDate(cursor.getUTCDate() + 1);
|
|
635
|
+
}
|
|
636
|
+
return days;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Idempotent YTD backfill from authoritative agent.model.usage into model_call_facts.
|
|
641
|
+
* Never rewrites billing rows or existing live facts (onConflictDoNothing).
|
|
642
|
+
* Commits in bounded batches so a long YTD run cannot pin one multi-hour txn.
|
|
643
|
+
*/
|
|
644
|
+
export async function backfillModelCallFactsFromSessionEvents(
|
|
645
|
+
db: Database,
|
|
646
|
+
input: {
|
|
647
|
+
workspaceId: string;
|
|
648
|
+
since: Date;
|
|
649
|
+
until?: Date;
|
|
650
|
+
limit?: number;
|
|
651
|
+
batchSize?: number;
|
|
652
|
+
},
|
|
653
|
+
): Promise<{ considered: number; upserted: number }> {
|
|
654
|
+
const until = input.until ?? new Date();
|
|
655
|
+
const limit = input.limit ?? 50_000;
|
|
656
|
+
const batchSize = Math.max(1, Math.min(input.batchSize ?? 500, limit));
|
|
657
|
+
const context = await rlsContextForWorkspace(db, input.workspaceId);
|
|
658
|
+
|
|
659
|
+
let considered = 0;
|
|
660
|
+
let upserted = 0;
|
|
661
|
+
let cursorOccurredAt = input.since;
|
|
662
|
+
let cursorId = "00000000-0000-0000-0000-000000000000";
|
|
663
|
+
|
|
664
|
+
while (considered < limit) {
|
|
665
|
+
const remaining = limit - considered;
|
|
666
|
+
const page = await withRlsContext(db, context, async (scopedDb) => {
|
|
667
|
+
return await scopedDb
|
|
668
|
+
.select({
|
|
669
|
+
id: schema.sessionEvents.id,
|
|
670
|
+
accountId: schema.sessionEvents.accountId,
|
|
671
|
+
workspaceId: schema.sessionEvents.workspaceId,
|
|
672
|
+
sessionId: schema.sessionEvents.sessionId,
|
|
673
|
+
turnId: schema.sessionEvents.turnId,
|
|
674
|
+
turnAttemptId: schema.sessionEvents.turnAttemptId,
|
|
675
|
+
payload: schema.sessionEvents.payload,
|
|
676
|
+
occurredAt: schema.sessionEvents.occurredAt,
|
|
677
|
+
})
|
|
678
|
+
.from(schema.sessionEvents)
|
|
679
|
+
.where(
|
|
680
|
+
and(
|
|
681
|
+
eq(schema.sessionEvents.workspaceId, input.workspaceId),
|
|
682
|
+
eq(schema.sessionEvents.type, "agent.model.usage"),
|
|
683
|
+
eq(schema.sessionEvents.turnAssociation, "current"),
|
|
684
|
+
lt(schema.sessionEvents.occurredAt, until),
|
|
685
|
+
sql`${schema.sessionEvents.turnId} is not null`,
|
|
686
|
+
// Keyset on (occurred_at, id) so same-millisecond bursts cannot be skipped.
|
|
687
|
+
sql`(${schema.sessionEvents.occurredAt}, ${schema.sessionEvents.id}) > (${cursorOccurredAt}, ${cursorId}::uuid)`,
|
|
688
|
+
),
|
|
689
|
+
)
|
|
690
|
+
.orderBy(schema.sessionEvents.occurredAt, schema.sessionEvents.id)
|
|
691
|
+
.limit(Math.min(batchSize, remaining));
|
|
692
|
+
});
|
|
693
|
+
if (page.length === 0) break;
|
|
694
|
+
considered += page.length;
|
|
695
|
+
const last = page[page.length - 1]!;
|
|
696
|
+
cursorOccurredAt = last.occurredAt;
|
|
697
|
+
cursorId = last.id;
|
|
698
|
+
|
|
699
|
+
const batchUpserted = await withRlsContext(db, context, async (scopedDb) => {
|
|
700
|
+
let inserted = 0;
|
|
701
|
+
for (const event of page) {
|
|
702
|
+
if (!event.turnId) continue;
|
|
703
|
+
const payload =
|
|
704
|
+
event.payload && typeof event.payload === "object" && !Array.isArray(event.payload)
|
|
705
|
+
? (event.payload as Record<string, unknown>)
|
|
706
|
+
: null;
|
|
707
|
+
if (!payload) continue;
|
|
708
|
+
const sourceKey = typeof payload.sourceKey === "string" ? payload.sourceKey : null;
|
|
709
|
+
const provider = typeof payload.provider === "string" ? payload.provider : null;
|
|
710
|
+
const providerApi = typeof payload.providerApi === "string" ? payload.providerApi : null;
|
|
711
|
+
const model = typeof payload.model === "string" ? payload.model : null;
|
|
712
|
+
if (!sourceKey || !provider || !providerApi || !model) continue;
|
|
713
|
+
|
|
714
|
+
const sourceResourceId = `${event.turnId}:${sourceKey}`;
|
|
715
|
+
const [cost] = await scopedDb
|
|
716
|
+
.select({ quantity: schema.usageEvents.quantity })
|
|
717
|
+
.from(schema.usageEvents)
|
|
718
|
+
.where(
|
|
719
|
+
and(
|
|
720
|
+
eq(schema.usageEvents.workspaceId, input.workspaceId),
|
|
721
|
+
eq(schema.usageEvents.eventType, "model.cost"),
|
|
722
|
+
eq(schema.usageEvents.sourceResourceId, sourceResourceId),
|
|
723
|
+
),
|
|
724
|
+
)
|
|
725
|
+
.limit(1);
|
|
726
|
+
const [tokenRow] = await scopedDb
|
|
727
|
+
.select({ id: schema.usageEvents.id })
|
|
728
|
+
.from(schema.usageEvents)
|
|
729
|
+
.where(
|
|
730
|
+
and(
|
|
731
|
+
eq(schema.usageEvents.workspaceId, input.workspaceId),
|
|
732
|
+
eq(schema.usageEvents.eventType, "model.tokens"),
|
|
733
|
+
eq(schema.usageEvents.sourceResourceId, sourceResourceId),
|
|
734
|
+
),
|
|
735
|
+
)
|
|
736
|
+
.limit(1);
|
|
737
|
+
|
|
738
|
+
// External turns always write model.cost=0 and never model.tokens.
|
|
739
|
+
// Credits turns with totalTokens=0 write neither — keep those as credits.
|
|
740
|
+
const pricedCostMicros = cost ? Number(cost.quantity) : 0;
|
|
741
|
+
const billingPath =
|
|
742
|
+
cost != null && pricedCostMicros === 0 && !tokenRow ? "external" : "opengeni_credits";
|
|
743
|
+
|
|
744
|
+
const [turn] = await scopedDb
|
|
745
|
+
.select({
|
|
746
|
+
source: schema.sessionTurns.source,
|
|
747
|
+
initiatorKind: schema.sessionTurns.initiatorKind,
|
|
748
|
+
initiatorSubjectId: schema.sessionTurns.initiatorSubjectId,
|
|
749
|
+
initiatorContext: schema.sessionTurns.initiatorContext,
|
|
750
|
+
})
|
|
751
|
+
.from(schema.sessionTurns)
|
|
752
|
+
.where(
|
|
753
|
+
and(
|
|
754
|
+
eq(schema.sessionTurns.workspaceId, input.workspaceId),
|
|
755
|
+
eq(schema.sessionTurns.id, event.turnId),
|
|
756
|
+
),
|
|
757
|
+
)
|
|
758
|
+
.limit(1);
|
|
759
|
+
|
|
760
|
+
let scheduledTaskId: string | null = null;
|
|
761
|
+
const runIds =
|
|
762
|
+
turn?.initiatorContext &&
|
|
763
|
+
typeof turn.initiatorContext === "object" &&
|
|
764
|
+
!Array.isArray(turn.initiatorContext) &&
|
|
765
|
+
Array.isArray((turn.initiatorContext as Record<string, unknown>).scheduledRunIds)
|
|
766
|
+
? (
|
|
767
|
+
(turn.initiatorContext as Record<string, unknown>).scheduledRunIds as unknown[]
|
|
768
|
+
).filter((value): value is string => typeof value === "string")
|
|
769
|
+
: [];
|
|
770
|
+
if (runIds.length > 0) {
|
|
771
|
+
const [run] = await scopedDb
|
|
772
|
+
.select({ taskId: schema.scheduledTaskRuns.taskId })
|
|
773
|
+
.from(schema.scheduledTaskRuns)
|
|
774
|
+
.where(
|
|
775
|
+
and(
|
|
776
|
+
eq(schema.scheduledTaskRuns.workspaceId, input.workspaceId),
|
|
777
|
+
inArray(schema.scheduledTaskRuns.id, runIds),
|
|
778
|
+
),
|
|
779
|
+
)
|
|
780
|
+
.orderBy(schema.scheduledTaskRuns.createdAt)
|
|
781
|
+
.limit(1);
|
|
782
|
+
scheduledTaskId = run?.taskId ?? null;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const insertedRows = await scopedDb
|
|
786
|
+
.insert(schema.modelCallFacts)
|
|
787
|
+
.values({
|
|
788
|
+
accountId: event.accountId,
|
|
789
|
+
workspaceId: event.workspaceId,
|
|
790
|
+
sessionId: event.sessionId,
|
|
791
|
+
turnId: event.turnId,
|
|
792
|
+
turnAttemptId: event.turnAttemptId,
|
|
793
|
+
sourceKey,
|
|
794
|
+
provider,
|
|
795
|
+
providerApi,
|
|
796
|
+
model,
|
|
797
|
+
billingPath,
|
|
798
|
+
turnSource: turn?.source ?? null,
|
|
799
|
+
initiatorKind: turn?.initiatorKind ?? null,
|
|
800
|
+
initiatorSubjectId: turn?.initiatorSubjectId ?? null,
|
|
801
|
+
scheduledTaskId,
|
|
802
|
+
inputTokens: numberOrNull(payload.inputTokens),
|
|
803
|
+
outputTokens: numberOrNull(payload.outputTokens),
|
|
804
|
+
cachedTokens: numberOrNull(payload.cachedTokens),
|
|
805
|
+
cacheWriteTokens: numberOrNull(payload.cacheWriteTokens),
|
|
806
|
+
reasoningTokens: numberOrNull(payload.reasoningTokens),
|
|
807
|
+
totalTokens:
|
|
808
|
+
numberOrNull(payload.inputTokens) !== null ||
|
|
809
|
+
numberOrNull(payload.outputTokens) !== null
|
|
810
|
+
? (numberOrNull(payload.inputTokens) ?? 0) +
|
|
811
|
+
(numberOrNull(payload.outputTokens) ?? 0)
|
|
812
|
+
: null,
|
|
813
|
+
pricedCostMicros,
|
|
814
|
+
occurredAt: event.occurredAt,
|
|
815
|
+
})
|
|
816
|
+
.onConflictDoNothing({
|
|
817
|
+
target: [
|
|
818
|
+
schema.modelCallFacts.workspaceId,
|
|
819
|
+
schema.modelCallFacts.turnId,
|
|
820
|
+
schema.modelCallFacts.sourceKey,
|
|
821
|
+
],
|
|
822
|
+
})
|
|
823
|
+
.returning({ id: schema.modelCallFacts.id });
|
|
824
|
+
if (insertedRows.length > 0) inserted += 1;
|
|
825
|
+
}
|
|
826
|
+
return inserted;
|
|
827
|
+
});
|
|
828
|
+
upserted += batchUpserted;
|
|
829
|
+
if (page.length < Math.min(batchSize, remaining)) break;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return { considered, upserted };
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function numberOrNull(value: unknown): number | null {
|
|
836
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
837
|
+
}
|