@opengeni/db 3.7.4 → 4.0.0-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/capability-integrations.d.ts +2 -0
- package/dist/{chunk-6DPJA36C.js → chunk-2XEZK4LF.js} +7 -1
- package/dist/chunk-2XEZK4LF.js.map +1 -0
- package/dist/{chunk-4NAR7FBA.js → chunk-3RI33YQN.js} +67 -1
- package/dist/chunk-3RI33YQN.js.map +1 -0
- package/dist/{chunk-SZAWDXQC.js → chunk-CJROYHSC.js} +3 -3
- package/dist/{chunk-DZCYW2WT.js → chunk-MX6V5F65.js} +2 -2
- package/dist/{chunk-S3TDDCS2.js → chunk-N5VXJ4I2.js} +151 -78
- package/dist/chunk-N5VXJ4I2.js.map +1 -0
- package/dist/{chunk-U4WPPEBH.js → chunk-NLFHMPUB.js} +2 -2
- package/dist/{chunk-5NNCGFQY.js → chunk-P6NSEG2S.js} +2 -2
- package/dist/{chunk-O56DM5GZ.js → chunk-QM6YOWHZ.js} +624 -57
- package/dist/chunk-QM6YOWHZ.js.map +1 -0
- package/dist/{chunk-US4IS6IV.js → chunk-RZNOSEMC.js} +2 -2
- package/dist/{chunk-F6PBS6WE.js → chunk-UFAMOUWL.js} +2 -2
- package/dist/codemode-operations.d.ts +5 -0
- package/dist/codex-token-resolver.d.ts +6 -2
- package/dist/connection-token-resolver.d.ts +8 -0
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/generated-images.d.ts +15 -0
- package/dist/index.d.ts +393 -91
- package/dist/index.js +9992 -6069
- 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/managed-user-setup.d.ts +3 -0
- package/dist/mcp-oauth.d.ts +80 -0
- package/dist/migrate.d.ts +1 -0
- package/dist/migrate.js +3 -1
- package/dist/model-catalog.d.ts +153 -0
- package/dist/model-context-snapshots.d.ts +20 -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 +8 -8
- package/dist/schema.d.ts +7708 -5050
- package/dist/schema.js +25 -1
- package/dist/session-background-commands.d.ts +18 -6
- package/dist/session-background-commands.js +15 -9
- package/dist/session-control.d.ts +37 -2
- package/dist/session-goal-pacing.d.ts +3 -6
- package/dist/session-queue-commands.d.ts +17 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/tool-gateway-approvals.d.ts +31 -0
- package/dist/video-generation.js +3 -3
- package/dist/workspace-artifacts.d.ts +17 -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/drizzle/0400_session_attempt_model_context_snapshots.sql +65 -0
- package/drizzle/0401_organization_user_setup_token_transport.sql +178 -0
- package/drizzle/0402_session_input_wait_and_background_command_results.sql +1023 -0
- package/drizzle/0403_codex_unconditional_credential_leasing.sql +181 -0
- package/drizzle/0404_mcp_oauth_authorization_server.sql +471 -0
- package/drizzle/0405_tool_gateway_approval_capabilities.sql +184 -0
- package/drizzle/0406_workspace_html_site_sources.sql +71 -0
- package/package.json +6 -6
- package/src/automations.ts +9 -1
- package/src/capability-integrations.ts +8 -0
- package/src/codemode-operations.ts +31 -21
- package/src/codex-token-resolver.ts +16 -6
- package/src/connection-token-resolver.ts +52 -3
- package/src/generated-images.ts +70 -6
- package/src/index.ts +3894 -994
- package/src/insights-model-bundle.ts +54 -5
- package/src/insights.ts +53 -7
- package/src/managed-user-setup.ts +14 -3
- package/src/mcp-oauth.ts +488 -0
- package/src/migrate.ts +9 -1
- package/src/model-catalog.ts +682 -0
- package/src/model-context-snapshots.ts +151 -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 +4 -0
- package/src/runtime-posture.ts +64 -0
- package/src/schema.ts +666 -61
- package/src/session-background-commands.ts +180 -78
- package/src/session-control.ts +181 -12
- package/src/session-goal-pacing.ts +4 -8
- package/src/session-queue-commands.ts +157 -82
- package/src/tool-gateway-approvals.ts +168 -0
- package/src/workspace-artifacts.ts +633 -178
- package/src/xai-subscription.ts +66 -14
- package/dist/chunk-4NAR7FBA.js.map +0 -1
- package/dist/chunk-6DPJA36C.js.map +0 -1
- package/dist/chunk-O56DM5GZ.js.map +0 -1
- package/dist/chunk-S3TDDCS2.js.map +0 -1
- /package/dist/{chunk-SZAWDXQC.js.map → chunk-CJROYHSC.js.map} +0 -0
- /package/dist/{chunk-DZCYW2WT.js.map → chunk-MX6V5F65.js.map} +0 -0
- /package/dist/{chunk-U4WPPEBH.js.map → chunk-NLFHMPUB.js.map} +0 -0
- /package/dist/{chunk-5NNCGFQY.js.map → chunk-P6NSEG2S.js.map} +0 -0
- /package/dist/{chunk-US4IS6IV.js.map → chunk-RZNOSEMC.js.map} +0 -0
- /package/dist/{chunk-F6PBS6WE.js.map → chunk-UFAMOUWL.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
|
+
}
|