@opengeni/api-router 0.22.2 → 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/auth/managed-auth.d.ts +0 -30
- package/dist/browser-controller-authority.d.ts +43 -0
- package/dist/browser-state-authority.d.ts +27 -0
- package/dist/{chunk-HWXJW5C7.js → chunk-JIKNR5YL.js} +27993 -14546
- 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/http/sse.d.ts +2 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1824 -30
- 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 +30 -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 +14 -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/receipts.d.ts +28 -0
- package/dist/mcp/scheduled-task-view.d.ts +518 -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/auth-callout.d.ts +2 -0
- package/dist/sandbox/channel-a.d.ts +59 -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 +277 -41
- package/src/auth/managed-auth.ts +0 -16
- 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/http/sse.ts +101 -6
- package/src/index.ts +147 -23
- 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 +159 -89
- package/src/integrations/provider-oauth.ts +777 -0
- package/src/integrations/slack-bot.ts +31 -2
- package/src/integrations/slack-interactions.ts +610 -42
- package/src/integrations/social-api.ts +11 -0
- package/src/mcp/documents.ts +74 -26
- package/src/mcp/editable-artifact-query-schema.ts +236 -0
- package/src/mcp/editable-artifacts.ts +448 -0
- package/src/mcp/receipts.ts +95 -0
- package/src/mcp/scheduled-task-view.ts +642 -0
- package/src/mcp/server.ts +1718 -310
- package/src/memory-slack-delivery.ts +209 -0
- package/src/observability.ts +3 -3
- package/src/routes/api-integrations.ts +407 -0
- package/src/routes/api-keys.ts +7 -1
- package/src/routes/browser-identities.ts +136 -0
- package/src/routes/browser-sessions.ts +2543 -0
- package/src/routes/codex.ts +7 -4
- package/src/routes/company-profile.ts +255 -0
- package/src/routes/computer-sessions.ts +1247 -0
- package/src/routes/connections.ts +358 -102
- package/src/routes/documents.ts +22 -3
- package/src/routes/editable-artifacts.ts +1159 -0
- package/src/routes/enrollments.ts +54 -12
- package/src/routes/environments.ts +60 -11
- package/src/routes/files.ts +277 -65
- package/src/routes/github.ts +18 -2
- package/src/routes/install.ts +38 -2
- package/src/routes/integration-features.ts +258 -0
- package/src/routes/machines.ts +1 -1
- 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 +94 -42
- package/src/routes/sessions.ts +475 -234
- package/src/routes/skills.ts +174 -0
- package/src/routes/transcription-recordings.ts +65 -33
- package/src/routes/video-generation.ts +132 -0
- package/src/routes/workspaces.ts +46 -24
- package/src/sandbox/auth-callout.ts +16 -4
- package/src/sandbox/channel-a.ts +809 -85
- package/src/sandbox/enrollment.ts +13 -3
- package/src/sandbox/machines.ts +1 -1
- package/src/sandbox/metrics-ingestion.ts +121 -3
- package/src/sandbox/rematerialize.ts +35 -47
- package/src/sandbox/viewer.ts +58 -29
- package/src/temporal-schedule-cleanup.ts +135 -0
- package/dist/chunk-HWXJW5C7.js.map +0 -1
- package/dist/mcp/toolspace.d.ts +0 -62
- package/src/mcp/toolspace.ts +0 -1186
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,
|
|
@@ -307,7 +344,6 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
307
344
|
const start = performance.now();
|
|
308
345
|
const span = observability.startSpan(`HTTP ${c.req.method} ${route}`, {
|
|
309
346
|
"http.request.method": c.req.method,
|
|
310
|
-
"url.path": url.pathname,
|
|
311
347
|
"opengeni.route": route,
|
|
312
348
|
});
|
|
313
349
|
try {
|
|
@@ -366,7 +402,7 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
366
402
|
spanId: span.spanId,
|
|
367
403
|
correlationId,
|
|
368
404
|
errorCode,
|
|
369
|
-
errorClass:
|
|
405
|
+
errorClass: "HttpOperationError",
|
|
370
406
|
});
|
|
371
407
|
throw error;
|
|
372
408
|
}
|
|
@@ -500,17 +536,13 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
500
536
|
throw error;
|
|
501
537
|
}
|
|
502
538
|
const grant = await requireMcpAccessGrant(c, routeDeps, workspaceId);
|
|
503
|
-
const toolspaceGrant = isToolspaceGrant(routeDeps.settings, grant);
|
|
504
539
|
const boundSessionId = grant.metadata?.sessionId;
|
|
505
|
-
if (
|
|
506
|
-
if (typeof boundSessionId !== "string") {
|
|
507
|
-
throw new HTTPException(404, { message: "session not found" });
|
|
508
|
-
}
|
|
540
|
+
if (typeof boundSessionId === "string") {
|
|
509
541
|
try {
|
|
510
542
|
await requireSessionAuthorization(routeDeps, grant, {
|
|
511
543
|
sessionId: boundSessionId,
|
|
512
|
-
operation:
|
|
513
|
-
surface:
|
|
544
|
+
operation: "session.first_party_mcp.call",
|
|
545
|
+
surface: "first_party_mcp",
|
|
514
546
|
});
|
|
515
547
|
} catch (error) {
|
|
516
548
|
if (error instanceof SessionAuthorizationDeniedError) {
|
|
@@ -524,22 +556,6 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
524
556
|
throw error;
|
|
525
557
|
}
|
|
526
558
|
}
|
|
527
|
-
let toolspace: Awaited<ReturnType<typeof prepareToolspaceMcpSurface>> = null;
|
|
528
|
-
if (toolspaceGrant) {
|
|
529
|
-
try {
|
|
530
|
-
toolspace = await prepareToolspaceMcpSurface({
|
|
531
|
-
deps: routeDeps,
|
|
532
|
-
grant,
|
|
533
|
-
});
|
|
534
|
-
} catch (error) {
|
|
535
|
-
if (error instanceof McpPayloadTooLargeError) {
|
|
536
|
-
throw new HTTPException(413, {
|
|
537
|
-
message: "MCP tool list exceeds the safety limit",
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
throw error;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
559
|
const workspace = await getWorkspace(routeDeps.db, workspaceId);
|
|
544
560
|
const workspaceMemoryEnabled = resolveWorkspaceMemoryEnabled(workspace?.settings);
|
|
545
561
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
@@ -547,42 +563,101 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
547
563
|
});
|
|
548
564
|
const mcp = buildOpenGeniMcpServer(routeDeps, grant, {
|
|
549
565
|
requestOrigin: new URL(c.req.url).origin,
|
|
550
|
-
toolspace,
|
|
551
566
|
workspaceMemoryEnabled,
|
|
552
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
|
+
}
|
|
553
612
|
try {
|
|
554
|
-
await
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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);
|
|
558
622
|
}
|
|
559
623
|
});
|
|
560
624
|
|
|
561
625
|
registerFileRoutes(app, routeDeps);
|
|
562
626
|
registerApiKeyRoutes(app, routeDeps);
|
|
563
627
|
registerBillingRoutes(app, routeDeps);
|
|
628
|
+
registerBrowserIdentityRoutes(app, routeDeps);
|
|
629
|
+
registerBrowserSessionRoutes(app, routeDeps);
|
|
630
|
+
registerComputerSessionRoutes(app, routeDeps);
|
|
564
631
|
registerDocumentRoutes(app, routeDeps);
|
|
565
632
|
registerGitHubRoutes(app, routeDeps);
|
|
566
633
|
registerInstallRoutes(app, routeDeps);
|
|
567
634
|
registerWorkspaceRoutes(app, routeDeps);
|
|
568
635
|
registerInsightsRoutes(app, routeDeps);
|
|
569
636
|
registerWorkspaceInstructionPolicyRoutes(app, routeDeps);
|
|
637
|
+
registerCompanyProfileRoutes(app, routeDeps);
|
|
570
638
|
registerWorkspaceStateRoutes(app, routeDeps);
|
|
639
|
+
registerMemorySlackPublicationRoutes(app, routeDeps);
|
|
571
640
|
registerWorkspaceArtifactRoutes(app, routeDeps);
|
|
572
641
|
registerPreferenceRegistryRoutes(app, routeDeps);
|
|
573
642
|
registerSocialRoutes(app, routeDeps);
|
|
574
643
|
registerConnectionRoutes(app, routeDeps);
|
|
575
644
|
registerCapabilityRoutes(app, routeDeps);
|
|
645
|
+
registerApiIntegrationRoutes(app, routeDeps);
|
|
646
|
+
registerIntegrationFeatureRoutes(app, routeDeps);
|
|
576
647
|
registerCatalogAssetRoutes(app, routeDeps);
|
|
577
648
|
registerEnrollmentRoutes(app, routeDeps);
|
|
578
649
|
registerMachineRoutes(app, routeDeps);
|
|
579
650
|
registerEnvironmentRoutes(app, routeDeps);
|
|
580
651
|
registerRigRoutes(app, routeDeps);
|
|
581
652
|
registerPackRoutes(app, routeDeps);
|
|
653
|
+
registerPluginRoutes(app, routeDeps);
|
|
654
|
+
registerSkillRoutes(app, routeDeps);
|
|
582
655
|
registerSessionRoutes(app, routeDeps);
|
|
583
656
|
registerScheduledTaskRoutes(app, routeDeps);
|
|
584
657
|
registerCodexRoutes(app, routeDeps);
|
|
585
658
|
registerTranscriptionRoutes(app, routeDeps);
|
|
659
|
+
registerEditableArtifactRoutes(app, routeDeps);
|
|
660
|
+
registerVideoGenerationRoutes(app, routeDeps);
|
|
586
661
|
registerSlackInteractionRoutes(app, routeDeps);
|
|
587
662
|
|
|
588
663
|
app.notFound((c) => {
|
|
@@ -654,8 +729,8 @@ async function requireMcpAccessGrant(
|
|
|
654
729
|
if (hasPermission(grant.permissions, "workspace:read")) {
|
|
655
730
|
return grant;
|
|
656
731
|
}
|
|
657
|
-
if (
|
|
658
|
-
|
|
732
|
+
if (isCodemodeGrant(grant)) {
|
|
733
|
+
requirePermission(grant, "workspace:read");
|
|
659
734
|
}
|
|
660
735
|
// A worker-signed session-bound grant is allowed to reach the transport
|
|
661
736
|
// without inheriting broad workspace read access. The exact session
|
|
@@ -688,6 +763,48 @@ function clientAuthConfig(settings: AppDependencies["settings"]) {
|
|
|
688
763
|
return { mode: "none" as const };
|
|
689
764
|
}
|
|
690
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
|
+
|
|
691
808
|
function clientAnalyticsConfig(settings: AppDependencies["settings"]) {
|
|
692
809
|
if (!settings.analyticsEnabled) {
|
|
693
810
|
return { consentRequired: true, providers: {} };
|
|
@@ -722,10 +839,6 @@ function structuredServicesHint(backend: string): {
|
|
|
722
839
|
return { fileSystem: hasBox, git: hasBox, terminalEvents: hasBox };
|
|
723
840
|
}
|
|
724
841
|
|
|
725
|
-
export function allowedCorsOrigin(pattern: string, origin: string): boolean {
|
|
726
|
-
return new RegExp(`^(?:${pattern})$`).test(origin);
|
|
727
|
-
}
|
|
728
|
-
|
|
729
842
|
function codexCompactionV2ProviderLockedError(
|
|
730
843
|
error: unknown,
|
|
731
844
|
): CodexCompactionV2ProviderLockedError | null {
|
|
@@ -837,12 +950,12 @@ async function runReadinessChecks<const Checks extends Readonly<Record<string, R
|
|
|
837
950
|
try {
|
|
838
951
|
await withTimeout(Promise.resolve().then(check), timeoutMs);
|
|
839
952
|
return [name, { ok: true }] as const;
|
|
840
|
-
} catch
|
|
953
|
+
} catch {
|
|
841
954
|
return [
|
|
842
955
|
name,
|
|
843
956
|
{
|
|
844
957
|
ok: false,
|
|
845
|
-
error:
|
|
958
|
+
error: "dependency_unavailable",
|
|
846
959
|
},
|
|
847
960
|
] as const;
|
|
848
961
|
}
|
|
@@ -946,6 +1059,23 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
946
1059
|
pattern: /^\/v1\/workspaces\/[^/]+\/inference-control$/,
|
|
947
1060
|
label: "/v1/workspaces/:workspaceId/inference-control",
|
|
948
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
|
+
},
|
|
949
1079
|
{
|
|
950
1080
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/events\/stream$/,
|
|
951
1081
|
label: "/v1/workspaces/:workspaceId/sessions/:id/events/stream",
|
|
@@ -1142,6 +1272,80 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
1142
1272
|
pattern: /^\/v1\/workspaces\/[^/]+\/capabilities\/[^/]+\/disable$/,
|
|
1143
1273
|
label: "/v1/workspaces/:workspaceId/capabilities/:id/disable",
|
|
1144
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
|
+
},
|
|
1145
1349
|
{
|
|
1146
1350
|
pattern: /^\/v1\/workspaces\/[^/]+\/environments$/,
|
|
1147
1351
|
label: "/v1/workspaces/:workspaceId/environments",
|
|
@@ -1174,6 +1378,26 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
1174
1378
|
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/[^/]+$/,
|
|
1175
1379
|
label: "/v1/workspaces/:workspaceId/packs/:id",
|
|
1176
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
|
+
},
|
|
1177
1401
|
{
|
|
1178
1402
|
pattern: /^\/v1\/workspaces\/[^/]+\/social\/connections$/,
|
|
1179
1403
|
label: "/v1/workspaces/:workspaceId/social/connections",
|
|
@@ -1190,6 +1414,10 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
1190
1414
|
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/oauth\/start$/,
|
|
1191
1415
|
label: "/v1/workspaces/:workspaceId/connections/oauth/start",
|
|
1192
1416
|
},
|
|
1417
|
+
{
|
|
1418
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/oauth\/start$/,
|
|
1419
|
+
label: "/v1/workspaces/:workspaceId/integrations/oauth/start",
|
|
1420
|
+
},
|
|
1193
1421
|
{
|
|
1194
1422
|
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/slack-bot\/install$/,
|
|
1195
1423
|
label: "/v1/workspaces/:workspaceId/connections/slack-bot/install",
|
|
@@ -1203,6 +1431,14 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
1203
1431
|
pattern: /^\/v1\/integrations\/oauth\/callback$/,
|
|
1204
1432
|
label: "/v1/integrations/oauth/callback",
|
|
1205
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
|
+
},
|
|
1206
1442
|
{
|
|
1207
1443
|
pattern: /^\/v1\/integrations\/oauth\/client-metadata\.json$/,
|
|
1208
1444
|
label: "/v1/integrations/oauth/client-metadata.json",
|
package/src/auth/managed-auth.ts
CHANGED
|
@@ -151,22 +151,6 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
|
|
|
151
151
|
}) as ManagedAuth;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
export async function managedSessionAccessContext(
|
|
155
|
-
auth: ManagedAuth,
|
|
156
|
-
db: Database,
|
|
157
|
-
headers: Headers,
|
|
158
|
-
) {
|
|
159
|
-
const session = await auth.api.getSession({ headers });
|
|
160
|
-
if (!session?.user) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
return await ensureManagedAccessForUser(db, {
|
|
164
|
-
userId: session.user.id,
|
|
165
|
-
email: session.user.email,
|
|
166
|
-
name: session.user.name,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
154
|
function betterAuthBaseUrl(settings: Settings) {
|
|
171
155
|
const allowedHosts = splitCsv(settings.betterAuthAllowedHosts);
|
|
172
156
|
if (allowedHosts.length === 0) {
|
|
@@ -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
|
+
}
|