@opengeni/api-router 0.23.1 → 0.26.1
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 +2 -2
- package/dist/app.js +1 -1
- package/dist/browser-controller-authority.d.ts +43 -0
- package/dist/browser-state-authority.d.ts +27 -0
- package/dist/{chunk-T4T2PGU4.js → chunk-JIKNR5YL.js} +26181 -14408
- package/dist/chunk-JIKNR5YL.js.map +1 -0
- package/dist/codemode.d.ts +23 -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/cors.d.ts +1 -0
- 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/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 +19 -0
- package/dist/integrations/slack-bot.d.ts +4 -0
- package/dist/integrations/slack-interactions.d.ts +8 -2
- package/dist/integrations/social-api.d.ts +2 -1
- 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 +14 -3
- package/dist/memory-slack-delivery.d.ts +9 -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/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-features.d.ts +3 -0
- package/dist/routes/memory-slack-publications.d.ts +6 -0
- package/dist/routes/plugins.d.ts +8 -0
- package/dist/routes/sessions.d.ts +17 -2
- package/dist/routes/skills.d.ts +6 -0
- package/dist/routes/video-generation.d.ts +3 -0
- package/dist/sandbox/channel-a.d.ts +55 -2
- package/dist/sandbox/metrics-ingestion.d.ts +6 -1
- package/dist/sandbox/viewer.d.ts +4 -2
- package/dist/temporal-schedule-cleanup.d.ts +26 -0
- package/package.json +19 -14
- package/src/app.ts +274 -37
- package/src/browser-controller-authority.ts +137 -0
- package/src/browser-state-authority.ts +236 -0
- package/src/codemode.ts +186 -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 +2 -0
- package/src/http/cors.ts +3 -0
- package/src/index.ts +119 -20
- package/src/integrations/api-integrations.ts +350 -0
- package/src/integrations/atlassian.ts +1621 -0
- package/src/integrations/github-skill-source.ts +142 -0
- package/src/integrations/google-drive.ts +1000 -64
- package/src/integrations/oauth-client.ts +123 -33
- package/src/integrations/provider-oauth.ts +777 -0
- package/src/integrations/slack-bot.ts +31 -2
- package/src/integrations/slack-interactions.ts +487 -27
- package/src/integrations/social-api.ts +11 -0
- package/src/mcp/documents.ts +32 -1
- 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 +923 -145
- package/src/memory-slack-delivery.ts +209 -0
- package/src/routes/api-integrations.ts +407 -0
- package/src/routes/browser-identities.ts +136 -0
- package/src/routes/browser-sessions.ts +2543 -0
- package/src/routes/company-profile.ts +255 -0
- package/src/routes/computer-sessions.ts +1247 -0
- package/src/routes/connections.ts +284 -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-features.ts +258 -0
- package/src/routes/memory-slack-publications.ts +216 -0
- package/src/routes/packs.ts +437 -7
- package/src/routes/plugins.ts +751 -0
- package/src/routes/rigs.ts +77 -20
- package/src/routes/scheduled-tasks.ts +67 -40
- package/src/routes/sessions.ts +421 -245
- package/src/routes/skills.ts +174 -0
- package/src/routes/video-generation.ts +132 -0
- package/src/routes/workspaces.ts +46 -24
- package/src/sandbox/channel-a.ts +723 -116
- package/src/sandbox/metrics-ingestion.ts +121 -3
- package/src/sandbox/rematerialize.ts +35 -47
- package/src/sandbox/viewer.ts +29 -9
- package/src/temporal-schedule-cleanup.ts +135 -0
- package/dist/chunk-T4T2PGU4.js.map +0 -1
- package/dist/mcp/toolspace.d.ts +0 -71
- package/src/mcp/toolspace.ts +0 -1190
package/src/app.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "@opengeni/config";
|
|
8
8
|
import {
|
|
9
9
|
ClientConfig,
|
|
10
|
+
CodemodeCallRequest,
|
|
10
11
|
ErrorEnvelope,
|
|
11
12
|
OPENGENI_API_CONTRACT_HEADER,
|
|
12
13
|
OPENGENI_API_CONTRACT_REVISION,
|
|
@@ -23,7 +24,17 @@ import {
|
|
|
23
24
|
indexDocumentNow,
|
|
24
25
|
type DocumentServices,
|
|
25
26
|
} from "@opengeni/documents";
|
|
26
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
CodemodeCallBudgetExceededError,
|
|
29
|
+
CodemodeOperationConflictError,
|
|
30
|
+
CodemodeOperationNotExecutableError,
|
|
31
|
+
CodemodePayloadTooLargeError,
|
|
32
|
+
CodemodeToolApprovalRequiredError,
|
|
33
|
+
CodemodeToolNotInCatalogError,
|
|
34
|
+
dbSql,
|
|
35
|
+
getWorkspace,
|
|
36
|
+
rlsContextForWorkspace,
|
|
37
|
+
} from "@opengeni/db";
|
|
27
38
|
import { createObservability } from "@opengeni/observability";
|
|
28
39
|
import { createObjectStorage } from "@opengeni/storage";
|
|
29
40
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
@@ -48,9 +59,16 @@ import { createManagedAuth } from "./auth/managed-auth";
|
|
|
48
59
|
import { createApiSandboxClient, makeResumeBoxById } from "./sandbox/access";
|
|
49
60
|
import { requireLimit } from "@opengeni/core";
|
|
50
61
|
import { buildOpenGeniMcpServer } from "./mcp/server";
|
|
51
|
-
import {
|
|
62
|
+
import {
|
|
63
|
+
CodemodeAuthorityError,
|
|
64
|
+
isCodemodeGrant,
|
|
65
|
+
readCodemodeOperation,
|
|
66
|
+
requireActiveCodemodeCatalog,
|
|
67
|
+
submitAndDispatchCodemodeCall,
|
|
68
|
+
} from "./codemode";
|
|
52
69
|
import { boundedMcpRequest, McpPayloadTooLargeError } from "@opengeni/runtime/mcp-network";
|
|
53
70
|
import { requireAccessKey } from "./http/auth";
|
|
71
|
+
import { allowedCorsOrigin } from "./http/cors";
|
|
54
72
|
import { registerCapabilityRoutes } from "./routes/capabilities";
|
|
55
73
|
import { registerCatalogAssetRoutes } from "./routes/catalog-assets";
|
|
56
74
|
import { registerCodexRoutes } from "./routes/codex";
|
|
@@ -58,29 +76,42 @@ import { registerConnectionRoutes } from "./routes/connections";
|
|
|
58
76
|
import { registerDocumentRoutes } from "./routes/documents";
|
|
59
77
|
import { registerEnrollmentRoutes } from "./routes/enrollments";
|
|
60
78
|
import { registerMachineRoutes } from "./routes/machines";
|
|
79
|
+
import { registerMemorySlackPublicationRoutes } from "./routes/memory-slack-publications";
|
|
61
80
|
import { registerEnvironmentRoutes } from "./routes/environments";
|
|
62
81
|
import { registerFileRoutes } from "./routes/files";
|
|
63
82
|
import { registerApiKeyRoutes } from "./routes/api-keys";
|
|
64
83
|
import { registerBillingRoutes } from "./routes/billing";
|
|
84
|
+
import { registerBrowserIdentityRoutes } from "./routes/browser-identities";
|
|
85
|
+
import { registerBrowserSessionRoutes } from "./routes/browser-sessions";
|
|
86
|
+
import { registerComputerSessionRoutes } from "./routes/computer-sessions";
|
|
65
87
|
import { registerGitHubRoutes } from "./routes/github";
|
|
66
88
|
import { registerInstallRoutes } from "./routes/install";
|
|
89
|
+
import { registerApiIntegrationRoutes } from "./routes/api-integrations";
|
|
90
|
+
import { registerIntegrationFeatureRoutes } from "./routes/integration-features";
|
|
67
91
|
import { registerPackRoutes } from "./routes/packs";
|
|
92
|
+
import { registerPluginRoutes } from "./routes/plugins";
|
|
93
|
+
import { registerSkillRoutes } from "./routes/skills";
|
|
68
94
|
import { registerRigRoutes } from "./routes/rigs";
|
|
69
95
|
import { registerScheduledTaskRoutes } from "./routes/scheduled-tasks";
|
|
70
96
|
import { registerSessionRoutes } from "./routes/sessions";
|
|
71
97
|
import { registerSocialRoutes } from "./routes/social";
|
|
72
98
|
import { registerWorkspaceRoutes } from "./routes/workspaces";
|
|
73
99
|
import { registerWorkspaceInstructionPolicyRoutes } from "./routes/workspace-instruction-policies";
|
|
100
|
+
import { registerCompanyProfileRoutes } from "./routes/company-profile";
|
|
74
101
|
import { registerWorkspaceStateRoutes } from "./routes/workspace-state";
|
|
75
102
|
import { registerWorkspaceArtifactRoutes } from "./routes/workspace-artifacts";
|
|
76
103
|
import { registerPreferenceRegistryRoutes } from "./routes/preference-registry";
|
|
77
104
|
import { registerInsightsRoutes } from "./routes/insights";
|
|
78
105
|
import { registerTranscriptionRoutes } from "./routes/transcriptions";
|
|
106
|
+
import { registerEditableArtifactRoutes } from "./routes/editable-artifacts";
|
|
107
|
+
import { registerVideoGenerationRoutes } from "./routes/video-generation";
|
|
79
108
|
import { projectClientModel } from "./model-catalog";
|
|
80
109
|
import { createTranscriptionService } from "./transcription/service";
|
|
81
110
|
import { createFfmpegTranscriptionSegmenter } from "./transcription/segmenter";
|
|
82
111
|
import { registerSlackInteractionRoutes } from "./integrations/slack-interactions";
|
|
83
112
|
|
|
113
|
+
export { allowedCorsOrigin } from "./http/cors";
|
|
114
|
+
|
|
84
115
|
export type {
|
|
85
116
|
ApiRouteDeps,
|
|
86
117
|
AppDependencies,
|
|
@@ -249,12 +280,18 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
249
280
|
"Accept",
|
|
250
281
|
"Authorization",
|
|
251
282
|
"Content-Type",
|
|
283
|
+
"Range",
|
|
252
284
|
"X-OpenGeni-Access-Key",
|
|
253
285
|
"X-OpenGeni-Api-Contract",
|
|
254
286
|
"X-OpenGeni-Correlation-Id",
|
|
255
287
|
"X-OpenGeni-Subject",
|
|
256
288
|
],
|
|
257
|
-
exposeHeaders: [
|
|
289
|
+
exposeHeaders: [
|
|
290
|
+
"Accept-Ranges",
|
|
291
|
+
"Content-Range",
|
|
292
|
+
"X-OpenGeni-Api-Contract",
|
|
293
|
+
"X-OpenGeni-Correlation-Id",
|
|
294
|
+
],
|
|
258
295
|
};
|
|
259
296
|
const publicApiCors = cors({
|
|
260
297
|
...corsHeaders,
|
|
@@ -499,17 +536,13 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
499
536
|
throw error;
|
|
500
537
|
}
|
|
501
538
|
const grant = await requireMcpAccessGrant(c, routeDeps, workspaceId);
|
|
502
|
-
const toolspaceGrant = isToolspaceGrant(routeDeps.settings, grant);
|
|
503
539
|
const boundSessionId = grant.metadata?.sessionId;
|
|
504
|
-
if (
|
|
505
|
-
if (typeof boundSessionId !== "string") {
|
|
506
|
-
throw new HTTPException(404, { message: "session not found" });
|
|
507
|
-
}
|
|
540
|
+
if (typeof boundSessionId === "string") {
|
|
508
541
|
try {
|
|
509
542
|
await requireSessionAuthorization(routeDeps, grant, {
|
|
510
543
|
sessionId: boundSessionId,
|
|
511
|
-
operation:
|
|
512
|
-
surface:
|
|
544
|
+
operation: "session.first_party_mcp.call",
|
|
545
|
+
surface: "first_party_mcp",
|
|
513
546
|
});
|
|
514
547
|
} catch (error) {
|
|
515
548
|
if (error instanceof SessionAuthorizationDeniedError) {
|
|
@@ -523,22 +556,6 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
523
556
|
throw error;
|
|
524
557
|
}
|
|
525
558
|
}
|
|
526
|
-
let toolspace: Awaited<ReturnType<typeof prepareToolspaceMcpSurface>> = null;
|
|
527
|
-
if (toolspaceGrant) {
|
|
528
|
-
try {
|
|
529
|
-
toolspace = await prepareToolspaceMcpSurface({
|
|
530
|
-
deps: routeDeps,
|
|
531
|
-
grant,
|
|
532
|
-
});
|
|
533
|
-
} catch (error) {
|
|
534
|
-
if (error instanceof McpPayloadTooLargeError) {
|
|
535
|
-
throw new HTTPException(413, {
|
|
536
|
-
message: "MCP tool list exceeds the safety limit",
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
throw error;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
559
|
const workspace = await getWorkspace(routeDeps.db, workspaceId);
|
|
543
560
|
const workspaceMemoryEnabled = resolveWorkspaceMemoryEnabled(workspace?.settings);
|
|
544
561
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
@@ -546,42 +563,101 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
546
563
|
});
|
|
547
564
|
const mcp = buildOpenGeniMcpServer(routeDeps, grant, {
|
|
548
565
|
requestOrigin: new URL(c.req.url).origin,
|
|
549
|
-
toolspace,
|
|
550
566
|
workspaceMemoryEnabled,
|
|
551
567
|
});
|
|
568
|
+
await mcp.connect(transport);
|
|
569
|
+
return await transport.handleRequest(boundedRequest);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
app.get("/v1/workspaces/:workspaceId/codemode/catalog", async (c) => {
|
|
573
|
+
const workspaceId = c.req.param("workspaceId");
|
|
574
|
+
const grant = await requireAccessGrant(c, routeDeps, workspaceId);
|
|
575
|
+
if (!isCodemodeGrant(grant)) {
|
|
576
|
+
throw new HTTPException(403, { message: "Codemode access denied" });
|
|
577
|
+
}
|
|
578
|
+
try {
|
|
579
|
+
return c.json((await requireActiveCodemodeCatalog(routeDeps, grant)).catalog);
|
|
580
|
+
} catch (error) {
|
|
581
|
+
throw codemodeHttpError(error);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
app.post("/v1/workspaces/:workspaceId/codemode/calls", async (c) => {
|
|
586
|
+
const workspaceId = c.req.param("workspaceId");
|
|
587
|
+
const grant = await requireAccessGrant(c, routeDeps, workspaceId);
|
|
588
|
+
if (!isCodemodeGrant(grant)) {
|
|
589
|
+
throw new HTTPException(403, { message: "Codemode access denied" });
|
|
590
|
+
}
|
|
591
|
+
const parsed = CodemodeCallRequest.safeParse(await c.req.json().catch(() => null));
|
|
592
|
+
if (!parsed.success) {
|
|
593
|
+
throw new HTTPException(400, { message: "Invalid Codemode call" });
|
|
594
|
+
}
|
|
595
|
+
try {
|
|
596
|
+
const submission = await submitAndDispatchCodemodeCall(routeDeps, grant, parsed.data);
|
|
597
|
+
const terminal = ["completed", "failed", "outcome_unknown", "cancelled"].includes(
|
|
598
|
+
submission.operation.state,
|
|
599
|
+
);
|
|
600
|
+
return c.json(submission, terminal ? 200 : 202);
|
|
601
|
+
} catch (error) {
|
|
602
|
+
throw codemodeHttpError(error);
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
app.get("/v1/workspaces/:workspaceId/codemode/calls/:operationId", async (c) => {
|
|
607
|
+
const workspaceId = c.req.param("workspaceId");
|
|
608
|
+
const grant = await requireAccessGrant(c, routeDeps, workspaceId);
|
|
609
|
+
if (!isCodemodeGrant(grant)) {
|
|
610
|
+
throw new HTTPException(403, { message: "Codemode access denied" });
|
|
611
|
+
}
|
|
552
612
|
try {
|
|
553
|
-
await
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
613
|
+
const operation = await readCodemodeOperation(routeDeps, grant, c.req.param("operationId"));
|
|
614
|
+
if (!operation)
|
|
615
|
+
throw new HTTPException(404, {
|
|
616
|
+
message: "Codemode operation not found",
|
|
617
|
+
});
|
|
618
|
+
return c.json(operation);
|
|
619
|
+
} catch (error) {
|
|
620
|
+
if (error instanceof HTTPException) throw error;
|
|
621
|
+
throw codemodeHttpError(error);
|
|
557
622
|
}
|
|
558
623
|
});
|
|
559
624
|
|
|
560
625
|
registerFileRoutes(app, routeDeps);
|
|
561
626
|
registerApiKeyRoutes(app, routeDeps);
|
|
562
627
|
registerBillingRoutes(app, routeDeps);
|
|
628
|
+
registerBrowserIdentityRoutes(app, routeDeps);
|
|
629
|
+
registerBrowserSessionRoutes(app, routeDeps);
|
|
630
|
+
registerComputerSessionRoutes(app, routeDeps);
|
|
563
631
|
registerDocumentRoutes(app, routeDeps);
|
|
564
632
|
registerGitHubRoutes(app, routeDeps);
|
|
565
633
|
registerInstallRoutes(app, routeDeps);
|
|
566
634
|
registerWorkspaceRoutes(app, routeDeps);
|
|
567
635
|
registerInsightsRoutes(app, routeDeps);
|
|
568
636
|
registerWorkspaceInstructionPolicyRoutes(app, routeDeps);
|
|
637
|
+
registerCompanyProfileRoutes(app, routeDeps);
|
|
569
638
|
registerWorkspaceStateRoutes(app, routeDeps);
|
|
639
|
+
registerMemorySlackPublicationRoutes(app, routeDeps);
|
|
570
640
|
registerWorkspaceArtifactRoutes(app, routeDeps);
|
|
571
641
|
registerPreferenceRegistryRoutes(app, routeDeps);
|
|
572
642
|
registerSocialRoutes(app, routeDeps);
|
|
573
643
|
registerConnectionRoutes(app, routeDeps);
|
|
574
644
|
registerCapabilityRoutes(app, routeDeps);
|
|
645
|
+
registerApiIntegrationRoutes(app, routeDeps);
|
|
646
|
+
registerIntegrationFeatureRoutes(app, routeDeps);
|
|
575
647
|
registerCatalogAssetRoutes(app, routeDeps);
|
|
576
648
|
registerEnrollmentRoutes(app, routeDeps);
|
|
577
649
|
registerMachineRoutes(app, routeDeps);
|
|
578
650
|
registerEnvironmentRoutes(app, routeDeps);
|
|
579
651
|
registerRigRoutes(app, routeDeps);
|
|
580
652
|
registerPackRoutes(app, routeDeps);
|
|
653
|
+
registerPluginRoutes(app, routeDeps);
|
|
654
|
+
registerSkillRoutes(app, routeDeps);
|
|
581
655
|
registerSessionRoutes(app, routeDeps);
|
|
582
656
|
registerScheduledTaskRoutes(app, routeDeps);
|
|
583
657
|
registerCodexRoutes(app, routeDeps);
|
|
584
658
|
registerTranscriptionRoutes(app, routeDeps);
|
|
659
|
+
registerEditableArtifactRoutes(app, routeDeps);
|
|
660
|
+
registerVideoGenerationRoutes(app, routeDeps);
|
|
585
661
|
registerSlackInteractionRoutes(app, routeDeps);
|
|
586
662
|
|
|
587
663
|
app.notFound((c) => {
|
|
@@ -653,8 +729,8 @@ async function requireMcpAccessGrant(
|
|
|
653
729
|
if (hasPermission(grant.permissions, "workspace:read")) {
|
|
654
730
|
return grant;
|
|
655
731
|
}
|
|
656
|
-
if (
|
|
657
|
-
|
|
732
|
+
if (isCodemodeGrant(grant)) {
|
|
733
|
+
requirePermission(grant, "workspace:read");
|
|
658
734
|
}
|
|
659
735
|
// A worker-signed session-bound grant is allowed to reach the transport
|
|
660
736
|
// without inheriting broad workspace read access. The exact session
|
|
@@ -687,6 +763,48 @@ function clientAuthConfig(settings: AppDependencies["settings"]) {
|
|
|
687
763
|
return { mode: "none" as const };
|
|
688
764
|
}
|
|
689
765
|
|
|
766
|
+
function codemodeHttpError(error: unknown): HTTPException {
|
|
767
|
+
if (error instanceof SessionAuthorizationDeniedError) {
|
|
768
|
+
return new HTTPException(404, {
|
|
769
|
+
message: "session not found",
|
|
770
|
+
cause: error,
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
if (error instanceof SessionAuthorizationUnavailableError) {
|
|
774
|
+
return new HTTPException(503, {
|
|
775
|
+
message: "session authorization is unavailable",
|
|
776
|
+
cause: error,
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
if (
|
|
780
|
+
error instanceof CodemodeAuthorityError ||
|
|
781
|
+
error instanceof CodemodeOperationNotExecutableError
|
|
782
|
+
) {
|
|
783
|
+
return new HTTPException(409, { message: error.message, cause: error });
|
|
784
|
+
}
|
|
785
|
+
if (error instanceof CodemodeOperationConflictError) {
|
|
786
|
+
return new HTTPException(409, { message: error.message, cause: error });
|
|
787
|
+
}
|
|
788
|
+
if (error instanceof CodemodeCallBudgetExceededError) {
|
|
789
|
+
return new HTTPException(429, { message: error.message, cause: error });
|
|
790
|
+
}
|
|
791
|
+
if (error instanceof CodemodeToolNotInCatalogError) {
|
|
792
|
+
return new HTTPException(404, { message: error.message, cause: error });
|
|
793
|
+
}
|
|
794
|
+
if (error instanceof CodemodeToolApprovalRequiredError) {
|
|
795
|
+
return new HTTPException(409, { message: error.message, cause: error });
|
|
796
|
+
}
|
|
797
|
+
if (error instanceof CodemodePayloadTooLargeError) {
|
|
798
|
+
return new HTTPException(413, { message: error.message, cause: error });
|
|
799
|
+
}
|
|
800
|
+
return error instanceof HTTPException
|
|
801
|
+
? error
|
|
802
|
+
: new HTTPException(500, {
|
|
803
|
+
message: "Codemode request failed",
|
|
804
|
+
cause: error,
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
|
|
690
808
|
function clientAnalyticsConfig(settings: AppDependencies["settings"]) {
|
|
691
809
|
if (!settings.analyticsEnabled) {
|
|
692
810
|
return { consentRequired: true, providers: {} };
|
|
@@ -721,10 +839,6 @@ function structuredServicesHint(backend: string): {
|
|
|
721
839
|
return { fileSystem: hasBox, git: hasBox, terminalEvents: hasBox };
|
|
722
840
|
}
|
|
723
841
|
|
|
724
|
-
export function allowedCorsOrigin(pattern: string, origin: string): boolean {
|
|
725
|
-
return new RegExp(`^(?:${pattern})$`).test(origin);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
842
|
function codexCompactionV2ProviderLockedError(
|
|
729
843
|
error: unknown,
|
|
730
844
|
): CodexCompactionV2ProviderLockedError | null {
|
|
@@ -945,6 +1059,23 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
945
1059
|
pattern: /^\/v1\/workspaces\/[^/]+\/inference-control$/,
|
|
946
1060
|
label: "/v1/workspaces/:workspaceId/inference-control",
|
|
947
1061
|
},
|
|
1062
|
+
{
|
|
1063
|
+
pattern:
|
|
1064
|
+
/^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/fs\/(list|list-batch|read|write|delete|move|mkdir)$/,
|
|
1065
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/fs/:operation",
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/git\/(status|diff|read-batch|log|show)$/,
|
|
1069
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/git/:operation",
|
|
1070
|
+
},
|
|
1071
|
+
{
|
|
1072
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/terminal\/(exec|pty)$/,
|
|
1073
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/terminal/:operation",
|
|
1074
|
+
},
|
|
1075
|
+
{
|
|
1076
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/terminal\/pty\/(write|resize|close)$/,
|
|
1077
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/terminal/pty/:action",
|
|
1078
|
+
},
|
|
948
1079
|
{
|
|
949
1080
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/events\/stream$/,
|
|
950
1081
|
label: "/v1/workspaces/:workspaceId/sessions/:id/events/stream",
|
|
@@ -1141,6 +1272,80 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
1141
1272
|
pattern: /^\/v1\/workspaces\/[^/]+\/capabilities\/[^/]+\/disable$/,
|
|
1142
1273
|
label: "/v1/workspaces/:workspaceId/capabilities/:id/disable",
|
|
1143
1274
|
},
|
|
1275
|
+
{
|
|
1276
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/preview$/,
|
|
1277
|
+
label: "/v1/workspaces/:workspaceId/integrations/preview",
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/install$/,
|
|
1281
|
+
label: "/v1/workspaces/:workspaceId/integrations/install",
|
|
1282
|
+
},
|
|
1283
|
+
{
|
|
1284
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/presets$/,
|
|
1285
|
+
label: "/v1/workspaces/:workspaceId/integrations/presets",
|
|
1286
|
+
},
|
|
1287
|
+
{
|
|
1288
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/uninstall-preview$/,
|
|
1289
|
+
label:
|
|
1290
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/uninstall-preview",
|
|
1291
|
+
},
|
|
1292
|
+
{
|
|
1293
|
+
pattern:
|
|
1294
|
+
/^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/features\/[^/]+\/browse$/,
|
|
1295
|
+
label:
|
|
1296
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/features/:featureKey/browse",
|
|
1297
|
+
},
|
|
1298
|
+
{
|
|
1299
|
+
pattern:
|
|
1300
|
+
/^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/features\/[^/]+\/source$/,
|
|
1301
|
+
label:
|
|
1302
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/features/:featureKey/source",
|
|
1303
|
+
},
|
|
1304
|
+
{
|
|
1305
|
+
pattern:
|
|
1306
|
+
/^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/features\/[^/]+\/pause$/,
|
|
1307
|
+
label:
|
|
1308
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/features/:featureKey/pause",
|
|
1309
|
+
},
|
|
1310
|
+
{
|
|
1311
|
+
pattern:
|
|
1312
|
+
/^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/features\/[^/]+\/resume$/,
|
|
1313
|
+
label:
|
|
1314
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/features/:featureKey/resume",
|
|
1315
|
+
},
|
|
1316
|
+
{
|
|
1317
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/features\/[^/]+$/,
|
|
1318
|
+
label:
|
|
1319
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/features/:featureKey",
|
|
1320
|
+
},
|
|
1321
|
+
{
|
|
1322
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/features$/,
|
|
1323
|
+
label: "/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/features",
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+$/,
|
|
1327
|
+
label: "/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey",
|
|
1328
|
+
},
|
|
1329
|
+
{
|
|
1330
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/integrations$/,
|
|
1331
|
+
label: "/v1/workspaces/:workspaceId/integrations",
|
|
1332
|
+
},
|
|
1333
|
+
{
|
|
1334
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/skills\/preview$/,
|
|
1335
|
+
label: "/v1/workspaces/:workspaceId/skills/preview",
|
|
1336
|
+
},
|
|
1337
|
+
{
|
|
1338
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/skills\/install$/,
|
|
1339
|
+
label: "/v1/workspaces/:workspaceId/skills/install",
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/skills\/[^/]+\/uninstall-preview$/,
|
|
1343
|
+
label: "/v1/workspaces/:workspaceId/skills/:id/uninstall-preview",
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/skills\/[^/]+$/,
|
|
1347
|
+
label: "/v1/workspaces/:workspaceId/skills/:id",
|
|
1348
|
+
},
|
|
1144
1349
|
{
|
|
1145
1350
|
pattern: /^\/v1\/workspaces\/[^/]+\/environments$/,
|
|
1146
1351
|
label: "/v1/workspaces/:workspaceId/environments",
|
|
@@ -1173,6 +1378,26 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
1173
1378
|
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/[^/]+$/,
|
|
1174
1379
|
label: "/v1/workspaces/:workspaceId/packs/:id",
|
|
1175
1380
|
},
|
|
1381
|
+
{
|
|
1382
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/plugins\/preview$/,
|
|
1383
|
+
label: "/v1/workspaces/:workspaceId/plugins/preview",
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/plugins$/,
|
|
1387
|
+
label: "/v1/workspaces/:workspaceId/plugins",
|
|
1388
|
+
},
|
|
1389
|
+
{
|
|
1390
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/plugins\/install$/,
|
|
1391
|
+
label: "/v1/workspaces/:workspaceId/plugins/install",
|
|
1392
|
+
},
|
|
1393
|
+
{
|
|
1394
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/plugins\/[^/]+\/uninstall-preview$/,
|
|
1395
|
+
label: "/v1/workspaces/:workspaceId/plugins/:pluginKey/uninstall-preview",
|
|
1396
|
+
},
|
|
1397
|
+
{
|
|
1398
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/plugins\/[^/]+$/,
|
|
1399
|
+
label: "/v1/workspaces/:workspaceId/plugins/:pluginKey",
|
|
1400
|
+
},
|
|
1176
1401
|
{
|
|
1177
1402
|
pattern: /^\/v1\/workspaces\/[^/]+\/social\/connections$/,
|
|
1178
1403
|
label: "/v1/workspaces/:workspaceId/social/connections",
|
|
@@ -1189,6 +1414,10 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
1189
1414
|
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/oauth\/start$/,
|
|
1190
1415
|
label: "/v1/workspaces/:workspaceId/connections/oauth/start",
|
|
1191
1416
|
},
|
|
1417
|
+
{
|
|
1418
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/oauth\/start$/,
|
|
1419
|
+
label: "/v1/workspaces/:workspaceId/integrations/oauth/start",
|
|
1420
|
+
},
|
|
1192
1421
|
{
|
|
1193
1422
|
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/slack-bot\/install$/,
|
|
1194
1423
|
label: "/v1/workspaces/:workspaceId/connections/slack-bot/install",
|
|
@@ -1202,6 +1431,14 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
1202
1431
|
pattern: /^\/v1\/integrations\/oauth\/callback$/,
|
|
1203
1432
|
label: "/v1/integrations/oauth/callback",
|
|
1204
1433
|
},
|
|
1434
|
+
{
|
|
1435
|
+
pattern: /^\/v1\/integrations\/provider-oauth\/callback$/,
|
|
1436
|
+
label: "/v1/integrations/provider-oauth/callback",
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
pattern: /^\/v1\/integrations\/google-drive\/callback$/,
|
|
1440
|
+
label: "/v1/integrations/google-drive/callback",
|
|
1441
|
+
},
|
|
1205
1442
|
{
|
|
1206
1443
|
pattern: /^\/v1\/integrations\/oauth\/client-metadata\.json$/,
|
|
1207
1444
|
label: "/v1/integrations/oauth/client-metadata.json",
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
2
|
+
import type { InteractionPlacement } from "@opengeni/contracts";
|
|
3
|
+
|
|
4
|
+
type BrowserControllerAuthorityScope = {
|
|
5
|
+
rootSecret: string;
|
|
6
|
+
accountId: string;
|
|
7
|
+
workspaceId: string;
|
|
8
|
+
placement: InteractionPlacement;
|
|
9
|
+
placementInstanceId: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type BrowserSessionAuthorityScope = BrowserControllerAuthorityScope & {
|
|
13
|
+
browserSessionId: string;
|
|
14
|
+
controllerGeneration: string;
|
|
15
|
+
tokenGeneration: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type ComputerSessionAuthorityScope = BrowserControllerAuthorityScope & {
|
|
19
|
+
computerSessionId: string;
|
|
20
|
+
controllerGeneration: string;
|
|
21
|
+
tokenGeneration: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Stable placement-admin authority. This token is installed owner-only inside
|
|
25
|
+
* the exact placement and is never persisted or returned by the public API. */
|
|
26
|
+
export function deriveBrowserControllerAdminToken(scope: BrowserControllerAuthorityScope): string {
|
|
27
|
+
return derive(scope.rootSecret, "placement-admin.v1", [
|
|
28
|
+
scope.accountId,
|
|
29
|
+
scope.workspaceId,
|
|
30
|
+
placementKey(scope.placement),
|
|
31
|
+
scope.placementInstanceId,
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Deterministic controller credentials. The durable generations are the only
|
|
36
|
+
* stored authority; plaintext tokens can be reconstructed only by API replicas
|
|
37
|
+
* holding the deployment HMAC root. */
|
|
38
|
+
export function deriveBrowserSessionControllerTokens(scope: BrowserSessionAuthorityScope): {
|
|
39
|
+
controlToken: string;
|
|
40
|
+
viewToken: string;
|
|
41
|
+
} {
|
|
42
|
+
const fields = [
|
|
43
|
+
scope.accountId,
|
|
44
|
+
scope.workspaceId,
|
|
45
|
+
placementKey(scope.placement),
|
|
46
|
+
scope.placementInstanceId,
|
|
47
|
+
scope.browserSessionId,
|
|
48
|
+
scope.controllerGeneration,
|
|
49
|
+
scope.tokenGeneration,
|
|
50
|
+
] as const;
|
|
51
|
+
return {
|
|
52
|
+
controlToken: derive(scope.rootSecret, "session-control.v1", fields),
|
|
53
|
+
viewToken: derive(scope.rootSecret, "session-view.v1", fields),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function deriveBrowserViewGrantToken(
|
|
58
|
+
scope: BrowserSessionAuthorityScope & { grantId: string; expiresAt: string },
|
|
59
|
+
): string {
|
|
60
|
+
return derive(scope.rootSecret, "frame-view-grant.v1", [
|
|
61
|
+
scope.accountId,
|
|
62
|
+
scope.workspaceId,
|
|
63
|
+
placementKey(scope.placement),
|
|
64
|
+
scope.placementInstanceId,
|
|
65
|
+
scope.browserSessionId,
|
|
66
|
+
scope.controllerGeneration,
|
|
67
|
+
scope.tokenGeneration,
|
|
68
|
+
scope.grantId,
|
|
69
|
+
scope.expiresAt,
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** ComputerSession credentials share the placement controller root while using
|
|
74
|
+
* resource-specific domains. Browser and computer authority cannot collide. */
|
|
75
|
+
export function deriveComputerSessionControllerTokens(scope: ComputerSessionAuthorityScope): {
|
|
76
|
+
controlToken: string;
|
|
77
|
+
viewToken: string;
|
|
78
|
+
} {
|
|
79
|
+
const fields = [
|
|
80
|
+
scope.accountId,
|
|
81
|
+
scope.workspaceId,
|
|
82
|
+
placementKey(scope.placement),
|
|
83
|
+
scope.placementInstanceId,
|
|
84
|
+
scope.computerSessionId,
|
|
85
|
+
scope.controllerGeneration,
|
|
86
|
+
scope.tokenGeneration,
|
|
87
|
+
] as const;
|
|
88
|
+
return {
|
|
89
|
+
controlToken: derive(scope.rootSecret, "computer-session-control.v1", fields),
|
|
90
|
+
viewToken: derive(scope.rootSecret, "computer-session-view.v1", fields),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function deriveComputerViewGrantToken(
|
|
95
|
+
scope: ComputerSessionAuthorityScope & { grantId: string; expiresAt: string },
|
|
96
|
+
): string {
|
|
97
|
+
return derive(scope.rootSecret, "computer-frame-view-grant.v1", [
|
|
98
|
+
scope.accountId,
|
|
99
|
+
scope.workspaceId,
|
|
100
|
+
placementKey(scope.placement),
|
|
101
|
+
scope.placementInstanceId,
|
|
102
|
+
scope.computerSessionId,
|
|
103
|
+
scope.controllerGeneration,
|
|
104
|
+
scope.tokenGeneration,
|
|
105
|
+
scope.grantId,
|
|
106
|
+
scope.expiresAt,
|
|
107
|
+
]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function derive(rootSecret: string, domain: string, fields: readonly (string | number)[]): string {
|
|
111
|
+
if (!rootSecret) throw new Error("browser controller authority root is unavailable");
|
|
112
|
+
const hmac = createHmac("sha256", rootSecret);
|
|
113
|
+
hmac.update("opengeni.browser-controller-authority\0", "utf8");
|
|
114
|
+
writeField(hmac, domain);
|
|
115
|
+
for (const field of fields) writeField(hmac, String(field));
|
|
116
|
+
return `ogb.${hmac.digest("base64url")}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function writeField(hmac: ReturnType<typeof createHmac>, value: string): void {
|
|
120
|
+
const bytes = Buffer.from(value, "utf8");
|
|
121
|
+
hmac.update(String(bytes.byteLength), "utf8");
|
|
122
|
+
hmac.update(":", "utf8");
|
|
123
|
+
hmac.update(bytes);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function placementKey(placement: InteractionPlacement): string {
|
|
127
|
+
switch (placement.kind) {
|
|
128
|
+
case "sandbox_group":
|
|
129
|
+
return `sandbox_group:${placement.sandboxGroupId}`;
|
|
130
|
+
case "connected_machine":
|
|
131
|
+
return `connected_machine:${placement.sandboxId}`;
|
|
132
|
+
case "attached_device":
|
|
133
|
+
return `attached_device:${placement.deviceId}`;
|
|
134
|
+
case "external_provider":
|
|
135
|
+
return `external_provider:${placement.providerId}:${placement.placementId}`;
|
|
136
|
+
}
|
|
137
|
+
}
|