bopodev-db 0.1.12 → 0.1.14
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-typecheck.log +1 -1
- package/LICENSE +1 -1
- package/dist/repositories.d.ts +247 -4
- package/dist/schema.d.ts +3124 -830
- package/package.json +1 -1
- package/src/bootstrap.ts +143 -0
- package/src/repositories.ts +509 -3
- package/src/schema.ts +118 -9
package/src/repositories.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { and, desc, eq, inArray, notInArray, sql } from "drizzle-orm";
|
|
1
|
+
import { and, asc, desc, eq, gt, inArray, notInArray, sql } from "drizzle-orm";
|
|
2
2
|
import { nanoid } from "nanoid";
|
|
3
3
|
import type { BopoDb } from "./client";
|
|
4
4
|
import {
|
|
@@ -11,8 +11,14 @@ import {
|
|
|
11
11
|
costLedger,
|
|
12
12
|
goals,
|
|
13
13
|
heartbeatRuns,
|
|
14
|
+
heartbeatRunMessages,
|
|
15
|
+
issueAttachments,
|
|
14
16
|
issueComments,
|
|
15
17
|
issues,
|
|
18
|
+
modelPricing,
|
|
19
|
+
pluginConfigs,
|
|
20
|
+
pluginRuns,
|
|
21
|
+
plugins,
|
|
16
22
|
projects,
|
|
17
23
|
touchUpdatedAtSql
|
|
18
24
|
} from "./schema";
|
|
@@ -106,6 +112,7 @@ export async function listProjects(db: BopoDb, companyId: string) {
|
|
|
106
112
|
export async function createProject(
|
|
107
113
|
db: BopoDb,
|
|
108
114
|
input: {
|
|
115
|
+
id?: string;
|
|
109
116
|
companyId: string;
|
|
110
117
|
name: string;
|
|
111
118
|
description?: string | null;
|
|
@@ -303,6 +310,76 @@ export async function deleteIssue(db: BopoDb, companyId: string, id: string) {
|
|
|
303
310
|
return Boolean(deletedIssue);
|
|
304
311
|
}
|
|
305
312
|
|
|
313
|
+
export async function addIssueAttachment(
|
|
314
|
+
db: BopoDb,
|
|
315
|
+
input: {
|
|
316
|
+
id?: string;
|
|
317
|
+
companyId: string;
|
|
318
|
+
issueId: string;
|
|
319
|
+
projectId: string;
|
|
320
|
+
fileName: string;
|
|
321
|
+
mimeType?: string | null;
|
|
322
|
+
fileSizeBytes: number;
|
|
323
|
+
relativePath: string;
|
|
324
|
+
uploadedByActorType?: "human" | "agent" | "system";
|
|
325
|
+
uploadedByActorId?: string | null;
|
|
326
|
+
}
|
|
327
|
+
) {
|
|
328
|
+
await assertIssueBelongsToCompany(db, input.companyId, input.issueId);
|
|
329
|
+
await assertProjectBelongsToCompany(db, input.companyId, input.projectId);
|
|
330
|
+
const id = input.id ?? nanoid(14);
|
|
331
|
+
await db.insert(issueAttachments).values({
|
|
332
|
+
id,
|
|
333
|
+
companyId: input.companyId,
|
|
334
|
+
issueId: input.issueId,
|
|
335
|
+
projectId: input.projectId,
|
|
336
|
+
fileName: input.fileName,
|
|
337
|
+
mimeType: input.mimeType ?? null,
|
|
338
|
+
fileSizeBytes: input.fileSizeBytes,
|
|
339
|
+
relativePath: input.relativePath,
|
|
340
|
+
uploadedByActorType: input.uploadedByActorType ?? "human",
|
|
341
|
+
uploadedByActorId: input.uploadedByActorId ?? null
|
|
342
|
+
});
|
|
343
|
+
return { id, ...input };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export async function listIssueAttachments(db: BopoDb, companyId: string, issueId: string) {
|
|
347
|
+
return db
|
|
348
|
+
.select()
|
|
349
|
+
.from(issueAttachments)
|
|
350
|
+
.where(and(eq(issueAttachments.companyId, companyId), eq(issueAttachments.issueId, issueId)))
|
|
351
|
+
.orderBy(desc(issueAttachments.createdAt));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export async function getIssueAttachment(db: BopoDb, companyId: string, issueId: string, attachmentId: string) {
|
|
355
|
+
const [attachment] = await db
|
|
356
|
+
.select()
|
|
357
|
+
.from(issueAttachments)
|
|
358
|
+
.where(
|
|
359
|
+
and(
|
|
360
|
+
eq(issueAttachments.companyId, companyId),
|
|
361
|
+
eq(issueAttachments.issueId, issueId),
|
|
362
|
+
eq(issueAttachments.id, attachmentId)
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
.limit(1);
|
|
366
|
+
return attachment ?? null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export async function deleteIssueAttachment(db: BopoDb, companyId: string, issueId: string, attachmentId: string) {
|
|
370
|
+
const [deletedAttachment] = await db
|
|
371
|
+
.delete(issueAttachments)
|
|
372
|
+
.where(
|
|
373
|
+
and(
|
|
374
|
+
eq(issueAttachments.companyId, companyId),
|
|
375
|
+
eq(issueAttachments.issueId, issueId),
|
|
376
|
+
eq(issueAttachments.id, attachmentId)
|
|
377
|
+
)
|
|
378
|
+
)
|
|
379
|
+
.returning();
|
|
380
|
+
return deletedAttachment ?? null;
|
|
381
|
+
}
|
|
382
|
+
|
|
306
383
|
export async function addIssueComment(
|
|
307
384
|
db: BopoDb,
|
|
308
385
|
input: {
|
|
@@ -461,7 +538,16 @@ export async function createAgent(
|
|
|
461
538
|
managerAgentId?: string | null;
|
|
462
539
|
role: string;
|
|
463
540
|
name: string;
|
|
464
|
-
providerType:
|
|
541
|
+
providerType:
|
|
542
|
+
| "claude_code"
|
|
543
|
+
| "codex"
|
|
544
|
+
| "cursor"
|
|
545
|
+
| "opencode"
|
|
546
|
+
| "gemini_cli"
|
|
547
|
+
| "openai_api"
|
|
548
|
+
| "anthropic_api"
|
|
549
|
+
| "http"
|
|
550
|
+
| "shell";
|
|
465
551
|
heartbeatCron: string;
|
|
466
552
|
monthlyBudgetUsd: string;
|
|
467
553
|
canHireAgents?: boolean;
|
|
@@ -523,7 +609,16 @@ export async function updateAgent(
|
|
|
523
609
|
managerAgentId?: string | null;
|
|
524
610
|
role?: string;
|
|
525
611
|
name?: string;
|
|
526
|
-
providerType?:
|
|
612
|
+
providerType?:
|
|
613
|
+
| "claude_code"
|
|
614
|
+
| "codex"
|
|
615
|
+
| "cursor"
|
|
616
|
+
| "opencode"
|
|
617
|
+
| "gemini_cli"
|
|
618
|
+
| "openai_api"
|
|
619
|
+
| "anthropic_api"
|
|
620
|
+
| "http"
|
|
621
|
+
| "shell";
|
|
527
622
|
status?: string;
|
|
528
623
|
heartbeatCron?: string;
|
|
529
624
|
monthlyBudgetUsd?: string;
|
|
@@ -751,6 +846,10 @@ export async function appendCost(
|
|
|
751
846
|
input: {
|
|
752
847
|
companyId: string;
|
|
753
848
|
providerType: string;
|
|
849
|
+
runtimeModelId?: string | null;
|
|
850
|
+
pricingProviderType?: string | null;
|
|
851
|
+
pricingModelId?: string | null;
|
|
852
|
+
pricingSource?: "exact" | "missing" | null;
|
|
754
853
|
tokenInput: number;
|
|
755
854
|
tokenOutput: number;
|
|
756
855
|
usdCost: string;
|
|
@@ -764,6 +863,10 @@ export async function appendCost(
|
|
|
764
863
|
id,
|
|
765
864
|
companyId: input.companyId,
|
|
766
865
|
providerType: input.providerType,
|
|
866
|
+
runtimeModelId: input.runtimeModelId ?? null,
|
|
867
|
+
pricingProviderType: input.pricingProviderType ?? null,
|
|
868
|
+
pricingModelId: input.pricingModelId ?? null,
|
|
869
|
+
pricingSource: input.pricingSource ?? null,
|
|
767
870
|
tokenInput: input.tokenInput,
|
|
768
871
|
tokenOutput: input.tokenOutput,
|
|
769
872
|
usdCost: input.usdCost,
|
|
@@ -792,6 +895,177 @@ export async function listHeartbeatRuns(db: BopoDb, companyId: string, limit = 1
|
|
|
792
895
|
.limit(limit);
|
|
793
896
|
}
|
|
794
897
|
|
|
898
|
+
export async function getHeartbeatRun(db: BopoDb, companyId: string, runId: string) {
|
|
899
|
+
const [run] = await db
|
|
900
|
+
.select()
|
|
901
|
+
.from(heartbeatRuns)
|
|
902
|
+
.where(and(eq(heartbeatRuns.companyId, companyId), eq(heartbeatRuns.id, runId)))
|
|
903
|
+
.limit(1);
|
|
904
|
+
return run ?? null;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
export async function appendHeartbeatRunMessages(
|
|
908
|
+
db: BopoDb,
|
|
909
|
+
input: {
|
|
910
|
+
companyId: string;
|
|
911
|
+
runId: string;
|
|
912
|
+
messages: Array<{
|
|
913
|
+
id?: string;
|
|
914
|
+
sequence: number;
|
|
915
|
+
kind: string;
|
|
916
|
+
label?: string | null;
|
|
917
|
+
text?: string | null;
|
|
918
|
+
payloadJson?: string | null;
|
|
919
|
+
signalLevel?: "high" | "medium" | "low" | "noise" | null;
|
|
920
|
+
groupKey?: string | null;
|
|
921
|
+
source?: "stdout" | "stderr" | "trace_fallback" | null;
|
|
922
|
+
createdAt?: Date;
|
|
923
|
+
}>;
|
|
924
|
+
}
|
|
925
|
+
) {
|
|
926
|
+
if (input.messages.length === 0) {
|
|
927
|
+
return [] as string[];
|
|
928
|
+
}
|
|
929
|
+
const values = input.messages.map((message) => ({
|
|
930
|
+
id: message.id ?? nanoid(14),
|
|
931
|
+
companyId: input.companyId,
|
|
932
|
+
runId: input.runId,
|
|
933
|
+
sequence: message.sequence,
|
|
934
|
+
kind: message.kind,
|
|
935
|
+
label: message.label ?? null,
|
|
936
|
+
text: message.text ?? null,
|
|
937
|
+
payloadJson: message.payloadJson ?? null,
|
|
938
|
+
signalLevel: message.signalLevel ?? null,
|
|
939
|
+
groupKey: message.groupKey ?? null,
|
|
940
|
+
source: message.source ?? null,
|
|
941
|
+
createdAt: message.createdAt ?? new Date()
|
|
942
|
+
}));
|
|
943
|
+
await db.insert(heartbeatRunMessages).values(values);
|
|
944
|
+
return values.map((message) => message.id);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
export async function listHeartbeatRunMessages(
|
|
948
|
+
db: BopoDb,
|
|
949
|
+
input: { companyId: string; runId: string; afterSequence?: number; limit?: number }
|
|
950
|
+
) {
|
|
951
|
+
const limit = Math.min(Math.max(input.limit ?? 200, 1), 500);
|
|
952
|
+
const whereClause =
|
|
953
|
+
input.afterSequence !== undefined
|
|
954
|
+
? and(
|
|
955
|
+
eq(heartbeatRunMessages.companyId, input.companyId),
|
|
956
|
+
eq(heartbeatRunMessages.runId, input.runId),
|
|
957
|
+
gt(heartbeatRunMessages.sequence, input.afterSequence)
|
|
958
|
+
)
|
|
959
|
+
: and(eq(heartbeatRunMessages.companyId, input.companyId), eq(heartbeatRunMessages.runId, input.runId));
|
|
960
|
+
const rows = await db
|
|
961
|
+
.select()
|
|
962
|
+
.from(heartbeatRunMessages)
|
|
963
|
+
.where(whereClause)
|
|
964
|
+
.orderBy(asc(heartbeatRunMessages.sequence))
|
|
965
|
+
.limit(limit + 1);
|
|
966
|
+
const hasMore = rows.length > limit;
|
|
967
|
+
const items = hasMore ? rows.slice(0, limit) : rows;
|
|
968
|
+
return {
|
|
969
|
+
items,
|
|
970
|
+
nextCursor: hasMore ? String(items[items.length - 1]?.sequence ?? "") : null
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
export async function listHeartbeatRunMessagesForRuns(
|
|
975
|
+
db: BopoDb,
|
|
976
|
+
input: { companyId: string; runIds: string[]; perRunLimit?: number }
|
|
977
|
+
) {
|
|
978
|
+
const runIds = Array.from(new Set(input.runIds.filter((runId) => runId.trim().length > 0)));
|
|
979
|
+
if (runIds.length === 0) {
|
|
980
|
+
return new Map<string, { items: Array<(typeof heartbeatRunMessages.$inferSelect)>; nextCursor: string | null }>();
|
|
981
|
+
}
|
|
982
|
+
const perRunLimit = Math.min(Math.max(input.perRunLimit ?? 60, 1), 500);
|
|
983
|
+
const runIdValues = sql.join(runIds.map((runId) => sql`(${runId})`), sql`, `);
|
|
984
|
+
const rankedRows = await db.execute(sql`
|
|
985
|
+
WITH requested(run_id) AS (
|
|
986
|
+
VALUES ${runIdValues}
|
|
987
|
+
),
|
|
988
|
+
ranked AS (
|
|
989
|
+
SELECT
|
|
990
|
+
m.id,
|
|
991
|
+
m.company_id,
|
|
992
|
+
m.run_id,
|
|
993
|
+
m.sequence,
|
|
994
|
+
m.kind,
|
|
995
|
+
m.label,
|
|
996
|
+
m.text,
|
|
997
|
+
m.payload_json,
|
|
998
|
+
m.signal_level,
|
|
999
|
+
m.group_key,
|
|
1000
|
+
m.source,
|
|
1001
|
+
m.created_at,
|
|
1002
|
+
ROW_NUMBER() OVER (PARTITION BY m.run_id ORDER BY m.sequence DESC) AS rn,
|
|
1003
|
+
COUNT(*) OVER (PARTITION BY m.run_id) AS total_count
|
|
1004
|
+
FROM heartbeat_run_messages m
|
|
1005
|
+
JOIN requested r ON r.run_id = m.run_id
|
|
1006
|
+
WHERE m.company_id = ${input.companyId}
|
|
1007
|
+
)
|
|
1008
|
+
SELECT
|
|
1009
|
+
id,
|
|
1010
|
+
company_id,
|
|
1011
|
+
run_id,
|
|
1012
|
+
sequence,
|
|
1013
|
+
kind,
|
|
1014
|
+
label,
|
|
1015
|
+
text,
|
|
1016
|
+
payload_json,
|
|
1017
|
+
signal_level,
|
|
1018
|
+
group_key,
|
|
1019
|
+
source,
|
|
1020
|
+
created_at,
|
|
1021
|
+
total_count
|
|
1022
|
+
FROM ranked
|
|
1023
|
+
WHERE rn <= ${perRunLimit}
|
|
1024
|
+
ORDER BY run_id ASC, sequence ASC
|
|
1025
|
+
`);
|
|
1026
|
+
const rows = (rankedRows.rows ?? []) as Array<{
|
|
1027
|
+
id: string;
|
|
1028
|
+
company_id: string;
|
|
1029
|
+
run_id: string;
|
|
1030
|
+
sequence: number;
|
|
1031
|
+
kind: string;
|
|
1032
|
+
label: string | null;
|
|
1033
|
+
text: string | null;
|
|
1034
|
+
payload_json: string | null;
|
|
1035
|
+
signal_level: string | null;
|
|
1036
|
+
group_key: string | null;
|
|
1037
|
+
source: string | null;
|
|
1038
|
+
created_at: Date | string;
|
|
1039
|
+
total_count: number;
|
|
1040
|
+
}>;
|
|
1041
|
+
const grouped = new Map<string, { items: Array<(typeof heartbeatRunMessages.$inferSelect)>; nextCursor: string | null }>();
|
|
1042
|
+
for (const runId of runIds) {
|
|
1043
|
+
grouped.set(runId, { items: [], nextCursor: null });
|
|
1044
|
+
}
|
|
1045
|
+
for (const row of rows) {
|
|
1046
|
+
const bucket = grouped.get(row.run_id) ?? { items: [], nextCursor: null };
|
|
1047
|
+
bucket.items.push({
|
|
1048
|
+
id: row.id,
|
|
1049
|
+
companyId: row.company_id,
|
|
1050
|
+
runId: row.run_id,
|
|
1051
|
+
sequence: row.sequence,
|
|
1052
|
+
kind: row.kind,
|
|
1053
|
+
label: row.label,
|
|
1054
|
+
text: row.text,
|
|
1055
|
+
payloadJson: row.payload_json,
|
|
1056
|
+
signalLevel: row.signal_level,
|
|
1057
|
+
groupKey: row.group_key,
|
|
1058
|
+
source: row.source,
|
|
1059
|
+
createdAt: row.created_at instanceof Date ? row.created_at : new Date(row.created_at)
|
|
1060
|
+
});
|
|
1061
|
+
if (row.total_count > perRunLimit) {
|
|
1062
|
+
bucket.nextCursor = String(row.sequence);
|
|
1063
|
+
}
|
|
1064
|
+
grouped.set(row.run_id, bucket);
|
|
1065
|
+
}
|
|
1066
|
+
return grouped;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
795
1069
|
export async function appendActivity(
|
|
796
1070
|
db: BopoDb,
|
|
797
1071
|
input: {
|
|
@@ -816,6 +1090,238 @@ export async function appendActivity(
|
|
|
816
1090
|
return id;
|
|
817
1091
|
}
|
|
818
1092
|
|
|
1093
|
+
export async function upsertPlugin(
|
|
1094
|
+
db: BopoDb,
|
|
1095
|
+
input: {
|
|
1096
|
+
id: string;
|
|
1097
|
+
name: string;
|
|
1098
|
+
version: string;
|
|
1099
|
+
kind: string;
|
|
1100
|
+
runtimeType: string;
|
|
1101
|
+
runtimeEntrypoint: string;
|
|
1102
|
+
hooksJson?: string;
|
|
1103
|
+
capabilitiesJson?: string;
|
|
1104
|
+
manifestJson?: string;
|
|
1105
|
+
}
|
|
1106
|
+
) {
|
|
1107
|
+
await db
|
|
1108
|
+
.insert(plugins)
|
|
1109
|
+
.values({
|
|
1110
|
+
id: input.id,
|
|
1111
|
+
name: input.name,
|
|
1112
|
+
version: input.version,
|
|
1113
|
+
kind: input.kind,
|
|
1114
|
+
runtimeType: input.runtimeType,
|
|
1115
|
+
runtimeEntrypoint: input.runtimeEntrypoint,
|
|
1116
|
+
hooksJson: input.hooksJson ?? "[]",
|
|
1117
|
+
capabilitiesJson: input.capabilitiesJson ?? "[]",
|
|
1118
|
+
manifestJson: input.manifestJson ?? "{}"
|
|
1119
|
+
})
|
|
1120
|
+
.onConflictDoUpdate({
|
|
1121
|
+
target: plugins.id,
|
|
1122
|
+
set: {
|
|
1123
|
+
name: input.name,
|
|
1124
|
+
version: input.version,
|
|
1125
|
+
kind: input.kind,
|
|
1126
|
+
runtimeType: input.runtimeType,
|
|
1127
|
+
runtimeEntrypoint: input.runtimeEntrypoint,
|
|
1128
|
+
hooksJson: input.hooksJson ?? "[]",
|
|
1129
|
+
capabilitiesJson: input.capabilitiesJson ?? "[]",
|
|
1130
|
+
manifestJson: input.manifestJson ?? "{}",
|
|
1131
|
+
updatedAt: touchUpdatedAtSql
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
return input.id;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
export async function listPlugins(db: BopoDb) {
|
|
1138
|
+
return db.select().from(plugins).orderBy(asc(plugins.name));
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
export async function updatePluginConfig(
|
|
1142
|
+
db: BopoDb,
|
|
1143
|
+
input: {
|
|
1144
|
+
companyId: string;
|
|
1145
|
+
pluginId: string;
|
|
1146
|
+
enabled?: boolean;
|
|
1147
|
+
priority?: number;
|
|
1148
|
+
configJson?: string;
|
|
1149
|
+
grantedCapabilitiesJson?: string;
|
|
1150
|
+
}
|
|
1151
|
+
) {
|
|
1152
|
+
await db
|
|
1153
|
+
.insert(pluginConfigs)
|
|
1154
|
+
.values({
|
|
1155
|
+
companyId: input.companyId,
|
|
1156
|
+
pluginId: input.pluginId,
|
|
1157
|
+
enabled: input.enabled ?? false,
|
|
1158
|
+
priority: input.priority ?? 100,
|
|
1159
|
+
configJson: input.configJson ?? "{}",
|
|
1160
|
+
grantedCapabilitiesJson: input.grantedCapabilitiesJson ?? "[]"
|
|
1161
|
+
})
|
|
1162
|
+
.onConflictDoUpdate({
|
|
1163
|
+
target: [pluginConfigs.companyId, pluginConfigs.pluginId],
|
|
1164
|
+
set: compactUpdate({
|
|
1165
|
+
enabled: input.enabled,
|
|
1166
|
+
priority: input.priority,
|
|
1167
|
+
configJson: input.configJson,
|
|
1168
|
+
grantedCapabilitiesJson: input.grantedCapabilitiesJson,
|
|
1169
|
+
updatedAt: touchUpdatedAtSql
|
|
1170
|
+
})
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
export async function deletePluginConfig(
|
|
1175
|
+
db: BopoDb,
|
|
1176
|
+
input: {
|
|
1177
|
+
companyId: string;
|
|
1178
|
+
pluginId: string;
|
|
1179
|
+
}
|
|
1180
|
+
) {
|
|
1181
|
+
await db
|
|
1182
|
+
.delete(pluginConfigs)
|
|
1183
|
+
.where(and(eq(pluginConfigs.companyId, input.companyId), eq(pluginConfigs.pluginId, input.pluginId)));
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
export async function deletePluginById(db: BopoDb, pluginId: string) {
|
|
1187
|
+
await db.delete(plugins).where(eq(plugins.id, pluginId));
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
export async function listCompanyPluginConfigs(db: BopoDb, companyId: string) {
|
|
1191
|
+
return db
|
|
1192
|
+
.select({
|
|
1193
|
+
companyId: pluginConfigs.companyId,
|
|
1194
|
+
pluginId: pluginConfigs.pluginId,
|
|
1195
|
+
enabled: pluginConfigs.enabled,
|
|
1196
|
+
priority: pluginConfigs.priority,
|
|
1197
|
+
configJson: pluginConfigs.configJson,
|
|
1198
|
+
grantedCapabilitiesJson: pluginConfigs.grantedCapabilitiesJson,
|
|
1199
|
+
pluginName: plugins.name,
|
|
1200
|
+
pluginVersion: plugins.version,
|
|
1201
|
+
pluginKind: plugins.kind,
|
|
1202
|
+
runtimeType: plugins.runtimeType,
|
|
1203
|
+
runtimeEntrypoint: plugins.runtimeEntrypoint,
|
|
1204
|
+
hooksJson: plugins.hooksJson,
|
|
1205
|
+
capabilitiesJson: plugins.capabilitiesJson,
|
|
1206
|
+
manifestJson: plugins.manifestJson
|
|
1207
|
+
})
|
|
1208
|
+
.from(pluginConfigs)
|
|
1209
|
+
.innerJoin(plugins, eq(pluginConfigs.pluginId, plugins.id))
|
|
1210
|
+
.where(eq(pluginConfigs.companyId, companyId))
|
|
1211
|
+
.orderBy(asc(pluginConfigs.priority), asc(pluginConfigs.pluginId));
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
export async function appendPluginRun(
|
|
1215
|
+
db: BopoDb,
|
|
1216
|
+
input: {
|
|
1217
|
+
companyId: string;
|
|
1218
|
+
runId?: string | null;
|
|
1219
|
+
pluginId: string;
|
|
1220
|
+
hook: string;
|
|
1221
|
+
status: string;
|
|
1222
|
+
durationMs: number;
|
|
1223
|
+
error?: string | null;
|
|
1224
|
+
diagnosticsJson?: string;
|
|
1225
|
+
}
|
|
1226
|
+
) {
|
|
1227
|
+
const id = nanoid(14);
|
|
1228
|
+
await db.insert(pluginRuns).values({
|
|
1229
|
+
id,
|
|
1230
|
+
companyId: input.companyId,
|
|
1231
|
+
runId: input.runId ?? null,
|
|
1232
|
+
pluginId: input.pluginId,
|
|
1233
|
+
hook: input.hook,
|
|
1234
|
+
status: input.status,
|
|
1235
|
+
durationMs: Math.max(0, Math.floor(input.durationMs)),
|
|
1236
|
+
error: input.error ?? null,
|
|
1237
|
+
diagnosticsJson: input.diagnosticsJson ?? "{}"
|
|
1238
|
+
});
|
|
1239
|
+
return id;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
export async function listPluginRuns(
|
|
1243
|
+
db: BopoDb,
|
|
1244
|
+
input: { companyId: string; pluginId?: string; runId?: string; limit?: number }
|
|
1245
|
+
) {
|
|
1246
|
+
const limit = Math.min(Math.max(input.limit ?? 200, 1), 1000);
|
|
1247
|
+
return db
|
|
1248
|
+
.select()
|
|
1249
|
+
.from(pluginRuns)
|
|
1250
|
+
.where(
|
|
1251
|
+
and(
|
|
1252
|
+
eq(pluginRuns.companyId, input.companyId),
|
|
1253
|
+
input.pluginId ? eq(pluginRuns.pluginId, input.pluginId) : undefined,
|
|
1254
|
+
input.runId ? eq(pluginRuns.runId, input.runId) : undefined
|
|
1255
|
+
)
|
|
1256
|
+
)
|
|
1257
|
+
.orderBy(desc(pluginRuns.createdAt))
|
|
1258
|
+
.limit(limit);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
export async function listModelPricing(db: BopoDb, companyId: string) {
|
|
1262
|
+
return db
|
|
1263
|
+
.select()
|
|
1264
|
+
.from(modelPricing)
|
|
1265
|
+
.where(eq(modelPricing.companyId, companyId))
|
|
1266
|
+
.orderBy(asc(modelPricing.providerType), asc(modelPricing.modelId));
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
export async function getModelPricing(
|
|
1270
|
+
db: BopoDb,
|
|
1271
|
+
input: { companyId: string; providerType: string; modelId: string }
|
|
1272
|
+
) {
|
|
1273
|
+
const rows = await db
|
|
1274
|
+
.select()
|
|
1275
|
+
.from(modelPricing)
|
|
1276
|
+
.where(
|
|
1277
|
+
and(
|
|
1278
|
+
eq(modelPricing.companyId, input.companyId),
|
|
1279
|
+
eq(modelPricing.providerType, input.providerType),
|
|
1280
|
+
eq(modelPricing.modelId, input.modelId)
|
|
1281
|
+
)
|
|
1282
|
+
)
|
|
1283
|
+
.limit(1);
|
|
1284
|
+
return rows[0] ?? null;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
export async function upsertModelPricing(
|
|
1288
|
+
db: BopoDb,
|
|
1289
|
+
input: {
|
|
1290
|
+
companyId: string;
|
|
1291
|
+
providerType: string;
|
|
1292
|
+
modelId: string;
|
|
1293
|
+
displayName?: string | null;
|
|
1294
|
+
inputUsdPer1M?: string | null;
|
|
1295
|
+
outputUsdPer1M?: string | null;
|
|
1296
|
+
currency?: string | null;
|
|
1297
|
+
updatedBy?: string | null;
|
|
1298
|
+
}
|
|
1299
|
+
) {
|
|
1300
|
+
await db
|
|
1301
|
+
.insert(modelPricing)
|
|
1302
|
+
.values({
|
|
1303
|
+
companyId: input.companyId,
|
|
1304
|
+
providerType: input.providerType,
|
|
1305
|
+
modelId: input.modelId,
|
|
1306
|
+
displayName: input.displayName ?? null,
|
|
1307
|
+
inputUsdPer1M: input.inputUsdPer1M ?? "0.000000",
|
|
1308
|
+
outputUsdPer1M: input.outputUsdPer1M ?? "0.000000",
|
|
1309
|
+
currency: input.currency ?? "USD",
|
|
1310
|
+
updatedBy: input.updatedBy ?? null
|
|
1311
|
+
})
|
|
1312
|
+
.onConflictDoUpdate({
|
|
1313
|
+
target: [modelPricing.companyId, modelPricing.providerType, modelPricing.modelId],
|
|
1314
|
+
set: compactUpdate({
|
|
1315
|
+
displayName: input.displayName ?? null,
|
|
1316
|
+
inputUsdPer1M: input.inputUsdPer1M ?? "0.000000",
|
|
1317
|
+
outputUsdPer1M: input.outputUsdPer1M ?? "0.000000",
|
|
1318
|
+
currency: input.currency ?? "USD",
|
|
1319
|
+
updatedBy: input.updatedBy ?? null,
|
|
1320
|
+
updatedAt: touchUpdatedAtSql
|
|
1321
|
+
})
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
|
|
819
1325
|
function compactUpdate<T extends Record<string, unknown>>(input: T) {
|
|
820
1326
|
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
|
|
821
1327
|
}
|