@opengeni/api-router 0.26.1 → 0.30.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.
Files changed (87) hide show
  1. package/dist/app.d.ts +1 -0
  2. package/dist/app.js +5 -3
  3. package/dist/auth/canonical-human-session-admission.d.ts +27 -0
  4. package/dist/browser-auth-broker.d.ts +20 -0
  5. package/dist/browser-controller-authority.d.ts +19 -1
  6. package/dist/browser-network-route.d.ts +8 -0
  7. package/dist/{chunk-JIKNR5YL.js → chunk-2PQMSRCB.js} +15518 -6863
  8. package/dist/chunk-2PQMSRCB.js.map +1 -0
  9. package/dist/controller-data-plane.d.ts +12 -0
  10. package/dist/http/auth.d.ts +1 -1
  11. package/dist/http/sse.d.ts +18 -1
  12. package/dist/index.js +1 -1
  13. package/dist/integrations/api-integrations.d.ts +2 -2
  14. package/dist/integrations/fiken.d.ts +233 -0
  15. package/dist/integrations/google-drive.d.ts +6 -6
  16. package/dist/integrations/provider-oauth.d.ts +0 -2
  17. package/dist/integrations/slack-bot.d.ts +171 -1
  18. package/dist/integrations/slack-interactions.d.ts +17 -5
  19. package/dist/interaction-holder-heartbeat.d.ts +17 -0
  20. package/dist/interaction-metrics.d.ts +11 -0
  21. package/dist/mcp/server.d.ts +3 -2
  22. package/dist/model-catalog.d.ts +1 -0
  23. package/dist/routes/canonical-human-identities.d.ts +3 -0
  24. package/dist/routes/channels.d.ts +3 -0
  25. package/dist/routes/integration-facets.d.ts +3 -0
  26. package/dist/routes/interaction-resources.d.ts +5 -0
  27. package/dist/routes/organization-memberships.d.ts +3 -0
  28. package/dist/routes/sessions.d.ts +2 -1
  29. package/dist/routes/slack-task-policy.d.ts +3 -0
  30. package/dist/routes/supergrok.d.ts +3 -0
  31. package/dist/sandbox/channel-a.d.ts +8 -1
  32. package/dist/sandbox/machines.d.ts +2 -2
  33. package/dist/sandbox/viewer.d.ts +3 -1
  34. package/dist/slack-reaction-files.d.ts +27 -0
  35. package/dist/transcription/providers/xai-subscription.d.ts +8 -0
  36. package/dist/xai-realtime.d.ts +26 -0
  37. package/dist/xai-subscription-auth.d.ts +23 -0
  38. package/package.json +17 -16
  39. package/src/app.ts +352 -49
  40. package/src/auth/canonical-human-session-admission.ts +116 -0
  41. package/src/auth/managed-auth.ts +101 -0
  42. package/src/browser-auth-broker.ts +155 -0
  43. package/src/browser-controller-authority.ts +50 -1
  44. package/src/browser-network-route.ts +34 -0
  45. package/src/controller-data-plane.ts +35 -0
  46. package/src/http/auth.ts +27 -9
  47. package/src/http/sse.ts +229 -5
  48. package/src/integrations/api-integrations.ts +57 -42
  49. package/src/integrations/fiken.ts +1230 -0
  50. package/src/integrations/google-drive.ts +31 -26
  51. package/src/integrations/provider-oauth.ts +70 -91
  52. package/src/integrations/slack-bot.ts +638 -9
  53. package/src/integrations/slack-interactions.ts +1583 -73
  54. package/src/interaction-holder-heartbeat.ts +95 -0
  55. package/src/interaction-metrics.ts +159 -0
  56. package/src/mcp/documents.ts +115 -6
  57. package/src/mcp/server.ts +865 -242
  58. package/src/model-catalog.ts +19 -6
  59. package/src/routes/api-integrations.ts +32 -29
  60. package/src/routes/browser-identities.ts +24 -0
  61. package/src/routes/browser-sessions.ts +2122 -183
  62. package/src/routes/canonical-human-identities.ts +156 -0
  63. package/src/routes/channels.ts +90 -0
  64. package/src/routes/computer-sessions.ts +441 -86
  65. package/src/routes/connections.ts +141 -4
  66. package/src/routes/{integration-features.ts → integration-facets.ts} +63 -63
  67. package/src/routes/interaction-resources.ts +595 -0
  68. package/src/routes/organization-memberships.ts +53 -0
  69. package/src/routes/plugins.ts +2 -1
  70. package/src/routes/sessions.ts +419 -43
  71. package/src/routes/skills.ts +92 -10
  72. package/src/routes/slack-task-policy.ts +115 -0
  73. package/src/routes/supergrok.ts +717 -0
  74. package/src/routes/transcription-recordings.ts +9 -2
  75. package/src/routes/transcriptions.ts +2 -1
  76. package/src/routes/video-generation.ts +34 -4
  77. package/src/routes/workspaces.ts +22 -4
  78. package/src/sandbox/channel-a.ts +29 -2
  79. package/src/sandbox/machines.ts +5 -5
  80. package/src/sandbox/viewer.ts +10 -3
  81. package/src/slack-reaction-files.ts +181 -0
  82. package/src/transcription/providers/xai-subscription.ts +110 -0
  83. package/src/transcription/service.ts +17 -2
  84. package/src/xai-realtime.ts +216 -0
  85. package/src/xai-subscription-auth.ts +132 -0
  86. package/dist/chunk-JIKNR5YL.js.map +0 -1
  87. package/dist/routes/integration-features.d.ts +0 -3
@@ -0,0 +1,156 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import {
3
+ CanonicalHumanBindingOperationRequest,
4
+ CanonicalHumanIdentityMutationResponse,
5
+ CanonicalHumanIdentityProjection,
6
+ LinkCanonicalHumanLoginBindingRequest,
7
+ } from "@opengeni/contracts/canonical-human-identities";
8
+ import { requireCanonicalHumanRequestIdentity } from "@opengeni/core/canonical-human-identities";
9
+ import type { ApiRouteDeps } from "@opengeni/core";
10
+ import {
11
+ applyCanonicalHumanIdentityOperation,
12
+ CanonicalHumanIdentityAuthorityError,
13
+ CanonicalHumanIdentityConflictError,
14
+ CanonicalHumanIdentityNotFoundError,
15
+ CanonicalHumanIdentityOperationReuseError,
16
+ getCanonicalHumanIdentityProjection,
17
+ } from "@opengeni/db/canonical-human-identities";
18
+ import type { Context, Hono } from "hono";
19
+ import { HTTPException } from "hono/http-exception";
20
+ import { z } from "zod";
21
+
22
+ const BindingId = z.string().uuid();
23
+
24
+ async function requestIdentity(context: Context, deps: ApiRouteDeps) {
25
+ return await requireCanonicalHumanRequestIdentity(context, {
26
+ db: deps.db,
27
+ ...(deps.managedAuth === undefined ? {} : { managedAuth: deps.managedAuth }),
28
+ allowRecovery: true,
29
+ });
30
+ }
31
+
32
+ async function parseBody<S extends z.ZodType>(context: Context, schema: S): Promise<z.infer<S>> {
33
+ const parsed = schema.safeParse(await context.req.json().catch(() => null));
34
+ if (!parsed.success) {
35
+ throw new HTTPException(422, { message: "Invalid canonical human identity request" });
36
+ }
37
+ return parsed.data;
38
+ }
39
+
40
+ function bindingId(context: Context): string {
41
+ const parsed = BindingId.safeParse(context.req.param("bindingId"));
42
+ if (!parsed.success) throw new HTTPException(422, { message: "Invalid login binding id" });
43
+ return parsed.data;
44
+ }
45
+
46
+ function identityError(context: Context, error: unknown): Response {
47
+ if (error instanceof CanonicalHumanIdentityConflictError) {
48
+ return context.json({ code: error.code, message: error.message }, 409);
49
+ }
50
+ if (error instanceof CanonicalHumanIdentityOperationReuseError) {
51
+ return context.json({ code: error.code, message: error.message }, 409);
52
+ }
53
+ if (error instanceof CanonicalHumanIdentityNotFoundError) {
54
+ return context.json(
55
+ { code: "CANONICAL_HUMAN_IDENTITY_NOT_FOUND", message: error.message },
56
+ 404,
57
+ );
58
+ }
59
+ if (error instanceof CanonicalHumanIdentityAuthorityError) {
60
+ return context.json(
61
+ { code: "CANONICAL_HUMAN_IDENTITY_AUTHORITY_DENIED", message: error.message },
62
+ 403,
63
+ );
64
+ }
65
+ throw error;
66
+ }
67
+
68
+ async function mutate(
69
+ context: Context,
70
+ deps: ApiRouteDeps,
71
+ input: {
72
+ operationType: "link" | "unlink" | "begin_recovery" | "recover";
73
+ bindingId?: string;
74
+ providerId?: string;
75
+ providerAccountId?: string;
76
+ operationId: string;
77
+ expectedIdentityRevision: number;
78
+ reason: string;
79
+ },
80
+ ): Promise<Response> {
81
+ const identity = await requestIdentity(context, deps);
82
+ try {
83
+ return context.json(
84
+ CanonicalHumanIdentityMutationResponse.parse(
85
+ await applyCanonicalHumanIdentityOperation(deps.db, {
86
+ operationId: input.operationId,
87
+ authUserId: identity.authUserId,
88
+ expectedIdentityRevision: input.expectedIdentityRevision,
89
+ operationType: input.operationType,
90
+ ...(input.bindingId ? { bindingId: input.bindingId } : {}),
91
+ ...(input.providerId ? { providerId: input.providerId } : {}),
92
+ ...(input.providerAccountId ? { providerAccountId: input.providerAccountId } : {}),
93
+ reason: input.reason,
94
+ }),
95
+ ),
96
+ );
97
+ } catch (error) {
98
+ return identityError(context, error);
99
+ }
100
+ }
101
+
102
+ export function registerCanonicalHumanIdentityRoutes(app: Hono, deps: ApiRouteDeps): void {
103
+ const base = "/v1/identity";
104
+
105
+ app.get(base, async (context) => {
106
+ const identity = await requestIdentity(context, deps);
107
+ try {
108
+ return context.json(
109
+ CanonicalHumanIdentityProjection.parse(
110
+ await getCanonicalHumanIdentityProjection(deps.db, identity.authUserId),
111
+ ),
112
+ );
113
+ } catch (error) {
114
+ return identityError(context, error);
115
+ }
116
+ });
117
+
118
+ app.post(`${base}/login-bindings`, async (context) => {
119
+ const request = await parseBody(context, LinkCanonicalHumanLoginBindingRequest);
120
+ return await mutate(context, deps, {
121
+ operationType: "link",
122
+ ...request,
123
+ operationId: request.operationId ?? randomUUID(),
124
+ });
125
+ });
126
+
127
+ app.delete(`${base}/login-bindings/:bindingId`, async (context) => {
128
+ const request = await parseBody(context, CanonicalHumanBindingOperationRequest);
129
+ return await mutate(context, deps, {
130
+ operationType: "unlink",
131
+ bindingId: bindingId(context),
132
+ ...request,
133
+ operationId: request.operationId ?? randomUUID(),
134
+ });
135
+ });
136
+
137
+ app.post(`${base}/login-bindings/:bindingId/recovery`, async (context) => {
138
+ const request = await parseBody(context, CanonicalHumanBindingOperationRequest);
139
+ return await mutate(context, deps, {
140
+ operationType: "begin_recovery",
141
+ bindingId: bindingId(context),
142
+ ...request,
143
+ operationId: request.operationId ?? randomUUID(),
144
+ });
145
+ });
146
+
147
+ app.post(`${base}/login-bindings/:bindingId/recovery/complete`, async (context) => {
148
+ const request = await parseBody(context, CanonicalHumanBindingOperationRequest);
149
+ return await mutate(context, deps, {
150
+ operationType: "recover",
151
+ bindingId: bindingId(context),
152
+ ...request,
153
+ operationId: request.operationId ?? randomUUID(),
154
+ });
155
+ });
156
+ }
@@ -0,0 +1,90 @@
1
+ import { z } from "zod";
2
+ import { CreateChannelRequest, UpdateChannelRequest } from "@opengeni/contracts";
3
+ import {
4
+ ChannelNameConflictError,
5
+ createChannel,
6
+ deleteChannel,
7
+ listChannels,
8
+ updateChannel,
9
+ } from "@opengeni/db";
10
+ import type { Hono } from "hono";
11
+ import { HTTPException } from "hono/http-exception";
12
+ import { requireAccessGrant } from "@opengeni/core";
13
+ import type { ApiRouteDeps } from "@opengeni/core";
14
+
15
+ // Workspace-shared channels organize root sessions ("workstreams") by work
16
+ // type in the rail. Deliberately session-permission-gated (not workspace:admin):
17
+ // anyone who can read sessions can see how they are organized, and anyone who
18
+ // can create sessions can organize them.
19
+ // Reject malformed ids before they reach UUID-typed persistence queries,
20
+ // preserving the non-enumerating 404 contract over a driver-level 500.
21
+ function channelIdParam(value: string): string {
22
+ if (!z.string().uuid().safeParse(value).success) {
23
+ throw new HTTPException(404, { message: "channel not found" });
24
+ }
25
+ return value;
26
+ }
27
+
28
+ export function registerChannelRoutes(app: Hono, deps: ApiRouteDeps): void {
29
+ const { db } = deps;
30
+
31
+ app.get("/v1/workspaces/:workspaceId/channels", async (c) => {
32
+ const workspaceId = c.req.param("workspaceId");
33
+ await requireAccessGrant(c, deps, workspaceId, "sessions:read");
34
+ return c.json(await listChannels(db, workspaceId));
35
+ });
36
+
37
+ app.post("/v1/workspaces/:workspaceId/channels", async (c) => {
38
+ const workspaceId = c.req.param("workspaceId");
39
+ const grant = await requireAccessGrant(c, deps, workspaceId, "sessions:create");
40
+ const payload = CreateChannelRequest.parse(await c.req.json());
41
+ try {
42
+ const channel = await createChannel(db, {
43
+ accountId: grant.accountId,
44
+ workspaceId,
45
+ name: payload.name,
46
+ description: payload.description ?? null,
47
+ createdBy: grant.subjectId,
48
+ });
49
+ return c.json(channel, 201);
50
+ } catch (error) {
51
+ if (error instanceof ChannelNameConflictError) {
52
+ throw new HTTPException(409, { message: error.message });
53
+ }
54
+ throw error;
55
+ }
56
+ });
57
+
58
+ app.patch("/v1/workspaces/:workspaceId/channels/:channelId", async (c) => {
59
+ const workspaceId = c.req.param("workspaceId");
60
+ await requireAccessGrant(c, deps, workspaceId, "sessions:create");
61
+ const payload = UpdateChannelRequest.parse(await c.req.json());
62
+ try {
63
+ const channel = await updateChannel(
64
+ db,
65
+ workspaceId,
66
+ channelIdParam(c.req.param("channelId")),
67
+ payload,
68
+ );
69
+ if (!channel) {
70
+ throw new HTTPException(404, { message: "channel not found" });
71
+ }
72
+ return c.json(channel);
73
+ } catch (error) {
74
+ if (error instanceof ChannelNameConflictError) {
75
+ throw new HTTPException(409, { message: error.message });
76
+ }
77
+ throw error;
78
+ }
79
+ });
80
+
81
+ app.delete("/v1/workspaces/:workspaceId/channels/:channelId", async (c) => {
82
+ const workspaceId = c.req.param("workspaceId");
83
+ await requireAccessGrant(c, deps, workspaceId, "sessions:create");
84
+ const deleted = await deleteChannel(db, workspaceId, channelIdParam(c.req.param("channelId")));
85
+ if (!deleted) {
86
+ throw new HTTPException(404, { message: "channel not found" });
87
+ }
88
+ return c.json({ ok: true });
89
+ });
90
+ }