@opengeni/core 2.5.3 → 2.6.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.
- package/dist/access/index.d.ts +24 -0
- package/dist/billing/limits.d.ts +5 -0
- package/dist/canonical-human-identities.js +2 -2
- package/dist/{chunk-ZVZJTMSV.js → chunk-OF65T3PM.js} +2 -2
- package/dist/{chunk-YGOMUGYS.js → chunk-QO5GVFFO.js} +17 -8
- package/dist/{chunk-YGOMUGYS.js.map → chunk-QO5GVFFO.js.map} +1 -1
- package/dist/dependencies.d.ts +10 -1
- package/dist/domain/company-brain-governed-writes.d.ts +15 -6
- package/dist/domain/company-profile-agent-admin.d.ts +3 -2
- package/dist/domain/environments.d.ts +1 -1
- package/dist/domain/memory-slack-delivery.d.ts +4 -1
- package/dist/domain/personal-connection-delegations.d.ts +1 -0
- package/dist/domain/pr-review.d.ts +1 -1
- package/dist/domain/scheduled-tasks.d.ts +12 -0
- package/dist/domain/sessions.d.ts +37 -11
- package/dist/domain/workspace-members.d.ts +8 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1706 -667
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-sets.d.ts +18 -0
- package/dist/managed-auth-session-sets.js +3 -1
- package/dist/model-catalog.d.ts +89 -0
- package/dist/sandbox/fleet.d.ts +6 -4
- package/dist/sandbox/routing.d.ts +7 -2
- package/dist/sandbox/runtime-settings.d.ts +17 -1
- package/package.json +10 -10
- package/src/access/index.ts +140 -5
- package/src/application/user-resource-grants.ts +31 -2
- package/src/billing/limits.ts +57 -24
- package/src/dependencies.ts +15 -1
- package/src/domain/company-brain-governed-writes.ts +29 -13
- package/src/domain/company-profile-agent-admin.ts +3 -2
- package/src/domain/environments.ts +6 -34
- package/src/domain/memory-slack-delivery.ts +30 -0
- package/src/domain/personal-connection-delegations.ts +40 -7
- package/src/domain/remember.ts +5 -6
- package/src/domain/scheduled-tasks.ts +146 -1
- package/src/domain/sessions.ts +912 -358
- package/src/domain/workspace-members.ts +34 -2
- package/src/index.ts +1 -0
- package/src/managed-auth-session-sets.ts +38 -11
- package/src/model-catalog.ts +565 -0
- package/src/sandbox/fleet.ts +20 -17
- package/src/sandbox/routing.ts +18 -4
- package/src/sandbox/runtime-settings.ts +32 -0
- /package/dist/{chunk-ZVZJTMSV.js.map → chunk-OF65T3PM.js.map} +0 -0
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyModelCatalogDocument,
|
|
3
|
+
configuredGatewayWorkspaceProductModelIds,
|
|
4
|
+
configuredModels,
|
|
5
|
+
configuredModelNotes,
|
|
6
|
+
configuredOpenRouterWorkspaceProductModelIds,
|
|
7
|
+
configuredProviders,
|
|
8
|
+
validateModelCatalogSettings,
|
|
9
|
+
withCodexCatalogProvider,
|
|
10
|
+
withWorkspaceGatewayCatalogProvider,
|
|
11
|
+
withWorkspaceOpenRouterCatalogProvider,
|
|
12
|
+
withXaiSubscriptionCatalogProvider,
|
|
13
|
+
WORKSPACE_GATEWAY_MODEL_ID_PREFIX,
|
|
14
|
+
WORKSPACE_OPENROUTER_MODEL_ID_PREFIX,
|
|
15
|
+
WORKSPACE_OPENROUTER_PROVIDER_ID,
|
|
16
|
+
type ConfiguredModel,
|
|
17
|
+
type Settings,
|
|
18
|
+
} from "@opengeni/config";
|
|
19
|
+
import {
|
|
20
|
+
evaluateWorkspaceModelPolicy,
|
|
21
|
+
type ModelAvailabilityV1,
|
|
22
|
+
type ModelCredentialReadinessV1,
|
|
23
|
+
type WorkspaceModelPolicyContract,
|
|
24
|
+
} from "@opengeni/contracts";
|
|
25
|
+
import {
|
|
26
|
+
getDeploymentModelCatalog,
|
|
27
|
+
getWorkspaceGatewayCustomModelForExecution,
|
|
28
|
+
getWorkspaceOpenRouterCustomModelForExecution,
|
|
29
|
+
listWorkspaceGatewayCustomModels,
|
|
30
|
+
listWorkspaceOpenRouterCustomModels,
|
|
31
|
+
type Database,
|
|
32
|
+
} from "@opengeni/db";
|
|
33
|
+
|
|
34
|
+
export type ResolvedCatalogSettings = {
|
|
35
|
+
settings: Settings;
|
|
36
|
+
source: "code" | "database";
|
|
37
|
+
version: number | null;
|
|
38
|
+
modelNotes: Record<string, string>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Curated workspace Gateway products and workspace-owned custom slugs share
|
|
43
|
+
* one public prefix. Only the latter have a mutable catalog row whose active
|
|
44
|
+
* generation must be rechecked at a fresh acceptance commit boundary.
|
|
45
|
+
*/
|
|
46
|
+
export function isWorkspaceGatewayCustomModelId(settings: Settings, modelId: string): boolean {
|
|
47
|
+
return (
|
|
48
|
+
modelId.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX) &&
|
|
49
|
+
!configuredGatewayWorkspaceProductModelIds(settings).includes(modelId)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function isWorkspaceOpenRouterCustomModelId(settings: Settings, modelId: string): boolean {
|
|
54
|
+
return (
|
|
55
|
+
modelId.startsWith(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX) &&
|
|
56
|
+
!configuredOpenRouterWorkspaceProductModelIds(settings).includes(modelId)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type WorkspaceCustomModelReference = {
|
|
61
|
+
providerKind: "vercel_gateway" | "openrouter";
|
|
62
|
+
upstreamModelId: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export function workspaceCustomModelReference(
|
|
66
|
+
settings: Settings,
|
|
67
|
+
modelId: string,
|
|
68
|
+
): WorkspaceCustomModelReference | null {
|
|
69
|
+
if (isWorkspaceGatewayCustomModelId(settings, modelId)) {
|
|
70
|
+
return {
|
|
71
|
+
providerKind: "vercel_gateway",
|
|
72
|
+
upstreamModelId: modelId.slice(WORKSPACE_GATEWAY_MODEL_ID_PREFIX.length),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (isWorkspaceOpenRouterCustomModelId(settings, modelId)) {
|
|
76
|
+
return {
|
|
77
|
+
providerKind: "openrouter",
|
|
78
|
+
upstreamModelId: modelId.slice(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX.length),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function isWorkspaceCustomModelId(settings: Settings, modelId: string): boolean {
|
|
85
|
+
return workspaceCustomModelReference(settings, modelId) !== null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the deployment catalog without making synchronous env settings read
|
|
90
|
+
* Postgres. Database mode fails closed when the singleton is absent or invalid;
|
|
91
|
+
* code mode preserves the already-validated env catalog.
|
|
92
|
+
*/
|
|
93
|
+
export async function resolveCatalogSettings(
|
|
94
|
+
db: Database,
|
|
95
|
+
envSettings: Settings,
|
|
96
|
+
): Promise<ResolvedCatalogSettings> {
|
|
97
|
+
if (envSettings.modelCatalogSource === "code") {
|
|
98
|
+
validateModelCatalogSettings(envSettings);
|
|
99
|
+
return {
|
|
100
|
+
settings: envSettings,
|
|
101
|
+
source: "code",
|
|
102
|
+
version: null,
|
|
103
|
+
modelNotes: configuredModelNotes(envSettings),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const row = await getDeploymentModelCatalog(db);
|
|
108
|
+
if (!row) {
|
|
109
|
+
throw new Error("database model catalog source is configured but the singleton row is missing");
|
|
110
|
+
}
|
|
111
|
+
const settings = applyModelCatalogDocument(envSettings, row.document);
|
|
112
|
+
validateModelCatalogSettings(settings);
|
|
113
|
+
return {
|
|
114
|
+
settings,
|
|
115
|
+
source: "database",
|
|
116
|
+
version: row.version,
|
|
117
|
+
modelNotes: configuredModelNotes(settings),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Resolve the deployment catalog and add only the custom Gateway slugs owned by
|
|
123
|
+
* one workspace. Use this at model-bearing workspace boundaries; public config
|
|
124
|
+
* and deployment-operator surfaces must continue to use `resolveCatalogSettings`.
|
|
125
|
+
*/
|
|
126
|
+
export async function resolveWorkspaceCatalogSettings(
|
|
127
|
+
db: Database,
|
|
128
|
+
envSettings: Settings,
|
|
129
|
+
input: {
|
|
130
|
+
accountId: string;
|
|
131
|
+
workspaceId: string;
|
|
132
|
+
retainedProductModelId?: string | null;
|
|
133
|
+
retainedProductModelIds?: readonly (string | null | undefined)[];
|
|
134
|
+
},
|
|
135
|
+
): Promise<ResolvedCatalogSettings> {
|
|
136
|
+
const retainedProductModelIds = [
|
|
137
|
+
...(input.retainedProductModelIds ?? []),
|
|
138
|
+
input.retainedProductModelId,
|
|
139
|
+
];
|
|
140
|
+
const retainedGatewayUpstreamModelIds = retainedProductModelIds.flatMap((productModelId) =>
|
|
141
|
+
productModelId?.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX)
|
|
142
|
+
? [productModelId.slice(WORKSPACE_GATEWAY_MODEL_ID_PREFIX.length)]
|
|
143
|
+
: [],
|
|
144
|
+
);
|
|
145
|
+
const retainedOpenRouterUpstreamModelIds = retainedProductModelIds.flatMap((productModelId) =>
|
|
146
|
+
productModelId?.startsWith(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX)
|
|
147
|
+
? [productModelId.slice(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX.length)]
|
|
148
|
+
: [],
|
|
149
|
+
);
|
|
150
|
+
const [
|
|
151
|
+
resolved,
|
|
152
|
+
activeGatewayCustomModels,
|
|
153
|
+
activeOpenRouterCustomModels,
|
|
154
|
+
retainedGatewayCustomModels,
|
|
155
|
+
retainedOpenRouterCustomModels,
|
|
156
|
+
] = await Promise.all([
|
|
157
|
+
resolveCatalogSettings(db, envSettings),
|
|
158
|
+
listWorkspaceGatewayCustomModels(db, {
|
|
159
|
+
accountId: input.accountId,
|
|
160
|
+
workspaceId: input.workspaceId,
|
|
161
|
+
}),
|
|
162
|
+
listWorkspaceOpenRouterCustomModels(db, {
|
|
163
|
+
accountId: input.accountId,
|
|
164
|
+
workspaceId: input.workspaceId,
|
|
165
|
+
}),
|
|
166
|
+
Promise.all(
|
|
167
|
+
[...new Set(retainedGatewayUpstreamModelIds)].map(
|
|
168
|
+
async (upstreamModelId) =>
|
|
169
|
+
await getWorkspaceGatewayCustomModelForExecution(db, {
|
|
170
|
+
accountId: input.accountId,
|
|
171
|
+
workspaceId: input.workspaceId,
|
|
172
|
+
upstreamModelId,
|
|
173
|
+
}),
|
|
174
|
+
),
|
|
175
|
+
),
|
|
176
|
+
Promise.all(
|
|
177
|
+
[...new Set(retainedOpenRouterUpstreamModelIds)].map(
|
|
178
|
+
async (upstreamModelId) =>
|
|
179
|
+
await getWorkspaceOpenRouterCustomModelForExecution(db, {
|
|
180
|
+
accountId: input.accountId,
|
|
181
|
+
workspaceId: input.workspaceId,
|
|
182
|
+
upstreamModelId,
|
|
183
|
+
}),
|
|
184
|
+
),
|
|
185
|
+
),
|
|
186
|
+
]);
|
|
187
|
+
const includeRetainedModels = <T extends { upstreamModelId: string }>(
|
|
188
|
+
activeModels: readonly T[],
|
|
189
|
+
retainedModels: readonly (T | null)[],
|
|
190
|
+
): T[] => {
|
|
191
|
+
const customModels = [...activeModels];
|
|
192
|
+
const includedUpstreamModelIds = new Set(activeModels.map((model) => model.upstreamModelId));
|
|
193
|
+
for (const retainedCustomModel of retainedModels) {
|
|
194
|
+
if (
|
|
195
|
+
retainedCustomModel &&
|
|
196
|
+
!includedUpstreamModelIds.has(retainedCustomModel.upstreamModelId)
|
|
197
|
+
) {
|
|
198
|
+
customModels.push(retainedCustomModel);
|
|
199
|
+
includedUpstreamModelIds.add(retainedCustomModel.upstreamModelId);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return customModels;
|
|
203
|
+
};
|
|
204
|
+
const gatewayCustomModels = includeRetainedModels(
|
|
205
|
+
activeGatewayCustomModels,
|
|
206
|
+
retainedGatewayCustomModels,
|
|
207
|
+
);
|
|
208
|
+
const openRouterCustomModels = includeRetainedModels(
|
|
209
|
+
activeOpenRouterCustomModels,
|
|
210
|
+
retainedOpenRouterCustomModels,
|
|
211
|
+
);
|
|
212
|
+
return {
|
|
213
|
+
...resolved,
|
|
214
|
+
settings: withWorkspaceOpenRouterCatalogProvider(
|
|
215
|
+
withWorkspaceGatewayCatalogProvider(resolved.settings, gatewayCustomModels),
|
|
216
|
+
openRouterCustomModels,
|
|
217
|
+
),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export type ModelAvailabilityObservation = {
|
|
222
|
+
status: "available" | "degraded" | "unavailable";
|
|
223
|
+
reason: "not_entitled" | "provider_unhealthy" | null;
|
|
224
|
+
checkedAt: string;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export type ModelCredentialReadinessObservation =
|
|
228
|
+
| { status: "ready"; checkedAt: string }
|
|
229
|
+
| {
|
|
230
|
+
status: "not_ready";
|
|
231
|
+
reason: "prerequisites_missing" | "needs_reauth";
|
|
232
|
+
checkedAt: string;
|
|
233
|
+
}
|
|
234
|
+
| { status: "error"; reason: "resolver_error"; checkedAt: string };
|
|
235
|
+
|
|
236
|
+
export const MODEL_CREDENTIAL_READINESS_OBSERVATION_MAX_AGE_MS = 5 * 60_000;
|
|
237
|
+
|
|
238
|
+
export type WorkspaceModelSelectionInput = {
|
|
239
|
+
settings: Settings;
|
|
240
|
+
policy: WorkspaceModelPolicyContract | null;
|
|
241
|
+
codexSubscriptionActive: boolean;
|
|
242
|
+
xaiSubscriptionActive?: boolean;
|
|
243
|
+
workspaceGatewayConnectionActive?: boolean;
|
|
244
|
+
workspaceOpenRouterConnectionActive?: boolean;
|
|
245
|
+
workspaceGatewayCustomModels?: readonly {
|
|
246
|
+
upstreamModelId: string;
|
|
247
|
+
label?: string | null;
|
|
248
|
+
}[];
|
|
249
|
+
workspaceOpenRouterCustomModels?: readonly {
|
|
250
|
+
upstreamModelId: string;
|
|
251
|
+
label?: string | null;
|
|
252
|
+
}[];
|
|
253
|
+
credentialReadinessObservations?:
|
|
254
|
+
| Readonly<Record<string, ModelCredentialReadinessObservation>>
|
|
255
|
+
| undefined;
|
|
256
|
+
observations?: Readonly<Record<string, ModelAvailabilityObservation>> | undefined;
|
|
257
|
+
now?: Date | undefined;
|
|
258
|
+
credentialReadinessMaxAgeMs?: number | undefined;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
export type WorkspaceModelSelection = {
|
|
262
|
+
model: ConfiguredModel;
|
|
263
|
+
credentialReadiness: ModelCredentialReadinessV1;
|
|
264
|
+
policyAllowed: boolean;
|
|
265
|
+
availability: ModelAvailabilityV1;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
function modelDefinitionRunnable(model: ConfiguredModel): boolean {
|
|
269
|
+
return (
|
|
270
|
+
model.capabilities.inputModalities.includes("text") &&
|
|
271
|
+
model.capabilities.outputModalities.includes("text") &&
|
|
272
|
+
model.capabilities.transports.sse.runnable
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function observedCredentialReadiness(input: {
|
|
277
|
+
observation: ModelCredentialReadinessObservation | undefined;
|
|
278
|
+
basis: "connection" | "resolver";
|
|
279
|
+
nowMs: number;
|
|
280
|
+
maxAgeMs: number;
|
|
281
|
+
}): ModelCredentialReadinessV1 {
|
|
282
|
+
if (!input.observation) {
|
|
283
|
+
return {
|
|
284
|
+
status: "not_ready",
|
|
285
|
+
reason: "prerequisites_missing",
|
|
286
|
+
basis: input.basis,
|
|
287
|
+
checkedAt: null,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
const checkedAtMs = Date.parse(input.observation.checkedAt);
|
|
291
|
+
if (!Number.isFinite(checkedAtMs)) {
|
|
292
|
+
return {
|
|
293
|
+
status: "error",
|
|
294
|
+
reason: "resolver_error",
|
|
295
|
+
basis: input.basis,
|
|
296
|
+
checkedAt: null,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const checkedAt = new Date(checkedAtMs).toISOString();
|
|
300
|
+
if (Math.abs(input.nowMs - checkedAtMs) > input.maxAgeMs) {
|
|
301
|
+
return {
|
|
302
|
+
status: "not_ready",
|
|
303
|
+
reason: "observation_stale",
|
|
304
|
+
basis: input.basis,
|
|
305
|
+
checkedAt,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
if (input.observation.status === "ready") {
|
|
309
|
+
return { status: "ready", reason: null, basis: input.basis, checkedAt };
|
|
310
|
+
}
|
|
311
|
+
if (input.observation.status === "not_ready") {
|
|
312
|
+
return {
|
|
313
|
+
status: "not_ready",
|
|
314
|
+
reason:
|
|
315
|
+
input.observation.reason === "needs_reauth" ? "needs_reauth" : "prerequisites_missing",
|
|
316
|
+
basis: input.basis,
|
|
317
|
+
checkedAt,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
status: "error",
|
|
322
|
+
reason: "resolver_error",
|
|
323
|
+
basis: input.basis,
|
|
324
|
+
checkedAt,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function credentialReadinessFor(input: {
|
|
329
|
+
model: ConfiguredModel;
|
|
330
|
+
provider: ReturnType<typeof configuredProviders>[number] | undefined;
|
|
331
|
+
codexSubscriptionActive: boolean;
|
|
332
|
+
xaiSubscriptionActive: boolean;
|
|
333
|
+
workspaceGatewayConnectionActive: boolean;
|
|
334
|
+
workspaceOpenRouterConnectionActive: boolean;
|
|
335
|
+
observation: ModelCredentialReadinessObservation | undefined;
|
|
336
|
+
nowMs: number;
|
|
337
|
+
maxAgeMs: number;
|
|
338
|
+
}): ModelCredentialReadinessV1 {
|
|
339
|
+
const source = input.model.credentialSource;
|
|
340
|
+
if (source.kind === "connected_subscription") {
|
|
341
|
+
const active =
|
|
342
|
+
source.provider === "xai" ? input.xaiSubscriptionActive : input.codexSubscriptionActive;
|
|
343
|
+
return active
|
|
344
|
+
? { status: "ready", reason: null, basis: "connection", checkedAt: null }
|
|
345
|
+
: {
|
|
346
|
+
status: "not_ready",
|
|
347
|
+
reason: "needs_reauth",
|
|
348
|
+
basis: "connection",
|
|
349
|
+
checkedAt: null,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
if (source.kind === "workspace_connection") {
|
|
353
|
+
const connectionActive =
|
|
354
|
+
input.model.providerId === WORKSPACE_OPENROUTER_PROVIDER_ID
|
|
355
|
+
? input.workspaceOpenRouterConnectionActive
|
|
356
|
+
: input.workspaceGatewayConnectionActive;
|
|
357
|
+
return connectionActive
|
|
358
|
+
? { status: "ready", reason: null, basis: "connection", checkedAt: null }
|
|
359
|
+
: {
|
|
360
|
+
status: "not_ready",
|
|
361
|
+
reason: "needs_reauth",
|
|
362
|
+
basis: "connection",
|
|
363
|
+
checkedAt: null,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
if (source.kind === "deployment" && source.mechanism === "none") {
|
|
367
|
+
return { status: "ready", reason: null, basis: "configuration", checkedAt: null };
|
|
368
|
+
}
|
|
369
|
+
if (source.kind === "deployment" && source.mechanism === "api_key") {
|
|
370
|
+
return input.provider?.apiKey
|
|
371
|
+
? { status: "ready", reason: null, basis: "configuration", checkedAt: null }
|
|
372
|
+
: {
|
|
373
|
+
status: "not_ready",
|
|
374
|
+
reason: "missing_credential",
|
|
375
|
+
basis: "configuration",
|
|
376
|
+
checkedAt: null,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
return observedCredentialReadiness({
|
|
380
|
+
observation: input.observation,
|
|
381
|
+
basis: "resolver",
|
|
382
|
+
nowMs: input.nowMs,
|
|
383
|
+
maxAgeMs: input.maxAgeMs,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function isXaiGrokModel(model: ConfiguredModel): boolean {
|
|
388
|
+
return model.providerId === "xai" && model.id.startsWith("xai/grok-");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function observationTimestamp(observation: ModelAvailabilityObservation | undefined): {
|
|
392
|
+
checkedAt: string | null;
|
|
393
|
+
checkedAtMs: number | null;
|
|
394
|
+
} {
|
|
395
|
+
if (!observation || typeof observation.checkedAt !== "string") {
|
|
396
|
+
return { checkedAt: null, checkedAtMs: null };
|
|
397
|
+
}
|
|
398
|
+
const checkedAtMs = Date.parse(observation.checkedAt);
|
|
399
|
+
if (!Number.isFinite(checkedAtMs)) {
|
|
400
|
+
return { checkedAt: null, checkedAtMs: null };
|
|
401
|
+
}
|
|
402
|
+
return { checkedAt: new Date(checkedAtMs).toISOString(), checkedAtMs };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function xaiGrokAvailabilityFor(input: {
|
|
406
|
+
observation: ModelAvailabilityObservation | undefined;
|
|
407
|
+
nowMs: number;
|
|
408
|
+
maxAgeMs: number;
|
|
409
|
+
}): ModelAvailabilityV1 {
|
|
410
|
+
const { checkedAt, checkedAtMs } = observationTimestamp(input.observation);
|
|
411
|
+
const freshSuccessfulObservation =
|
|
412
|
+
input.observation?.status === "available" &&
|
|
413
|
+
input.observation.reason === null &&
|
|
414
|
+
checkedAtMs !== null &&
|
|
415
|
+
checkedAtMs <= input.nowMs &&
|
|
416
|
+
input.nowMs - checkedAtMs <= input.maxAgeMs;
|
|
417
|
+
|
|
418
|
+
if (freshSuccessfulObservation) {
|
|
419
|
+
return {
|
|
420
|
+
status: "available",
|
|
421
|
+
selectable: true,
|
|
422
|
+
reason: null,
|
|
423
|
+
checkedAt,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
status: "unavailable",
|
|
429
|
+
selectable: false,
|
|
430
|
+
reason:
|
|
431
|
+
input.observation?.status === "unavailable"
|
|
432
|
+
? (input.observation.reason ?? "provider_unhealthy")
|
|
433
|
+
: "provider_unhealthy",
|
|
434
|
+
checkedAt,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function availabilityFor(input: {
|
|
439
|
+
model: ConfiguredModel;
|
|
440
|
+
credentialReadiness: ModelCredentialReadinessV1;
|
|
441
|
+
policyAllowed: boolean;
|
|
442
|
+
observation?: ModelAvailabilityObservation | undefined;
|
|
443
|
+
nowMs: number;
|
|
444
|
+
maxAgeMs: number;
|
|
445
|
+
}): ModelAvailabilityV1 {
|
|
446
|
+
if (!modelDefinitionRunnable(input.model)) {
|
|
447
|
+
return {
|
|
448
|
+
status: "unavailable",
|
|
449
|
+
selectable: false,
|
|
450
|
+
reason: "unsupported",
|
|
451
|
+
checkedAt: null,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
if (input.credentialReadiness.status !== "ready") {
|
|
455
|
+
return {
|
|
456
|
+
status: "unavailable",
|
|
457
|
+
selectable: false,
|
|
458
|
+
reason:
|
|
459
|
+
input.credentialReadiness.reason === "missing_credential"
|
|
460
|
+
? "missing_credential"
|
|
461
|
+
: input.credentialReadiness.reason === "needs_reauth"
|
|
462
|
+
? "needs_reauth"
|
|
463
|
+
: "credential_not_ready",
|
|
464
|
+
checkedAt: input.credentialReadiness.checkedAt,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
if (!input.policyAllowed) {
|
|
468
|
+
return {
|
|
469
|
+
status: "unavailable",
|
|
470
|
+
selectable: false,
|
|
471
|
+
reason: "policy_blocked",
|
|
472
|
+
checkedAt: null,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
if (isXaiGrokModel(input.model)) {
|
|
476
|
+
return xaiGrokAvailabilityFor({
|
|
477
|
+
observation: input.observation,
|
|
478
|
+
nowMs: input.nowMs,
|
|
479
|
+
maxAgeMs: input.maxAgeMs,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
if (!input.observation) {
|
|
483
|
+
return { status: "unknown", selectable: true, reason: null, checkedAt: null };
|
|
484
|
+
}
|
|
485
|
+
if (input.observation.status === "unavailable") {
|
|
486
|
+
return {
|
|
487
|
+
status: "unavailable",
|
|
488
|
+
selectable: false,
|
|
489
|
+
reason: input.observation.reason ?? "provider_unhealthy",
|
|
490
|
+
checkedAt: input.observation.checkedAt,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
status: input.observation.status,
|
|
495
|
+
selectable: true,
|
|
496
|
+
reason: null,
|
|
497
|
+
checkedAt: input.observation.checkedAt,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* One shared picker/tool decision. Catalog membership, credential readiness,
|
|
503
|
+
* workspace policy, and optional provider-health observations are evaluated in
|
|
504
|
+
* configured catalog order so every consumer exposes the same selectable set.
|
|
505
|
+
*/
|
|
506
|
+
export function resolveWorkspaceModelSelection(
|
|
507
|
+
input: WorkspaceModelSelectionInput,
|
|
508
|
+
): WorkspaceModelSelection[] {
|
|
509
|
+
const codexSettings = input.settings.codexSubscriptionEnabled
|
|
510
|
+
? withCodexCatalogProvider(input.settings)
|
|
511
|
+
: input.settings;
|
|
512
|
+
const xaiSettings = input.settings.supergrokSubscriptionEnabled
|
|
513
|
+
? withXaiSubscriptionCatalogProvider(codexSettings)
|
|
514
|
+
: codexSettings;
|
|
515
|
+
const catalogSettings = withWorkspaceOpenRouterCatalogProvider(
|
|
516
|
+
withWorkspaceGatewayCatalogProvider(xaiSettings, input.workspaceGatewayCustomModels ?? []),
|
|
517
|
+
input.workspaceOpenRouterCustomModels ?? [],
|
|
518
|
+
);
|
|
519
|
+
const providers = new Map(
|
|
520
|
+
configuredProviders(catalogSettings).map((provider) => [provider.id, provider]),
|
|
521
|
+
);
|
|
522
|
+
const requestedNowMs = input.now?.getTime();
|
|
523
|
+
const nowMs =
|
|
524
|
+
typeof requestedNowMs === "number" && Number.isFinite(requestedNowMs)
|
|
525
|
+
? requestedNowMs
|
|
526
|
+
: Date.now();
|
|
527
|
+
const maxAgeMs =
|
|
528
|
+
typeof input.credentialReadinessMaxAgeMs === "number" &&
|
|
529
|
+
Number.isFinite(input.credentialReadinessMaxAgeMs) &&
|
|
530
|
+
input.credentialReadinessMaxAgeMs >= 0
|
|
531
|
+
? input.credentialReadinessMaxAgeMs
|
|
532
|
+
: MODEL_CREDENTIAL_READINESS_OBSERVATION_MAX_AGE_MS;
|
|
533
|
+
|
|
534
|
+
return configuredModels(catalogSettings).map((model) => {
|
|
535
|
+
const provider = providers.get(model.providerId);
|
|
536
|
+
const policyAllowed = evaluateWorkspaceModelPolicy(input.policy, {
|
|
537
|
+
providerId: model.providerId,
|
|
538
|
+
modelId: model.id,
|
|
539
|
+
}).allowed;
|
|
540
|
+
const credentialReadiness = credentialReadinessFor({
|
|
541
|
+
model,
|
|
542
|
+
provider,
|
|
543
|
+
codexSubscriptionActive: input.codexSubscriptionActive,
|
|
544
|
+
xaiSubscriptionActive: input.xaiSubscriptionActive === true,
|
|
545
|
+
workspaceGatewayConnectionActive: input.workspaceGatewayConnectionActive === true,
|
|
546
|
+
workspaceOpenRouterConnectionActive: input.workspaceOpenRouterConnectionActive === true,
|
|
547
|
+
observation: input.credentialReadinessObservations?.[model.definitionVersion],
|
|
548
|
+
nowMs,
|
|
549
|
+
maxAgeMs,
|
|
550
|
+
});
|
|
551
|
+
return {
|
|
552
|
+
model,
|
|
553
|
+
credentialReadiness,
|
|
554
|
+
policyAllowed,
|
|
555
|
+
availability: availabilityFor({
|
|
556
|
+
model,
|
|
557
|
+
credentialReadiness,
|
|
558
|
+
policyAllowed,
|
|
559
|
+
observation: input.observations?.[model.definitionVersion],
|
|
560
|
+
nowMs,
|
|
561
|
+
maxAgeMs,
|
|
562
|
+
}),
|
|
563
|
+
};
|
|
564
|
+
});
|
|
565
|
+
}
|
package/src/sandbox/fleet.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// apps/api/src/sandbox/fleet.ts — the FLEET service backing the fleet MCP tools
|
|
2
2
|
// (M7): list / attach / swap / run_on / provision over the heterogeneous fleet
|
|
3
|
-
// (the session's
|
|
3
|
+
// (the session's managed group box + the workspace's enrolled selfhosted machines).
|
|
4
4
|
//
|
|
5
5
|
// Each operation is workspace-scoped (the caller's grant) and, for the
|
|
6
6
|
// session-pointer mutations (attach/swap), session-scoped (the worker-signed
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// single op WITHOUT touching the active pointer.
|
|
13
13
|
|
|
14
14
|
import type { Settings } from "@opengeni/config";
|
|
15
|
+
import type { SandboxBackend } from "@opengeni/contracts";
|
|
15
16
|
import {
|
|
16
17
|
authorizePersonalMachineForAttempt,
|
|
17
18
|
getEnrollment,
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
type SelfhostedOperationResourcePolicy,
|
|
46
47
|
} from "@opengeni/runtime/sandbox";
|
|
47
48
|
import { relayConfigFromSettings } from "./routing";
|
|
49
|
+
import { managedSessionGroupBackend } from "./runtime-settings";
|
|
48
50
|
|
|
49
51
|
export type FleetServices = {
|
|
50
52
|
db: Database;
|
|
@@ -74,8 +76,8 @@ export type FleetContext = {
|
|
|
74
76
|
/** The calling session (the pointer the attach/swap mutates + whose group box
|
|
75
77
|
* is the default fleet member). */
|
|
76
78
|
sessionId: string;
|
|
77
|
-
/** The session's
|
|
78
|
-
sessionBackend:
|
|
79
|
+
/** The session's durable home-compute policy. */
|
|
80
|
+
sessionBackend: SandboxBackend;
|
|
79
81
|
/** The session's own group sandbox id (the lease group). */
|
|
80
82
|
sessionGroupId: string;
|
|
81
83
|
};
|
|
@@ -264,8 +266,9 @@ async function probeEnrollment(
|
|
|
264
266
|
* List the fleet: the session's own group box when it has one (a synthetic
|
|
265
267
|
* entry) + the workspace's first-class selfhosted sandboxes (each probed for
|
|
266
268
|
* liveness), each with an `active` marker derived from the session's active
|
|
267
|
-
* pointer. A backend:none session
|
|
268
|
-
*
|
|
269
|
+
* pointer. A backend:none session and a machine-home session on a deployment
|
|
270
|
+
* without a managed provider have no synthetic group entry; a null pointer then
|
|
271
|
+
* means no compute is attached.
|
|
269
272
|
*/
|
|
270
273
|
export async function listFleet(
|
|
271
274
|
services: FleetServices,
|
|
@@ -285,8 +288,12 @@ export async function listFleet(
|
|
|
285
288
|
};
|
|
286
289
|
|
|
287
290
|
const entries: FleetSandboxEntry[] = [];
|
|
291
|
+
const groupBackend = managedSessionGroupBackend(
|
|
292
|
+
services.settings.sandboxBackend,
|
|
293
|
+
ctx.sessionBackend,
|
|
294
|
+
);
|
|
288
295
|
|
|
289
|
-
if (
|
|
296
|
+
if (groupBackend) {
|
|
290
297
|
// The session's own group box (the default/home sandbox; null active pointer ==
|
|
291
298
|
// this box). A session/group row is not provider existence. Online requires a
|
|
292
299
|
// warm lease, observed provider existence, and verified workspace readiness.
|
|
@@ -317,17 +324,10 @@ export async function listFleet(
|
|
|
317
324
|
? "unavailable"
|
|
318
325
|
: groupRecovering
|
|
319
326
|
? "recovering"
|
|
320
|
-
:
|
|
321
|
-
? "unavailable"
|
|
322
|
-
: "wakeable";
|
|
327
|
+
: "wakeable";
|
|
323
328
|
entries.push({
|
|
324
329
|
id: ctx.sessionGroupId,
|
|
325
|
-
kind:
|
|
326
|
-
ctx.sessionBackend === "selfhosted"
|
|
327
|
-
? "selfhosted"
|
|
328
|
-
: ctx.sessionBackend === "opensandbox"
|
|
329
|
-
? "opensandbox"
|
|
330
|
-
: "modal",
|
|
330
|
+
kind: groupBackend === "opensandbox" ? "opensandbox" : "modal",
|
|
331
331
|
name: "session sandbox",
|
|
332
332
|
liveness: groupOnline ? "online" : groupRecovering ? "reconnecting" : "offline",
|
|
333
333
|
active: groupActive,
|
|
@@ -467,10 +467,13 @@ async function resolveTarget(
|
|
|
467
467
|
> {
|
|
468
468
|
// The session's own group box → the default pointer (null).
|
|
469
469
|
if (target === ctx.sessionGroupId || target === "session" || target === "default") {
|
|
470
|
-
if (ctx.sessionBackend
|
|
470
|
+
if (!managedSessionGroupBackend(services.settings.sandboxBackend, ctx.sessionBackend)) {
|
|
471
471
|
return {
|
|
472
472
|
ok: false,
|
|
473
|
-
reason:
|
|
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",
|
|
474
477
|
code: "unsupported_backend_context",
|
|
475
478
|
};
|
|
476
479
|
}
|