@opengeni/core 2.6.4-canary.0 → 2.7.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.
@@ -1,18 +1,25 @@
1
1
  import {
2
2
  applyModelCatalogDocument,
3
3
  configuredGatewayWorkspaceProductModelIds,
4
+ configuredGatewayOrganizationProductModelIds,
4
5
  configuredModels,
5
6
  configuredModelNotes,
6
7
  configuredOpenRouterWorkspaceProductModelIds,
8
+ configuredOpenRouterOrganizationProductModelIds,
7
9
  configuredProviders,
8
10
  validateModelCatalogSettings,
9
11
  withCodexCatalogProvider,
12
+ withOrganizationGatewayCatalogProvider,
13
+ withOrganizationOpenRouterCatalogProvider,
10
14
  withWorkspaceGatewayCatalogProvider,
11
15
  withWorkspaceOpenRouterCatalogProvider,
12
16
  withXaiSubscriptionCatalogProvider,
13
17
  WORKSPACE_GATEWAY_MODEL_ID_PREFIX,
14
18
  WORKSPACE_OPENROUTER_MODEL_ID_PREFIX,
15
19
  WORKSPACE_OPENROUTER_PROVIDER_ID,
20
+ ORGANIZATION_OPENROUTER_PROVIDER_ID,
21
+ ORGANIZATION_GATEWAY_MODEL_ID_PREFIX,
22
+ ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX,
16
23
  type ConfiguredModel,
17
24
  type Settings,
18
25
  } from "@opengeni/config";
@@ -26,8 +33,13 @@ import {
26
33
  getDeploymentModelCatalog,
27
34
  getWorkspaceGatewayCustomModelForExecution,
28
35
  getWorkspaceOpenRouterCustomModelForExecution,
36
+ getOrganizationModelProviderCustomModelForExecution,
29
37
  listWorkspaceGatewayCustomModels,
30
38
  listWorkspaceOpenRouterCustomModels,
39
+ listOrganizationModelProviderCustomModelsForWorkspace,
40
+ lockActiveOrganizationModelProviderCustomModelForAdmission,
41
+ lockActiveWorkspaceGatewayCustomModelForAdmission,
42
+ lockActiveWorkspaceOpenRouterCustomModelForAdmission,
31
43
  type Database,
32
44
  } from "@opengeni/db";
33
45
 
@@ -58,6 +70,7 @@ export function isWorkspaceOpenRouterCustomModelId(settings: Settings, modelId:
58
70
  }
59
71
 
60
72
  export type WorkspaceCustomModelReference = {
73
+ scope: "workspace" | "organization";
61
74
  providerKind: "vercel_gateway" | "openrouter";
62
75
  upstreamModelId: string;
63
76
  };
@@ -68,16 +81,38 @@ export function workspaceCustomModelReference(
68
81
  ): WorkspaceCustomModelReference | null {
69
82
  if (isWorkspaceGatewayCustomModelId(settings, modelId)) {
70
83
  return {
84
+ scope: "workspace",
71
85
  providerKind: "vercel_gateway",
72
86
  upstreamModelId: modelId.slice(WORKSPACE_GATEWAY_MODEL_ID_PREFIX.length),
73
87
  };
74
88
  }
75
89
  if (isWorkspaceOpenRouterCustomModelId(settings, modelId)) {
76
90
  return {
91
+ scope: "workspace",
77
92
  providerKind: "openrouter",
78
93
  upstreamModelId: modelId.slice(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX.length),
79
94
  };
80
95
  }
96
+ if (
97
+ modelId.startsWith(ORGANIZATION_GATEWAY_MODEL_ID_PREFIX) &&
98
+ !configuredGatewayOrganizationProductModelIds(settings).includes(modelId)
99
+ ) {
100
+ return {
101
+ scope: "organization",
102
+ providerKind: "vercel_gateway",
103
+ upstreamModelId: modelId.slice(ORGANIZATION_GATEWAY_MODEL_ID_PREFIX.length),
104
+ };
105
+ }
106
+ if (
107
+ modelId.startsWith(ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX) &&
108
+ !configuredOpenRouterOrganizationProductModelIds(settings).includes(modelId)
109
+ ) {
110
+ return {
111
+ scope: "organization",
112
+ providerKind: "openrouter",
113
+ upstreamModelId: modelId.slice(ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX.length),
114
+ };
115
+ }
81
116
  return null;
82
117
  }
83
118
 
@@ -85,6 +120,39 @@ export function isWorkspaceCustomModelId(settings: Settings, modelId: string): b
85
120
  return workspaceCustomModelReference(settings, modelId) !== null;
86
121
  }
87
122
 
123
+ export async function lockActiveCustomModelForAdmission(
124
+ db: Database,
125
+ input: {
126
+ accountId: string;
127
+ workspaceId: string;
128
+ reference: WorkspaceCustomModelReference;
129
+ },
130
+ ): Promise<boolean> {
131
+ if (input.reference.scope === "organization") {
132
+ return Boolean(
133
+ await lockActiveOrganizationModelProviderCustomModelForAdmission(db, {
134
+ accountId: input.accountId,
135
+ workspaceId: input.workspaceId,
136
+ providerKind: input.reference.providerKind,
137
+ upstreamModelId: input.reference.upstreamModelId,
138
+ }),
139
+ );
140
+ }
141
+ return Boolean(
142
+ input.reference.providerKind === "openrouter"
143
+ ? await lockActiveWorkspaceOpenRouterCustomModelForAdmission(db, {
144
+ accountId: input.accountId,
145
+ workspaceId: input.workspaceId,
146
+ upstreamModelId: input.reference.upstreamModelId,
147
+ })
148
+ : await lockActiveWorkspaceGatewayCustomModelForAdmission(db, {
149
+ accountId: input.accountId,
150
+ workspaceId: input.workspaceId,
151
+ upstreamModelId: input.reference.upstreamModelId,
152
+ }),
153
+ );
154
+ }
155
+
88
156
  /**
89
157
  * Resolve the deployment catalog without making synchronous env settings read
90
158
  * Postgres. Database mode fails closed when the singleton is absent or invalid;
@@ -133,6 +201,11 @@ export async function resolveWorkspaceCatalogSettings(
133
201
  retainedProductModelIds?: readonly (string | null | undefined)[];
134
202
  },
135
203
  ): Promise<ResolvedCatalogSettings> {
204
+ // A few pure catalog tests inject the historical minimal DB port and mock the
205
+ // workspace model helpers directly. Real/injected runtime databases always
206
+ // expose transactions; keep that narrow test port compatible.
207
+ const supportsOrganizationProviderReads =
208
+ typeof (db as Database & { transaction?: unknown }).transaction === "function";
136
209
  const retainedProductModelIds = [
137
210
  ...(input.retainedProductModelIds ?? []),
138
211
  input.retainedProductModelId,
@@ -147,12 +220,28 @@ export async function resolveWorkspaceCatalogSettings(
147
220
  ? [productModelId.slice(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX.length)]
148
221
  : [],
149
222
  );
223
+ const retainedOrganizationGatewayUpstreamModelIds = retainedProductModelIds.flatMap(
224
+ (productModelId) =>
225
+ productModelId?.startsWith(ORGANIZATION_GATEWAY_MODEL_ID_PREFIX)
226
+ ? [productModelId.slice(ORGANIZATION_GATEWAY_MODEL_ID_PREFIX.length)]
227
+ : [],
228
+ );
229
+ const retainedOrganizationOpenRouterUpstreamModelIds = retainedProductModelIds.flatMap(
230
+ (productModelId) =>
231
+ productModelId?.startsWith(ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX)
232
+ ? [productModelId.slice(ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX.length)]
233
+ : [],
234
+ );
150
235
  const [
151
236
  resolved,
152
237
  activeGatewayCustomModels,
153
238
  activeOpenRouterCustomModels,
154
239
  retainedGatewayCustomModels,
155
240
  retainedOpenRouterCustomModels,
241
+ organizationGatewayCustomModels,
242
+ organizationOpenRouterCustomModels,
243
+ retainedOrganizationGatewayCustomModels,
244
+ retainedOrganizationOpenRouterCustomModels,
156
245
  ] = await Promise.all([
157
246
  resolveCatalogSettings(db, envSettings),
158
247
  listWorkspaceGatewayCustomModels(db, {
@@ -183,6 +272,46 @@ export async function resolveWorkspaceCatalogSettings(
183
272
  }),
184
273
  ),
185
274
  ),
275
+ supportsOrganizationProviderReads
276
+ ? listOrganizationModelProviderCustomModelsForWorkspace(db, {
277
+ accountId: input.accountId,
278
+ workspaceId: input.workspaceId,
279
+ providerKind: "vercel_gateway",
280
+ })
281
+ : Promise.resolve([]),
282
+ supportsOrganizationProviderReads
283
+ ? listOrganizationModelProviderCustomModelsForWorkspace(db, {
284
+ accountId: input.accountId,
285
+ workspaceId: input.workspaceId,
286
+ providerKind: "openrouter",
287
+ })
288
+ : Promise.resolve([]),
289
+ supportsOrganizationProviderReads
290
+ ? Promise.all(
291
+ [...new Set(retainedOrganizationGatewayUpstreamModelIds)].map(
292
+ async (upstreamModelId) =>
293
+ await getOrganizationModelProviderCustomModelForExecution(db, {
294
+ accountId: input.accountId,
295
+ workspaceId: input.workspaceId,
296
+ providerKind: "vercel_gateway",
297
+ upstreamModelId,
298
+ }),
299
+ ),
300
+ )
301
+ : Promise.resolve([]),
302
+ supportsOrganizationProviderReads
303
+ ? Promise.all(
304
+ [...new Set(retainedOrganizationOpenRouterUpstreamModelIds)].map(
305
+ async (upstreamModelId) =>
306
+ await getOrganizationModelProviderCustomModelForExecution(db, {
307
+ accountId: input.accountId,
308
+ workspaceId: input.workspaceId,
309
+ providerKind: "openrouter",
310
+ upstreamModelId,
311
+ }),
312
+ ),
313
+ )
314
+ : Promise.resolve([]),
186
315
  ]);
187
316
  const includeRetainedModels = <T extends { upstreamModelId: string }>(
188
317
  activeModels: readonly T[],
@@ -209,11 +338,25 @@ export async function resolveWorkspaceCatalogSettings(
209
338
  activeOpenRouterCustomModels,
210
339
  retainedOpenRouterCustomModels,
211
340
  );
341
+ const organizationGatewayModels = includeRetainedModels(
342
+ organizationGatewayCustomModels,
343
+ retainedOrganizationGatewayCustomModels,
344
+ );
345
+ const organizationOpenRouterModels = includeRetainedModels(
346
+ organizationOpenRouterCustomModels,
347
+ retainedOrganizationOpenRouterCustomModels,
348
+ );
212
349
  return {
213
350
  ...resolved,
214
- settings: withWorkspaceOpenRouterCatalogProvider(
215
- withWorkspaceGatewayCatalogProvider(resolved.settings, gatewayCustomModels),
216
- openRouterCustomModels,
351
+ settings: withOrganizationOpenRouterCatalogProvider(
352
+ withOrganizationGatewayCatalogProvider(
353
+ withWorkspaceOpenRouterCatalogProvider(
354
+ withWorkspaceGatewayCatalogProvider(resolved.settings, gatewayCustomModels),
355
+ openRouterCustomModels,
356
+ ),
357
+ organizationGatewayModels,
358
+ ),
359
+ organizationOpenRouterModels,
217
360
  ),
218
361
  };
219
362
  }
@@ -242,6 +385,8 @@ export type WorkspaceModelSelectionInput = {
242
385
  xaiSubscriptionActive?: boolean;
243
386
  workspaceGatewayConnectionActive?: boolean;
244
387
  workspaceOpenRouterConnectionActive?: boolean;
388
+ organizationGatewayConnectionActive?: boolean;
389
+ organizationOpenRouterConnectionActive?: boolean;
245
390
  workspaceGatewayCustomModels?: readonly {
246
391
  upstreamModelId: string;
247
392
  label?: string | null;
@@ -250,6 +395,14 @@ export type WorkspaceModelSelectionInput = {
250
395
  upstreamModelId: string;
251
396
  label?: string | null;
252
397
  }[];
398
+ organizationGatewayCustomModels?: readonly {
399
+ upstreamModelId: string;
400
+ label?: string | null;
401
+ }[];
402
+ organizationOpenRouterCustomModels?: readonly {
403
+ upstreamModelId: string;
404
+ label?: string | null;
405
+ }[];
253
406
  credentialReadinessObservations?:
254
407
  | Readonly<Record<string, ModelCredentialReadinessObservation>>
255
408
  | undefined;
@@ -332,6 +485,8 @@ function credentialReadinessFor(input: {
332
485
  xaiSubscriptionActive: boolean;
333
486
  workspaceGatewayConnectionActive: boolean;
334
487
  workspaceOpenRouterConnectionActive: boolean;
488
+ organizationGatewayConnectionActive: boolean;
489
+ organizationOpenRouterConnectionActive: boolean;
335
490
  observation: ModelCredentialReadinessObservation | undefined;
336
491
  nowMs: number;
337
492
  maxAgeMs: number;
@@ -363,6 +518,20 @@ function credentialReadinessFor(input: {
363
518
  checkedAt: null,
364
519
  };
365
520
  }
521
+ if (source.kind === "organization_connection") {
522
+ const connectionActive =
523
+ input.model.providerId === ORGANIZATION_OPENROUTER_PROVIDER_ID
524
+ ? input.organizationOpenRouterConnectionActive
525
+ : input.organizationGatewayConnectionActive;
526
+ return connectionActive
527
+ ? { status: "ready", reason: null, basis: "connection", checkedAt: null }
528
+ : {
529
+ status: "not_ready",
530
+ reason: "needs_reauth",
531
+ basis: "connection",
532
+ checkedAt: null,
533
+ };
534
+ }
366
535
  if (source.kind === "deployment" && source.mechanism === "none") {
367
536
  return { status: "ready", reason: null, basis: "configuration", checkedAt: null };
368
537
  }
@@ -512,9 +681,15 @@ export function resolveWorkspaceModelSelection(
512
681
  const xaiSettings = input.settings.supergrokSubscriptionEnabled
513
682
  ? withXaiSubscriptionCatalogProvider(codexSettings)
514
683
  : codexSettings;
515
- const catalogSettings = withWorkspaceOpenRouterCatalogProvider(
516
- withWorkspaceGatewayCatalogProvider(xaiSettings, input.workspaceGatewayCustomModels ?? []),
517
- input.workspaceOpenRouterCustomModels ?? [],
684
+ const catalogSettings = withOrganizationOpenRouterCatalogProvider(
685
+ withOrganizationGatewayCatalogProvider(
686
+ withWorkspaceOpenRouterCatalogProvider(
687
+ withWorkspaceGatewayCatalogProvider(xaiSettings, input.workspaceGatewayCustomModels ?? []),
688
+ input.workspaceOpenRouterCustomModels ?? [],
689
+ ),
690
+ input.organizationGatewayCustomModels ?? [],
691
+ ),
692
+ input.organizationOpenRouterCustomModels ?? [],
518
693
  );
519
694
  const providers = new Map(
520
695
  configuredProviders(catalogSettings).map((provider) => [provider.id, provider]),
@@ -544,6 +719,8 @@ export function resolveWorkspaceModelSelection(
544
719
  xaiSubscriptionActive: input.xaiSubscriptionActive === true,
545
720
  workspaceGatewayConnectionActive: input.workspaceGatewayConnectionActive === true,
546
721
  workspaceOpenRouterConnectionActive: input.workspaceOpenRouterConnectionActive === true,
722
+ organizationGatewayConnectionActive: input.organizationGatewayConnectionActive === true,
723
+ organizationOpenRouterConnectionActive: input.organizationOpenRouterConnectionActive === true,
547
724
  observation: input.credentialReadinessObservations?.[model.definitionVersion],
548
725
  nowMs,
549
726
  maxAgeMs,
@@ -454,31 +454,18 @@ export async function listFleet(
454
454
  };
455
455
  }
456
456
 
457
- /** Resolve a swap target id the value `setActiveSandbox` writes. The session's
458
- * own group id maps to NULL (the default pointer); a first-class sandbox id is
459
- * validated (workspace ownership + liveness) and written verbatim. */
460
- async function resolveTarget(
457
+ type FleetResourceContext = Pick<FleetContext, "accountId" | "workspaceId" | "subjectId">;
458
+
459
+ type ResolvedNamedSandboxTarget =
460
+ | { ok: true; targetSandboxId: string; workspaceRoot?: string }
461
+ | { ok: false; reason: string; code: BackendUnresolvableCode };
462
+
463
+ /** Resolve one named sandbox, independent of any existing session pointer. */
464
+ async function resolveNamedSandboxTarget(
461
465
  services: FleetServices,
462
- ctx: FleetContext,
466
+ ctx: FleetResourceContext,
463
467
  target: string,
464
- ): Promise<
465
- | { ok: true; targetSandboxId: string | null; workspaceRoot?: string }
466
- | { ok: false; reason: string; code: BackendUnresolvableCode }
467
- > {
468
- // The session's own group box → the default pointer (null).
469
- if (target === ctx.sessionGroupId || target === "session" || target === "default") {
470
- if (!managedSessionGroupBackend(services.settings.sandboxBackend, ctx.sessionBackend)) {
471
- return {
472
- ok: false,
473
- reason:
474
- ctx.sessionBackend === "none"
475
- ? "this session has no home sandbox; attach a Connected Machine"
476
- : "this deployment has no managed session sandbox; select an enrolled machine",
477
- code: "unsupported_backend_context",
478
- };
479
- }
480
- return { ok: true, targetSandboxId: null };
481
- }
468
+ ): Promise<ResolvedNamedSandboxTarget> {
482
469
  const sandbox = await getSandbox(
483
470
  services.db,
484
471
  ctx.subjectId
@@ -566,6 +553,81 @@ async function resolveTarget(
566
553
  };
567
554
  }
568
555
 
556
+ export type CreateTimeSandboxTargetPreflight =
557
+ | { ok: true; targetSandboxId: string; workingDir: string | null }
558
+ | {
559
+ ok: false;
560
+ reason: string;
561
+ code: BackendUnresolvableCode | "invalid_working_directory";
562
+ };
563
+
564
+ /**
565
+ * Validate a named create-time machine target before any session row exists.
566
+ * The caller still commits the pointer with `setActiveSandbox` inside the
567
+ * session-create transaction, which rechecks durable ownership/enrollment
568
+ * authority and closes the remove/revoke race without holding database locks
569
+ * across the liveness probe.
570
+ */
571
+ export async function preflightCreateTimeSandboxTarget(
572
+ services: FleetServices,
573
+ ctx: FleetResourceContext,
574
+ target: string,
575
+ workingDir: string | null,
576
+ ): Promise<CreateTimeSandboxTargetPreflight> {
577
+ const resolved = await resolveNamedSandboxTarget(services, ctx, target);
578
+ if (!resolved.ok) return resolved;
579
+ if (!resolved.workspaceRoot) {
580
+ return {
581
+ ok: true,
582
+ targetSandboxId: resolved.targetSandboxId,
583
+ workingDir,
584
+ };
585
+ }
586
+ try {
587
+ return {
588
+ ok: true,
589
+ targetSandboxId: resolved.targetSandboxId,
590
+ workingDir:
591
+ resolveConnectedMachineWorkspaceRoot(resolved.workspaceRoot, workingDir) ??
592
+ resolved.workspaceRoot,
593
+ };
594
+ } catch (error) {
595
+ return {
596
+ ok: false,
597
+ reason: error instanceof Error ? error.message : String(error),
598
+ code: "invalid_working_directory",
599
+ };
600
+ }
601
+ }
602
+
603
+ /** Resolve a swap target id → the value `setActiveSandbox` writes. The session's
604
+ * own group id maps to NULL (the default pointer); a first-class sandbox id is
605
+ * validated (workspace ownership + liveness) and written verbatim. */
606
+ async function resolveTarget(
607
+ services: FleetServices,
608
+ ctx: FleetContext,
609
+ target: string,
610
+ ): Promise<
611
+ | { ok: true; targetSandboxId: string | null; workspaceRoot?: string }
612
+ | { ok: false; reason: string; code: BackendUnresolvableCode }
613
+ > {
614
+ // The session's own group box → the default pointer (null).
615
+ if (target === ctx.sessionGroupId || target === "session" || target === "default") {
616
+ if (!managedSessionGroupBackend(services.settings.sandboxBackend, ctx.sessionBackend)) {
617
+ return {
618
+ ok: false,
619
+ reason:
620
+ ctx.sessionBackend === "none"
621
+ ? "this session has no home sandbox; attach a Connected Machine"
622
+ : "this deployment has no managed session sandbox; select an enrolled machine",
623
+ code: "unsupported_backend_context",
624
+ };
625
+ }
626
+ return { ok: true, targetSandboxId: null };
627
+ }
628
+ return await resolveNamedSandboxTarget(services, ctx, target);
629
+ }
630
+
569
631
  /**
570
632
  * THE SWAP (and attach — identical mechanic). Validate the target's ownership +
571
633
  * liveness, then repoint the session via the epoch-fenced CAS `setActiveSandbox`: