@opengeni/api-router 0.23.1 → 0.30.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/app.d.ts +3 -2
- package/dist/app.js +5 -3
- package/dist/auth/canonical-human-session-admission.d.ts +27 -0
- package/dist/browser-auth-broker.d.ts +20 -0
- package/dist/browser-controller-authority.d.ts +61 -0
- package/dist/browser-network-route.d.ts +8 -0
- package/dist/browser-state-authority.d.ts +27 -0
- package/dist/{chunk-T4T2PGU4.js → chunk-2PQMSRCB.js} +37891 -17463
- package/dist/chunk-2PQMSRCB.js.map +1 -0
- package/dist/codemode.d.ts +23 -0
- package/dist/controller-data-plane.d.ts +12 -0
- package/dist/editable-artifact-live-hints.d.ts +11 -0
- package/dist/editable-artifact-native-kernel.d.ts +37 -0
- package/dist/editable-artifact-office-import.d.ts +22 -0
- package/dist/editable-artifact-production.d.ts +29 -0
- package/dist/editable-artifact-websocket.d.ts +49 -0
- package/dist/editable-artifact-workspace-files.d.ts +23 -0
- package/dist/github-browser-flow.d.ts +6 -0
- package/dist/http/auth.d.ts +1 -1
- package/dist/http/cors.d.ts +1 -0
- package/dist/http/sse.d.ts +18 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1796 -22
- package/dist/index.js.map +1 -1
- package/dist/integrations/api-integrations.d.ts +24 -0
- package/dist/integrations/atlassian.d.ts +176 -0
- package/dist/integrations/fiken.d.ts +233 -0
- package/dist/integrations/github-skill-source.d.ts +5 -0
- package/dist/integrations/google-drive.d.ts +85 -0
- package/dist/integrations/oauth-client.d.ts +22 -1
- package/dist/integrations/provider-oauth.d.ts +17 -0
- package/dist/integrations/slack-bot.d.ts +175 -1
- package/dist/integrations/slack-interactions.d.ts +24 -6
- package/dist/integrations/social-api.d.ts +2 -1
- package/dist/interaction-holder-heartbeat.d.ts +17 -0
- package/dist/interaction-metrics.d.ts +11 -0
- package/dist/mcp/editable-artifact-query-schema.d.ts +4 -0
- package/dist/mcp/editable-artifacts.d.ts +13 -0
- package/dist/mcp/scheduled-task-view.d.ts +168 -0
- package/dist/mcp/server.d.ts +16 -4
- package/dist/memory-slack-delivery.d.ts +9 -0
- package/dist/model-catalog.d.ts +1 -0
- package/dist/routes/api-integrations.d.ts +8 -0
- package/dist/routes/browser-identities.d.ts +5 -0
- package/dist/routes/browser-sessions.d.ts +6 -0
- package/dist/routes/canonical-human-identities.d.ts +3 -0
- package/dist/routes/channels.d.ts +3 -0
- package/dist/routes/company-profile.d.ts +3 -0
- package/dist/routes/computer-sessions.d.ts +6 -0
- package/dist/routes/editable-artifacts.d.ts +44 -0
- package/dist/routes/integration-facets.d.ts +3 -0
- package/dist/routes/interaction-resources.d.ts +5 -0
- package/dist/routes/memory-slack-publications.d.ts +6 -0
- package/dist/routes/organization-memberships.d.ts +3 -0
- package/dist/routes/plugins.d.ts +8 -0
- package/dist/routes/sessions.d.ts +18 -2
- package/dist/routes/skills.d.ts +6 -0
- package/dist/routes/slack-task-policy.d.ts +3 -0
- package/dist/routes/supergrok.d.ts +3 -0
- package/dist/routes/video-generation.d.ts +3 -0
- package/dist/sandbox/channel-a.d.ts +62 -2
- package/dist/sandbox/machines.d.ts +2 -2
- package/dist/sandbox/metrics-ingestion.d.ts +6 -1
- package/dist/sandbox/viewer.d.ts +7 -3
- package/dist/slack-reaction-files.d.ts +27 -0
- package/dist/temporal-schedule-cleanup.d.ts +26 -0
- package/dist/transcription/providers/xai-subscription.d.ts +8 -0
- package/dist/xai-realtime.d.ts +26 -0
- package/dist/xai-subscription-auth.d.ts +23 -0
- package/package.json +20 -14
- package/src/app.ts +604 -64
- package/src/auth/canonical-human-session-admission.ts +116 -0
- package/src/auth/managed-auth.ts +101 -0
- package/src/browser-auth-broker.ts +155 -0
- package/src/browser-controller-authority.ts +186 -0
- package/src/browser-network-route.ts +34 -0
- package/src/browser-state-authority.ts +236 -0
- package/src/codemode.ts +186 -0
- package/src/controller-data-plane.ts +35 -0
- package/src/editable-artifact-live-hints.ts +64 -0
- package/src/editable-artifact-native-kernel.ts +659 -0
- package/src/editable-artifact-office-import.ts +230 -0
- package/src/editable-artifact-production.ts +419 -0
- package/src/editable-artifact-websocket.ts +311 -0
- package/src/editable-artifact-workspace-files.ts +186 -0
- package/src/github-browser-flow.ts +35 -6
- package/src/http/auth.ts +29 -9
- package/src/http/cors.ts +3 -0
- package/src/http/sse.ts +229 -5
- package/src/index.ts +119 -20
- package/src/integrations/api-integrations.ts +365 -0
- package/src/integrations/atlassian.ts +1621 -0
- package/src/integrations/fiken.ts +1230 -0
- package/src/integrations/github-skill-source.ts +142 -0
- package/src/integrations/google-drive.ts +1005 -64
- package/src/integrations/oauth-client.ts +123 -33
- package/src/integrations/provider-oauth.ts +756 -0
- package/src/integrations/slack-bot.ts +669 -11
- package/src/integrations/slack-interactions.ts +2063 -93
- package/src/integrations/social-api.ts +11 -0
- package/src/interaction-holder-heartbeat.ts +95 -0
- package/src/interaction-metrics.ts +159 -0
- package/src/mcp/documents.ts +146 -6
- package/src/mcp/editable-artifact-query-schema.ts +236 -0
- package/src/mcp/editable-artifacts.ts +448 -0
- package/src/mcp/scheduled-task-view.ts +34 -0
- package/src/mcp/server.ts +1756 -355
- package/src/memory-slack-delivery.ts +209 -0
- package/src/model-catalog.ts +19 -6
- package/src/routes/api-integrations.ts +410 -0
- package/src/routes/browser-identities.ts +160 -0
- package/src/routes/browser-sessions.ts +4482 -0
- package/src/routes/canonical-human-identities.ts +156 -0
- package/src/routes/channels.ts +90 -0
- package/src/routes/company-profile.ts +255 -0
- package/src/routes/computer-sessions.ts +1602 -0
- package/src/routes/connections.ts +421 -99
- package/src/routes/documents.ts +22 -3
- package/src/routes/editable-artifacts.ts +1159 -0
- package/src/routes/files.ts +106 -4
- package/src/routes/github.ts +18 -2
- package/src/routes/install.ts +7 -1
- package/src/routes/integration-facets.ts +258 -0
- package/src/routes/interaction-resources.ts +595 -0
- package/src/routes/memory-slack-publications.ts +216 -0
- package/src/routes/organization-memberships.ts +53 -0
- package/src/routes/packs.ts +437 -7
- package/src/routes/plugins.ts +752 -0
- package/src/routes/rigs.ts +77 -20
- package/src/routes/scheduled-tasks.ts +67 -40
- package/src/routes/sessions.ts +830 -278
- package/src/routes/skills.ts +256 -0
- package/src/routes/slack-task-policy.ts +115 -0
- package/src/routes/supergrok.ts +717 -0
- package/src/routes/transcription-recordings.ts +9 -2
- package/src/routes/transcriptions.ts +2 -1
- package/src/routes/video-generation.ts +162 -0
- package/src/routes/workspaces.ts +68 -28
- package/src/sandbox/channel-a.ts +750 -116
- package/src/sandbox/machines.ts +5 -5
- package/src/sandbox/metrics-ingestion.ts +121 -3
- package/src/sandbox/rematerialize.ts +35 -47
- package/src/sandbox/viewer.ts +39 -12
- package/src/slack-reaction-files.ts +181 -0
- package/src/temporal-schedule-cleanup.ts +135 -0
- package/src/transcription/providers/xai-subscription.ts +110 -0
- package/src/transcription/service.ts +17 -2
- package/src/xai-realtime.ts +216 -0
- package/src/xai-subscription-auth.ts +132 -0
- package/dist/chunk-T4T2PGU4.js.map +0 -1
- package/dist/mcp/toolspace.d.ts +0 -71
- package/src/mcp/toolspace.ts +0 -1190
|
@@ -362,12 +362,18 @@ export function registerResumableTranscriptionRoutes(app: Hono, deps: ApiRouteDe
|
|
|
362
362
|
if (
|
|
363
363
|
!deps.objectStorage ||
|
|
364
364
|
!service ||
|
|
365
|
-
!(await service.available({
|
|
365
|
+
!(await service.available({
|
|
366
|
+
workspaceId: authority.workspaceId,
|
|
367
|
+
subjectId: authority.subjectId,
|
|
368
|
+
}))
|
|
366
369
|
) {
|
|
367
370
|
return c.json({ code: "unavailable" }, 503);
|
|
368
371
|
}
|
|
369
372
|
const selectedProvider = service.selectProvider
|
|
370
|
-
? await service.selectProvider({
|
|
373
|
+
? await service.selectProvider({
|
|
374
|
+
workspaceId: authority.workspaceId,
|
|
375
|
+
subjectId: authority.subjectId,
|
|
376
|
+
})
|
|
371
377
|
: "host";
|
|
372
378
|
if (!selectedProvider) return c.json({ code: "unavailable" }, 503);
|
|
373
379
|
attemptId = correlationId(c);
|
|
@@ -418,6 +424,7 @@ export function registerResumableTranscriptionRoutes(app: Hono, deps: ApiRouteDe
|
|
|
418
424
|
const result = await service.transcribe({
|
|
419
425
|
workspaceId: authority.workspaceId,
|
|
420
426
|
accountId: authority.accountId,
|
|
427
|
+
subjectId: authority.subjectId,
|
|
421
428
|
audio: stored.bytes,
|
|
422
429
|
mimeType: "audio/wav",
|
|
423
430
|
durationSeconds: claim.segment.durationMilliseconds / 1_000,
|
|
@@ -18,7 +18,7 @@ export function registerTranscriptionRoutes(app: Hono, deps: ApiRouteDeps): void
|
|
|
18
18
|
return c.json({ code: "policy_blocked" }, 403);
|
|
19
19
|
}
|
|
20
20
|
const service = deps.transcription;
|
|
21
|
-
if (!service || !(await service.available())) {
|
|
21
|
+
if (!service || !(await service.available({ workspaceId, subjectId: grant.subjectId }))) {
|
|
22
22
|
return c.json({ code: "unavailable" }, 503);
|
|
23
23
|
}
|
|
24
24
|
try {
|
|
@@ -26,6 +26,7 @@ export function registerTranscriptionRoutes(app: Hono, deps: ApiRouteDeps): void
|
|
|
26
26
|
const result = await service.transcribe({
|
|
27
27
|
workspaceId,
|
|
28
28
|
accountId: grant.accountId,
|
|
29
|
+
subjectId: grant.subjectId,
|
|
29
30
|
audio: body.audio,
|
|
30
31
|
mimeType: body.mimeType,
|
|
31
32
|
durationSeconds: body.durationSeconds,
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UpdateVideoGenerationPolicyRequest,
|
|
3
|
+
VideoGenerationOperationSummary,
|
|
4
|
+
VideoGenerationPolicy,
|
|
5
|
+
WorkspaceVideoGenerationSettings,
|
|
6
|
+
} from "@opengeni/contracts";
|
|
7
|
+
import {
|
|
8
|
+
getVideoGenerationOperationSummary,
|
|
9
|
+
getWorkspaceVercelAiGatewayConnectionMetadata,
|
|
10
|
+
getWorkspaceVideoGenerationPolicy,
|
|
11
|
+
updateWorkspaceVideoGenerationPolicy,
|
|
12
|
+
VideoGenerationConflictError,
|
|
13
|
+
workspaceXaiSubscriptionActive,
|
|
14
|
+
} from "@opengeni/db";
|
|
15
|
+
import {
|
|
16
|
+
requireAccessGrant,
|
|
17
|
+
VIDEO_GENERATION_MODEL_CATALOG,
|
|
18
|
+
videoGenerationModelSupportsFundingSource,
|
|
19
|
+
videoGenerationCapabilitiesForPolicy,
|
|
20
|
+
type ApiRouteDeps,
|
|
21
|
+
} from "@opengeni/core";
|
|
22
|
+
import type { Hono } from "hono";
|
|
23
|
+
import { HTTPException } from "hono/http-exception";
|
|
24
|
+
|
|
25
|
+
export function registerVideoGenerationRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
26
|
+
app.get("/v1/workspaces/:workspaceId/video-generation", async (c) => {
|
|
27
|
+
const workspaceId = c.req.param("workspaceId");
|
|
28
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
29
|
+
const [policy, connection, supergrokConfigured] = await Promise.all([
|
|
30
|
+
getWorkspaceVideoGenerationPolicy(deps.db, workspaceId),
|
|
31
|
+
getWorkspaceVercelAiGatewayConnectionMetadata(deps.db, workspaceId),
|
|
32
|
+
workspaceXaiSubscriptionActive(deps.db, deps.settings, workspaceId, grant.subjectId),
|
|
33
|
+
]);
|
|
34
|
+
const fundingOptions = videoGenerationFundingOptions({
|
|
35
|
+
managedConfigured: managedVideoGenerationConfigured(deps),
|
|
36
|
+
workspaceGatewayConfigured: connection !== null,
|
|
37
|
+
supergrokConfigured,
|
|
38
|
+
});
|
|
39
|
+
const selectedFunding = fundingOptions.find((option) => option.source === policy.fundingSource);
|
|
40
|
+
const capabilities =
|
|
41
|
+
selectedFunding?.available && policy.defaultModelId && policy.enabledModelIds.length > 0
|
|
42
|
+
? videoGenerationCapabilitiesForPolicy({
|
|
43
|
+
policy,
|
|
44
|
+
credentialVersion:
|
|
45
|
+
policy.fundingSource === "workspace_gateway" ? (connection?.version ?? 0) : 1,
|
|
46
|
+
})
|
|
47
|
+
: null;
|
|
48
|
+
return c.json(
|
|
49
|
+
WorkspaceVideoGenerationSettings.parse({
|
|
50
|
+
schemaVersion: 1,
|
|
51
|
+
policy,
|
|
52
|
+
fundingOptions,
|
|
53
|
+
availableModels: VIDEO_GENERATION_MODEL_CATALOG,
|
|
54
|
+
capabilities,
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
app.put("/v1/workspaces/:workspaceId/video-generation/policy", async (c) => {
|
|
60
|
+
const workspaceId = c.req.param("workspaceId");
|
|
61
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
62
|
+
const payload = UpdateVideoGenerationPolicyRequest.parse(await c.req.json());
|
|
63
|
+
const [connection, supergrokConfigured] = await Promise.all([
|
|
64
|
+
getWorkspaceVercelAiGatewayConnectionMetadata(deps.db, workspaceId),
|
|
65
|
+
workspaceXaiSubscriptionActive(deps.db, deps.settings, workspaceId, grant.subjectId),
|
|
66
|
+
]);
|
|
67
|
+
const fundingOptions = videoGenerationFundingOptions({
|
|
68
|
+
managedConfigured: managedVideoGenerationConfigured(deps),
|
|
69
|
+
workspaceGatewayConfigured: connection !== null,
|
|
70
|
+
supergrokConfigured,
|
|
71
|
+
});
|
|
72
|
+
const selectedFunding = fundingOptions.find(
|
|
73
|
+
(option) => option.source === payload.fundingSource,
|
|
74
|
+
);
|
|
75
|
+
if (payload.enabledModelIds.length > 0 && !selectedFunding?.available) {
|
|
76
|
+
throw new HTTPException(422, {
|
|
77
|
+
message: selectedFunding?.unavailableReason ?? "Video generation funding is unavailable",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (
|
|
81
|
+
payload.enabledModelIds.some(
|
|
82
|
+
(modelId) => !videoGenerationModelSupportsFundingSource(modelId, payload.fundingSource),
|
|
83
|
+
)
|
|
84
|
+
) {
|
|
85
|
+
throw new HTTPException(422, {
|
|
86
|
+
message: "The selected video model is unavailable for this funding source",
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const policy = await updateWorkspaceVideoGenerationPolicy(deps.db, {
|
|
91
|
+
accountId: grant.accountId,
|
|
92
|
+
workspaceId,
|
|
93
|
+
subjectId: grant.subjectId,
|
|
94
|
+
...payload,
|
|
95
|
+
});
|
|
96
|
+
return c.json(VideoGenerationPolicy.parse(policy));
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error instanceof VideoGenerationConflictError) {
|
|
99
|
+
throw new HTTPException(409, { message: error.message });
|
|
100
|
+
}
|
|
101
|
+
if (error instanceof Error && error.message.startsWith("Unknown video generation model:")) {
|
|
102
|
+
throw new HTTPException(422, { message: error.message });
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
app.get("/v1/workspaces/:workspaceId/video-generation/operations/:operationId", async (c) => {
|
|
109
|
+
const workspaceId = c.req.param("workspaceId");
|
|
110
|
+
await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
111
|
+
const summary = await getVideoGenerationOperationSummary(
|
|
112
|
+
deps.db,
|
|
113
|
+
workspaceId,
|
|
114
|
+
c.req.param("operationId"),
|
|
115
|
+
);
|
|
116
|
+
if (!summary)
|
|
117
|
+
throw new HTTPException(404, {
|
|
118
|
+
message: "video generation operation not found",
|
|
119
|
+
});
|
|
120
|
+
return c.json(VideoGenerationOperationSummary.parse(summary));
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function managedVideoGenerationConfigured(deps: ApiRouteDeps): boolean {
|
|
125
|
+
return Boolean(deps.settings.vercelAiGatewayApiKey && deps.settings.environmentsEncryptionKey);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function videoGenerationFundingOptions(input: {
|
|
129
|
+
managedConfigured: boolean;
|
|
130
|
+
workspaceGatewayConfigured: boolean;
|
|
131
|
+
supergrokConfigured: boolean;
|
|
132
|
+
}) {
|
|
133
|
+
return [
|
|
134
|
+
{
|
|
135
|
+
source: "opengeni_credits" as const,
|
|
136
|
+
label: "OpenGeni",
|
|
137
|
+
description: "Uses OpenGeni credits through the managed Gateway route.",
|
|
138
|
+
available: input.managedConfigured,
|
|
139
|
+
unavailableReason: input.managedConfigured
|
|
140
|
+
? null
|
|
141
|
+
: "OpenGeni-managed video generation is not configured.",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
source: "supergrok_subscription" as const,
|
|
145
|
+
label: "SuperGrok",
|
|
146
|
+
description: "Uses your connected SuperGrok subscription.",
|
|
147
|
+
available: input.supergrokConfigured,
|
|
148
|
+
unavailableReason: input.supergrokConfigured
|
|
149
|
+
? null
|
|
150
|
+
: "Connect an eligible SuperGrok account first.",
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
source: "workspace_gateway" as const,
|
|
154
|
+
label: "Your Gateway",
|
|
155
|
+
description: "Uses your workspace Vercel AI Gateway key.",
|
|
156
|
+
available: input.workspaceGatewayConfigured,
|
|
157
|
+
unavailableReason: input.workspaceGatewayConfigured
|
|
158
|
+
? null
|
|
159
|
+
: "Connect a workspace Vercel AI Gateway key first.",
|
|
160
|
+
},
|
|
161
|
+
];
|
|
162
|
+
}
|
package/src/routes/workspaces.ts
CHANGED
|
@@ -19,14 +19,11 @@ import {
|
|
|
19
19
|
} from "@opengeni/contracts";
|
|
20
20
|
import {
|
|
21
21
|
allWorkspacePermissions,
|
|
22
|
-
countActiveSessionsForWorkspace,
|
|
23
|
-
countWorkspacesForAccount,
|
|
24
22
|
createWorkspace,
|
|
25
|
-
|
|
23
|
+
deleteWorkspaceIfQuiescent,
|
|
26
24
|
getManagedUserByEmail,
|
|
27
25
|
getWorkspaceModelPolicy,
|
|
28
26
|
grantWorkspaceAccess,
|
|
29
|
-
listScheduledTasks,
|
|
30
27
|
listWorkspaceMembers,
|
|
31
28
|
listWorkspaceControlEvents,
|
|
32
29
|
listWorkspacesForSubject,
|
|
@@ -38,6 +35,7 @@ import {
|
|
|
38
35
|
updateWorkspaceSettings,
|
|
39
36
|
upsertWorkspaceModelPolicy,
|
|
40
37
|
workspaceCodexSubscriptionActive,
|
|
38
|
+
workspaceXaiSubscriptionActive,
|
|
41
39
|
workspaceVercelAiGatewayConnectionActive,
|
|
42
40
|
} from "@opengeni/db";
|
|
43
41
|
import { boundWorkspaceControlHttpPage } from "@opengeni/events";
|
|
@@ -47,7 +45,6 @@ import { hasPermission, requireAccessContext, requireAccessGrant } from "@openge
|
|
|
47
45
|
import { requireLimit } from "@opengeni/core";
|
|
48
46
|
import type { ApiRouteDeps } from "@opengeni/core";
|
|
49
47
|
import {
|
|
50
|
-
assertWorkspaceDeletable,
|
|
51
48
|
assertWorkspaceMemberRemovable,
|
|
52
49
|
controlHumanWorkspace,
|
|
53
50
|
resolveMemberSubjectId,
|
|
@@ -55,9 +52,11 @@ import {
|
|
|
55
52
|
import { boundedLimit } from "../http/common";
|
|
56
53
|
import { sseWorkspaceControlStream } from "../http/sse";
|
|
57
54
|
import { buildWorkspaceModelCatalog } from "../model-catalog";
|
|
55
|
+
import { processTemporalScheduleCleanupClaims } from "../temporal-schedule-cleanup";
|
|
58
56
|
import {
|
|
59
57
|
AI_GATEWAY_REALTIME_MODELS,
|
|
60
58
|
CODEX_REALTIME_MODEL_ID,
|
|
59
|
+
SUPERGROK_REALTIME_MODEL_ID,
|
|
61
60
|
canonicalizeConfiguredModelId,
|
|
62
61
|
type Settings,
|
|
63
62
|
} from "@opengeni/config";
|
|
@@ -168,10 +167,16 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
168
167
|
// so bearer/federated definitions intentionally fail closed as not ready.
|
|
169
168
|
app.get("/v1/workspaces/:workspaceId/model-catalog", async (c) => {
|
|
170
169
|
const workspaceId = c.req.param("workspaceId");
|
|
171
|
-
await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
172
|
-
const [
|
|
170
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
171
|
+
const [
|
|
172
|
+
policy,
|
|
173
|
+
codexSubscriptionActive,
|
|
174
|
+
xaiSubscriptionActive,
|
|
175
|
+
workspaceGatewayConnectionActive,
|
|
176
|
+
] = await Promise.all([
|
|
173
177
|
getWorkspaceModelPolicy(deps.db, workspaceId),
|
|
174
178
|
workspaceCodexSubscriptionActive(deps.db, deps.settings, workspaceId),
|
|
179
|
+
workspaceXaiSubscriptionActive(deps.db, deps.settings, workspaceId, grant.subjectId),
|
|
175
180
|
workspaceVercelAiGatewayConnectionActive(deps.db, workspaceId),
|
|
176
181
|
]);
|
|
177
182
|
c.header("cache-control", "private, no-store");
|
|
@@ -181,6 +186,7 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
181
186
|
settings: deps.settings,
|
|
182
187
|
policy,
|
|
183
188
|
codexSubscriptionActive,
|
|
189
|
+
xaiSubscriptionActive,
|
|
184
190
|
workspaceGatewayConnectionActive,
|
|
185
191
|
}),
|
|
186
192
|
),
|
|
@@ -189,9 +195,10 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
189
195
|
|
|
190
196
|
app.get("/v1/workspaces/:workspaceId/realtime-model-catalog", async (c) => {
|
|
191
197
|
const workspaceId = c.req.param("workspaceId");
|
|
192
|
-
await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
193
|
-
const [codexConnected, workspaceGatewayConnected] = await Promise.all([
|
|
198
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
199
|
+
const [codexConnected, supergrokConnected, workspaceGatewayConnected] = await Promise.all([
|
|
194
200
|
workspaceCodexSubscriptionActive(deps.db, deps.settings, workspaceId),
|
|
201
|
+
workspaceXaiSubscriptionActive(deps.db, deps.settings, workspaceId, grant.subjectId),
|
|
195
202
|
workspaceVercelAiGatewayConnectionActive(deps.db, workspaceId),
|
|
196
203
|
]);
|
|
197
204
|
const availability = (
|
|
@@ -223,6 +230,14 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
223
230
|
...availability(codexConnected, "Connect Codex to use this voice model"),
|
|
224
231
|
recommended: false,
|
|
225
232
|
},
|
|
233
|
+
{
|
|
234
|
+
id: SUPERGROK_REALTIME_MODEL_ID,
|
|
235
|
+
label: "Grok Voice Think Fast 2.0",
|
|
236
|
+
provider: "Connected SuperGrok" as const,
|
|
237
|
+
description: "Direct SuperGrok speech-to-speech",
|
|
238
|
+
...availability(supergrokConnected, "Connect SuperGrok to use this voice model"),
|
|
239
|
+
recommended: false,
|
|
240
|
+
},
|
|
226
241
|
...gatewayModels.map((model) => ({
|
|
227
242
|
id: model.workspaceModelId,
|
|
228
243
|
label: model.label,
|
|
@@ -330,26 +345,51 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
330
345
|
app.delete("/v1/workspaces/:workspaceId", async (c) => {
|
|
331
346
|
const workspaceId = c.req.param("workspaceId");
|
|
332
347
|
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
)
|
|
348
|
+
// The DB transaction locks the account/workspace and every existing
|
|
349
|
+
// session/lease before checking runtime quiescence, then returns the exact
|
|
350
|
+
// external schedules removed by the cascade. A racing cold->warm transition
|
|
351
|
+
// can therefore never erase the only provider/capture ownership receipt.
|
|
352
|
+
const deleted = await deleteWorkspaceIfQuiescent(deps.db, {
|
|
353
|
+
accountId: grant.accountId,
|
|
354
|
+
workspaceId,
|
|
355
|
+
});
|
|
356
|
+
if (deleted.status === "not_found") {
|
|
357
|
+
throw new HTTPException(404, { message: "workspace not found" });
|
|
358
|
+
}
|
|
359
|
+
if (deleted.status === "only_workspace") {
|
|
360
|
+
throw new HTTPException(409, { message: "cannot delete the account's only workspace" });
|
|
361
|
+
}
|
|
362
|
+
if (deleted.status === "active_sessions") {
|
|
363
|
+
throw new HTTPException(409, {
|
|
364
|
+
message: "stop the workspace's running sessions before deleting it",
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
if (deleted.status === "active_video_generations") {
|
|
368
|
+
throw new HTTPException(409, {
|
|
369
|
+
message: "wait for the workspace's active video generations to finish before deleting it",
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
if (deleted.status === "live_sandboxes") {
|
|
373
|
+
throw new HTTPException(409, {
|
|
374
|
+
message: "wait for the workspace's active sandboxes to finish draining before deleting it",
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
if (deleted.status !== "deleted") {
|
|
378
|
+
throw new Error(`Unhandled workspace deletion outcome: ${deleted.status}`);
|
|
379
|
+
}
|
|
380
|
+
// The cleanup claims were inserted in the same transaction as the cascade.
|
|
381
|
+
// Try them immediately; failures are released to the replica-safe outbox
|
|
382
|
+
// pump, so a process crash or Temporal outage cannot orphan the schedules.
|
|
383
|
+
await processTemporalScheduleCleanupClaims(
|
|
384
|
+
{
|
|
385
|
+
db: deps.db,
|
|
386
|
+
deleteSchedule: async (temporalScheduleId) => {
|
|
387
|
+
await deps.workflowClient.deleteScheduledTaskSchedule({ temporalScheduleId });
|
|
388
|
+
},
|
|
389
|
+
...(deps.observability ? { observability: deps.observability } : {}),
|
|
390
|
+
},
|
|
391
|
+
deleted.temporalScheduleCleanups,
|
|
351
392
|
);
|
|
352
|
-
await deleteWorkspace(deps.db, workspaceId);
|
|
353
393
|
return c.body(null, 204);
|
|
354
394
|
});
|
|
355
395
|
|