@opengeni/api-router 2.5.0 → 2.6.4-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 (70) hide show
  1. package/dist/app.d.ts +2 -1
  2. package/dist/app.js +3 -1
  3. package/dist/auth/managed-auth-attempt-context.d.ts +5 -1
  4. package/dist/auth/managed-auth.d.ts +24 -1
  5. package/dist/{chunk-QESX7HDK.js → chunk-UOI7KAP3.js} +3240 -791
  6. package/dist/chunk-UOI7KAP3.js.map +1 -0
  7. package/dist/http/sse.d.ts +9 -0
  8. package/dist/index.js +117 -5
  9. package/dist/index.js.map +1 -1
  10. package/dist/interaction-metrics.d.ts +2 -0
  11. package/dist/mcp/company-brain-governed-writes.d.ts +1 -1
  12. package/dist/mcp/company-profile-agent-admin.d.ts +6 -6
  13. package/dist/mcp/remember.d.ts +2 -2
  14. package/dist/mcp/scheduled-task-view.d.ts +3 -3
  15. package/dist/mcp/server.d.ts +1 -0
  16. package/dist/mcp/session-view.d.ts +1 -0
  17. package/dist/mcp/session-wait.d.ts +19 -0
  18. package/dist/model-catalog.d.ts +5 -31
  19. package/dist/routes/api-keys.d.ts +2 -0
  20. package/dist/routes/browser-sessions.d.ts +1 -0
  21. package/dist/routes/computer-sessions.d.ts +12 -0
  22. package/dist/routes/managed-auth-session-sets.d.ts +7 -0
  23. package/dist/routes/workspaces.d.ts +1 -1
  24. package/dist/sandbox/metrics-ingestion.d.ts +16 -0
  25. package/dist/workspace-delete-observability.d.ts +10 -0
  26. package/package.json +19 -19
  27. package/src/app.ts +206 -29
  28. package/src/auth/managed-auth-attempt-context.ts +40 -3
  29. package/src/auth/managed-auth-session-adapter.ts +1 -0
  30. package/src/auth/managed-auth.ts +164 -4
  31. package/src/http/sse.ts +279 -45
  32. package/src/index.ts +13 -1
  33. package/src/integrations/oauth-client.ts +8 -1
  34. package/src/integrations/provider-oauth.ts +12 -2
  35. package/src/integrations/slack-interactions.ts +77 -37
  36. package/src/interaction-metrics.ts +30 -0
  37. package/src/mcp/company-brain-governed-writes.ts +38 -23
  38. package/src/mcp/company-profile-agent-admin.ts +7 -7
  39. package/src/mcp/remember.ts +19 -8
  40. package/src/mcp/server.ts +328 -27
  41. package/src/mcp/session-wait.ts +56 -8
  42. package/src/model-catalog.ts +45 -337
  43. package/src/routes/api-integrations.ts +2 -2
  44. package/src/routes/api-keys.ts +149 -7
  45. package/src/routes/automations.ts +186 -19
  46. package/src/routes/browser-sessions.ts +10 -2
  47. package/src/routes/capabilities.ts +3 -3
  48. package/src/routes/codex.ts +483 -74
  49. package/src/routes/company-profile.ts +64 -0
  50. package/src/routes/computer-sessions.ts +103 -1
  51. package/src/routes/connections.ts +271 -16
  52. package/src/routes/github.ts +0 -8
  53. package/src/routes/integration-facets.ts +8 -5
  54. package/src/routes/interaction-resources.ts +7 -1
  55. package/src/routes/managed-auth-session-sets.ts +199 -2
  56. package/src/routes/organization-memberships.ts +28 -8
  57. package/src/routes/packs.ts +5 -5
  58. package/src/routes/plugins.ts +2 -2
  59. package/src/routes/pr-review.ts +80 -4
  60. package/src/routes/scheduled-tasks.ts +47 -11
  61. package/src/routes/sessions.ts +75 -53
  62. package/src/routes/skills.ts +3 -3
  63. package/src/routes/supergrok.ts +3 -2
  64. package/src/routes/workspaces.ts +665 -56
  65. package/src/sandbox/channel-a.ts +10 -4
  66. package/src/sandbox/machines.ts +13 -6
  67. package/src/sandbox/metrics-ingestion.ts +157 -3
  68. package/src/sandbox/viewer.ts +20 -1
  69. package/src/workspace-delete-observability.ts +75 -0
  70. package/dist/chunk-QESX7HDK.js.map +0 -1
@@ -25,18 +25,26 @@ import {
25
25
  listAutomationRuns,
26
26
  listAutomationSources,
27
27
  listAutomationTriggers,
28
+ lockActiveWorkspaceGatewayCustomModelForAdmission,
29
+ lockActiveWorkspaceOpenRouterCustomModelForAdmission,
28
30
  recordAutomationEvent,
29
31
  resolveAutomationWebhookEndpoint,
30
32
  updateAutomationSource,
31
33
  updateAutomationTrigger,
34
+ withWorkspaceGatewayCustomModelReadLock,
35
+ withWorkspaceOpenRouterCustomModelReadLock,
32
36
  type AutomationSourceSecret,
33
37
  } from "@opengeni/db";
34
38
  import {
35
39
  automationRequestDigest,
40
+ assertWorkspaceModelPolicyAllows,
36
41
  buildAutomationAcceptedExecution,
42
+ canonicalConfiguredModel,
43
+ workspaceCustomModelReference,
37
44
  requireAccessGrant,
38
45
  requireAutomationAdapter,
39
46
  requirePermission,
47
+ resolveWorkspaceCatalogSettings,
40
48
  type ApiRouteDeps,
41
49
  } from "@opengeni/core";
42
50
  import type { Hono } from "hono";
@@ -157,13 +165,36 @@ export function registerAutomationRoutes(app: Hono, deps: ApiRouteDeps): void {
157
165
  for (const permission of request.sessionTemplate.firstPartyMcpPermissions) {
158
166
  requirePermission(grant, permission);
159
167
  }
168
+ const catalogSettings = (
169
+ await resolveWorkspaceCatalogSettings(deps.db, deps.settings, {
170
+ accountId: grant.accountId,
171
+ workspaceId,
172
+ })
173
+ ).settings;
174
+ const model = canonicalConfiguredModel(catalogSettings, request.sessionTemplate.model) ?? null;
175
+ if (model) {
176
+ await assertWorkspaceModelPolicyAllows(deps.db, catalogSettings, workspaceId, model);
177
+ }
178
+ const normalizedRequest = {
179
+ ...request,
180
+ sessionTemplate: { ...request.sessionTemplate, model },
181
+ };
182
+ const beforeCreateCommit = model
183
+ ? workspaceCustomModelCommitGuard({
184
+ settings: catalogSettings,
185
+ accountId: grant.accountId,
186
+ workspaceId,
187
+ modelId: model,
188
+ })
189
+ : undefined;
160
190
  return c.json(
161
191
  await createAutomationTrigger(deps.db, {
162
192
  accountId: grant.accountId,
163
193
  workspaceId,
164
194
  createdBySubjectId: grant.subjectId,
165
- request,
195
+ request: normalizedRequest,
166
196
  adapterId: adapter.id,
197
+ ...(beforeCreateCommit ? { beforeCreateCommit } : {}),
167
198
  }),
168
199
  201,
169
200
  );
@@ -194,12 +225,54 @@ export function registerAutomationRoutes(app: Hono, deps: ApiRouteDeps): void {
194
225
  requirePermission(grant, permission);
195
226
  }
196
227
  }
228
+ const catalogSettings = (
229
+ await resolveWorkspaceCatalogSettings(deps.db, deps.settings, {
230
+ accountId: grant.accountId,
231
+ workspaceId,
232
+ })
233
+ ).settings;
234
+ const normalizedRequest = request.sessionTemplate
235
+ ? {
236
+ ...request,
237
+ sessionTemplate: {
238
+ ...request.sessionTemplate,
239
+ model: canonicalConfiguredModel(catalogSettings, request.sessionTemplate.model) ?? null,
240
+ },
241
+ }
242
+ : request;
243
+ if (normalizedRequest.sessionTemplate?.model) {
244
+ await assertWorkspaceModelPolicyAllows(
245
+ deps.db,
246
+ catalogSettings,
247
+ workspaceId,
248
+ normalizedRequest.sessionTemplate.model,
249
+ );
250
+ }
251
+ const effectiveModel = normalizedRequest.sessionTemplate
252
+ ? normalizedRequest.sessionTemplate.model
253
+ : existing.sessionTemplate.model;
254
+ const materialExecutionChange =
255
+ request.eventTypes !== undefined ||
256
+ request.configuration !== undefined ||
257
+ request.parameters !== undefined ||
258
+ request.sessionTemplate !== undefined ||
259
+ (request.status === "active" && existing.status !== "active");
260
+ const beforeUpdateCommit =
261
+ materialExecutionChange && effectiveModel
262
+ ? workspaceCustomModelCommitGuard({
263
+ settings: catalogSettings,
264
+ accountId: grant.accountId,
265
+ workspaceId,
266
+ modelId: effectiveModel,
267
+ })
268
+ : undefined;
197
269
  try {
198
270
  const trigger = await updateAutomationTrigger(deps.db, {
199
271
  workspaceId,
200
272
  triggerId: c.req.param("triggerId"),
201
273
  subjectId: grant.subjectId,
202
- request,
274
+ request: normalizedRequest,
275
+ ...(beforeUpdateCommit ? { beforeUpdateCommit } : {}),
203
276
  });
204
277
  if (!trigger)
205
278
  throw new HTTPException(404, {
@@ -377,29 +450,86 @@ export async function acceptAutomationEvent(
377
450
  workspaceId: source.workspaceId,
378
451
  sourceId: source.id,
379
452
  });
380
- const matchingAtAcceptance = triggers.filter((trigger) =>
453
+ const matchingByEvent = triggers.filter((trigger) =>
381
454
  adapter.matches({ event: input.normalizedEvent, trigger }),
382
455
  );
383
- if (matchingAtAcceptance.length > AUTOMATION_MAX_MATCHED_TRIGGERS) {
456
+ if (matchingByEvent.length > AUTOMATION_MAX_MATCHED_TRIGGERS) {
384
457
  throw new HTTPException(422, {
385
458
  message: `automation event matches more than ${AUTOMATION_MAX_MATCHED_TRIGGERS} triggers`,
386
459
  });
387
460
  }
388
- const stored = await recordAutomationEvent(deps.db, {
389
- accountId: source.accountId,
390
- workspaceId: source.workspaceId,
391
- sourceId: source.id,
392
- sourceVersion: source.version,
393
- sourceConfiguration: source.configuration,
394
- matchedTriggerRevisions: matchingAtAcceptance.map((trigger) => ({
395
- triggerId: trigger.id,
396
- revision: trigger.revision,
397
- })),
398
- deliveryKey: input.deliveryKey,
399
- requestDigest: input.requestDigest,
400
- normalizedEvent: input.normalizedEvent,
401
- ignoredReason: matchingAtAcceptance.length === 0 ? "no_matching_triggers" : null,
402
- });
461
+ const stored =
462
+ matchingByEvent.length === 0
463
+ ? await recordAutomationEvent(deps.db, {
464
+ accountId: source.accountId,
465
+ workspaceId: source.workspaceId,
466
+ sourceId: source.id,
467
+ sourceVersion: source.version,
468
+ sourceConfiguration: source.configuration,
469
+ matchedTriggerRevisions: [],
470
+ deliveryKey: input.deliveryKey,
471
+ requestDigest: input.requestDigest,
472
+ normalizedEvent: input.normalizedEvent,
473
+ ignoredReason: "no_matching_triggers",
474
+ })
475
+ : await withWorkspaceGatewayCustomModelReadLock(
476
+ deps.db,
477
+ { accountId: source.accountId, workspaceId: source.workspaceId },
478
+ async (gatewayLockedDb) =>
479
+ await withWorkspaceOpenRouterCustomModelReadLock(
480
+ gatewayLockedDb,
481
+ { accountId: source.accountId, workspaceId: source.workspaceId },
482
+ async (lockedDb) => {
483
+ const catalogSourceSettings = deps.catalogSourceSettings ?? deps.settings;
484
+ const catalogSettings = (
485
+ await resolveWorkspaceCatalogSettings(lockedDb, catalogSourceSettings, {
486
+ accountId: source.accountId,
487
+ workspaceId: source.workspaceId,
488
+ })
489
+ ).settings;
490
+ const matchingAtAcceptance = [];
491
+ for (const trigger of matchingByEvent) {
492
+ try {
493
+ const render = adapter.render({
494
+ event: input.normalizedEvent,
495
+ trigger,
496
+ source,
497
+ });
498
+ const requestedModel =
499
+ render.sessionTemplate.model ?? catalogSettings.openaiModel;
500
+ const model = canonicalConfiguredModel(catalogSettings, requestedModel);
501
+ if (!model) continue;
502
+ await assertWorkspaceModelPolicyAllows(
503
+ lockedDb,
504
+ catalogSettings,
505
+ source.workspaceId,
506
+ model,
507
+ );
508
+ matchingAtAcceptance.push(trigger);
509
+ } catch (error) {
510
+ if (isUnprocessableEntity(error)) continue;
511
+ throw error;
512
+ }
513
+ }
514
+ return await recordAutomationEvent(lockedDb, {
515
+ accountId: source.accountId,
516
+ workspaceId: source.workspaceId,
517
+ sourceId: source.id,
518
+ sourceVersion: source.version,
519
+ sourceConfiguration: source.configuration,
520
+ matchedTriggerRevisions: matchingAtAcceptance.map((trigger) => ({
521
+ triggerId: trigger.id,
522
+ revision: trigger.revision,
523
+ })),
524
+ deliveryKey: input.deliveryKey,
525
+ requestDigest: input.requestDigest,
526
+ normalizedEvent: input.normalizedEvent,
527
+ ignoredReason:
528
+ matchingAtAcceptance.length === 0 ? "no_executable_triggers" : null,
529
+ });
530
+ },
531
+ ),
532
+ );
403
533
  const matching = await getAutomationTriggerRevisions(deps.db, {
404
534
  workspaceId: source.workspaceId,
405
535
  refs: stored.event.matchedTriggerRevisions,
@@ -457,6 +587,43 @@ export async function acceptAutomationEvent(
457
587
  });
458
588
  }
459
589
 
590
+ function isUnprocessableEntity(error: unknown): boolean {
591
+ return (
592
+ error instanceof Error &&
593
+ "status" in error &&
594
+ (error as Error & { status?: unknown }).status === 422
595
+ );
596
+ }
597
+
598
+ function workspaceCustomModelCommitGuard(input: {
599
+ settings: ApiRouteDeps["settings"];
600
+ accountId: string;
601
+ workspaceId: string;
602
+ modelId: string;
603
+ }): ((tx: ApiRouteDeps["db"]) => Promise<void>) | undefined {
604
+ const reference = workspaceCustomModelReference(input.settings, input.modelId);
605
+ if (!reference) return undefined;
606
+ return async (tx): Promise<void> => {
607
+ const active =
608
+ reference.providerKind === "openrouter"
609
+ ? await lockActiveWorkspaceOpenRouterCustomModelForAdmission(tx, {
610
+ accountId: input.accountId,
611
+ workspaceId: input.workspaceId,
612
+ upstreamModelId: reference.upstreamModelId,
613
+ })
614
+ : await lockActiveWorkspaceGatewayCustomModelForAdmission(tx, {
615
+ accountId: input.accountId,
616
+ workspaceId: input.workspaceId,
617
+ upstreamModelId: reference.upstreamModelId,
618
+ });
619
+ if (!active) {
620
+ throw new HTTPException(422, {
621
+ message: `model is not available: ${input.modelId}`,
622
+ });
623
+ }
624
+ };
625
+ }
626
+
460
627
  async function requireSource(
461
628
  deps: ApiRouteDeps,
462
629
  accountId: string,
@@ -3435,7 +3435,14 @@ async function ensureHeadedBrowserDisplayStack(
3435
3435
  headless: boolean,
3436
3436
  linkedComputer: boolean,
3437
3437
  ): Promise<void> {
3438
- if (!browserNeedsStandaloneDisplayStack({ headless, linkedComputer })) return;
3438
+ if (
3439
+ !browserNeedsStandaloneDisplayStack({
3440
+ headless,
3441
+ linkedComputer,
3442
+ nativeBrowserControl: typeof session.ensureBrowserControl === "function",
3443
+ })
3444
+ )
3445
+ return;
3439
3446
  if (typeof session.exec !== "function" && typeof session.execCommand !== "function") return;
3440
3447
  await ensureDisplayStack(session, {
3441
3448
  telemetryContext: { callerKind: "viewer" },
@@ -3445,8 +3452,9 @@ async function ensureHeadedBrowserDisplayStack(
3445
3452
  export function browserNeedsStandaloneDisplayStack(input: {
3446
3453
  headless: boolean;
3447
3454
  linkedComputer: boolean;
3455
+ nativeBrowserControl: boolean;
3448
3456
  }): boolean {
3449
- return !input.headless && !input.linkedComputer;
3457
+ return !input.headless && !input.linkedComputer && !input.nativeBrowserControl;
3450
3458
  }
3451
3459
 
3452
3460
  async function ensureLinkedComputerController(
@@ -32,7 +32,7 @@ export function registerCapabilityRoutes(app: Hono, deps: ApiRouteDeps): void {
32
32
 
33
33
  app.post("/v1/workspaces/:workspaceId/capabilities", async (c) => {
34
34
  const workspaceId = c.req.param("workspaceId");
35
- const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
35
+ const grant = await requireAccessGrant(c, deps, workspaceId, "capabilities:manage");
36
36
  const payload = CreateCapabilityCatalogItemRequest.parse(await c.req.json());
37
37
  return c.json(
38
38
  await createCatalogItem({ db, accountId: grant.accountId, workspaceId, payload }),
@@ -62,7 +62,7 @@ export function registerCapabilityRoutes(app: Hono, deps: ApiRouteDeps): void {
62
62
 
63
63
  app.post("/v1/workspaces/:workspaceId/capabilities/:capabilityId/enable", async (c) => {
64
64
  const workspaceId = c.req.param("workspaceId");
65
- const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
65
+ const grant = await requireAccessGrant(c, deps, workspaceId, "capabilities:manage");
66
66
  const payload = EnableCapabilityRequest.parse(await c.req.json());
67
67
  const installation = await enableCapability({
68
68
  db,
@@ -78,7 +78,7 @@ export function registerCapabilityRoutes(app: Hono, deps: ApiRouteDeps): void {
78
78
 
79
79
  app.post("/v1/workspaces/:workspaceId/capabilities/:capabilityId/disable", async (c) => {
80
80
  const workspaceId = c.req.param("workspaceId");
81
- const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
81
+ const grant = await requireAccessGrant(c, deps, workspaceId, "capabilities:manage");
82
82
  const installation = await disableCapability({
83
83
  db,
84
84
  accountId: grant.accountId,