@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
package/src/routes/files.ts
CHANGED
|
@@ -8,7 +8,10 @@ import {
|
|
|
8
8
|
RETAINED_OUTPUT_MAX_PAGE_BYTES,
|
|
9
9
|
RetainedArtifactMetadataSchema,
|
|
10
10
|
retainedArtifactReferenceFromFile,
|
|
11
|
+
retainedGeneratedImageReferenceFromFile,
|
|
12
|
+
retainedGeneratedVideoReferenceFromFile,
|
|
11
13
|
retainedScreenshotReferenceFromFile,
|
|
14
|
+
VideoArtifactPlaybackSource,
|
|
12
15
|
resolveRetainedOutputRange,
|
|
13
16
|
type RetainedArtifactMetadata,
|
|
14
17
|
type RetainedOutputUnavailableReason,
|
|
@@ -19,10 +22,14 @@ import {
|
|
|
19
22
|
completeFileUpload,
|
|
20
23
|
createFileUpload,
|
|
21
24
|
getFileUpload,
|
|
25
|
+
getGeneratedImageArtifact,
|
|
26
|
+
getGeneratedVideoArtifact,
|
|
22
27
|
getRetainedFileArtifact,
|
|
23
28
|
getRetainedScreenshotArtifact,
|
|
24
29
|
requireFile,
|
|
25
30
|
type RetainedFileArtifact,
|
|
31
|
+
type GeneratedImageArtifact,
|
|
32
|
+
type GeneratedVideoArtifact,
|
|
26
33
|
type RetainedScreenshotArtifact,
|
|
27
34
|
} from "@opengeni/db";
|
|
28
35
|
import type { Context, Hono } from "hono";
|
|
@@ -270,18 +277,18 @@ export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
270
277
|
const workspaceId = c.req.param("workspaceId");
|
|
271
278
|
await requireAccessGrant(c, deps, workspaceId, "files:read");
|
|
272
279
|
const artifactId = retainedArtifactId(c.req.param("artifactId"));
|
|
273
|
-
const artifact = await
|
|
280
|
+
const artifact = await getWorkspaceArtifact(db, workspaceId, artifactId);
|
|
274
281
|
if (!artifact) {
|
|
275
282
|
return c.json(retainedArtifactUnavailable(artifactId, "deleted"), 404);
|
|
276
283
|
}
|
|
277
|
-
return c.json(
|
|
284
|
+
return c.json(artifact.metadata);
|
|
278
285
|
});
|
|
279
286
|
|
|
280
287
|
app.get("/v1/workspaces/:workspaceId/artifacts/:artifactId/content", async (c) => {
|
|
281
288
|
const workspaceId = c.req.param("workspaceId");
|
|
282
289
|
await requireAccessGrant(c, deps, workspaceId, "files:read");
|
|
283
290
|
const artifactId = retainedArtifactId(c.req.param("artifactId"));
|
|
284
|
-
const artifact = await
|
|
291
|
+
const artifact = await getWorkspaceArtifact(db, workspaceId, artifactId);
|
|
285
292
|
if (!artifact) {
|
|
286
293
|
return c.json(retainedArtifactUnavailable(artifactId, "deleted"), 404);
|
|
287
294
|
}
|
|
@@ -291,7 +298,40 @@ export function registerFileRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
291
298
|
objectStorage,
|
|
292
299
|
artifactId,
|
|
293
300
|
artifact.file,
|
|
294
|
-
|
|
301
|
+
artifact.metadata,
|
|
302
|
+
);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
app.post("/v1/workspaces/:workspaceId/artifacts/:artifactId/playback-source", async (c) => {
|
|
306
|
+
const workspaceId = c.req.param("workspaceId");
|
|
307
|
+
await requireAccessGrant(c, deps, workspaceId, "files:read");
|
|
308
|
+
if (!objectStorage) {
|
|
309
|
+
throw new HTTPException(503, { message: "object storage is not configured" });
|
|
310
|
+
}
|
|
311
|
+
const artifactId = retainedArtifactId(c.req.param("artifactId"));
|
|
312
|
+
const retained = await getGeneratedVideoArtifact(db, workspaceId, artifactId);
|
|
313
|
+
if (!retained || retained.artifact.deletedAt) {
|
|
314
|
+
throw new HTTPException(404, { message: "generated video not found" });
|
|
315
|
+
}
|
|
316
|
+
if (retained.file.status !== "ready") {
|
|
317
|
+
throw new HTTPException(409, { message: `generated video is ${retained.file.status}` });
|
|
318
|
+
}
|
|
319
|
+
const file = generatedVideoFileAsset(retained.file);
|
|
320
|
+
if (!(await objectStorage.fileExists(file))) {
|
|
321
|
+
throw new HTTPException(410, { message: "generated video bytes are unavailable" });
|
|
322
|
+
}
|
|
323
|
+
const signed = await objectStorage.createGetUrl({ key: file.objectKey });
|
|
324
|
+
return c.json(
|
|
325
|
+
VideoArtifactPlaybackSource.parse({
|
|
326
|
+
schemaVersion: 1,
|
|
327
|
+
artifactId,
|
|
328
|
+
url: signed.url,
|
|
329
|
+
expiresAt: signed.expiresAt.toISOString(),
|
|
330
|
+
contentType: "video/mp4",
|
|
331
|
+
sizeBytes: file.sizeBytes,
|
|
332
|
+
sha256: file.sha256,
|
|
333
|
+
acceptRanges: "bytes",
|
|
334
|
+
}),
|
|
295
335
|
);
|
|
296
336
|
});
|
|
297
337
|
|
|
@@ -495,6 +535,68 @@ function retainedArtifactMetadata(artifact: RetainedFileArtifact): RetainedArtif
|
|
|
495
535
|
return retainedArtifactUnavailable(file.id, "unsupported");
|
|
496
536
|
}
|
|
497
537
|
|
|
538
|
+
async function getWorkspaceArtifact(
|
|
539
|
+
db: ApiRouteDeps["db"],
|
|
540
|
+
workspaceId: string,
|
|
541
|
+
artifactId: string,
|
|
542
|
+
): Promise<{ file: RetainedFileArtifact["file"]; metadata: RetainedArtifactMetadata } | null> {
|
|
543
|
+
const generated = await getGeneratedImageArtifact(db, workspaceId, artifactId);
|
|
544
|
+
if (generated) {
|
|
545
|
+
return { file: generated.file, metadata: retainedGeneratedImageMetadata(generated) };
|
|
546
|
+
}
|
|
547
|
+
const generatedVideo = await getGeneratedVideoArtifact(db, workspaceId, artifactId);
|
|
548
|
+
if (generatedVideo && !generatedVideo.artifact.deletedAt) {
|
|
549
|
+
const file = generatedVideoFileAsset(generatedVideo.file);
|
|
550
|
+
return {
|
|
551
|
+
file,
|
|
552
|
+
metadata: retainedGeneratedVideoMetadata({
|
|
553
|
+
artifact: generatedVideo.artifact,
|
|
554
|
+
file,
|
|
555
|
+
}),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
const artifact = await getRetainedFileArtifact(db, workspaceId, artifactId);
|
|
559
|
+
return artifact ? { file: artifact.file, metadata: retainedArtifactMetadata(artifact) } : null;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function generatedVideoFileAsset(
|
|
563
|
+
file: NonNullable<Awaited<ReturnType<typeof getGeneratedVideoArtifact>>>["file"],
|
|
564
|
+
): RetainedFileArtifact["file"] {
|
|
565
|
+
return FileAsset.parse({
|
|
566
|
+
...file,
|
|
567
|
+
createdAt: file.createdAt.toISOString(),
|
|
568
|
+
updatedAt: file.updatedAt.toISOString(),
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function retainedGeneratedVideoMetadata(artifact: {
|
|
573
|
+
artifact: GeneratedVideoArtifact;
|
|
574
|
+
file: RetainedFileArtifact["file"];
|
|
575
|
+
}): RetainedArtifactMetadata {
|
|
576
|
+
const reference = retainedGeneratedVideoReferenceFromFile({
|
|
577
|
+
...artifact.file,
|
|
578
|
+
id: artifact.artifact.id,
|
|
579
|
+
width: artifact.artifact.width,
|
|
580
|
+
height: artifact.artifact.height,
|
|
581
|
+
updatedAt: artifact.artifact.readyAt.toISOString(),
|
|
582
|
+
});
|
|
583
|
+
return reference ?? retainedArtifactUnavailable(artifact.artifact.id, "failed");
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function retainedGeneratedImageMetadata(
|
|
587
|
+
artifact: GeneratedImageArtifact,
|
|
588
|
+
): RetainedArtifactMetadata {
|
|
589
|
+
if (artifact.status === "ready") {
|
|
590
|
+
const reference = retainedGeneratedImageReferenceFromFile({
|
|
591
|
+
...artifact.file,
|
|
592
|
+
width: artifact.width,
|
|
593
|
+
height: artifact.height,
|
|
594
|
+
});
|
|
595
|
+
if (reference) return reference;
|
|
596
|
+
}
|
|
597
|
+
return retainedArtifactUnavailable(artifact.artifactId, "pending");
|
|
598
|
+
}
|
|
599
|
+
|
|
498
600
|
function retainedScreenshotMetadata(
|
|
499
601
|
artifact: RetainedScreenshotArtifact,
|
|
500
602
|
): RetainedArtifactMetadata {
|
package/src/routes/github.ts
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
githubBrowserBaseUrl,
|
|
39
39
|
githubBrowserGrantClaims,
|
|
40
40
|
githubBrowserGrantFromState,
|
|
41
|
+
githubSessionReturnPath,
|
|
41
42
|
} from "../github-browser-flow";
|
|
42
43
|
import {
|
|
43
44
|
githubBindingStatus,
|
|
@@ -64,12 +65,14 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
64
65
|
const status = githubBindingStatus(missing.length === 0, installations);
|
|
65
66
|
const setupMode = settings.productAccessMode === "managed" ? "platform" : "operator";
|
|
66
67
|
const canManage = hasPermission(grant.permissions, "github:manage");
|
|
68
|
+
const returnPath = githubSessionReturnPath(c.req.query("returnPath"), grant.workspaceId);
|
|
67
69
|
const connectState =
|
|
68
70
|
missing.length === 0 && slug && canManage
|
|
69
71
|
? createSignedState(githubStateSecret, {
|
|
70
72
|
accountId: grant.accountId,
|
|
71
73
|
workspaceId: grant.workspaceId,
|
|
72
74
|
intent: "installation_authority",
|
|
75
|
+
...(returnPath ? { returnPath } : {}),
|
|
73
76
|
...githubBrowserGrantClaims(settings, grant),
|
|
74
77
|
})
|
|
75
78
|
: null;
|
|
@@ -517,7 +520,12 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
517
520
|
return c.html(
|
|
518
521
|
githubSetupSuccessHtml(
|
|
519
522
|
proof.installation.accountLogin ?? `installation ${installationId}`,
|
|
520
|
-
openGeniReturnUrl(
|
|
523
|
+
openGeniReturnUrl(
|
|
524
|
+
settings,
|
|
525
|
+
c,
|
|
526
|
+
grant.workspaceId,
|
|
527
|
+
typeof statePayload.returnPath === "string" ? statePayload.returnPath : null,
|
|
528
|
+
),
|
|
521
529
|
),
|
|
522
530
|
);
|
|
523
531
|
});
|
|
@@ -879,8 +887,16 @@ function openGeniReturnUrl(
|
|
|
879
887
|
settings: ApiRouteDeps["settings"],
|
|
880
888
|
c: Context,
|
|
881
889
|
workspaceId: string,
|
|
890
|
+
returnPath: string | null = null,
|
|
882
891
|
): string {
|
|
883
|
-
const
|
|
892
|
+
const baseUrl = openGeniBaseUrl(settings, c) || new URL(c.req.url).origin;
|
|
893
|
+
const safeReturnPath = githubSessionReturnPath(returnPath, workspaceId);
|
|
894
|
+
const url = safeReturnPath ? new URL(safeReturnPath, `${baseUrl}/`) : new URL(baseUrl);
|
|
895
|
+
if (safeReturnPath) {
|
|
896
|
+
url.searchParams.delete("capability_auth");
|
|
897
|
+
url.searchParams.set("github", "connected");
|
|
898
|
+
return url.toString();
|
|
899
|
+
}
|
|
884
900
|
url.searchParams.set("workspaceId", workspaceId);
|
|
885
901
|
return url.toString();
|
|
886
902
|
}
|
package/src/routes/install.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
1
2
|
import { readFile, stat } from "node:fs/promises";
|
|
2
3
|
import type { Hono } from "hono";
|
|
3
4
|
import { HTTPException } from "hono/http-exception";
|
|
@@ -27,7 +28,12 @@ import type { ApiRouteDeps } from "@opengeni/core";
|
|
|
27
28
|
// The committed install artifacts, resolved relative to this module so the API
|
|
28
29
|
// (run from source under /app via bun) locates the sibling agent/install/ dir at
|
|
29
30
|
// runtime. apps/api/src/routes -> ../../../../agent/install.
|
|
30
|
-
const INSTALL_DIR =
|
|
31
|
+
const INSTALL_DIR =
|
|
32
|
+
[
|
|
33
|
+
new URL("./assets/agent-install/", import.meta.url),
|
|
34
|
+
new URL("../../../../agent/install/", import.meta.url),
|
|
35
|
+
].find((candidate) => existsSync(candidate)) ??
|
|
36
|
+
new URL("../../../../agent/install/", import.meta.url);
|
|
31
37
|
|
|
32
38
|
// The baked release-binary dir (a sibling of the committed scripts). The build's
|
|
33
39
|
// signing step writes the per-SHA Linux musl binaries + their `.sha256`/`.minisig`
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IntegrationFacetMutationResult,
|
|
3
|
+
IntegrationFacetRemovalResult,
|
|
4
|
+
IntegrationInstanceFacetsResponse,
|
|
5
|
+
MutateIntegrationFacetRequest,
|
|
6
|
+
UpsertIntegrationFacetRequest,
|
|
7
|
+
} from "@opengeni/contracts";
|
|
8
|
+
import {
|
|
9
|
+
hasPermission,
|
|
10
|
+
requireAccessGrant,
|
|
11
|
+
requireAccessGrantAuthorization,
|
|
12
|
+
type ApiRouteDeps,
|
|
13
|
+
} from "@opengeni/core";
|
|
14
|
+
import {
|
|
15
|
+
configureIntegrationFacet,
|
|
16
|
+
IntegrationFacetBindingOwnershipConflictError,
|
|
17
|
+
IntegrationFacetBindingVersionConflictError,
|
|
18
|
+
IntegrationFacetBindingVersionRequiredError,
|
|
19
|
+
IntegrationFacetConfigError,
|
|
20
|
+
IntegrationFacetConnectionError,
|
|
21
|
+
IntegrationFacetNotFoundError,
|
|
22
|
+
IntegrationFacetOperationIdempotencyError,
|
|
23
|
+
listIntegrationInstanceFacets,
|
|
24
|
+
removeIntegrationFacet,
|
|
25
|
+
setIntegrationFacetLifecycle,
|
|
26
|
+
} from "@opengeni/db";
|
|
27
|
+
import type { Hono } from "hono";
|
|
28
|
+
import { HTTPException } from "hono/http-exception";
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
browseGoogleDriveFacetSource,
|
|
32
|
+
saveGoogleDriveFacetSource,
|
|
33
|
+
} from "../integrations/google-drive";
|
|
34
|
+
|
|
35
|
+
export function registerIntegrationFacetRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
36
|
+
app.get(
|
|
37
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets/:facetKey/browse",
|
|
38
|
+
async (c) => {
|
|
39
|
+
const workspaceId = c.req.param("workspaceId");
|
|
40
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "connections:read");
|
|
41
|
+
try {
|
|
42
|
+
return c.json(
|
|
43
|
+
await browseGoogleDriveFacetSource(deps, {
|
|
44
|
+
workspaceId,
|
|
45
|
+
subjectId: grant.subjectId,
|
|
46
|
+
capabilityId: decoded(c.req.param("capabilityId")),
|
|
47
|
+
instanceKey: decoded(c.req.param("instanceKey")),
|
|
48
|
+
facetKey: decoded(c.req.param("facetKey")),
|
|
49
|
+
parentId: c.req.query("parentId") ?? "root",
|
|
50
|
+
...(c.req.query("pageToken") ? { pageToken: c.req.query("pageToken") } : {}),
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw facetHttpError(error);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
app.put(
|
|
60
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets/:facetKey/source",
|
|
61
|
+
async (c) => {
|
|
62
|
+
const workspaceId = c.req.param("workspaceId");
|
|
63
|
+
const authorization = await requireAccessGrantAuthorization(
|
|
64
|
+
c,
|
|
65
|
+
deps,
|
|
66
|
+
workspaceId,
|
|
67
|
+
"workspace:admin",
|
|
68
|
+
);
|
|
69
|
+
const { grant } = authorization;
|
|
70
|
+
try {
|
|
71
|
+
return c.json(
|
|
72
|
+
IntegrationFacetMutationResult.parse(
|
|
73
|
+
await saveGoogleDriveFacetSource(deps, {
|
|
74
|
+
accountId: grant.accountId,
|
|
75
|
+
workspaceId,
|
|
76
|
+
subjectId: grant.subjectId,
|
|
77
|
+
capabilityId: decoded(c.req.param("capabilityId")),
|
|
78
|
+
instanceKey: decoded(c.req.param("instanceKey")),
|
|
79
|
+
facetKey: decoded(c.req.param("facetKey")),
|
|
80
|
+
payload: await c.req.json(),
|
|
81
|
+
canManageOrganizationDestination:
|
|
82
|
+
authorization.accountGrant?.permissions.includes("account:admin") === true,
|
|
83
|
+
canManageWorkspaceDestination: hasPermission(grant.permissions, "workspace:admin"),
|
|
84
|
+
canManagePersonalDestination:
|
|
85
|
+
authorization.contextIntegrity &&
|
|
86
|
+
authorization.authenticatedSubjectId === grant.subjectId,
|
|
87
|
+
}),
|
|
88
|
+
),
|
|
89
|
+
);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
throw facetHttpError(error);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
app.get(
|
|
97
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets",
|
|
98
|
+
async (c) => {
|
|
99
|
+
const workspaceId = c.req.param("workspaceId");
|
|
100
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
101
|
+
try {
|
|
102
|
+
return c.json(
|
|
103
|
+
IntegrationInstanceFacetsResponse.parse(
|
|
104
|
+
await listIntegrationInstanceFacets(
|
|
105
|
+
deps.db,
|
|
106
|
+
workspaceId,
|
|
107
|
+
grant.subjectId,
|
|
108
|
+
decoded(c.req.param("capabilityId")),
|
|
109
|
+
decoded(c.req.param("instanceKey")),
|
|
110
|
+
),
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw facetHttpError(error);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
app.put(
|
|
120
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets/:facetKey",
|
|
121
|
+
async (c) => {
|
|
122
|
+
const workspaceId = c.req.param("workspaceId");
|
|
123
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
124
|
+
const payload = UpsertIntegrationFacetRequest.parse(await c.req.json());
|
|
125
|
+
const capabilityId = decoded(c.req.param("capabilityId"));
|
|
126
|
+
const instanceKey = decoded(c.req.param("instanceKey"));
|
|
127
|
+
const facetKey = decoded(c.req.param("facetKey"));
|
|
128
|
+
try {
|
|
129
|
+
const instance = await listIntegrationInstanceFacets(
|
|
130
|
+
deps.db,
|
|
131
|
+
workspaceId,
|
|
132
|
+
grant.subjectId,
|
|
133
|
+
capabilityId,
|
|
134
|
+
instanceKey,
|
|
135
|
+
);
|
|
136
|
+
const definition = instance.facets.find(
|
|
137
|
+
(facet) => facet.definition.facetKey === facetKey,
|
|
138
|
+
)?.definition;
|
|
139
|
+
if (
|
|
140
|
+
definition?.kind === "knowledge_source" &&
|
|
141
|
+
definition.capabilities.provider === "google-drive"
|
|
142
|
+
) {
|
|
143
|
+
throw new HTTPException(422, {
|
|
144
|
+
message:
|
|
145
|
+
"Google Drive source configuration requires the provider-specific source route",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return c.json(
|
|
149
|
+
IntegrationFacetMutationResult.parse(
|
|
150
|
+
await configureIntegrationFacet(deps.db, {
|
|
151
|
+
accountId: grant.accountId,
|
|
152
|
+
workspaceId,
|
|
153
|
+
subjectId: grant.subjectId,
|
|
154
|
+
capabilityId,
|
|
155
|
+
instanceKey,
|
|
156
|
+
facetKey,
|
|
157
|
+
displayName: payload.displayName,
|
|
158
|
+
config: payload.config,
|
|
159
|
+
...(payload.expectedVersion !== undefined
|
|
160
|
+
? { expectedVersion: payload.expectedVersion }
|
|
161
|
+
: {}),
|
|
162
|
+
idempotencyKey: payload.idempotencyKey,
|
|
163
|
+
}),
|
|
164
|
+
),
|
|
165
|
+
payload.expectedVersion === undefined ? 201 : 200,
|
|
166
|
+
);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
throw facetHttpError(error);
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
for (const action of ["pause", "resume"] as const) {
|
|
174
|
+
app.post(
|
|
175
|
+
`/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets/:facetKey/${action}`,
|
|
176
|
+
async (c) => {
|
|
177
|
+
const workspaceId = c.req.param("workspaceId");
|
|
178
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
179
|
+
const payload = MutateIntegrationFacetRequest.parse(await c.req.json());
|
|
180
|
+
try {
|
|
181
|
+
return c.json(
|
|
182
|
+
IntegrationFacetMutationResult.parse(
|
|
183
|
+
await setIntegrationFacetLifecycle(deps.db, {
|
|
184
|
+
accountId: grant.accountId,
|
|
185
|
+
workspaceId,
|
|
186
|
+
subjectId: grant.subjectId,
|
|
187
|
+
capabilityId: decoded(c.req.param("capabilityId")),
|
|
188
|
+
instanceKey: decoded(c.req.param("instanceKey")),
|
|
189
|
+
facetKey: decoded(c.req.param("facetKey")),
|
|
190
|
+
action,
|
|
191
|
+
expectedVersion: payload.expectedVersion,
|
|
192
|
+
idempotencyKey: payload.idempotencyKey,
|
|
193
|
+
}),
|
|
194
|
+
),
|
|
195
|
+
);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
throw facetHttpError(error);
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
app.delete(
|
|
204
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets/:facetKey",
|
|
205
|
+
async (c) => {
|
|
206
|
+
const workspaceId = c.req.param("workspaceId");
|
|
207
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
208
|
+
const payload = MutateIntegrationFacetRequest.parse(await c.req.json());
|
|
209
|
+
try {
|
|
210
|
+
return c.json(
|
|
211
|
+
IntegrationFacetRemovalResult.parse(
|
|
212
|
+
await removeIntegrationFacet(deps.db, {
|
|
213
|
+
accountId: grant.accountId,
|
|
214
|
+
workspaceId,
|
|
215
|
+
subjectId: grant.subjectId,
|
|
216
|
+
capabilityId: decoded(c.req.param("capabilityId")),
|
|
217
|
+
instanceKey: decoded(c.req.param("instanceKey")),
|
|
218
|
+
facetKey: decoded(c.req.param("facetKey")),
|
|
219
|
+
expectedVersion: payload.expectedVersion,
|
|
220
|
+
idempotencyKey: payload.idempotencyKey,
|
|
221
|
+
}),
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
throw facetHttpError(error);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function decoded(value: string): string {
|
|
232
|
+
return decodeURIComponent(value);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function facetHttpError(error: unknown): HTTPException {
|
|
236
|
+
if (error instanceof HTTPException) return error;
|
|
237
|
+
if (error instanceof IntegrationFacetNotFoundError) {
|
|
238
|
+
return new HTTPException(404, { message: error.message });
|
|
239
|
+
}
|
|
240
|
+
if (
|
|
241
|
+
error instanceof IntegrationFacetBindingVersionConflictError ||
|
|
242
|
+
error instanceof IntegrationFacetBindingVersionRequiredError ||
|
|
243
|
+
error instanceof IntegrationFacetBindingOwnershipConflictError ||
|
|
244
|
+
error instanceof IntegrationFacetOperationIdempotencyError
|
|
245
|
+
) {
|
|
246
|
+
return new HTTPException(409, {
|
|
247
|
+
message:
|
|
248
|
+
"The Integration facet changed, is shared, or reused an idempotency key. Refresh before retrying.",
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (
|
|
252
|
+
error instanceof IntegrationFacetConfigError ||
|
|
253
|
+
error instanceof IntegrationFacetConnectionError
|
|
254
|
+
) {
|
|
255
|
+
return new HTTPException(422, { message: error.message });
|
|
256
|
+
}
|
|
257
|
+
throw error;
|
|
258
|
+
}
|