@opengeni/api-router 2.6.4 → 2.10.0-canary.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.
Files changed (75) hide show
  1. package/dist/access-grant-rls.d.ts +3 -0
  2. package/dist/app.d.ts +2 -1
  3. package/dist/app.js +3 -1
  4. package/dist/auth/organization-user-setup.d.ts +23 -0
  5. package/dist/{chunk-XTLI3CBH.js → chunk-XXH7DW34.js} +6866 -3554
  6. package/dist/chunk-XXH7DW34.js.map +1 -0
  7. package/dist/codemode.d.ts +6 -0
  8. package/dist/github-access.d.ts +25 -2
  9. package/dist/http/request-source.d.ts +14 -0
  10. package/dist/index.js +19 -3
  11. package/dist/index.js.map +1 -1
  12. package/dist/integrations/oauth-client.d.ts +2 -11
  13. package/dist/integrations/personal-github-repositories.d.ts +41 -2
  14. package/dist/integrations/slack-interactions.d.ts +1 -1
  15. package/dist/mcp/server.d.ts +158 -1
  16. package/dist/mcp/session-view.d.ts +56 -2
  17. package/dist/mcp/session-wait.d.ts +1 -1
  18. package/dist/mcp-oauth.d.ts +19 -0
  19. package/dist/model-catalog.d.ts +5 -31
  20. package/dist/routes/codex.d.ts +27 -2
  21. package/dist/routes/github.d.ts +2 -0
  22. package/dist/routes/organization-model-providers.d.ts +3 -0
  23. package/dist/routes/workspace-artifacts.d.ts +2 -1
  24. package/dist/work-discovery-observability.d.ts +1 -1
  25. package/dist/workspace-artifact-content.d.ts +28 -0
  26. package/dist/workspace-artifact-provenance.d.ts +7 -0
  27. package/dist/workspace-deletion.d.ts +6 -0
  28. package/dist/workspace-tool-gateway-observability.d.ts +17 -0
  29. package/dist/workspace-tool-gateway.d.ts +118 -0
  30. package/package.json +19 -18
  31. package/src/access-grant-rls.ts +40 -0
  32. package/src/app.ts +296 -53
  33. package/src/auth/organization-user-setup.ts +95 -5
  34. package/src/codemode.ts +33 -4
  35. package/src/github-access.ts +117 -1
  36. package/src/http/auth.ts +11 -0
  37. package/src/http/request-source.ts +37 -0
  38. package/src/http/sse.ts +15 -7
  39. package/src/index.ts +18 -2
  40. package/src/integrations/oauth-client.ts +168 -203
  41. package/src/integrations/personal-github-repositories.ts +238 -9
  42. package/src/integrations/slack-app-home.ts +2 -2
  43. package/src/integrations/slack-interactions.ts +77 -37
  44. package/src/mcp/company-brain-governed-writes.ts +2 -2
  45. package/src/mcp/company-profile-agent-admin.ts +1 -1
  46. package/src/mcp/remember.ts +1 -1
  47. package/src/mcp/server.ts +504 -133
  48. package/src/mcp/session-view.ts +20 -1
  49. package/src/mcp/session-wait.ts +3 -0
  50. package/src/mcp-oauth.ts +539 -0
  51. package/src/model-catalog.ts +45 -337
  52. package/src/routes/automations.ts +178 -19
  53. package/src/routes/codex.ts +242 -73
  54. package/src/routes/connections.ts +271 -16
  55. package/src/routes/documents.ts +67 -19
  56. package/src/routes/files.ts +54 -6
  57. package/src/routes/github.ts +116 -15
  58. package/src/routes/organization-memberships.ts +59 -16
  59. package/src/routes/organization-model-providers.ts +231 -0
  60. package/src/routes/packs.ts +1 -0
  61. package/src/routes/personal-github.ts +39 -0
  62. package/src/routes/pr-review.ts +72 -4
  63. package/src/routes/scheduled-tasks.ts +40 -13
  64. package/src/routes/sessions.ts +153 -65
  65. package/src/routes/supergrok.ts +3 -2
  66. package/src/routes/workspace-artifacts.ts +176 -67
  67. package/src/routes/workspaces.ts +405 -62
  68. package/src/sandbox/channel-a.ts +17 -4
  69. package/src/work-discovery-observability.ts +3 -3
  70. package/src/workspace-artifact-content.ts +163 -0
  71. package/src/workspace-artifact-provenance.ts +104 -0
  72. package/src/workspace-deletion.ts +64 -0
  73. package/src/workspace-tool-gateway-observability.ts +100 -0
  74. package/src/workspace-tool-gateway.ts +691 -0
  75. package/dist/chunk-XTLI3CBH.js.map +0 -1
@@ -3,22 +3,32 @@ import {
3
3
  CreateWorkspaceArtifactRequest,
4
4
  PublishWorkspaceArtifactVersionRequest,
5
5
  RollbackWorkspaceArtifactRequest,
6
+ SetWorkspaceArtifactStatusRequest,
6
7
  WorkspaceArtifactContentResponse,
7
8
  WorkspaceArtifactDetailResponse,
8
9
  WorkspaceArtifactListQuery,
9
10
  WorkspaceArtifactListResponse,
10
11
  WorkspaceArtifactMutationResponse,
11
12
  normalizeWorkspaceArtifactSlug,
13
+ type AccessGrant,
12
14
  } from "@opengeni/contracts";
13
- import { requireAccessGrant, type ApiRouteDeps } from "@opengeni/core";
14
- import { retryWhileMissing } from "@opengeni/storage";
15
+ import {
16
+ requireAccessGrant,
17
+ requireAccessGrantAuthorization,
18
+ requireSessionAuthorization,
19
+ SessionAuthorizationDeniedError,
20
+ SessionAuthorizationUnavailableError,
21
+ type ApiRouteDeps,
22
+ } from "@opengeni/core";
15
23
  import {
16
24
  createWorkspaceArtifact,
17
25
  getWorkspaceArtifact,
18
26
  getWorkspaceArtifactContentRef,
19
27
  listWorkspaceArtifacts,
28
+ nestedPostgresSqlState,
20
29
  publishWorkspaceArtifactVersion,
21
30
  rollbackWorkspaceArtifact,
31
+ setWorkspaceArtifactStatus,
22
32
  WorkspaceArtifactConflictError,
23
33
  WorkspaceArtifactNotFoundError,
24
34
  WorkspaceArtifactOperationError,
@@ -26,10 +36,17 @@ import {
26
36
  import type { Context, Hono } from "hono";
27
37
  import { HTTPException } from "hono/http-exception";
28
38
  import { z } from "zod";
39
+ import {
40
+ prepareWorkspaceArtifactContent,
41
+ readWorkspaceArtifactContent,
42
+ } from "../workspace-artifact-content";
43
+ import {
44
+ projectWorkspaceArtifactDetailProvenance,
45
+ projectWorkspaceArtifactMutationProvenance,
46
+ redactWorkspaceArtifactListProvenance,
47
+ } from "../workspace-artifact-provenance";
29
48
 
30
49
  const ArtifactId = z.string().uuid();
31
- const encoder = new TextEncoder();
32
- const decoder = new TextDecoder("utf-8", { fatal: true });
33
50
 
34
51
  async function body<S extends z.ZodType>(context: Context, schema: S): Promise<z.infer<S>> {
35
52
  const parsed = schema.safeParse(await context.req.json().catch(() => null));
@@ -43,7 +60,7 @@ function artifactId(context: Context): string {
43
60
  return parsed.data;
44
61
  }
45
62
 
46
- function errorResponse(context: Context, error: unknown): Response {
63
+ export function workspaceArtifactErrorResponse(context: Context, error: unknown): Response {
47
64
  if (error instanceof WorkspaceArtifactNotFoundError) {
48
65
  return context.json({ code: "WORKSPACE_ARTIFACT_NOT_FOUND", message: error.message }, 404);
49
66
  }
@@ -63,41 +80,28 @@ function errorResponse(context: Context, error: unknown): Response {
63
80
  422,
64
81
  );
65
82
  }
66
- if (typeof error === "object" && error !== null && "code" in error && error.code === "23505") {
83
+ if (nestedPostgresSqlState(error) === "23505") {
67
84
  return context.json(
68
- { code: "WORKSPACE_ARTIFACT_CONFLICT", message: "Artifact slug or operation already exists" },
85
+ {
86
+ code: "WORKSPACE_ARTIFACT_CONFLICT",
87
+ message: "Artifact slug or operation already exists",
88
+ },
69
89
  409,
70
90
  );
71
91
  }
72
92
  throw error;
73
93
  }
74
94
 
75
- function contentMetadata(workspaceId: string, html: string) {
76
- const bytes = encoder.encode(html);
77
- const sha256 = createHash("sha256").update(bytes).digest("hex");
78
- return {
79
- bytes,
80
- contentSha256: sha256,
81
- sizeBytes: bytes.byteLength,
82
- contentKey: `workspaces/${workspaceId}/workspace-artifacts/blobs/${sha256}.html`,
83
- };
84
- }
85
-
86
- function prepareHtml(deps: ApiRouteDeps, workspaceId: string, html: string) {
95
+ function prepareContent(
96
+ deps: ApiRouteDeps,
97
+ workspaceId: string,
98
+ input: Parameters<typeof prepareWorkspaceArtifactContent>[2],
99
+ ) {
87
100
  if (!deps.objectStorage)
88
- throw new HTTPException(503, { message: "Object storage is not configured" });
89
- const content = contentMetadata(workspaceId, html);
90
- return {
91
- ...content,
92
- persistContent: async () => {
93
- await deps.objectStorage!.putObject({
94
- key: content.contentKey,
95
- contentType: "text/html; charset=utf-8",
96
- body: content.bytes,
97
- sha256: content.contentSha256,
98
- });
99
- },
100
- };
101
+ throw new HTTPException(503, {
102
+ message: "Object storage is not configured",
103
+ });
104
+ return prepareWorkspaceArtifactContent(deps.objectStorage, workspaceId, input);
101
105
  }
102
106
 
103
107
  function provenance(subjectId: string, idempotencyKey: string) {
@@ -112,6 +116,41 @@ function provenance(subjectId: string, idempotencyKey: string) {
112
116
  };
113
117
  }
114
118
 
119
+ async function canReadProvenanceSession(
120
+ deps: ApiRouteDeps,
121
+ grant: AccessGrant,
122
+ sessionId: string,
123
+ ): Promise<boolean> {
124
+ try {
125
+ await requireSessionAuthorization(deps, grant, {
126
+ sessionId,
127
+ operation: "session.read",
128
+ surface: "http",
129
+ });
130
+ return true;
131
+ } catch (error) {
132
+ if (error instanceof SessionAuthorizationDeniedError) return false;
133
+ if (error instanceof SessionAuthorizationUnavailableError) {
134
+ throw new HTTPException(503, {
135
+ message: "Session authorization is unavailable",
136
+ });
137
+ }
138
+ throw error;
139
+ }
140
+ }
141
+
142
+ async function mutationResponse(
143
+ deps: ApiRouteDeps,
144
+ grant: AccessGrant,
145
+ response: WorkspaceArtifactMutationResponse,
146
+ ): Promise<WorkspaceArtifactMutationResponse> {
147
+ return WorkspaceArtifactMutationResponse.parse(
148
+ await projectWorkspaceArtifactMutationProvenance(response, (sessionId) =>
149
+ canReadProvenanceSession(deps, grant, sessionId),
150
+ ),
151
+ );
152
+ }
153
+
115
154
  export function registerWorkspaceArtifactRoutes(app: Hono, deps: ApiRouteDeps): void {
116
155
  // `published-artifacts` deliberately avoids the existing `/artifacts/:id`
117
156
  // retained-output API. The product route remains simply `/artifacts`.
@@ -123,38 +162,55 @@ export function registerWorkspaceArtifactRoutes(app: Hono, deps: ApiRouteDeps):
123
162
  const query = WorkspaceArtifactListQuery.safeParse({
124
163
  limit: context.req.query("limit"),
125
164
  cursor: context.req.query("cursor"),
165
+ status: context.req.query("status"),
126
166
  });
127
167
  if (!query.success) throw new HTTPException(422, { message: "Invalid artifact list query" });
128
168
  try {
129
169
  return context.json(
130
170
  WorkspaceArtifactListResponse.parse(
131
- await listWorkspaceArtifacts(deps.db, workspaceId, {
132
- limit: query.data.limit,
133
- ...(query.data.cursor ? { cursor: query.data.cursor } : {}),
134
- }),
171
+ redactWorkspaceArtifactListProvenance(
172
+ await listWorkspaceArtifacts(deps.db, workspaceId, {
173
+ limit: query.data.limit,
174
+ ...(query.data.cursor ? { cursor: query.data.cursor } : {}),
175
+ ...(query.data.status ? { status: query.data.status } : {}),
176
+ }),
177
+ ),
135
178
  ),
136
179
  );
137
180
  } catch (error) {
138
- return errorResponse(context, error);
181
+ return workspaceArtifactErrorResponse(context, error);
139
182
  }
140
183
  });
141
184
 
142
185
  app.post(base, async (context) => {
143
186
  const workspaceId = context.req.param("workspaceId");
144
- const grant = await requireAccessGrant(context, deps, workspaceId, "artifacts:publish");
187
+ const authorization = await requireAccessGrantAuthorization(
188
+ context,
189
+ deps,
190
+ workspaceId,
191
+ "artifacts:publish",
192
+ );
193
+ const grant = authorization.grant;
145
194
  const request = await body(context, CreateWorkspaceArtifactRequest);
146
195
  const id = crypto.randomUUID();
147
196
  const slugBase = request.slug ?? (normalizeWorkspaceArtifactSlug(request.title) || "artifact");
148
197
  const slug = request.slug ?? `${slugBase.slice(0, 87)}-${id.slice(0, 8)}`;
149
- const content = prepareHtml(deps, workspaceId, request.html);
198
+ const content = prepareContent(deps, workspaceId, {
199
+ html: request.html,
200
+ ...(request.source ? { source: request.source } : {}),
201
+ ...(request.requestedTools ? { requestedTools: request.requestedTools } : {}),
202
+ });
150
203
  try {
151
204
  return context.json(
152
- WorkspaceArtifactMutationResponse.parse(
205
+ await mutationResponse(
206
+ deps,
207
+ grant,
153
208
  await createWorkspaceArtifact(deps.db, {
154
209
  accountId: grant.accountId,
155
210
  workspaceId,
156
211
  artifactId: id,
157
212
  slug,
213
+ requestedSlug: request.slug ?? null,
158
214
  title: request.title,
159
215
  description: request.description ?? null,
160
216
  ...content,
@@ -164,21 +220,24 @@ export function registerWorkspaceArtifactRoutes(app: Hono, deps: ApiRouteDeps):
164
220
  201,
165
221
  );
166
222
  } catch (error) {
167
- return errorResponse(context, error);
223
+ return workspaceArtifactErrorResponse(context, error);
168
224
  }
169
225
  });
170
226
 
171
227
  app.get(`${base}/:artifactId`, async (context) => {
172
228
  const workspaceId = context.req.param("workspaceId");
173
- await requireAccessGrant(context, deps, workspaceId, "artifacts:read");
229
+ const grant = await requireAccessGrant(context, deps, workspaceId, "artifacts:read");
174
230
  try {
231
+ const detail = await getWorkspaceArtifact(deps.db, workspaceId, artifactId(context));
175
232
  return context.json(
176
233
  WorkspaceArtifactDetailResponse.parse(
177
- await getWorkspaceArtifact(deps.db, workspaceId, artifactId(context)),
234
+ await projectWorkspaceArtifactDetailProvenance(detail, (sessionId) =>
235
+ canReadProvenanceSession(deps, grant, sessionId),
236
+ ),
178
237
  ),
179
238
  );
180
239
  } catch (error) {
181
- return errorResponse(context, error);
240
+ return workspaceArtifactErrorResponse(context, error);
182
241
  }
183
242
  });
184
243
 
@@ -186,7 +245,9 @@ export function registerWorkspaceArtifactRoutes(app: Hono, deps: ApiRouteDeps):
186
245
  const workspaceId = context.req.param("workspaceId");
187
246
  await requireAccessGrant(context, deps, workspaceId, "artifacts:read");
188
247
  if (!deps.objectStorage)
189
- throw new HTTPException(503, { message: "Object storage is not configured" });
248
+ throw new HTTPException(503, {
249
+ message: "Object storage is not configured",
250
+ });
190
251
  const parsedVersion = context.req.query("versionId");
191
252
  if (parsedVersion && !ArtifactId.safeParse(parsedVersion).success) {
192
253
  throw new HTTPException(422, { message: "Invalid artifact version id" });
@@ -198,19 +259,14 @@ export function registerWorkspaceArtifactRoutes(app: Hono, deps: ApiRouteDeps):
198
259
  artifactId(context),
199
260
  parsedVersion,
200
261
  );
201
- const object = await retryWhileMissing(async () =>
202
- deps.objectStorage!.getObjectBytes(ref.contentKey),
203
- );
204
- if (!object) throw new HTTPException(503, { message: "Artifact content is unavailable" });
205
- const actualHash = createHash("sha256").update(object.bytes).digest("hex");
206
- if (actualHash !== ref.version.contentSha256) {
207
- throw new HTTPException(503, { message: "Artifact content failed integrity verification" });
208
- }
209
- let html: string;
262
+ let content: Awaited<ReturnType<typeof readWorkspaceArtifactContent>>;
210
263
  try {
211
- html = decoder.decode(object.bytes);
212
- } catch {
213
- throw new HTTPException(503, { message: "Artifact content is not valid UTF-8" });
264
+ content = await readWorkspaceArtifactContent(deps.objectStorage, ref);
265
+ } catch (error) {
266
+ throw new HTTPException(503, {
267
+ message: error instanceof Error ? error.message : "Artifact content is unavailable",
268
+ cause: error,
269
+ });
214
270
  }
215
271
  return context.json(
216
272
  WorkspaceArtifactContentResponse.parse({
@@ -218,23 +274,35 @@ export function registerWorkspaceArtifactRoutes(app: Hono, deps: ApiRouteDeps):
218
274
  versionId: ref.version.id,
219
275
  contentType: "text/html",
220
276
  contentSha256: ref.version.contentSha256,
221
- html,
277
+ ...content,
222
278
  }),
223
279
  );
224
280
  } catch (error) {
225
- return errorResponse(context, error);
281
+ return workspaceArtifactErrorResponse(context, error);
226
282
  }
227
283
  });
228
284
 
229
285
  app.post(`${base}/:artifactId/versions`, async (context) => {
230
286
  const workspaceId = context.req.param("workspaceId");
231
- const grant = await requireAccessGrant(context, deps, workspaceId, "artifacts:publish");
287
+ const authorization = await requireAccessGrantAuthorization(
288
+ context,
289
+ deps,
290
+ workspaceId,
291
+ "artifacts:publish",
292
+ );
293
+ const grant = authorization.grant;
232
294
  const request = await body(context, PublishWorkspaceArtifactVersionRequest);
233
295
  const id = artifactId(context);
234
- const content = prepareHtml(deps, workspaceId, request.html);
296
+ const content = prepareContent(deps, workspaceId, {
297
+ html: request.html,
298
+ ...(request.source ? { source: request.source } : {}),
299
+ ...(request.requestedTools ? { requestedTools: request.requestedTools } : {}),
300
+ });
235
301
  try {
236
302
  return context.json(
237
- WorkspaceArtifactMutationResponse.parse(
303
+ await mutationResponse(
304
+ deps,
305
+ grant,
238
306
  await publishWorkspaceArtifactVersion(deps.db, {
239
307
  accountId: grant.accountId,
240
308
  workspaceId,
@@ -248,21 +316,30 @@ export function registerWorkspaceArtifactRoutes(app: Hono, deps: ApiRouteDeps):
248
316
  ),
249
317
  );
250
318
  } catch (error) {
251
- return errorResponse(context, error);
319
+ return workspaceArtifactErrorResponse(context, error);
252
320
  }
253
321
  });
254
322
 
255
323
  app.post(`${base}/:artifactId/rollback`, async (context) => {
256
324
  const workspaceId = context.req.param("workspaceId");
257
- const grant = await requireAccessGrant(context, deps, workspaceId, "artifacts:publish");
325
+ const authorization = await requireAccessGrantAuthorization(
326
+ context,
327
+ deps,
328
+ workspaceId,
329
+ "artifacts:publish",
330
+ );
331
+ const grant = authorization.grant;
258
332
  const request = await body(context, RollbackWorkspaceArtifactRequest);
333
+ const id = artifactId(context);
259
334
  try {
260
335
  return context.json(
261
- WorkspaceArtifactMutationResponse.parse(
336
+ await mutationResponse(
337
+ deps,
338
+ grant,
262
339
  await rollbackWorkspaceArtifact(deps.db, {
263
340
  accountId: grant.accountId,
264
341
  workspaceId,
265
- artifactId: artifactId(context),
342
+ artifactId: id,
266
343
  versionId: request.versionId,
267
344
  expectedCurrentVersionId: request.expectedCurrentVersionId,
268
345
  reason: request.reason,
@@ -271,7 +348,39 @@ export function registerWorkspaceArtifactRoutes(app: Hono, deps: ApiRouteDeps):
271
348
  ),
272
349
  );
273
350
  } catch (error) {
274
- return errorResponse(context, error);
351
+ return workspaceArtifactErrorResponse(context, error);
352
+ }
353
+ });
354
+
355
+ app.patch(`${base}/:artifactId/status`, async (context) => {
356
+ const workspaceId = context.req.param("workspaceId");
357
+ const authorization = await requireAccessGrantAuthorization(
358
+ context,
359
+ deps,
360
+ workspaceId,
361
+ "artifacts:publish",
362
+ );
363
+ const grant = authorization.grant;
364
+ const request = await body(context, SetWorkspaceArtifactStatusRequest);
365
+ const id = artifactId(context);
366
+ try {
367
+ return context.json(
368
+ await mutationResponse(
369
+ deps,
370
+ grant,
371
+ await setWorkspaceArtifactStatus(deps.db, {
372
+ accountId: grant.accountId,
373
+ workspaceId,
374
+ artifactId: id,
375
+ status: request.status,
376
+ expectedCurrentVersionId: request.expectedCurrentVersionId,
377
+ reason: request.reason,
378
+ ...provenance(grant.subjectId, request.idempotencyKey),
379
+ }),
380
+ ),
381
+ );
382
+ } catch (error) {
383
+ return workspaceArtifactErrorResponse(context, error);
275
384
  }
276
385
  });
277
386
  }