@opengeni/db 3.6.0 → 3.7.4-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.
Files changed (92) hide show
  1. package/dist/automations.d.ts +4 -0
  2. package/dist/canonical-human-identities.js +3 -3
  3. package/dist/{chunk-UGDVSQ6U.js → chunk-3ZMN4EXC.js} +2 -2
  4. package/dist/{chunk-744DFC65.js → chunk-6FIVOJ3V.js} +96 -6
  5. package/dist/chunk-6FIVOJ3V.js.map +1 -0
  6. package/dist/{chunk-UJ2EW3QI.js → chunk-DXJ2WY6W.js} +427 -85
  7. package/dist/chunk-DXJ2WY6W.js.map +1 -0
  8. package/dist/{chunk-CDMMSUWJ.js → chunk-JP6IYGYI.js} +3 -3
  9. package/dist/{chunk-Q5YUGQVK.js → chunk-JS3AWDKZ.js} +3 -3
  10. package/dist/{chunk-G3732M3T.js → chunk-LHKB62GC.js} +2 -2
  11. package/dist/{chunk-2NT476HD.js → chunk-QL4AHBYD.js} +2 -2
  12. package/dist/{chunk-IW6O6G7R.js → chunk-VDXHLVDE.js} +2 -2
  13. package/dist/{chunk-WW7AJ5IC.js → chunk-ZTSJPFDH.js} +5 -7
  14. package/dist/chunk-ZTSJPFDH.js.map +1 -0
  15. package/dist/codex-token-resolver.d.ts +12 -1
  16. package/dist/company-profile-agent-admin.d.ts +4 -2
  17. package/dist/company-profile-agent-policy.d.ts +20 -0
  18. package/dist/company-profile-schema.d.ts +573 -0
  19. package/dist/editable-artifact-durable-export.js +2 -2
  20. package/dist/editable-artifacts.js +3 -3
  21. package/dist/index.d.ts +487 -20
  22. package/dist/index.js +9489 -6041
  23. package/dist/index.js.map +1 -1
  24. package/dist/managed-auth-session-sets.js +3 -3
  25. package/dist/managed-human-provisioning.d.ts +3 -3
  26. package/dist/memory-domain.d.ts +5 -3
  27. package/dist/model-catalog.d.ts +153 -0
  28. package/dist/pr-review.d.ts +9 -0
  29. package/dist/provision-roles.js +1 -1
  30. package/dist/runtime-posture.d.ts +7 -7
  31. package/dist/schema.d.ts +1069 -6
  32. package/dist/schema.js +17 -1
  33. package/dist/session-background-commands.js +3 -3
  34. package/dist/session-control.d.ts +75 -5
  35. package/dist/session-queue-commands.d.ts +19 -0
  36. package/dist/session-tenancy.js +3 -3
  37. package/dist/user-resource-authority.d.ts +8 -0
  38. package/dist/video-generation.js +3 -3
  39. package/dist/xai-subscription.d.ts +8 -0
  40. package/drizzle/0369_local_human_personal_authority.sql +350 -0
  41. package/drizzle/0370_workspace_member_management_scope.sql +98 -0
  42. package/drizzle/0371_workspace_member_candidate_inventory.sql +128 -0
  43. package/drizzle/0372_ordered_variable_set_runtime_authority.sql +287 -0
  44. package/drizzle/0373_sandbox_shared_preparation.sql +59 -0
  45. package/drizzle/0374_session_event_cursors.sql +315 -0
  46. package/drizzle/0375_session_recovery_observability.sql +73 -0
  47. package/drizzle/0376_organization_workspace_inventory.sql +88 -0
  48. package/drizzle/0377_scoped_rig_health_projection.sql +80 -0
  49. package/drizzle/0378_scoped_rig_health_audit_timestamp.sql +77 -0
  50. package/drizzle/0379_session_event_raw_lane_activation.sql +518 -0
  51. package/drizzle/0380_autonomous_company_profile_agent_policy.sql +829 -0
  52. package/drizzle/0381_organization_codex_subscription_inheritance.sql +720 -0
  53. package/drizzle/0382_organization_api_key_provenance.sql +91 -0
  54. package/drizzle/0383_codex_cooldown_reconciliation.sql +51 -0
  55. package/drizzle/0384_codex_cooldown_revision_guard_privileges.sql +6 -0
  56. package/drizzle/0385_connected_machine_legacy_tilde_workdirs.sql +25 -0
  57. package/drizzle/0386_local_organization_administration.sql +157 -0
  58. package/drizzle/0387_bound_host_export_session_payloads.sql +165 -0
  59. package/drizzle/0388_sandbox_provider_deadline_interactions.sql +491 -0
  60. package/drizzle/0389_model_catalog_and_gateway_custom_models.sql +241 -0
  61. package/package.json +6 -6
  62. package/src/automations.ts +9 -1
  63. package/src/codex-token-resolver.ts +26 -1
  64. package/src/company-brain-governed-writes.ts +30 -1
  65. package/src/company-profile-agent-admin.ts +68 -1
  66. package/src/company-profile-agent-policy.ts +126 -0
  67. package/src/company-profile-schema.ts +115 -1
  68. package/src/database.ts +3 -5
  69. package/src/index.ts +4473 -570
  70. package/src/insights.ts +21 -7
  71. package/src/managed-human-provisioning.ts +3 -3
  72. package/src/memory-domain.ts +9 -4
  73. package/src/model-catalog.ts +682 -0
  74. package/src/organization-membership-lifecycle.ts +0 -1
  75. package/src/pr-review.ts +19 -0
  76. package/src/provision-roles.ts +83 -4
  77. package/src/runtime-posture.ts +26 -0
  78. package/src/schema.ts +259 -10
  79. package/src/session-control.ts +347 -59
  80. package/src/session-queue-commands.ts +303 -122
  81. package/src/session-tool-call-settlement.ts +34 -7
  82. package/src/user-resource-authority.ts +56 -0
  83. package/src/xai-subscription.ts +66 -14
  84. package/dist/chunk-744DFC65.js.map +0 -1
  85. package/dist/chunk-UJ2EW3QI.js.map +0 -1
  86. package/dist/chunk-WW7AJ5IC.js.map +0 -1
  87. /package/dist/{chunk-UGDVSQ6U.js.map → chunk-3ZMN4EXC.js.map} +0 -0
  88. /package/dist/{chunk-CDMMSUWJ.js.map → chunk-JP6IYGYI.js.map} +0 -0
  89. /package/dist/{chunk-Q5YUGQVK.js.map → chunk-JS3AWDKZ.js.map} +0 -0
  90. /package/dist/{chunk-G3732M3T.js.map → chunk-LHKB62GC.js.map} +0 -0
  91. /package/dist/{chunk-2NT476HD.js.map → chunk-QL4AHBYD.js.map} +0 -0
  92. /package/dist/{chunk-IW6O6G7R.js.map → chunk-VDXHLVDE.js.map} +0 -0
@@ -0,0 +1,682 @@
1
+ import { and, count, eq, isNull, sql } from "drizzle-orm";
2
+
3
+ import { withRlsContext, type Database } from "./database";
4
+ import * as schema from "./schema";
5
+
6
+ export type DeploymentModelCatalogRow = {
7
+ document: unknown;
8
+ version: number;
9
+ updatedAt: Date;
10
+ };
11
+
12
+ export type WorkspaceCustomModelProviderKind = "vercel_gateway" | "openrouter";
13
+
14
+ export type WorkspaceProviderCustomModel = {
15
+ id: string;
16
+ accountId: string;
17
+ workspaceId: string;
18
+ providerKind: WorkspaceCustomModelProviderKind;
19
+ upstreamModelId: string;
20
+ label: string | null;
21
+ version: number;
22
+ createdBySubjectId: string;
23
+ retiredAt?: Date | null;
24
+ createdAt: Date;
25
+ updatedAt: Date;
26
+ };
27
+
28
+ export type WorkspaceGatewayCustomModel = WorkspaceProviderCustomModel & {
29
+ providerKind: "vercel_gateway";
30
+ };
31
+
32
+ export type WorkspaceOpenRouterCustomModel = WorkspaceProviderCustomModel & {
33
+ providerKind: "openrouter";
34
+ };
35
+
36
+ export const MAX_WORKSPACE_GATEWAY_CUSTOM_MODELS = 100;
37
+ export const MAX_WORKSPACE_GATEWAY_CUSTOM_MODEL_RECORDS = 1_000;
38
+ export const MAX_WORKSPACE_OPENROUTER_CUSTOM_MODELS = MAX_WORKSPACE_GATEWAY_CUSTOM_MODELS;
39
+ export const MAX_WORKSPACE_OPENROUTER_CUSTOM_MODEL_RECORDS =
40
+ MAX_WORKSPACE_GATEWAY_CUSTOM_MODEL_RECORDS;
41
+
42
+ export class WorkspaceGatewayCustomModelLimitError extends Error {
43
+ constructor() {
44
+ super(`workspace Gateway custom model limit reached (${MAX_WORKSPACE_GATEWAY_CUSTOM_MODELS})`);
45
+ this.name = "WorkspaceGatewayCustomModelLimitError";
46
+ }
47
+ }
48
+
49
+ export class WorkspaceGatewayCustomModelHistoryLimitError extends Error {
50
+ constructor() {
51
+ super(
52
+ `workspace Gateway custom model history limit reached (${MAX_WORKSPACE_GATEWAY_CUSTOM_MODEL_RECORDS})`,
53
+ );
54
+ this.name = "WorkspaceGatewayCustomModelHistoryLimitError";
55
+ }
56
+ }
57
+
58
+ export class WorkspaceOpenRouterCustomModelLimitError extends Error {
59
+ constructor() {
60
+ super(
61
+ `workspace OpenRouter custom model limit reached (${MAX_WORKSPACE_OPENROUTER_CUSTOM_MODELS})`,
62
+ );
63
+ this.name = "WorkspaceOpenRouterCustomModelLimitError";
64
+ }
65
+ }
66
+
67
+ export class WorkspaceOpenRouterCustomModelHistoryLimitError extends Error {
68
+ constructor() {
69
+ super(
70
+ `workspace OpenRouter custom model history limit reached (${MAX_WORKSPACE_OPENROUTER_CUSTOM_MODEL_RECORDS})`,
71
+ );
72
+ this.name = "WorkspaceOpenRouterCustomModelHistoryLimitError";
73
+ }
74
+ }
75
+
76
+ function customModelLimitError(providerKind: WorkspaceCustomModelProviderKind): Error {
77
+ return providerKind === "vercel_gateway"
78
+ ? new WorkspaceGatewayCustomModelLimitError()
79
+ : new WorkspaceOpenRouterCustomModelLimitError();
80
+ }
81
+
82
+ function customModelHistoryLimitError(providerKind: WorkspaceCustomModelProviderKind): Error {
83
+ return providerKind === "vercel_gateway"
84
+ ? new WorkspaceGatewayCustomModelHistoryLimitError()
85
+ : new WorkspaceOpenRouterCustomModelHistoryLimitError();
86
+ }
87
+
88
+ function workspaceCustomModelLockKey(
89
+ workspaceId: string,
90
+ providerKind: WorkspaceCustomModelProviderKind,
91
+ ): string {
92
+ return providerKind === "vercel_gateway"
93
+ ? `workspace-gateway-custom-models:${workspaceId}`
94
+ : `workspace-openrouter-custom-models:${workspaceId}`;
95
+ }
96
+
97
+ /** Serialize one provider's custom-model mutations while allowing concurrent acceptance reads. */
98
+ export async function withWorkspaceProviderCustomModelReadLock<T>(
99
+ db: Database,
100
+ input: {
101
+ accountId: string;
102
+ workspaceId: string;
103
+ providerKind: WorkspaceCustomModelProviderKind;
104
+ },
105
+ fn: (db: Database) => Promise<T>,
106
+ ): Promise<T> {
107
+ return await withRlsContext(db, input, async (scopedDb) => {
108
+ await scopedDb.execute(
109
+ sql`select pg_advisory_xact_lock_shared(hashtextextended(${workspaceCustomModelLockKey(input.workspaceId, input.providerKind)}, 0))`,
110
+ );
111
+ return await fn(scopedDb);
112
+ });
113
+ }
114
+
115
+ /** Compatibility wrapper for existing Vercel AI Gateway admission callers. */
116
+ export async function withWorkspaceGatewayCustomModelReadLock<T>(
117
+ db: Database,
118
+ input: { accountId: string; workspaceId: string },
119
+ fn: (db: Database) => Promise<T>,
120
+ ): Promise<T> {
121
+ return await withWorkspaceProviderCustomModelReadLock(
122
+ db,
123
+ { ...input, providerKind: "vercel_gateway" },
124
+ fn,
125
+ );
126
+ }
127
+
128
+ export async function withWorkspaceOpenRouterCustomModelReadLock<T>(
129
+ db: Database,
130
+ input: { accountId: string; workspaceId: string },
131
+ fn: (db: Database) => Promise<T>,
132
+ ): Promise<T> {
133
+ return await withWorkspaceProviderCustomModelReadLock(
134
+ db,
135
+ { ...input, providerKind: "openrouter" },
136
+ fn,
137
+ );
138
+ }
139
+
140
+ async function withWorkspaceProviderCustomModelWriteLock<T>(
141
+ db: Database,
142
+ input: {
143
+ accountId: string;
144
+ workspaceId: string;
145
+ providerKind: WorkspaceCustomModelProviderKind;
146
+ },
147
+ fn: (db: Database) => Promise<T>,
148
+ ): Promise<T> {
149
+ return await withRlsContext(db, input, async (scopedDb) => {
150
+ await scopedDb.execute(
151
+ sql`select pg_advisory_xact_lock(hashtextextended(${workspaceCustomModelLockKey(input.workspaceId, input.providerKind)}, 0))`,
152
+ );
153
+ return await fn(scopedDb);
154
+ });
155
+ }
156
+
157
+ function mapCustomModel(
158
+ row: typeof schema.workspaceGatewayCustomModels.$inferSelect,
159
+ ): WorkspaceProviderCustomModel {
160
+ return {
161
+ id: row.id,
162
+ accountId: row.accountId,
163
+ workspaceId: row.workspaceId,
164
+ providerKind: row.providerKind,
165
+ upstreamModelId: row.upstreamModelId,
166
+ label: row.label,
167
+ version: row.version,
168
+ createdBySubjectId: row.createdBySubjectId,
169
+ retiredAt: row.retiredAt,
170
+ createdAt: row.createdAt,
171
+ updatedAt: row.updatedAt,
172
+ };
173
+ }
174
+
175
+ function asGatewayCustomModel(model: WorkspaceProviderCustomModel): WorkspaceGatewayCustomModel {
176
+ if (model.providerKind !== "vercel_gateway") {
177
+ throw new Error("workspace custom model provider mismatch");
178
+ }
179
+ return model as WorkspaceGatewayCustomModel;
180
+ }
181
+
182
+ function asOpenRouterCustomModel(
183
+ model: WorkspaceProviderCustomModel,
184
+ ): WorkspaceOpenRouterCustomModel {
185
+ if (model.providerKind !== "openrouter") {
186
+ throw new Error("workspace custom model provider mismatch");
187
+ }
188
+ return model as WorkspaceOpenRouterCustomModel;
189
+ }
190
+
191
+ /** Runtime read of the deployment-global singleton. Returns null when absent. */
192
+ export async function getDeploymentModelCatalog(
193
+ db: Database,
194
+ ): Promise<DeploymentModelCatalogRow | null> {
195
+ const [row] = await db
196
+ .select({
197
+ document: schema.deploymentModelCatalog.document,
198
+ version: schema.deploymentModelCatalog.version,
199
+ updatedAt: schema.deploymentModelCatalog.updatedAt,
200
+ })
201
+ .from(schema.deploymentModelCatalog)
202
+ .where(eq(schema.deploymentModelCatalog.singleton, true))
203
+ .limit(1);
204
+ return row ?? null;
205
+ }
206
+
207
+ async function listWorkspaceProviderCustomModels(
208
+ db: Database,
209
+ input: {
210
+ accountId: string;
211
+ workspaceId: string;
212
+ providerKind: WorkspaceCustomModelProviderKind;
213
+ },
214
+ ): Promise<WorkspaceProviderCustomModel[]> {
215
+ return await withRlsContext(db, input, async (scopedDb) => {
216
+ const rows = await scopedDb
217
+ .select()
218
+ .from(schema.workspaceGatewayCustomModels)
219
+ .where(
220
+ and(
221
+ eq(schema.workspaceGatewayCustomModels.accountId, input.accountId),
222
+ eq(schema.workspaceGatewayCustomModels.workspaceId, input.workspaceId),
223
+ eq(schema.workspaceGatewayCustomModels.providerKind, input.providerKind),
224
+ isNull(schema.workspaceGatewayCustomModels.retiredAt),
225
+ ),
226
+ )
227
+ .orderBy(
228
+ schema.workspaceGatewayCustomModels.createdAt,
229
+ schema.workspaceGatewayCustomModels.id,
230
+ )
231
+ .limit(MAX_WORKSPACE_GATEWAY_CUSTOM_MODELS + 1);
232
+ if (rows.length > MAX_WORKSPACE_GATEWAY_CUSTOM_MODELS) {
233
+ throw customModelLimitError(input.providerKind);
234
+ }
235
+ return rows.map(mapCustomModel);
236
+ });
237
+ }
238
+
239
+ export async function listWorkspaceGatewayCustomModels(
240
+ db: Database,
241
+ input: { accountId: string; workspaceId: string },
242
+ ): Promise<WorkspaceGatewayCustomModel[]> {
243
+ return (
244
+ await listWorkspaceProviderCustomModels(db, {
245
+ ...input,
246
+ providerKind: "vercel_gateway",
247
+ })
248
+ ).map(asGatewayCustomModel);
249
+ }
250
+
251
+ export async function listWorkspaceOpenRouterCustomModels(
252
+ db: Database,
253
+ input: { accountId: string; workspaceId: string },
254
+ ): Promise<WorkspaceOpenRouterCustomModel[]> {
255
+ return (
256
+ await listWorkspaceProviderCustomModels(db, {
257
+ ...input,
258
+ providerKind: "openrouter",
259
+ })
260
+ ).map(asOpenRouterCustomModel);
261
+ }
262
+
263
+ async function lockActiveWorkspaceProviderCustomModelForAdmission(
264
+ db: Database,
265
+ input: {
266
+ accountId: string;
267
+ workspaceId: string;
268
+ providerKind: WorkspaceCustomModelProviderKind;
269
+ upstreamModelId: string;
270
+ },
271
+ ): Promise<WorkspaceProviderCustomModel | null> {
272
+ return await withWorkspaceProviderCustomModelReadLock(db, input, async (scopedDb) => {
273
+ const [row] = await scopedDb
274
+ .select()
275
+ .from(schema.workspaceGatewayCustomModels)
276
+ .where(
277
+ and(
278
+ eq(schema.workspaceGatewayCustomModels.accountId, input.accountId),
279
+ eq(schema.workspaceGatewayCustomModels.workspaceId, input.workspaceId),
280
+ eq(schema.workspaceGatewayCustomModels.providerKind, input.providerKind),
281
+ eq(schema.workspaceGatewayCustomModels.upstreamModelId, input.upstreamModelId),
282
+ isNull(schema.workspaceGatewayCustomModels.retiredAt),
283
+ ),
284
+ )
285
+ .limit(1);
286
+ return row ? mapCustomModel(row) : null;
287
+ });
288
+ }
289
+
290
+ /**
291
+ * Lock and re-read one active Gateway custom model at a fresh-selection commit
292
+ * boundary. The shared lock remains held until an existing outer transaction commits.
293
+ */
294
+ export async function lockActiveWorkspaceGatewayCustomModelForAdmission(
295
+ db: Database,
296
+ input: { accountId: string; workspaceId: string; upstreamModelId: string },
297
+ ): Promise<WorkspaceGatewayCustomModel | null> {
298
+ const model = await lockActiveWorkspaceProviderCustomModelForAdmission(db, {
299
+ ...input,
300
+ providerKind: "vercel_gateway",
301
+ });
302
+ return model ? asGatewayCustomModel(model) : null;
303
+ }
304
+
305
+ export async function lockActiveWorkspaceOpenRouterCustomModelForAdmission(
306
+ db: Database,
307
+ input: { accountId: string; workspaceId: string; upstreamModelId: string },
308
+ ): Promise<WorkspaceOpenRouterCustomModel | null> {
309
+ const model = await lockActiveWorkspaceProviderCustomModelForAdmission(db, {
310
+ ...input,
311
+ providerKind: "openrouter",
312
+ });
313
+ return model ? asOpenRouterCustomModel(model) : null;
314
+ }
315
+
316
+ type ReplayWorkspaceProviderCustomModelCreateResult =
317
+ | { outcome: "missing" }
318
+ | { outcome: "conflict" }
319
+ | { outcome: "success"; model: WorkspaceProviderCustomModel };
320
+
321
+ async function replayWorkspaceProviderCustomModelCreate(
322
+ db: Database,
323
+ input: {
324
+ accountId: string;
325
+ workspaceId: string;
326
+ providerKind: WorkspaceCustomModelProviderKind;
327
+ operationId: string;
328
+ requestHash: string;
329
+ },
330
+ ): Promise<ReplayWorkspaceProviderCustomModelCreateResult> {
331
+ return await withRlsContext(db, input, async (scopedDb) => {
332
+ const [row] = await scopedDb
333
+ .select()
334
+ .from(schema.workspaceGatewayCustomModels)
335
+ .where(
336
+ and(
337
+ eq(schema.workspaceGatewayCustomModels.accountId, input.accountId),
338
+ eq(schema.workspaceGatewayCustomModels.workspaceId, input.workspaceId),
339
+ eq(schema.workspaceGatewayCustomModels.providerKind, input.providerKind),
340
+ eq(schema.workspaceGatewayCustomModels.createOperationId, input.operationId),
341
+ ),
342
+ )
343
+ .limit(1);
344
+ if (!row) return { outcome: "missing" as const };
345
+ if (row.createRequestHash !== input.requestHash || row.retiredAt) {
346
+ return { outcome: "conflict" as const };
347
+ }
348
+ return { outcome: "success" as const, model: mapCustomModel(row) };
349
+ });
350
+ }
351
+
352
+ export async function replayWorkspaceGatewayCustomModelCreate(
353
+ db: Database,
354
+ input: {
355
+ accountId: string;
356
+ workspaceId: string;
357
+ operationId: string;
358
+ requestHash: string;
359
+ },
360
+ ): Promise<
361
+ | { outcome: "missing" }
362
+ | { outcome: "conflict" }
363
+ | { outcome: "success"; model: WorkspaceGatewayCustomModel }
364
+ > {
365
+ const result = await replayWorkspaceProviderCustomModelCreate(db, {
366
+ ...input,
367
+ providerKind: "vercel_gateway",
368
+ });
369
+ return result.outcome === "success"
370
+ ? { ...result, model: asGatewayCustomModel(result.model) }
371
+ : result;
372
+ }
373
+
374
+ export async function replayWorkspaceOpenRouterCustomModelCreate(
375
+ db: Database,
376
+ input: {
377
+ accountId: string;
378
+ workspaceId: string;
379
+ operationId: string;
380
+ requestHash: string;
381
+ },
382
+ ): Promise<
383
+ | { outcome: "missing" }
384
+ | { outcome: "conflict" }
385
+ | { outcome: "success"; model: WorkspaceOpenRouterCustomModel }
386
+ > {
387
+ const result = await replayWorkspaceProviderCustomModelCreate(db, {
388
+ ...input,
389
+ providerKind: "openrouter",
390
+ });
391
+ return result.outcome === "success"
392
+ ? { ...result, model: asOpenRouterCustomModel(result.model) }
393
+ : result;
394
+ }
395
+
396
+ type CreateWorkspaceProviderCustomModelInput = {
397
+ accountId: string;
398
+ workspaceId: string;
399
+ upstreamModelId: string;
400
+ label?: string | null;
401
+ operationId: string;
402
+ requestHash: string;
403
+ createdBySubjectId: string;
404
+ };
405
+
406
+ async function createWorkspaceProviderCustomModel(
407
+ db: Database,
408
+ input: CreateWorkspaceProviderCustomModelInput & {
409
+ providerKind: WorkspaceCustomModelProviderKind;
410
+ },
411
+ ): Promise<WorkspaceProviderCustomModel | null> {
412
+ return await withWorkspaceProviderCustomModelWriteLock(db, input, async (scopedDb) => {
413
+ const [operationRow] = await scopedDb
414
+ .select()
415
+ .from(schema.workspaceGatewayCustomModels)
416
+ .where(
417
+ and(
418
+ eq(schema.workspaceGatewayCustomModels.accountId, input.accountId),
419
+ eq(schema.workspaceGatewayCustomModels.workspaceId, input.workspaceId),
420
+ eq(schema.workspaceGatewayCustomModels.providerKind, input.providerKind),
421
+ eq(schema.workspaceGatewayCustomModels.createOperationId, input.operationId),
422
+ ),
423
+ )
424
+ .for("update")
425
+ .limit(1);
426
+ if (operationRow) {
427
+ return operationRow.createRequestHash === input.requestHash
428
+ ? mapCustomModel(operationRow)
429
+ : null;
430
+ }
431
+ const [active] = await scopedDb
432
+ .select({ id: schema.workspaceGatewayCustomModels.id })
433
+ .from(schema.workspaceGatewayCustomModels)
434
+ .where(
435
+ and(
436
+ eq(schema.workspaceGatewayCustomModels.accountId, input.accountId),
437
+ eq(schema.workspaceGatewayCustomModels.workspaceId, input.workspaceId),
438
+ eq(schema.workspaceGatewayCustomModels.providerKind, input.providerKind),
439
+ eq(schema.workspaceGatewayCustomModels.upstreamModelId, input.upstreamModelId),
440
+ isNull(schema.workspaceGatewayCustomModels.retiredAt),
441
+ ),
442
+ )
443
+ .for("update")
444
+ .limit(1);
445
+ if (active) return null;
446
+ const [total] = await scopedDb
447
+ .select({ value: count() })
448
+ .from(schema.workspaceGatewayCustomModels)
449
+ .where(
450
+ and(
451
+ eq(schema.workspaceGatewayCustomModels.accountId, input.accountId),
452
+ eq(schema.workspaceGatewayCustomModels.workspaceId, input.workspaceId),
453
+ eq(schema.workspaceGatewayCustomModels.providerKind, input.providerKind),
454
+ ),
455
+ );
456
+ if ((total?.value ?? 0) >= MAX_WORKSPACE_GATEWAY_CUSTOM_MODEL_RECORDS) {
457
+ throw customModelHistoryLimitError(input.providerKind);
458
+ }
459
+ const [current] = await scopedDb
460
+ .select({ value: count() })
461
+ .from(schema.workspaceGatewayCustomModels)
462
+ .where(
463
+ and(
464
+ eq(schema.workspaceGatewayCustomModels.accountId, input.accountId),
465
+ eq(schema.workspaceGatewayCustomModels.workspaceId, input.workspaceId),
466
+ eq(schema.workspaceGatewayCustomModels.providerKind, input.providerKind),
467
+ isNull(schema.workspaceGatewayCustomModels.retiredAt),
468
+ ),
469
+ );
470
+ if ((current?.value ?? 0) >= MAX_WORKSPACE_GATEWAY_CUSTOM_MODELS) {
471
+ throw customModelLimitError(input.providerKind);
472
+ }
473
+ const [row] = await scopedDb
474
+ .insert(schema.workspaceGatewayCustomModels)
475
+ .values({
476
+ accountId: input.accountId,
477
+ workspaceId: input.workspaceId,
478
+ providerKind: input.providerKind,
479
+ upstreamModelId: input.upstreamModelId,
480
+ label: input.label ?? null,
481
+ createOperationId: input.operationId,
482
+ createRequestHash: input.requestHash,
483
+ createdBySubjectId: input.createdBySubjectId,
484
+ })
485
+ .returning();
486
+ if (!row) throw new Error("workspace custom model write returned no row");
487
+ return mapCustomModel(row);
488
+ });
489
+ }
490
+
491
+ export async function createWorkspaceGatewayCustomModel(
492
+ db: Database,
493
+ input: CreateWorkspaceProviderCustomModelInput,
494
+ ): Promise<WorkspaceGatewayCustomModel | null> {
495
+ const model = await createWorkspaceProviderCustomModel(db, {
496
+ ...input,
497
+ providerKind: "vercel_gateway",
498
+ });
499
+ return model ? asGatewayCustomModel(model) : null;
500
+ }
501
+
502
+ export async function createWorkspaceOpenRouterCustomModel(
503
+ db: Database,
504
+ input: CreateWorkspaceProviderCustomModelInput,
505
+ ): Promise<WorkspaceOpenRouterCustomModel | null> {
506
+ const model = await createWorkspaceProviderCustomModel(db, {
507
+ ...input,
508
+ providerKind: "openrouter",
509
+ });
510
+ return model ? asOpenRouterCustomModel(model) : null;
511
+ }
512
+
513
+ type DeleteWorkspaceProviderCustomModelInput = {
514
+ accountId: string;
515
+ workspaceId: string;
516
+ customModelId: string;
517
+ expectedVersion: number;
518
+ operationId: string;
519
+ requestHash: string;
520
+ };
521
+
522
+ type DeleteWorkspaceProviderCustomModelResult =
523
+ | { outcome: "success"; model: WorkspaceProviderCustomModel }
524
+ | { outcome: "conflict" }
525
+ | { outcome: "not_found" };
526
+
527
+ async function deleteWorkspaceProviderCustomModel(
528
+ db: Database,
529
+ input: DeleteWorkspaceProviderCustomModelInput & {
530
+ providerKind: WorkspaceCustomModelProviderKind;
531
+ },
532
+ ): Promise<DeleteWorkspaceProviderCustomModelResult> {
533
+ return await withWorkspaceProviderCustomModelWriteLock(db, input, async (scopedDb) => {
534
+ const [operationRow] = await scopedDb
535
+ .select()
536
+ .from(schema.workspaceGatewayCustomModels)
537
+ .where(
538
+ and(
539
+ eq(schema.workspaceGatewayCustomModels.accountId, input.accountId),
540
+ eq(schema.workspaceGatewayCustomModels.workspaceId, input.workspaceId),
541
+ eq(schema.workspaceGatewayCustomModels.providerKind, input.providerKind),
542
+ eq(schema.workspaceGatewayCustomModels.deleteOperationId, input.operationId),
543
+ ),
544
+ )
545
+ .for("update")
546
+ .limit(1);
547
+ if (operationRow) {
548
+ return operationRow.id === input.customModelId &&
549
+ operationRow.deleteRequestHash === input.requestHash
550
+ ? { outcome: "success" as const, model: mapCustomModel(operationRow) }
551
+ : { outcome: "conflict" as const };
552
+ }
553
+ const [target] = await scopedDb
554
+ .select()
555
+ .from(schema.workspaceGatewayCustomModels)
556
+ .where(
557
+ and(
558
+ eq(schema.workspaceGatewayCustomModels.accountId, input.accountId),
559
+ eq(schema.workspaceGatewayCustomModels.workspaceId, input.workspaceId),
560
+ eq(schema.workspaceGatewayCustomModels.providerKind, input.providerKind),
561
+ eq(schema.workspaceGatewayCustomModels.id, input.customModelId),
562
+ ),
563
+ )
564
+ .for("update")
565
+ .limit(1);
566
+ if (!target) return { outcome: "not_found" as const };
567
+ if (target.retiredAt || target.version !== input.expectedVersion) {
568
+ return { outcome: "conflict" as const };
569
+ }
570
+ const [removed] = await scopedDb
571
+ .update(schema.workspaceGatewayCustomModels)
572
+ .set({
573
+ version: target.version + 1,
574
+ deleteOperationId: input.operationId,
575
+ deleteRequestHash: input.requestHash,
576
+ retiredAt: new Date(),
577
+ updatedAt: new Date(),
578
+ })
579
+ .where(
580
+ and(
581
+ eq(schema.workspaceGatewayCustomModels.accountId, input.accountId),
582
+ eq(schema.workspaceGatewayCustomModels.workspaceId, input.workspaceId),
583
+ eq(schema.workspaceGatewayCustomModels.providerKind, input.providerKind),
584
+ eq(schema.workspaceGatewayCustomModels.id, input.customModelId),
585
+ eq(schema.workspaceGatewayCustomModels.version, input.expectedVersion),
586
+ isNull(schema.workspaceGatewayCustomModels.retiredAt),
587
+ ),
588
+ )
589
+ .returning();
590
+ return removed
591
+ ? { outcome: "success" as const, model: mapCustomModel(removed) }
592
+ : { outcome: "conflict" as const };
593
+ });
594
+ }
595
+
596
+ export async function deleteWorkspaceGatewayCustomModel(
597
+ db: Database,
598
+ input: DeleteWorkspaceProviderCustomModelInput,
599
+ ): Promise<
600
+ | { outcome: "success"; model: WorkspaceGatewayCustomModel }
601
+ | { outcome: "conflict" }
602
+ | { outcome: "not_found" }
603
+ > {
604
+ const result = await deleteWorkspaceProviderCustomModel(db, {
605
+ ...input,
606
+ providerKind: "vercel_gateway",
607
+ });
608
+ return result.outcome === "success"
609
+ ? { ...result, model: asGatewayCustomModel(result.model) }
610
+ : result;
611
+ }
612
+
613
+ export async function deleteWorkspaceOpenRouterCustomModel(
614
+ db: Database,
615
+ input: DeleteWorkspaceProviderCustomModelInput,
616
+ ): Promise<
617
+ | { outcome: "success"; model: WorkspaceOpenRouterCustomModel }
618
+ | { outcome: "conflict" }
619
+ | { outcome: "not_found" }
620
+ > {
621
+ const result = await deleteWorkspaceProviderCustomModel(db, {
622
+ ...input,
623
+ providerKind: "openrouter",
624
+ });
625
+ return result.outcome === "success"
626
+ ? { ...result, model: asOpenRouterCustomModel(result.model) }
627
+ : result;
628
+ }
629
+
630
+ /** Retired rows remain executable for an accepted turn or existing continuation. */
631
+ async function getWorkspaceProviderCustomModelForExecution(
632
+ db: Database,
633
+ input: {
634
+ accountId: string;
635
+ workspaceId: string;
636
+ providerKind: WorkspaceCustomModelProviderKind;
637
+ upstreamModelId: string;
638
+ },
639
+ ): Promise<WorkspaceProviderCustomModel | null> {
640
+ return await withRlsContext(db, input, async (scopedDb) => {
641
+ const [row] = await scopedDb
642
+ .select()
643
+ .from(schema.workspaceGatewayCustomModels)
644
+ .where(
645
+ and(
646
+ eq(schema.workspaceGatewayCustomModels.accountId, input.accountId),
647
+ eq(schema.workspaceGatewayCustomModels.workspaceId, input.workspaceId),
648
+ eq(schema.workspaceGatewayCustomModels.providerKind, input.providerKind),
649
+ eq(schema.workspaceGatewayCustomModels.upstreamModelId, input.upstreamModelId),
650
+ ),
651
+ )
652
+ .orderBy(
653
+ sql`(${schema.workspaceGatewayCustomModels.retiredAt} is null) desc`,
654
+ sql`${schema.workspaceGatewayCustomModels.updatedAt} desc`,
655
+ sql`${schema.workspaceGatewayCustomModels.createdAt} desc`,
656
+ )
657
+ .limit(1);
658
+ return row ? mapCustomModel(row) : null;
659
+ });
660
+ }
661
+
662
+ export async function getWorkspaceGatewayCustomModelForExecution(
663
+ db: Database,
664
+ input: { accountId: string; workspaceId: string; upstreamModelId: string },
665
+ ): Promise<WorkspaceGatewayCustomModel | null> {
666
+ const model = await getWorkspaceProviderCustomModelForExecution(db, {
667
+ ...input,
668
+ providerKind: "vercel_gateway",
669
+ });
670
+ return model ? asGatewayCustomModel(model) : null;
671
+ }
672
+
673
+ export async function getWorkspaceOpenRouterCustomModelForExecution(
674
+ db: Database,
675
+ input: { accountId: string; workspaceId: string; upstreamModelId: string },
676
+ ): Promise<WorkspaceOpenRouterCustomModel | null> {
677
+ const model = await getWorkspaceProviderCustomModelForExecution(db, {
678
+ ...input,
679
+ providerKind: "openrouter",
680
+ });
681
+ return model ? asOpenRouterCustomModel(model) : null;
682
+ }
@@ -284,7 +284,6 @@ async function settleOrganizationMembershipProtocols(
284
284
  eq(schema.sessions.accountId, settlement.accountId),
285
285
  eq(schema.sessions.workspaceId, workspaceId),
286
286
  eq(schema.sessions.id, settlement.sessionId),
287
- eq(schema.sessions.lastSequence, session.lastSequence),
288
287
  ),
289
288
  )
290
289
  .returning({ id: schema.sessions.id });