@opengeni/api-router 0.12.7 → 0.14.3
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 +18 -38
- package/dist/app.js +3 -1
- package/dist/auth/managed-auth.d.ts +35 -0
- package/dist/{chunk-SD5GJTZ4.js → chunk-36PW33ND.js} +3744 -638
- package/dist/chunk-36PW33ND.js.map +1 -0
- package/dist/codex-redemption-security.d.ts +15 -0
- package/dist/github-access.d.ts +12 -0
- package/dist/github-browser-flow.d.ts +14 -0
- package/dist/http/auth.d.ts +3 -0
- package/dist/http/common.d.ts +3 -0
- package/dist/http/sse.d.ts +70 -0
- package/dist/index.d.ts +11 -13
- package/dist/index.js +1 -1
- package/dist/integrations/google-drive.d.ts +76 -0
- package/dist/integrations/oauth-client.d.ts +42 -0
- package/dist/integrations/provider-domain.d.ts +7 -0
- package/dist/integrations/slack-bot.d.ts +343 -0
- package/dist/mcp/documents.d.ts +8 -0
- package/dist/mcp/files.d.ts +5 -0
- package/dist/mcp/server.d.ts +107 -0
- package/dist/mcp/session-view.d.ts +191 -0
- package/dist/mcp/toolspace.d.ts +61 -0
- package/dist/model-catalog.d.ts +39 -0
- package/dist/observability.d.ts +3 -0
- package/dist/routes/api-keys.d.ts +3 -0
- package/dist/routes/billing.d.ts +16 -0
- package/dist/routes/capabilities.d.ts +3 -0
- package/dist/routes/catalog-assets.d.ts +5 -0
- package/dist/routes/codex.d.ts +10 -0
- package/dist/routes/connections.d.ts +3 -0
- package/dist/routes/documents.d.ts +3 -0
- package/dist/routes/enrollments.d.ts +3 -0
- package/dist/routes/environments.d.ts +5 -0
- package/dist/routes/files.d.ts +4 -0
- package/dist/routes/github.d.ts +3 -0
- package/dist/routes/insights.d.ts +3 -0
- package/dist/routes/install.d.ts +5 -0
- package/dist/routes/machines.d.ts +3 -0
- package/dist/routes/packs.d.ts +3 -0
- package/dist/routes/preference-registry.d.ts +5 -0
- package/dist/routes/rigs.d.ts +3 -0
- package/dist/routes/scheduled-tasks.d.ts +3 -0
- package/dist/routes/sessions.d.ts +15 -0
- package/dist/routes/social.d.ts +3 -0
- package/dist/routes/transcriptions.d.ts +3 -0
- package/dist/routes/workspace-capture.d.ts +59 -0
- package/dist/routes/workspace-instruction-policies.d.ts +3 -0
- package/dist/routes/workspace-state.d.ts +3 -0
- package/dist/routes/workspaces.d.ts +5 -0
- package/dist/sandbox/access.d.ts +21 -0
- package/dist/sandbox/auth-callout.d.ts +30 -0
- package/dist/sandbox/channel-a.d.ts +37 -0
- package/dist/sandbox/enrollment.d.ts +120 -0
- package/dist/sandbox/machines.d.ts +29 -0
- package/dist/sandbox/metrics-ingestion.d.ts +128 -0
- package/dist/sandbox/rematerialize.d.ts +24 -0
- package/dist/sandbox/viewer.d.ts +251 -0
- package/dist/transcription/providers/azure-openai.d.ts +9 -0
- package/dist/transcription/providers/codex-subscription.d.ts +9 -0
- package/dist/transcription/providers/openai.d.ts +9 -0
- package/dist/transcription/service.d.ts +10 -0
- package/dist/workspace-state-projection.d.ts +16 -0
- package/package.json +13 -13
- package/src/app.ts +75 -5
- package/src/github-access.ts +1 -0
- package/src/integrations/google-drive.ts +869 -0
- package/src/integrations/oauth-client.ts +41 -6
- package/src/integrations/slack-bot.ts +756 -26
- package/src/mcp/server.ts +226 -42
- package/src/mcp/session-view.ts +1 -0
- package/src/mcp/toolspace.ts +17 -9
- package/src/routes/connections.ts +137 -7
- package/src/routes/documents.ts +13 -2
- package/src/routes/github.ts +327 -20
- package/src/routes/insights.ts +30 -0
- package/src/routes/preference-registry.ts +482 -0
- package/src/routes/sessions.ts +22 -23
- package/src/routes/transcriptions.ts +155 -0
- package/src/routes/workspace-capture.ts +108 -8
- package/src/routes/workspace-state.ts +61 -0
- package/src/sandbox/channel-a.ts +2 -0
- package/src/sandbox/rematerialize.ts +111 -4
- package/src/sandbox/viewer.ts +29 -12
- package/src/transcription/providers/azure-openai.ts +45 -0
- package/src/transcription/providers/codex-subscription.ts +100 -0
- package/src/transcription/providers/openai.ts +93 -0
- package/src/transcription/service.ts +121 -0
- package/src/workspace-state-projection.ts +244 -0
- package/dist/chunk-SD5GJTZ4.js.map +0 -1
|
@@ -10,6 +10,12 @@ import {
|
|
|
10
10
|
OAuthStartResponse,
|
|
11
11
|
UpdateConnectionRequest,
|
|
12
12
|
} from "@opengeni/contracts";
|
|
13
|
+
import {
|
|
14
|
+
GOOGLE_DRIVE_PROVIDER_DOMAIN,
|
|
15
|
+
GoogleDriveConnectionMetadata,
|
|
16
|
+
GoogleDriveOAuthStartRequest,
|
|
17
|
+
GoogleDriveOAuthStartResponse,
|
|
18
|
+
} from "@opengeni/contracts/google-drive";
|
|
13
19
|
import {
|
|
14
20
|
hasPermission,
|
|
15
21
|
hasReservedOpenGeniSlackBotMetadata,
|
|
@@ -38,6 +44,12 @@ import {
|
|
|
38
44
|
import type { ApiRouteDeps } from "@opengeni/core";
|
|
39
45
|
import type { Hono } from "hono";
|
|
40
46
|
import { HTTPException } from "hono/http-exception";
|
|
47
|
+
import {
|
|
48
|
+
browseGoogleDrive,
|
|
49
|
+
completeGoogleDriveOAuthCallback,
|
|
50
|
+
saveGoogleDriveSource,
|
|
51
|
+
startGoogleDriveOAuth,
|
|
52
|
+
} from "../integrations/google-drive";
|
|
41
53
|
import {
|
|
42
54
|
completeMcpOAuthCallback,
|
|
43
55
|
integrationBaseUrl,
|
|
@@ -96,6 +108,7 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
96
108
|
const subjectId = writableSubjectId(payload.subjectId, grant.subjectId);
|
|
97
109
|
const providerDomain = canonicalProviderDomain(payload.providerDomain);
|
|
98
110
|
assertNotDirectPersonalSlackOAuth(providerDomain, payload.kind);
|
|
111
|
+
assertNotDirectGoogleDriveOAuth(providerDomain, payload.kind, payload.metadata);
|
|
99
112
|
const connection = await createConnection(db, {
|
|
100
113
|
accountId: grant.accountId,
|
|
101
114
|
workspaceId,
|
|
@@ -243,6 +256,72 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
243
256
|
}
|
|
244
257
|
});
|
|
245
258
|
|
|
259
|
+
app.post("/v1/workspaces/:workspaceId/connections/google-drive/install", async (c) => {
|
|
260
|
+
assertIntegrationsEnabled();
|
|
261
|
+
const workspaceId = c.req.param("workspaceId");
|
|
262
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "connections:write");
|
|
263
|
+
const parsed = GoogleDriveOAuthStartRequest.safeParse(await c.req.json());
|
|
264
|
+
if (!parsed.success) {
|
|
265
|
+
throw new HTTPException(400, { message: "invalid Google Drive install request" });
|
|
266
|
+
}
|
|
267
|
+
return c.json(
|
|
268
|
+
GoogleDriveOAuthStartResponse.parse(
|
|
269
|
+
await startGoogleDriveOAuth(deps, {
|
|
270
|
+
accountId: grant.accountId,
|
|
271
|
+
workspaceId,
|
|
272
|
+
subjectId: grant.subjectId,
|
|
273
|
+
requestUrl: c.req.url,
|
|
274
|
+
payload: parsed.data,
|
|
275
|
+
}),
|
|
276
|
+
),
|
|
277
|
+
);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
app.get("/v1/integrations/google-drive/callback", async (c) => {
|
|
281
|
+
assertIntegrationsEnabled();
|
|
282
|
+
const result = await completeGoogleDriveOAuthCallback(deps, {
|
|
283
|
+
...(c.req.query("code") ? { code: c.req.query("code") } : {}),
|
|
284
|
+
...(c.req.query("state") ? { state: c.req.query("state") } : {}),
|
|
285
|
+
...(c.req.query("error") ? { error: c.req.query("error") } : {}),
|
|
286
|
+
requestUrl: c.req.url,
|
|
287
|
+
});
|
|
288
|
+
return c.redirect(result.redirectTo, 302);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
app.get(
|
|
292
|
+
"/v1/workspaces/:workspaceId/connections/google-drive/:connectionId/browse",
|
|
293
|
+
async (c) => {
|
|
294
|
+
assertIntegrationsEnabled();
|
|
295
|
+
const workspaceId = c.req.param("workspaceId");
|
|
296
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "connections:read");
|
|
297
|
+
return c.json(
|
|
298
|
+
await browseGoogleDrive(deps, {
|
|
299
|
+
workspaceId,
|
|
300
|
+
subjectId: grant.subjectId,
|
|
301
|
+
connectionId: c.req.param("connectionId"),
|
|
302
|
+
parentId: c.req.query("parentId") ?? "root",
|
|
303
|
+
...(c.req.query("pageToken") ? { pageToken: c.req.query("pageToken") } : {}),
|
|
304
|
+
}),
|
|
305
|
+
);
|
|
306
|
+
},
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
app.post(
|
|
310
|
+
"/v1/workspaces/:workspaceId/connections/google-drive/:connectionId/source",
|
|
311
|
+
async (c) => {
|
|
312
|
+
assertIntegrationsEnabled();
|
|
313
|
+
const workspaceId = c.req.param("workspaceId");
|
|
314
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "connections:write");
|
|
315
|
+
const connection = await saveGoogleDriveSource(deps, {
|
|
316
|
+
workspaceId,
|
|
317
|
+
subjectId: grant.subjectId,
|
|
318
|
+
connectionId: c.req.param("connectionId"),
|
|
319
|
+
payload: await c.req.json(),
|
|
320
|
+
});
|
|
321
|
+
return c.json(ConnectionResponse.parse({ connection }));
|
|
322
|
+
},
|
|
323
|
+
);
|
|
324
|
+
|
|
246
325
|
app.get("/v1/workspaces/:workspaceId/connections/:connectionId", async (c) => {
|
|
247
326
|
const workspaceId = c.req.param("workspaceId");
|
|
248
327
|
const grant = await requireAccessGrant(c, deps, workspaceId, "connections:read");
|
|
@@ -274,11 +353,18 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
274
353
|
message: "use the dedicated OpenGeni Slack bot reinstall flow to update this connection",
|
|
275
354
|
});
|
|
276
355
|
}
|
|
356
|
+
if (existing && GoogleDriveConnectionMetadata.safeParse(existing.metadata).success) {
|
|
357
|
+
throw new HTTPException(422, {
|
|
358
|
+
message: "use the dedicated Google Drive reconnect or source-selection flow",
|
|
359
|
+
});
|
|
360
|
+
}
|
|
277
361
|
if (existing) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
payload.kind ?? existing.kind,
|
|
362
|
+
const providerDomain = canonicalProviderDomain(
|
|
363
|
+
payload.providerDomain ?? existing.providerDomain,
|
|
281
364
|
);
|
|
365
|
+
const kind = payload.kind ?? existing.kind;
|
|
366
|
+
assertNotDirectPersonalSlackOAuth(providerDomain, kind);
|
|
367
|
+
assertNotDirectGoogleDriveOAuth(providerDomain, kind, payload.metadata ?? existing.metadata);
|
|
282
368
|
}
|
|
283
369
|
// Status is not a free-form field: revocation goes through DELETE, and the
|
|
284
370
|
// broker owns needs_reauth/error. Reactivating a connection is only
|
|
@@ -415,7 +501,7 @@ async function persistOpenGeniSlackBotConnection(input: {
|
|
|
415
501
|
const { db, settings } = input.deps;
|
|
416
502
|
const key = requireEnvironmentEncryption(settings);
|
|
417
503
|
const credentialEncrypted = encryptCredentialBundle(key, slackBotCredentialBundle(input.token));
|
|
418
|
-
const
|
|
504
|
+
const requestedExisting = input.state.connectionId
|
|
419
505
|
? await getConnectionMetadata(
|
|
420
506
|
db,
|
|
421
507
|
input.state.workspaceId,
|
|
@@ -423,7 +509,7 @@ async function persistOpenGeniSlackBotConnection(input: {
|
|
|
423
509
|
input.state.subjectId,
|
|
424
510
|
)
|
|
425
511
|
: null;
|
|
426
|
-
if (input.state.connectionId && !
|
|
512
|
+
if (input.state.connectionId && !requestedExisting) {
|
|
427
513
|
throw new SlackInstallCallbackError(
|
|
428
514
|
404,
|
|
429
515
|
"connection_conflict",
|
|
@@ -431,7 +517,7 @@ async function persistOpenGeniSlackBotConnection(input: {
|
|
|
431
517
|
"principal_validation",
|
|
432
518
|
);
|
|
433
519
|
}
|
|
434
|
-
if (
|
|
520
|
+
if (requestedExisting && !isOpenGeniSlackBotConnection(requestedExisting)) {
|
|
435
521
|
throw new SlackInstallCallbackError(
|
|
436
522
|
422,
|
|
437
523
|
"connection_conflict",
|
|
@@ -439,7 +525,7 @@ async function persistOpenGeniSlackBotConnection(input: {
|
|
|
439
525
|
"principal_validation",
|
|
440
526
|
);
|
|
441
527
|
}
|
|
442
|
-
if (
|
|
528
|
+
if (requestedExisting && requestedExisting.version !== input.state.connectionVersion) {
|
|
443
529
|
throw new SlackInstallCallbackError(
|
|
444
530
|
409,
|
|
445
531
|
"connection_conflict",
|
|
@@ -447,6 +533,13 @@ async function persistOpenGeniSlackBotConnection(input: {
|
|
|
447
533
|
"principal_validation",
|
|
448
534
|
);
|
|
449
535
|
}
|
|
536
|
+
const existing =
|
|
537
|
+
requestedExisting ??
|
|
538
|
+
(await findMatchingOpenGeniSlackBotConnection(
|
|
539
|
+
db,
|
|
540
|
+
input.state.workspaceId,
|
|
541
|
+
input.verified.metadata,
|
|
542
|
+
));
|
|
450
543
|
const existingMetadata = existing ? openGeniSlackBotMetadata(existing.metadata) : null;
|
|
451
544
|
if (existingMetadata && existingMetadata.slackTeamId !== input.verified.metadata.slackTeamId) {
|
|
452
545
|
throw new SlackInstallCallbackError(
|
|
@@ -526,6 +619,28 @@ async function persistOpenGeniSlackBotConnection(input: {
|
|
|
526
619
|
return connection;
|
|
527
620
|
}
|
|
528
621
|
|
|
622
|
+
async function findMatchingOpenGeniSlackBotConnection(
|
|
623
|
+
db: ApiRouteDeps["db"],
|
|
624
|
+
workspaceId: string,
|
|
625
|
+
verified: Awaited<ReturnType<typeof verifyOpenGeniSlackBotCredential>>["metadata"],
|
|
626
|
+
) {
|
|
627
|
+
const connections = await listConnectionsMetadata(db, workspaceId, null);
|
|
628
|
+
return (
|
|
629
|
+
connections.find((connection) => {
|
|
630
|
+
const metadata = openGeniSlackBotMetadata(connection.metadata);
|
|
631
|
+
return (
|
|
632
|
+
connection.subjectId === null &&
|
|
633
|
+
connection.providerDomain === "slack.com" &&
|
|
634
|
+
connection.kind === "app_install" &&
|
|
635
|
+
metadata?.credentialRole === OPENGENI_SLACK_BOT_CREDENTIAL_ROLE &&
|
|
636
|
+
metadata.slackTeamId === verified.slackTeamId &&
|
|
637
|
+
metadata.botId === verified.botId &&
|
|
638
|
+
metadata.botUserId === verified.botUserId
|
|
639
|
+
);
|
|
640
|
+
}) ?? null
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
529
644
|
function requireOpenGeniSlackOAuthSettings(settings: ApiRouteDeps["settings"]): {
|
|
530
645
|
clientId: string;
|
|
531
646
|
clientSecret: string;
|
|
@@ -685,6 +800,21 @@ function assertNotDirectPersonalSlackOAuth(providerDomain: string, kind: string)
|
|
|
685
800
|
}
|
|
686
801
|
}
|
|
687
802
|
|
|
803
|
+
function assertNotDirectGoogleDriveOAuth(
|
|
804
|
+
providerDomain: string,
|
|
805
|
+
kind: string,
|
|
806
|
+
metadata: Record<string, unknown> | undefined,
|
|
807
|
+
): void {
|
|
808
|
+
if (
|
|
809
|
+
(providerDomain === GOOGLE_DRIVE_PROVIDER_DOMAIN && kind === "oauth2") ||
|
|
810
|
+
GoogleDriveConnectionMetadata.safeParse(metadata).success
|
|
811
|
+
) {
|
|
812
|
+
throw new HTTPException(422, {
|
|
813
|
+
message: "Google Drive credentials must use the dedicated OAuth connection flow",
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
688
818
|
function assertNotReservedSlackBotMetadata(metadata: Record<string, unknown> | undefined): void {
|
|
689
819
|
if (hasReservedOpenGeniSlackBotMetadata(metadata)) {
|
|
690
820
|
throw new HTTPException(422, {
|
package/src/routes/documents.ts
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
searchDocuments,
|
|
38
38
|
} from "@opengeni/documents";
|
|
39
39
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
40
|
-
import type { Hono } from "hono";
|
|
40
|
+
import type { Context, Hono } from "hono";
|
|
41
41
|
import { HTTPException } from "hono/http-exception";
|
|
42
42
|
import { requireAccessGrant } from "@opengeni/core";
|
|
43
43
|
import { recordWorkspaceUsage, requireLimit } from "@opengeni/core";
|
|
@@ -252,7 +252,7 @@ export function registerDocumentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
252
252
|
app.post("/v1/workspaces/:workspaceId/knowledge/search", async (c) => {
|
|
253
253
|
const workspaceId = c.req.param("workspaceId");
|
|
254
254
|
const grant = await requireAccessGrant(c, deps, workspaceId, "documents:search");
|
|
255
|
-
const payload =
|
|
255
|
+
const payload = await parseDocumentSearchRequest(c, "invalid knowledge search request");
|
|
256
256
|
return c.json({
|
|
257
257
|
results: await searchDocuments(
|
|
258
258
|
db,
|
|
@@ -567,6 +567,17 @@ export function registerDocumentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
567
567
|
});
|
|
568
568
|
}
|
|
569
569
|
|
|
570
|
+
async function parseDocumentSearchRequest(
|
|
571
|
+
context: Context,
|
|
572
|
+
message: string,
|
|
573
|
+
): Promise<DocumentSearchRequest> {
|
|
574
|
+
const parsed = DocumentSearchRequest.safeParse(await context.req.json().catch(() => null));
|
|
575
|
+
if (!parsed.success) {
|
|
576
|
+
throw new HTTPException(422, { message });
|
|
577
|
+
}
|
|
578
|
+
return parsed.data;
|
|
579
|
+
}
|
|
580
|
+
|
|
570
581
|
/** Derive a .txt filename for a raw-text drop from its optional title/filename. */
|
|
571
582
|
function dropFilename(preferred: string | undefined): string {
|
|
572
583
|
const stem = (preferred ?? "").trim() || `note-${new Date().toISOString().slice(0, 10)}`;
|