@opengeni/api-router 0.2.1 → 0.3.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.
package/dist/app.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  validateToolRefs,
15
15
  withDefaultEnabledCapabilityMcpTools,
16
16
  workflowIdForSession
17
- } from "./chunk-XSYUDIX3.js";
17
+ } from "./chunk-SVBM6RM6.js";
18
18
  export {
19
19
  allowedCorsOrigin,
20
20
  createApp,
@@ -267,6 +267,7 @@ import { requireLimit as requireLimit8 } from "@opengeni/core";
267
267
  // src/mcp/server.ts
268
268
  import {
269
269
  CreateScheduledTaskRequest,
270
+ SessionMcpCredentialUpdateInput,
270
271
  WorkspaceEnvironmentVariableName,
271
272
  UpdateScheduledTaskRequest
272
273
  } from "@opengeni/contracts";
@@ -931,6 +932,9 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, json) {
931
932
  goal: z4.unknown().optional(),
932
933
  resources: z4.array(z4.unknown()).optional(),
933
934
  tools: z4.array(z4.unknown()).optional(),
935
+ // Per-session third-party MCP servers. Credential header values are
936
+ // accepted only at create and never appear in responses/events.
937
+ mcpServers: z4.array(z4.unknown()).optional(),
934
938
  environmentId: z4.string().uuid().optional(),
935
939
  model: z4.string().min(1).optional(),
936
940
  reasoningEffort: z4.string().optional(),
@@ -989,12 +993,16 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, json) {
989
993
  description: "Post a user message into an existing session; the session queues a turn and resumes if idle.",
990
994
  inputSchema: {
991
995
  sessionId: z4.string().uuid(),
992
- text: z4.string().min(1)
996
+ text: z4.string().min(1),
997
+ // Header-value rotation only. URL/name/tool settings are immutable
998
+ // after create; core enforces mcp_servers:attach on this field.
999
+ mcpCredentialUpdates: z4.array(z4.unknown()).optional()
993
1000
  }
994
- }, async ({ sessionId, text }) => {
1001
+ }, async ({ sessionId, text, mcpCredentialUpdates }) => {
995
1002
  const { accepted, turn } = await acceptSessionUserMessage(deps, grant, grant.workspaceId, sessionId, {
996
1003
  text,
997
- toolsProvided: false
1004
+ toolsProvided: false,
1005
+ mcpCredentialUpdates: (mcpCredentialUpdates ?? []).map((update) => SessionMcpCredentialUpdateInput.parse(update))
998
1006
  });
999
1007
  return json({ event: accepted, turnId: turn.id });
1000
1008
  });
@@ -4257,7 +4265,7 @@ import {
4257
4265
  updatePtySessionActivity,
4258
4266
  updateQueuedSessionTurn
4259
4267
  } from "@opengeni/db";
4260
- import { appendAndPublishEvents as appendAndPublishEvents4 } from "@opengeni/events";
4268
+ import { appendAndPublishEvents as appendAndPublishEvents4, coalesceSessionEventDeltas } from "@opengeni/events";
4261
4269
 
4262
4270
  // src/sandbox/channel-a.ts
4263
4271
  import { applyGitAuthPointerEnvironment, hasGitHubRepositorySelection, stableSandboxEnvironmentForRun } from "@opengeni/config";
@@ -4579,6 +4587,12 @@ function viewerHeartbeatIntervalMs(settings) {
4579
4587
  async function dropEstablishedHandle2(established) {
4580
4588
  void established;
4581
4589
  }
4590
+ function resolveActiveDesktopTransport(selfhostedActive, interactive) {
4591
+ if (selfhostedActive) {
4592
+ return { transport: "relay-frames", client: "frames", mode: "read-only" };
4593
+ }
4594
+ return { transport: "vnc-ws", client: "novnc", mode: interactive ? "interactive" : "read-only" };
4595
+ }
4582
4596
  async function mintDesktopStream(services, input) {
4583
4597
  const { db, settings, bus } = services;
4584
4598
  const { accountId, workspaceId, session } = input;
@@ -4883,7 +4897,7 @@ function viewerIdAsUuid(rawViewerId) {
4883
4897
  }
4884
4898
 
4885
4899
  // src/routes/sessions.ts
4886
- import { settingsWithEnabledCapabilityMcpServers } from "@opengeni/core";
4900
+ import { settingsWithEnabledCapabilityMcpServers, settingsWithSessionMcpServerMetadata } from "@opengeni/core";
4887
4901
  import {
4888
4902
  normalizeResources,
4889
4903
  validateFileResources,
@@ -5138,9 +5152,16 @@ function registerSessionRoutes(app, deps) {
5138
5152
  await requireAccessGrant12(c, deps, workspaceId, "sessions:read");
5139
5153
  const sessionId = c.req.param("sessionId");
5140
5154
  await assertSessionExists(db, workspaceId, sessionId);
5141
- const after = Number(c.req.query("after") ?? 0);
5142
- const limit = Number(c.req.query("limit") ?? 500);
5143
- return c.json(await listSessionEvents3(db, workspaceId, sessionId, Number.isFinite(after) ? after : 0, Number.isFinite(limit) ? limit : 500));
5155
+ const after = eventSequence(c.req.query("after"), 0);
5156
+ const before = optionalEventSequence(c.req.query("before"));
5157
+ const compact = compactEvents(c.req.query("compact"));
5158
+ const limit = eventListLimit(c.req.query("limit"), compact ? 5e3 : 2e3);
5159
+ const events = await listSessionEvents3(db, workspaceId, sessionId, {
5160
+ after,
5161
+ ...before !== void 0 ? { before } : {},
5162
+ limit
5163
+ });
5164
+ return c.json(compact ? coalesceSessionEventDeltas(events) : events);
5144
5165
  });
5145
5166
  app.get("/v1/workspaces/:workspaceId/sessions/:sessionId/events/stream", async (c) => {
5146
5167
  const workspaceId = c.req.param("workspaceId");
@@ -5166,14 +5187,17 @@ function registerSessionRoutes(app, deps) {
5166
5187
  const existing = await requireQueuedTurnForApi(db, workspaceId, sessionId, turnId);
5167
5188
  const payload = UpdateSessionTurnRequest.parse(await c.req.json());
5168
5189
  assertConfiguredModel(settings, payload.model);
5169
- const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
5190
+ const session = await requireSession2(db, workspaceId, sessionId);
5191
+ const runtimeSettings = settingsWithSessionMcpServerMetadata(
5192
+ await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings),
5193
+ session.mcpServers
5194
+ );
5170
5195
  const resources = payload.resources !== void 0 ? normalizeResources(payload.resources) : existing.resources;
5171
5196
  const tools = payload.tools !== void 0 ? validateToolRefs(payload.tools, runtimeSettings) : existing.tools;
5172
5197
  if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
5173
5198
  throw new HTTPException15(503, { message: "object storage is not configured" });
5174
5199
  }
5175
5200
  await validateFileResources(db, workspaceId, resources);
5176
- const session = await requireSession2(db, workspaceId, sessionId);
5177
5201
  await validateGitHubRepositorySelection(db, workspaceId, [...session.resources, ...resources]);
5178
5202
  const turn = await updateQueuedSessionTurn(db, workspaceId, turnId, {
5179
5203
  ...payload.prompt !== void 0 ? { prompt: payload.prompt.trim() } : {},
@@ -5234,6 +5258,7 @@ function registerSessionRoutes(app, deps) {
5234
5258
  toolsProvided: userMessagePayloadHasOwnProperty(rawEvent, "tools"),
5235
5259
  model: event.payload.model ?? null,
5236
5260
  reasoningEffort: event.payload.reasoningEffort ?? null,
5261
+ mcpCredentialUpdates: event.payload.mcpCredentialUpdates ?? [],
5237
5262
  ...event.clientEventId ? { clientEventId: event.clientEventId } : {}
5238
5263
  });
5239
5264
  return c.json(accepted2, 202);
@@ -5361,19 +5386,13 @@ function registerSessionRoutes(app, deps) {
5361
5386
  } : {}
5362
5387
  });
5363
5388
  let responseCapabilities = capabilities;
5364
- if (session.activeSandboxId && capabilities.DesktopStream.transport === "vnc-ws") {
5365
- const activeSandbox = await getSandbox2(db, workspaceId, session.activeSandboxId);
5366
- if (activeSandbox?.kind === "selfhosted") {
5367
- responseCapabilities = {
5368
- ...capabilities,
5369
- DesktopStream: {
5370
- ...capabilities.DesktopStream,
5371
- transport: "relay-frames",
5372
- client: "frames",
5373
- mode: "read-only"
5374
- }
5375
- };
5376
- }
5389
+ if (capabilities.DesktopStream.transport !== null) {
5390
+ const activeSandbox = session.activeSandboxId ? await getSandbox2(db, workspaceId, session.activeSandboxId) : null;
5391
+ const wire = resolveActiveDesktopTransport(activeSandbox?.kind === "selfhosted", settings.sandboxDesktopInteractive !== false);
5392
+ responseCapabilities = {
5393
+ ...capabilities,
5394
+ DesktopStream: { ...capabilities.DesktopStream, ...wire }
5395
+ };
5377
5396
  }
5378
5397
  return c.json(responseCapabilities);
5379
5398
  });
@@ -5497,8 +5516,13 @@ function registerSessionRoutes(app, deps) {
5497
5516
  streamToken: stream?.token ?? null,
5498
5517
  streamExpiresAt: stream?.expiresAt ?? null,
5499
5518
  resolution: stream?.resolution ?? null,
5500
- transport: stream ? "vnc-ws" : null,
5501
- client: stream ? "novnc" : null,
5519
+ // Transport MUST match where the pixels were minted: a selfhosted-active box
5520
+ // serves the RELAY framebuffer (relay-frames/frames), a Modal box serves noVNC
5521
+ // (vnc-ws/novnc). Hardcoding vnc-ws here handed a machine's relay URL to the
5522
+ // noVNC renderer (and vice-versa on the swap-away case) → "closed before it
5523
+ // opened". Key off the SAME selfhostedActive the mint routed on.
5524
+ transport: stream ? selfhostedActive ? "relay-frames" : "vnc-ws" : null,
5525
+ client: stream ? selfhostedActive ? "frames" : "novnc" : null,
5502
5526
  // The REAL PTY terminal address (pty-ws), null when degraded.
5503
5527
  terminalUrl: terminal?.url ?? null,
5504
5528
  terminalToken: terminal?.token ?? null,
@@ -5727,6 +5751,33 @@ function registerSessionRoutes(app, deps) {
5727
5751
  return c.body(null, 204);
5728
5752
  });
5729
5753
  }
5754
+ function eventListLimit(raw, max = 2e3) {
5755
+ const limit = Number(raw ?? 500);
5756
+ if (!Number.isFinite(limit)) {
5757
+ return 500;
5758
+ }
5759
+ return Math.min(max, Math.max(1, Math.floor(limit)));
5760
+ }
5761
+ function compactEvents(raw) {
5762
+ return raw === "1" || raw === "true";
5763
+ }
5764
+ function eventSequence(raw, fallback) {
5765
+ const sequence = Number(raw ?? fallback);
5766
+ if (!Number.isFinite(sequence)) {
5767
+ return fallback;
5768
+ }
5769
+ return Math.floor(sequence);
5770
+ }
5771
+ function optionalEventSequence(raw) {
5772
+ if (raw === void 0) {
5773
+ return void 0;
5774
+ }
5775
+ const sequence = Number(raw);
5776
+ if (!Number.isFinite(sequence)) {
5777
+ return void 0;
5778
+ }
5779
+ return Math.floor(sequence);
5780
+ }
5730
5781
  function hasOwnProperty(value, key) {
5731
5782
  return Boolean(value && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, key));
5732
5783
  }
@@ -6328,4 +6379,4 @@ export {
6328
6379
  withDefaultEnabledCapabilityMcpTools,
6329
6380
  workflowIdForSession3 as workflowIdForSession
6330
6381
  };
6331
- //# sourceMappingURL=chunk-XSYUDIX3.js.map
6382
+ //# sourceMappingURL=chunk-SVBM6RM6.js.map