@opengeni/db 3.7.4 → 3.8.2-canary.0
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/automations.d.ts +4 -0
- package/dist/canonical-human-identities.js +3 -3
- package/dist/{chunk-O56DM5GZ.js → chunk-53A2PEHT.js} +328 -31
- package/dist/chunk-53A2PEHT.js.map +1 -0
- package/dist/{chunk-SZAWDXQC.js → chunk-5S4BQAHX.js} +3 -3
- package/dist/{chunk-S3TDDCS2.js → chunk-CITYUGMW.js} +3 -3
- package/dist/{chunk-4NAR7FBA.js → chunk-F5YOZEYQ.js} +49 -1
- package/dist/chunk-F5YOZEYQ.js.map +1 -0
- package/dist/{chunk-F6PBS6WE.js → chunk-HZV7O6JR.js} +2 -2
- package/dist/{chunk-5NNCGFQY.js → chunk-RLQVPOVR.js} +2 -2
- package/dist/{chunk-DZCYW2WT.js → chunk-UKOGHUGC.js} +2 -2
- package/dist/{chunk-US4IS6IV.js → chunk-X6TO5HII.js} +2 -2
- package/dist/{chunk-U4WPPEBH.js → chunk-Y53WEFYB.js} +2 -2
- package/dist/connection-token-resolver.d.ts +4 -0
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +164 -14
- package/dist/index.js +7327 -5442
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +9 -0
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/model-catalog.d.ts +153 -0
- package/dist/new-session-drafts.d.ts +12 -0
- package/dist/organization-membership-lifecycle.d.ts +8 -1
- package/dist/organization-model-providers.d.ts +101 -0
- package/dist/pack-components.d.ts +1 -0
- package/dist/pr-review.d.ts +9 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +7 -7
- package/dist/schema.d.ts +989 -2
- package/dist/schema.js +11 -1
- package/dist/session-background-commands.js +3 -3
- package/dist/session-control.d.ts +35 -0
- package/dist/session-queue-commands.d.ts +17 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/video-generation.js +3 -3
- package/dist/xai-subscription.d.ts +8 -0
- package/drizzle/0387_bound_host_export_session_payloads.sql +165 -0
- package/drizzle/0388_sandbox_provider_deadline_interactions.sql +491 -0
- package/drizzle/0389_model_catalog_and_gateway_custom_models.sql +241 -0
- package/drizzle/0390_organization_model_provider_connections.sql +197 -0
- package/drizzle/0391_sandbox_provider_deadline_interaction_followup.sql +295 -0
- package/drizzle/0392_context_compaction_pending_observability.sql +198 -0
- package/drizzle/0393_workspace_memory_and_learning_defaults.sql +102 -0
- package/drizzle/0394_session_selected_skill_activation.sql +20 -0
- package/drizzle/0395_scheduled_task_unclaimed_occurrence_invalidation.sql +154 -0
- package/drizzle/0396_model_call_equivalent_credit_cost.sql +28 -0
- package/drizzle/0397_sandbox_deadline_rotation_preemption.sql +113 -0
- package/drizzle/0398_organization_workspace_management_entry.sql +206 -0
- package/drizzle/0399_additional_managed_organization_creation.sql +691 -0
- package/package.json +6 -6
- package/src/automations.ts +9 -1
- package/src/connection-token-resolver.ts +12 -0
- package/src/index.ts +1551 -272
- package/src/insights-model-bundle.ts +54 -5
- package/src/insights.ts +53 -7
- package/src/model-catalog.ts +682 -0
- package/src/new-session-drafts.ts +66 -21
- package/src/organization-membership-lifecycle.ts +47 -5
- package/src/organization-model-providers.ts +614 -0
- package/src/pack-components.ts +15 -2
- package/src/pr-review.ts +19 -0
- package/src/provision-roles.ts +2 -0
- package/src/runtime-posture.ts +48 -0
- package/src/schema.ts +348 -32
- package/src/session-control.ts +170 -0
- package/src/session-queue-commands.ts +157 -82
- package/src/xai-subscription.ts +66 -14
- package/dist/chunk-4NAR7FBA.js.map +0 -1
- package/dist/chunk-O56DM5GZ.js.map +0 -1
- /package/dist/{chunk-SZAWDXQC.js.map → chunk-5S4BQAHX.js.map} +0 -0
- /package/dist/{chunk-S3TDDCS2.js.map → chunk-CITYUGMW.js.map} +0 -0
- /package/dist/{chunk-F6PBS6WE.js.map → chunk-HZV7O6JR.js.map} +0 -0
- /package/dist/{chunk-5NNCGFQY.js.map → chunk-RLQVPOVR.js.map} +0 -0
- /package/dist/{chunk-DZCYW2WT.js.map → chunk-UKOGHUGC.js.map} +0 -0
- /package/dist/{chunk-US4IS6IV.js.map → chunk-X6TO5HII.js.map} +0 -0
- /package/dist/{chunk-U4WPPEBH.js.map → chunk-Y53WEFYB.js.map} +0 -0
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { environmentsEncryptionKeyBytes, type Settings } from "@opengeni/config";
|
|
3
|
+
import { and, count, eq, exists, isNull, sql } from "drizzle-orm";
|
|
4
|
+
|
|
5
|
+
import { type Database, setSubjectRlsContext, withRlsContext } from "./database";
|
|
6
|
+
import { decryptEnvironmentValue } from "./environment-crypto";
|
|
7
|
+
import * as schema from "./schema";
|
|
8
|
+
|
|
9
|
+
export type OrganizationModelProviderKind = "vercel_gateway" | "openrouter";
|
|
10
|
+
export type OrganizationModelProviderConnection = {
|
|
11
|
+
providerKind: OrganizationModelProviderKind;
|
|
12
|
+
status: "active" | "revoked";
|
|
13
|
+
version: number;
|
|
14
|
+
createdAt: Date;
|
|
15
|
+
updatedAt: Date;
|
|
16
|
+
};
|
|
17
|
+
export type OrganizationModelProviderCustomModel = {
|
|
18
|
+
id: string;
|
|
19
|
+
accountId: string;
|
|
20
|
+
providerKind: OrganizationModelProviderKind;
|
|
21
|
+
upstreamModelId: string;
|
|
22
|
+
label: string | null;
|
|
23
|
+
version: number;
|
|
24
|
+
retiredAt: Date | null;
|
|
25
|
+
createdAt: Date;
|
|
26
|
+
updatedAt: Date;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class OrganizationModelProviderConflictError extends Error {}
|
|
30
|
+
export class OrganizationModelProviderLimitError extends Error {}
|
|
31
|
+
|
|
32
|
+
function digest(value: unknown): string {
|
|
33
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function organizationModelProviderCredentialDigest(credential: string): string {
|
|
37
|
+
return createHash("sha256").update(credential).digest("hex");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function withOrganizationProviderAdministrator<T>(
|
|
41
|
+
db: Database,
|
|
42
|
+
input: { organizationId: string; actorSubjectId: string },
|
|
43
|
+
use: (scopedDb: Database) => Promise<T>,
|
|
44
|
+
): Promise<T> {
|
|
45
|
+
return await withRlsContext(
|
|
46
|
+
db,
|
|
47
|
+
{ accountId: input.organizationId, workspaceId: null },
|
|
48
|
+
async (scopedDb) => {
|
|
49
|
+
await setSubjectRlsContext(scopedDb, input.actorSubjectId);
|
|
50
|
+
await scopedDb.execute(sql`
|
|
51
|
+
select get_organization_administration_overview(
|
|
52
|
+
${input.organizationId}::uuid, ${input.actorSubjectId}
|
|
53
|
+
)
|
|
54
|
+
`);
|
|
55
|
+
return await use(scopedDb);
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function mapConnection(
|
|
61
|
+
row: typeof schema.organizationModelProviderConnections.$inferSelect,
|
|
62
|
+
): OrganizationModelProviderConnection {
|
|
63
|
+
return {
|
|
64
|
+
providerKind: row.providerKind,
|
|
65
|
+
status: row.status,
|
|
66
|
+
version: row.version,
|
|
67
|
+
createdAt: row.createdAt,
|
|
68
|
+
updatedAt: row.updatedAt,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function mapConnectionOperation(
|
|
73
|
+
row: typeof schema.organizationModelProviderConnectionOperations.$inferSelect,
|
|
74
|
+
): OrganizationModelProviderConnection {
|
|
75
|
+
return {
|
|
76
|
+
providerKind: row.providerKind,
|
|
77
|
+
status: row.resultStatus,
|
|
78
|
+
version: row.resultVersion,
|
|
79
|
+
createdAt: row.resultCreatedAt,
|
|
80
|
+
updatedAt: row.resultUpdatedAt,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function recordConnectionOperation(
|
|
85
|
+
db: Database,
|
|
86
|
+
input: {
|
|
87
|
+
accountId: string;
|
|
88
|
+
operationId: string;
|
|
89
|
+
requestHash: string;
|
|
90
|
+
connection: OrganizationModelProviderConnection;
|
|
91
|
+
},
|
|
92
|
+
): Promise<void> {
|
|
93
|
+
await db.insert(schema.organizationModelProviderConnectionOperations).values({
|
|
94
|
+
accountId: input.accountId,
|
|
95
|
+
providerKind: input.connection.providerKind,
|
|
96
|
+
operationId: input.operationId,
|
|
97
|
+
requestHash: input.requestHash,
|
|
98
|
+
resultStatus: input.connection.status,
|
|
99
|
+
resultVersion: input.connection.version,
|
|
100
|
+
resultCreatedAt: input.connection.createdAt,
|
|
101
|
+
resultUpdatedAt: input.connection.updatedAt,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function mapModel(
|
|
106
|
+
row: typeof schema.organizationModelProviderCustomModels.$inferSelect,
|
|
107
|
+
): OrganizationModelProviderCustomModel {
|
|
108
|
+
return {
|
|
109
|
+
id: row.id,
|
|
110
|
+
accountId: row.accountId,
|
|
111
|
+
providerKind: row.providerKind,
|
|
112
|
+
upstreamModelId: row.upstreamModelId,
|
|
113
|
+
label: row.label,
|
|
114
|
+
version: row.version,
|
|
115
|
+
retiredAt: row.retiredAt,
|
|
116
|
+
createdAt: row.createdAt,
|
|
117
|
+
updatedAt: row.updatedAt,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function getOrganizationModelProviderConnection(
|
|
122
|
+
db: Database,
|
|
123
|
+
input: {
|
|
124
|
+
organizationId: string;
|
|
125
|
+
actorSubjectId: string;
|
|
126
|
+
providerKind: OrganizationModelProviderKind;
|
|
127
|
+
},
|
|
128
|
+
): Promise<OrganizationModelProviderConnection | null> {
|
|
129
|
+
return await withOrganizationProviderAdministrator(db, input, async (scopedDb) => {
|
|
130
|
+
const [row] = await scopedDb
|
|
131
|
+
.select()
|
|
132
|
+
.from(schema.organizationModelProviderConnections)
|
|
133
|
+
.where(
|
|
134
|
+
and(
|
|
135
|
+
eq(schema.organizationModelProviderConnections.accountId, input.organizationId),
|
|
136
|
+
eq(schema.organizationModelProviderConnections.providerKind, input.providerKind),
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
.limit(1);
|
|
140
|
+
return row ? mapConnection(row) : null;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function upsertOrganizationModelProviderConnection(
|
|
145
|
+
db: Database,
|
|
146
|
+
input: {
|
|
147
|
+
organizationId: string;
|
|
148
|
+
actorSubjectId: string;
|
|
149
|
+
providerKind: OrganizationModelProviderKind;
|
|
150
|
+
credentialEncrypted: string;
|
|
151
|
+
credentialDigest: string;
|
|
152
|
+
operationId: string;
|
|
153
|
+
expectedVersion?: number;
|
|
154
|
+
},
|
|
155
|
+
): Promise<OrganizationModelProviderConnection> {
|
|
156
|
+
return await withOrganizationProviderAdministrator(db, input, async (scopedDb) => {
|
|
157
|
+
await scopedDb.execute(
|
|
158
|
+
sql`select pg_advisory_xact_lock(hashtextextended(${`organization-model-provider:${input.organizationId}:${input.providerKind}`}, 0))`,
|
|
159
|
+
);
|
|
160
|
+
const requestHash = digest({
|
|
161
|
+
operation: "upsert",
|
|
162
|
+
expectedVersion: input.expectedVersion,
|
|
163
|
+
credentialDigest: input.credentialDigest,
|
|
164
|
+
});
|
|
165
|
+
const [replay] = await scopedDb
|
|
166
|
+
.select()
|
|
167
|
+
.from(schema.organizationModelProviderConnectionOperations)
|
|
168
|
+
.where(
|
|
169
|
+
and(
|
|
170
|
+
eq(schema.organizationModelProviderConnectionOperations.accountId, input.organizationId),
|
|
171
|
+
eq(schema.organizationModelProviderConnectionOperations.providerKind, input.providerKind),
|
|
172
|
+
eq(schema.organizationModelProviderConnectionOperations.operationId, input.operationId),
|
|
173
|
+
),
|
|
174
|
+
)
|
|
175
|
+
.limit(1);
|
|
176
|
+
if (replay) {
|
|
177
|
+
if (replay.requestHash !== requestHash) throw new OrganizationModelProviderConflictError();
|
|
178
|
+
return mapConnectionOperation(replay);
|
|
179
|
+
}
|
|
180
|
+
const [existing] = await scopedDb
|
|
181
|
+
.select()
|
|
182
|
+
.from(schema.organizationModelProviderConnections)
|
|
183
|
+
.where(
|
|
184
|
+
and(
|
|
185
|
+
eq(schema.organizationModelProviderConnections.accountId, input.organizationId),
|
|
186
|
+
eq(schema.organizationModelProviderConnections.providerKind, input.providerKind),
|
|
187
|
+
),
|
|
188
|
+
)
|
|
189
|
+
.for("update")
|
|
190
|
+
.limit(1);
|
|
191
|
+
const expected = input.expectedVersion ?? 0;
|
|
192
|
+
if ((existing?.version ?? 0) !== expected) throw new OrganizationModelProviderConflictError();
|
|
193
|
+
const [row] = existing
|
|
194
|
+
? await scopedDb
|
|
195
|
+
.update(schema.organizationModelProviderConnections)
|
|
196
|
+
.set({
|
|
197
|
+
status: "active",
|
|
198
|
+
credentialEncrypted: input.credentialEncrypted,
|
|
199
|
+
version: existing.version + 1,
|
|
200
|
+
operationId: input.operationId,
|
|
201
|
+
requestHash,
|
|
202
|
+
updatedBySubjectId: input.actorSubjectId,
|
|
203
|
+
updatedAt: new Date(),
|
|
204
|
+
})
|
|
205
|
+
.where(eq(schema.organizationModelProviderConnections.id, existing.id))
|
|
206
|
+
.returning()
|
|
207
|
+
: await scopedDb
|
|
208
|
+
.insert(schema.organizationModelProviderConnections)
|
|
209
|
+
.values({
|
|
210
|
+
accountId: input.organizationId,
|
|
211
|
+
providerKind: input.providerKind,
|
|
212
|
+
credentialEncrypted: input.credentialEncrypted,
|
|
213
|
+
operationId: input.operationId,
|
|
214
|
+
requestHash,
|
|
215
|
+
updatedBySubjectId: input.actorSubjectId,
|
|
216
|
+
})
|
|
217
|
+
.returning();
|
|
218
|
+
if (!row) throw new Error("organization provider connection write returned no row");
|
|
219
|
+
const connection = mapConnection(row);
|
|
220
|
+
await recordConnectionOperation(scopedDb, {
|
|
221
|
+
accountId: input.organizationId,
|
|
222
|
+
operationId: input.operationId,
|
|
223
|
+
requestHash,
|
|
224
|
+
connection,
|
|
225
|
+
});
|
|
226
|
+
return connection;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export async function revokeOrganizationModelProviderConnection(
|
|
231
|
+
db: Database,
|
|
232
|
+
input: {
|
|
233
|
+
organizationId: string;
|
|
234
|
+
actorSubjectId: string;
|
|
235
|
+
providerKind: OrganizationModelProviderKind;
|
|
236
|
+
operationId: string;
|
|
237
|
+
expectedVersion: number;
|
|
238
|
+
},
|
|
239
|
+
): Promise<OrganizationModelProviderConnection> {
|
|
240
|
+
return await withOrganizationProviderAdministrator(db, input, async (scopedDb) => {
|
|
241
|
+
await scopedDb.execute(
|
|
242
|
+
sql`select pg_advisory_xact_lock(hashtextextended(${`organization-model-provider:${input.organizationId}:${input.providerKind}`}, 0))`,
|
|
243
|
+
);
|
|
244
|
+
await scopedDb.execute(
|
|
245
|
+
sql`select pg_advisory_xact_lock(hashtextextended(${`organization-model-provider-models:${input.organizationId}:${input.providerKind}`}, 0))`,
|
|
246
|
+
);
|
|
247
|
+
const requestHash = digest({ operation: "revoke", expectedVersion: input.expectedVersion });
|
|
248
|
+
const [replay] = await scopedDb
|
|
249
|
+
.select()
|
|
250
|
+
.from(schema.organizationModelProviderConnectionOperations)
|
|
251
|
+
.where(
|
|
252
|
+
and(
|
|
253
|
+
eq(schema.organizationModelProviderConnectionOperations.accountId, input.organizationId),
|
|
254
|
+
eq(schema.organizationModelProviderConnectionOperations.providerKind, input.providerKind),
|
|
255
|
+
eq(schema.organizationModelProviderConnectionOperations.operationId, input.operationId),
|
|
256
|
+
),
|
|
257
|
+
)
|
|
258
|
+
.limit(1);
|
|
259
|
+
if (replay) {
|
|
260
|
+
if (replay.requestHash !== requestHash) throw new OrganizationModelProviderConflictError();
|
|
261
|
+
return mapConnectionOperation(replay);
|
|
262
|
+
}
|
|
263
|
+
const [existing] = await scopedDb
|
|
264
|
+
.select()
|
|
265
|
+
.from(schema.organizationModelProviderConnections)
|
|
266
|
+
.where(
|
|
267
|
+
and(
|
|
268
|
+
eq(schema.organizationModelProviderConnections.accountId, input.organizationId),
|
|
269
|
+
eq(schema.organizationModelProviderConnections.providerKind, input.providerKind),
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
.for("update")
|
|
273
|
+
.limit(1);
|
|
274
|
+
if (!existing) throw new OrganizationModelProviderConflictError();
|
|
275
|
+
if (existing.version !== input.expectedVersion)
|
|
276
|
+
throw new OrganizationModelProviderConflictError();
|
|
277
|
+
const [row] = await scopedDb
|
|
278
|
+
.update(schema.organizationModelProviderConnections)
|
|
279
|
+
.set({
|
|
280
|
+
status: "revoked",
|
|
281
|
+
credentialEncrypted: "",
|
|
282
|
+
version: existing.version + 1,
|
|
283
|
+
operationId: input.operationId,
|
|
284
|
+
requestHash,
|
|
285
|
+
updatedBySubjectId: input.actorSubjectId,
|
|
286
|
+
updatedAt: new Date(),
|
|
287
|
+
})
|
|
288
|
+
.where(eq(schema.organizationModelProviderConnections.id, existing.id))
|
|
289
|
+
.returning();
|
|
290
|
+
if (!row) throw new Error("organization provider revoke returned no row");
|
|
291
|
+
const connection = mapConnection(row);
|
|
292
|
+
await recordConnectionOperation(scopedDb, {
|
|
293
|
+
accountId: input.organizationId,
|
|
294
|
+
operationId: input.operationId,
|
|
295
|
+
requestHash,
|
|
296
|
+
connection,
|
|
297
|
+
});
|
|
298
|
+
return connection;
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export async function organizationModelProviderConnectionActiveForWorkspace(
|
|
303
|
+
db: Database,
|
|
304
|
+
input: { accountId: string; workspaceId: string; providerKind: OrganizationModelProviderKind },
|
|
305
|
+
): Promise<boolean> {
|
|
306
|
+
return await withRlsContext(db, input, async (scopedDb) => {
|
|
307
|
+
const [row] = await scopedDb
|
|
308
|
+
.select({ id: schema.organizationModelProviderConnections.id })
|
|
309
|
+
.from(schema.organizationModelProviderConnections)
|
|
310
|
+
.where(
|
|
311
|
+
and(
|
|
312
|
+
eq(schema.organizationModelProviderConnections.accountId, input.accountId),
|
|
313
|
+
eq(schema.organizationModelProviderConnections.providerKind, input.providerKind),
|
|
314
|
+
eq(schema.organizationModelProviderConnections.status, "active"),
|
|
315
|
+
),
|
|
316
|
+
)
|
|
317
|
+
.limit(1);
|
|
318
|
+
return Boolean(row);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export async function loadOrganizationModelProviderApiKey(
|
|
323
|
+
db: Database,
|
|
324
|
+
settings: Settings,
|
|
325
|
+
input: { accountId: string; workspaceId: string; providerKind: OrganizationModelProviderKind },
|
|
326
|
+
): Promise<string | null> {
|
|
327
|
+
const key = environmentsEncryptionKeyBytes(settings);
|
|
328
|
+
if (!key) return null;
|
|
329
|
+
return await withRlsContext(db, input, async (scopedDb) => {
|
|
330
|
+
const [row] = await scopedDb
|
|
331
|
+
.select({
|
|
332
|
+
credentialEncrypted: schema.organizationModelProviderConnections.credentialEncrypted,
|
|
333
|
+
})
|
|
334
|
+
.from(schema.organizationModelProviderConnections)
|
|
335
|
+
.where(
|
|
336
|
+
and(
|
|
337
|
+
eq(schema.organizationModelProviderConnections.accountId, input.accountId),
|
|
338
|
+
eq(schema.organizationModelProviderConnections.providerKind, input.providerKind),
|
|
339
|
+
eq(schema.organizationModelProviderConnections.status, "active"),
|
|
340
|
+
),
|
|
341
|
+
)
|
|
342
|
+
.limit(1);
|
|
343
|
+
return row ? decryptEnvironmentValue(key, row.credentialEncrypted) : null;
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export async function listOrganizationModelProviderCustomModelsForWorkspace(
|
|
348
|
+
db: Database,
|
|
349
|
+
input: {
|
|
350
|
+
accountId: string;
|
|
351
|
+
workspaceId: string;
|
|
352
|
+
providerKind: OrganizationModelProviderKind;
|
|
353
|
+
includeRetired?: boolean;
|
|
354
|
+
},
|
|
355
|
+
): Promise<OrganizationModelProviderCustomModel[]> {
|
|
356
|
+
return await withRlsContext(db, input, async (scopedDb) => {
|
|
357
|
+
const rows = await scopedDb
|
|
358
|
+
.select()
|
|
359
|
+
.from(schema.organizationModelProviderCustomModels)
|
|
360
|
+
.where(
|
|
361
|
+
and(
|
|
362
|
+
eq(schema.organizationModelProviderCustomModels.accountId, input.accountId),
|
|
363
|
+
eq(schema.organizationModelProviderCustomModels.providerKind, input.providerKind),
|
|
364
|
+
...(input.includeRetired
|
|
365
|
+
? []
|
|
366
|
+
: [isNull(schema.organizationModelProviderCustomModels.retiredAt)]),
|
|
367
|
+
),
|
|
368
|
+
);
|
|
369
|
+
return rows.map(mapModel);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export async function getOrganizationModelProviderCustomModelForExecution(
|
|
374
|
+
db: Database,
|
|
375
|
+
input: {
|
|
376
|
+
accountId: string;
|
|
377
|
+
workspaceId: string;
|
|
378
|
+
providerKind: OrganizationModelProviderKind;
|
|
379
|
+
upstreamModelId: string;
|
|
380
|
+
},
|
|
381
|
+
): Promise<OrganizationModelProviderCustomModel | null> {
|
|
382
|
+
return await withRlsContext(db, input, async (scopedDb) => {
|
|
383
|
+
const [row] = await scopedDb
|
|
384
|
+
.select()
|
|
385
|
+
.from(schema.organizationModelProviderCustomModels)
|
|
386
|
+
.where(
|
|
387
|
+
and(
|
|
388
|
+
eq(schema.organizationModelProviderCustomModels.accountId, input.accountId),
|
|
389
|
+
eq(schema.organizationModelProviderCustomModels.providerKind, input.providerKind),
|
|
390
|
+
eq(schema.organizationModelProviderCustomModels.upstreamModelId, input.upstreamModelId),
|
|
391
|
+
),
|
|
392
|
+
)
|
|
393
|
+
.orderBy(
|
|
394
|
+
sql`(${schema.organizationModelProviderCustomModels.retiredAt} is null) desc`,
|
|
395
|
+
sql`${schema.organizationModelProviderCustomModels.updatedAt} desc`,
|
|
396
|
+
)
|
|
397
|
+
.limit(1);
|
|
398
|
+
return row ? mapModel(row) : null;
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export async function withOrganizationModelProviderCustomModelReadLock<T>(
|
|
403
|
+
db: Database,
|
|
404
|
+
input: { accountId: string; workspaceId: string; providerKind: OrganizationModelProviderKind },
|
|
405
|
+
use: (scopedDb: Database) => Promise<T>,
|
|
406
|
+
): Promise<T> {
|
|
407
|
+
return await withRlsContext(db, input, async (scopedDb) => {
|
|
408
|
+
await scopedDb.execute(
|
|
409
|
+
sql`select pg_advisory_xact_lock_shared(hashtextextended(${`organization-model-provider-models:${input.accountId}:${input.providerKind}`}, 0))`,
|
|
410
|
+
);
|
|
411
|
+
return await use(scopedDb);
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export async function lockActiveOrganizationModelProviderCustomModelForAdmission(
|
|
416
|
+
db: Database,
|
|
417
|
+
input: {
|
|
418
|
+
accountId: string;
|
|
419
|
+
workspaceId: string;
|
|
420
|
+
providerKind: OrganizationModelProviderKind;
|
|
421
|
+
upstreamModelId: string;
|
|
422
|
+
},
|
|
423
|
+
): Promise<OrganizationModelProviderCustomModel | null> {
|
|
424
|
+
return await withOrganizationModelProviderCustomModelReadLock(db, input, async (scopedDb) => {
|
|
425
|
+
const [row] = await scopedDb
|
|
426
|
+
.select()
|
|
427
|
+
.from(schema.organizationModelProviderCustomModels)
|
|
428
|
+
.where(
|
|
429
|
+
and(
|
|
430
|
+
eq(schema.organizationModelProviderCustomModels.accountId, input.accountId),
|
|
431
|
+
eq(schema.organizationModelProviderCustomModels.providerKind, input.providerKind),
|
|
432
|
+
eq(schema.organizationModelProviderCustomModels.upstreamModelId, input.upstreamModelId),
|
|
433
|
+
isNull(schema.organizationModelProviderCustomModels.retiredAt),
|
|
434
|
+
exists(
|
|
435
|
+
scopedDb
|
|
436
|
+
.select({ id: schema.organizationModelProviderConnections.id })
|
|
437
|
+
.from(schema.organizationModelProviderConnections)
|
|
438
|
+
.where(
|
|
439
|
+
and(
|
|
440
|
+
eq(schema.organizationModelProviderConnections.accountId, input.accountId),
|
|
441
|
+
eq(schema.organizationModelProviderConnections.providerKind, input.providerKind),
|
|
442
|
+
eq(schema.organizationModelProviderConnections.status, "active"),
|
|
443
|
+
),
|
|
444
|
+
),
|
|
445
|
+
),
|
|
446
|
+
),
|
|
447
|
+
)
|
|
448
|
+
.limit(1);
|
|
449
|
+
return row ? mapModel(row) : null;
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export async function listOrganizationModelProviderCustomModels(
|
|
454
|
+
db: Database,
|
|
455
|
+
input: {
|
|
456
|
+
organizationId: string;
|
|
457
|
+
actorSubjectId: string;
|
|
458
|
+
providerKind: OrganizationModelProviderKind;
|
|
459
|
+
},
|
|
460
|
+
): Promise<OrganizationModelProviderCustomModel[]> {
|
|
461
|
+
return await withOrganizationProviderAdministrator(db, input, async (scopedDb) => {
|
|
462
|
+
const rows = await scopedDb
|
|
463
|
+
.select()
|
|
464
|
+
.from(schema.organizationModelProviderCustomModels)
|
|
465
|
+
.where(
|
|
466
|
+
and(
|
|
467
|
+
eq(schema.organizationModelProviderCustomModels.accountId, input.organizationId),
|
|
468
|
+
eq(schema.organizationModelProviderCustomModels.providerKind, input.providerKind),
|
|
469
|
+
isNull(schema.organizationModelProviderCustomModels.retiredAt),
|
|
470
|
+
),
|
|
471
|
+
);
|
|
472
|
+
return rows.map(mapModel);
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export async function createOrganizationModelProviderCustomModel(
|
|
477
|
+
db: Database,
|
|
478
|
+
input: {
|
|
479
|
+
organizationId: string;
|
|
480
|
+
actorSubjectId: string;
|
|
481
|
+
providerKind: OrganizationModelProviderKind;
|
|
482
|
+
upstreamModelId: string;
|
|
483
|
+
label?: string;
|
|
484
|
+
operationId: string;
|
|
485
|
+
},
|
|
486
|
+
): Promise<OrganizationModelProviderCustomModel> {
|
|
487
|
+
return await withOrganizationProviderAdministrator(db, input, async (scopedDb) => {
|
|
488
|
+
await scopedDb.execute(
|
|
489
|
+
sql`select pg_advisory_xact_lock(hashtextextended(${`organization-model-provider-models:${input.organizationId}:${input.providerKind}`}, 0))`,
|
|
490
|
+
);
|
|
491
|
+
const requestHash = digest({
|
|
492
|
+
upstreamModelId: input.upstreamModelId,
|
|
493
|
+
label: input.label ?? null,
|
|
494
|
+
});
|
|
495
|
+
const [replay] = await scopedDb
|
|
496
|
+
.select()
|
|
497
|
+
.from(schema.organizationModelProviderCustomModels)
|
|
498
|
+
.where(
|
|
499
|
+
and(
|
|
500
|
+
eq(schema.organizationModelProviderCustomModels.accountId, input.organizationId),
|
|
501
|
+
eq(schema.organizationModelProviderCustomModels.providerKind, input.providerKind),
|
|
502
|
+
eq(schema.organizationModelProviderCustomModels.createOperationId, input.operationId),
|
|
503
|
+
),
|
|
504
|
+
)
|
|
505
|
+
.limit(1);
|
|
506
|
+
if (replay) {
|
|
507
|
+
if (replay.createRequestHash !== requestHash)
|
|
508
|
+
throw new OrganizationModelProviderConflictError();
|
|
509
|
+
return mapModel(replay);
|
|
510
|
+
}
|
|
511
|
+
const [{ active = 0, total = 0 } = {}] = await scopedDb
|
|
512
|
+
.select({
|
|
513
|
+
active: count(
|
|
514
|
+
sql`case when ${schema.organizationModelProviderCustomModels.retiredAt} is null then 1 end`,
|
|
515
|
+
),
|
|
516
|
+
total: count(),
|
|
517
|
+
})
|
|
518
|
+
.from(schema.organizationModelProviderCustomModels)
|
|
519
|
+
.where(
|
|
520
|
+
and(
|
|
521
|
+
eq(schema.organizationModelProviderCustomModels.accountId, input.organizationId),
|
|
522
|
+
eq(schema.organizationModelProviderCustomModels.providerKind, input.providerKind),
|
|
523
|
+
),
|
|
524
|
+
);
|
|
525
|
+
if (Number(active) >= 100 || Number(total) >= 1000)
|
|
526
|
+
throw new OrganizationModelProviderLimitError();
|
|
527
|
+
const [duplicate] = await scopedDb
|
|
528
|
+
.select({ id: schema.organizationModelProviderCustomModels.id })
|
|
529
|
+
.from(schema.organizationModelProviderCustomModels)
|
|
530
|
+
.where(
|
|
531
|
+
and(
|
|
532
|
+
eq(schema.organizationModelProviderCustomModels.accountId, input.organizationId),
|
|
533
|
+
eq(schema.organizationModelProviderCustomModels.providerKind, input.providerKind),
|
|
534
|
+
eq(schema.organizationModelProviderCustomModels.upstreamModelId, input.upstreamModelId),
|
|
535
|
+
isNull(schema.organizationModelProviderCustomModels.retiredAt),
|
|
536
|
+
),
|
|
537
|
+
)
|
|
538
|
+
.limit(1);
|
|
539
|
+
if (duplicate) throw new OrganizationModelProviderConflictError();
|
|
540
|
+
const [row] = await scopedDb
|
|
541
|
+
.insert(schema.organizationModelProviderCustomModels)
|
|
542
|
+
.values({
|
|
543
|
+
accountId: input.organizationId,
|
|
544
|
+
providerKind: input.providerKind,
|
|
545
|
+
upstreamModelId: input.upstreamModelId,
|
|
546
|
+
label: input.label ?? null,
|
|
547
|
+
createOperationId: input.operationId,
|
|
548
|
+
createRequestHash: requestHash,
|
|
549
|
+
createdBySubjectId: input.actorSubjectId,
|
|
550
|
+
})
|
|
551
|
+
.returning();
|
|
552
|
+
if (!row) throw new Error("organization custom model create returned no row");
|
|
553
|
+
return mapModel(row);
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export async function retireOrganizationModelProviderCustomModel(
|
|
558
|
+
db: Database,
|
|
559
|
+
input: {
|
|
560
|
+
organizationId: string;
|
|
561
|
+
actorSubjectId: string;
|
|
562
|
+
providerKind: OrganizationModelProviderKind;
|
|
563
|
+
customModelId: string;
|
|
564
|
+
expectedVersion: number;
|
|
565
|
+
operationId: string;
|
|
566
|
+
},
|
|
567
|
+
): Promise<OrganizationModelProviderCustomModel> {
|
|
568
|
+
return await withOrganizationProviderAdministrator(db, input, async (scopedDb) => {
|
|
569
|
+
await scopedDb.execute(
|
|
570
|
+
sql`select pg_advisory_xact_lock(hashtextextended(${`organization-model-provider-models:${input.organizationId}:${input.providerKind}`}, 0))`,
|
|
571
|
+
);
|
|
572
|
+
const requestHash = digest({
|
|
573
|
+
customModelId: input.customModelId,
|
|
574
|
+
expectedVersion: input.expectedVersion,
|
|
575
|
+
});
|
|
576
|
+
const [replay] = await scopedDb
|
|
577
|
+
.select()
|
|
578
|
+
.from(schema.organizationModelProviderCustomModels)
|
|
579
|
+
.where(
|
|
580
|
+
and(
|
|
581
|
+
eq(schema.organizationModelProviderCustomModels.accountId, input.organizationId),
|
|
582
|
+
eq(schema.organizationModelProviderCustomModels.providerKind, input.providerKind),
|
|
583
|
+
eq(schema.organizationModelProviderCustomModels.deleteOperationId, input.operationId),
|
|
584
|
+
),
|
|
585
|
+
)
|
|
586
|
+
.limit(1);
|
|
587
|
+
if (replay) {
|
|
588
|
+
if (replay.deleteRequestHash !== requestHash)
|
|
589
|
+
throw new OrganizationModelProviderConflictError();
|
|
590
|
+
return mapModel(replay);
|
|
591
|
+
}
|
|
592
|
+
const [row] = await scopedDb
|
|
593
|
+
.update(schema.organizationModelProviderCustomModels)
|
|
594
|
+
.set({
|
|
595
|
+
retiredAt: new Date(),
|
|
596
|
+
deleteOperationId: input.operationId,
|
|
597
|
+
deleteRequestHash: requestHash,
|
|
598
|
+
version: input.expectedVersion + 1,
|
|
599
|
+
updatedAt: new Date(),
|
|
600
|
+
})
|
|
601
|
+
.where(
|
|
602
|
+
and(
|
|
603
|
+
eq(schema.organizationModelProviderCustomModels.accountId, input.organizationId),
|
|
604
|
+
eq(schema.organizationModelProviderCustomModels.providerKind, input.providerKind),
|
|
605
|
+
eq(schema.organizationModelProviderCustomModels.id, input.customModelId),
|
|
606
|
+
eq(schema.organizationModelProviderCustomModels.version, input.expectedVersion),
|
|
607
|
+
isNull(schema.organizationModelProviderCustomModels.retiredAt),
|
|
608
|
+
),
|
|
609
|
+
)
|
|
610
|
+
.returning();
|
|
611
|
+
if (!row) throw new OrganizationModelProviderConflictError();
|
|
612
|
+
return mapModel(row);
|
|
613
|
+
});
|
|
614
|
+
}
|
package/src/pack-components.ts
CHANGED
|
@@ -29,6 +29,7 @@ export type PackInlineSkillRequirement = {
|
|
|
29
29
|
key: string;
|
|
30
30
|
capabilityId: string;
|
|
31
31
|
name: string;
|
|
32
|
+
activationMode: "workspace_managed" | "session_selected";
|
|
32
33
|
contentSha256: string;
|
|
33
34
|
};
|
|
34
35
|
|
|
@@ -97,9 +98,14 @@ export async function resolvePackInlineSkillReferences(
|
|
|
97
98
|
.select({
|
|
98
99
|
facetInstallationId: schema.capabilityFacetInstallations.id,
|
|
99
100
|
name: schema.capabilitySkillFacets.name,
|
|
101
|
+
activationMode: schema.capabilityFacets.activationMode,
|
|
100
102
|
contentSha256: schema.capabilitySkillFacets.contentSha256,
|
|
101
103
|
})
|
|
102
104
|
.from(schema.capabilitySkillFacets)
|
|
105
|
+
.innerJoin(
|
|
106
|
+
schema.capabilityFacets,
|
|
107
|
+
eq(schema.capabilityFacets.id, schema.capabilitySkillFacets.facetId),
|
|
108
|
+
)
|
|
103
109
|
.innerJoin(
|
|
104
110
|
schema.capabilityFacetInstallations,
|
|
105
111
|
eq(schema.capabilityFacetInstallations.facetId, schema.capabilitySkillFacets.facetId),
|
|
@@ -141,9 +147,16 @@ export async function resolvePackInlineSkillReferences(
|
|
|
141
147
|
(row) => row.name.toLowerCase() === requirement.name.toLowerCase(),
|
|
142
148
|
);
|
|
143
149
|
const exact = candidates.find(
|
|
144
|
-
(candidate) =>
|
|
150
|
+
(candidate) =>
|
|
151
|
+
candidate.contentSha256 === requirement.contentSha256 &&
|
|
152
|
+
candidate.activationMode === requirement.activationMode,
|
|
145
153
|
);
|
|
146
|
-
const mismatch =
|
|
154
|
+
const mismatch =
|
|
155
|
+
candidates.find(
|
|
156
|
+
(candidate) =>
|
|
157
|
+
candidate.contentSha256 !== requirement.contentSha256 ||
|
|
158
|
+
candidate.activationMode !== requirement.activationMode,
|
|
159
|
+
) ?? null;
|
|
147
160
|
return {
|
|
148
161
|
key: requirement.key,
|
|
149
162
|
kind: "inline_skill" as const,
|
package/src/pr-review.ts
CHANGED
|
@@ -318,6 +318,8 @@ export async function createPrReviewRepositoryBinding(
|
|
|
318
318
|
eventTypes: string[];
|
|
319
319
|
configuration: Record<string, unknown>;
|
|
320
320
|
sessionTemplate: AutomationSessionTemplate;
|
|
321
|
+
/** Trusted database-only admission seam. Throwing rolls the binding and trigger back. */
|
|
322
|
+
beforeCreateCommit?: (tx: Database) => Promise<void>;
|
|
321
323
|
},
|
|
322
324
|
): Promise<PrReviewRepositoryBinding> {
|
|
323
325
|
return await withRlsContext(db, input, async (scopedDb) => {
|
|
@@ -345,6 +347,7 @@ export async function createPrReviewRepositoryBinding(
|
|
|
345
347
|
"PR Review repository provider does not match its app",
|
|
346
348
|
);
|
|
347
349
|
}
|
|
350
|
+
await input.beforeCreateCommit?.(tx);
|
|
348
351
|
const bindingId = randomUUID();
|
|
349
352
|
const [trigger] = await tx
|
|
350
353
|
.insert(schema.automationTriggers)
|
|
@@ -842,6 +845,16 @@ export async function updatePrReviewRepositoryBinding(
|
|
|
842
845
|
model?: string | null;
|
|
843
846
|
additionalInstructions?: string | null;
|
|
844
847
|
status?: "active" | "disabled";
|
|
848
|
+
/** Trusted database-only admission seam. Throwing rolls the binding revision back. */
|
|
849
|
+
beforeUpdateCommit?: (
|
|
850
|
+
tx: Database,
|
|
851
|
+
context: {
|
|
852
|
+
currentModel: string | null;
|
|
853
|
+
currentStatus: "active" | "disabled";
|
|
854
|
+
nextModel: string | null;
|
|
855
|
+
nextStatus: "active" | "disabled";
|
|
856
|
+
},
|
|
857
|
+
) => Promise<void>;
|
|
845
858
|
},
|
|
846
859
|
): Promise<PrReviewRepositoryBinding | null> {
|
|
847
860
|
return await withRlsContext(db, input, async (scopedDb) => {
|
|
@@ -891,6 +904,12 @@ export async function updatePrReviewRepositoryBinding(
|
|
|
891
904
|
: input.additionalInstructions,
|
|
892
905
|
status: input.status ?? (current.binding.status as "active" | "disabled"),
|
|
893
906
|
};
|
|
907
|
+
await input.beforeUpdateCommit?.(tx, {
|
|
908
|
+
currentModel: current.binding.model,
|
|
909
|
+
currentStatus: current.binding.status as "active" | "disabled",
|
|
910
|
+
nextModel: next.model,
|
|
911
|
+
nextStatus: next.status,
|
|
912
|
+
});
|
|
894
913
|
const nextRevision = current.trigger.currentRevision + 1;
|
|
895
914
|
await tx.insert(schema.automationTriggerRevisions).values({
|
|
896
915
|
triggerId: current.trigger.id,
|