@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.
Files changed (89) hide show
  1. package/dist/app.d.ts +18 -38
  2. package/dist/app.js +3 -1
  3. package/dist/auth/managed-auth.d.ts +35 -0
  4. package/dist/{chunk-SD5GJTZ4.js → chunk-36PW33ND.js} +3744 -638
  5. package/dist/chunk-36PW33ND.js.map +1 -0
  6. package/dist/codex-redemption-security.d.ts +15 -0
  7. package/dist/github-access.d.ts +12 -0
  8. package/dist/github-browser-flow.d.ts +14 -0
  9. package/dist/http/auth.d.ts +3 -0
  10. package/dist/http/common.d.ts +3 -0
  11. package/dist/http/sse.d.ts +70 -0
  12. package/dist/index.d.ts +11 -13
  13. package/dist/index.js +1 -1
  14. package/dist/integrations/google-drive.d.ts +76 -0
  15. package/dist/integrations/oauth-client.d.ts +42 -0
  16. package/dist/integrations/provider-domain.d.ts +7 -0
  17. package/dist/integrations/slack-bot.d.ts +343 -0
  18. package/dist/mcp/documents.d.ts +8 -0
  19. package/dist/mcp/files.d.ts +5 -0
  20. package/dist/mcp/server.d.ts +107 -0
  21. package/dist/mcp/session-view.d.ts +191 -0
  22. package/dist/mcp/toolspace.d.ts +61 -0
  23. package/dist/model-catalog.d.ts +39 -0
  24. package/dist/observability.d.ts +3 -0
  25. package/dist/routes/api-keys.d.ts +3 -0
  26. package/dist/routes/billing.d.ts +16 -0
  27. package/dist/routes/capabilities.d.ts +3 -0
  28. package/dist/routes/catalog-assets.d.ts +5 -0
  29. package/dist/routes/codex.d.ts +10 -0
  30. package/dist/routes/connections.d.ts +3 -0
  31. package/dist/routes/documents.d.ts +3 -0
  32. package/dist/routes/enrollments.d.ts +3 -0
  33. package/dist/routes/environments.d.ts +5 -0
  34. package/dist/routes/files.d.ts +4 -0
  35. package/dist/routes/github.d.ts +3 -0
  36. package/dist/routes/insights.d.ts +3 -0
  37. package/dist/routes/install.d.ts +5 -0
  38. package/dist/routes/machines.d.ts +3 -0
  39. package/dist/routes/packs.d.ts +3 -0
  40. package/dist/routes/preference-registry.d.ts +5 -0
  41. package/dist/routes/rigs.d.ts +3 -0
  42. package/dist/routes/scheduled-tasks.d.ts +3 -0
  43. package/dist/routes/sessions.d.ts +15 -0
  44. package/dist/routes/social.d.ts +3 -0
  45. package/dist/routes/transcriptions.d.ts +3 -0
  46. package/dist/routes/workspace-capture.d.ts +59 -0
  47. package/dist/routes/workspace-instruction-policies.d.ts +3 -0
  48. package/dist/routes/workspace-state.d.ts +3 -0
  49. package/dist/routes/workspaces.d.ts +5 -0
  50. package/dist/sandbox/access.d.ts +21 -0
  51. package/dist/sandbox/auth-callout.d.ts +30 -0
  52. package/dist/sandbox/channel-a.d.ts +37 -0
  53. package/dist/sandbox/enrollment.d.ts +120 -0
  54. package/dist/sandbox/machines.d.ts +29 -0
  55. package/dist/sandbox/metrics-ingestion.d.ts +128 -0
  56. package/dist/sandbox/rematerialize.d.ts +24 -0
  57. package/dist/sandbox/viewer.d.ts +251 -0
  58. package/dist/transcription/providers/azure-openai.d.ts +9 -0
  59. package/dist/transcription/providers/codex-subscription.d.ts +9 -0
  60. package/dist/transcription/providers/openai.d.ts +9 -0
  61. package/dist/transcription/service.d.ts +10 -0
  62. package/dist/workspace-state-projection.d.ts +16 -0
  63. package/package.json +13 -13
  64. package/src/app.ts +75 -5
  65. package/src/github-access.ts +1 -0
  66. package/src/integrations/google-drive.ts +869 -0
  67. package/src/integrations/oauth-client.ts +41 -6
  68. package/src/integrations/slack-bot.ts +756 -26
  69. package/src/mcp/server.ts +226 -42
  70. package/src/mcp/session-view.ts +1 -0
  71. package/src/mcp/toolspace.ts +17 -9
  72. package/src/routes/connections.ts +137 -7
  73. package/src/routes/documents.ts +13 -2
  74. package/src/routes/github.ts +327 -20
  75. package/src/routes/insights.ts +30 -0
  76. package/src/routes/preference-registry.ts +482 -0
  77. package/src/routes/sessions.ts +22 -23
  78. package/src/routes/transcriptions.ts +155 -0
  79. package/src/routes/workspace-capture.ts +108 -8
  80. package/src/routes/workspace-state.ts +61 -0
  81. package/src/sandbox/channel-a.ts +2 -0
  82. package/src/sandbox/rematerialize.ts +111 -4
  83. package/src/sandbox/viewer.ts +29 -12
  84. package/src/transcription/providers/azure-openai.ts +45 -0
  85. package/src/transcription/providers/codex-subscription.ts +100 -0
  86. package/src/transcription/providers/openai.ts +93 -0
  87. package/src/transcription/service.ts +121 -0
  88. package/src/workspace-state-projection.ts +244 -0
  89. 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
- assertNotDirectPersonalSlackOAuth(
279
- canonicalProviderDomain(payload.providerDomain ?? existing.providerDomain),
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 existing = input.state.connectionId
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 && !existing) {
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 (existing && !isOpenGeniSlackBotConnection(existing)) {
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 (existing?.version !== input.state.connectionVersion) {
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, {
@@ -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 = DocumentSearchRequest.parse(await c.req.json());
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)}`;