@opengeni/api-router 0.2.2 → 0.4.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-I2EDWHVF.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";
@@ -928,9 +929,17 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, json) {
928
929
  description: "Spawn a new agent session (a worker) with an initial message and optional goal, resources (e.g. repositories from github_repositories_list), tools, and workspace environment attachment. Environment attachment happens at creation only \u2014 it cannot be added to a running session \u2014 and requires the environments:use permission. When targetSandboxId names a machine, workingDir sets the working directory (cwd) the spawned session runs under on that machine.",
929
930
  inputSchema: {
930
931
  initialMessage: z4.string().min(1),
932
+ // Per-session agent persona/system instructions for the spawned worker
933
+ // (a per-agent-type prompt). Delivered system-level, composed AFTER the
934
+ // workspace persona; never shown in the worker's timeline. Trimmed,
935
+ // non-empty, max 32768 chars (re-validated by the contracts schema).
936
+ instructions: z4.string().min(1).max(32768).optional(),
931
937
  goal: z4.unknown().optional(),
932
938
  resources: z4.array(z4.unknown()).optional(),
933
939
  tools: z4.array(z4.unknown()).optional(),
940
+ // Per-session third-party MCP servers. Credential header values are
941
+ // accepted only at create and never appear in responses/events.
942
+ mcpServers: z4.array(z4.unknown()).optional(),
934
943
  environmentId: z4.string().uuid().optional(),
935
944
  model: z4.string().min(1).optional(),
936
945
  reasoningEffort: z4.string().optional(),
@@ -989,12 +998,16 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, json) {
989
998
  description: "Post a user message into an existing session; the session queues a turn and resumes if idle.",
990
999
  inputSchema: {
991
1000
  sessionId: z4.string().uuid(),
992
- text: z4.string().min(1)
1001
+ text: z4.string().min(1),
1002
+ // Header-value rotation only. URL/name/tool settings are immutable
1003
+ // after create; core enforces mcp_servers:attach on this field.
1004
+ mcpCredentialUpdates: z4.array(z4.unknown()).optional()
993
1005
  }
994
- }, async ({ sessionId, text }) => {
1006
+ }, async ({ sessionId, text, mcpCredentialUpdates }) => {
995
1007
  const { accepted, turn } = await acceptSessionUserMessage(deps, grant, grant.workspaceId, sessionId, {
996
1008
  text,
997
- toolsProvided: false
1009
+ toolsProvided: false,
1010
+ mcpCredentialUpdates: (mcpCredentialUpdates ?? []).map((update) => SessionMcpCredentialUpdateInput.parse(update))
998
1011
  });
999
1012
  return json({ event: accepted, turnId: turn.id });
1000
1013
  });
@@ -4257,7 +4270,7 @@ import {
4257
4270
  updatePtySessionActivity,
4258
4271
  updateQueuedSessionTurn
4259
4272
  } from "@opengeni/db";
4260
- import { appendAndPublishEvents as appendAndPublishEvents4 } from "@opengeni/events";
4273
+ import { appendAndPublishEvents as appendAndPublishEvents4, coalesceSessionEventDeltas } from "@opengeni/events";
4261
4274
 
4262
4275
  // src/sandbox/channel-a.ts
4263
4276
  import { applyGitAuthPointerEnvironment, hasGitHubRepositorySelection, stableSandboxEnvironmentForRun } from "@opengeni/config";
@@ -4579,6 +4592,12 @@ function viewerHeartbeatIntervalMs(settings) {
4579
4592
  async function dropEstablishedHandle2(established) {
4580
4593
  void established;
4581
4594
  }
4595
+ function resolveActiveDesktopTransport(selfhostedActive, interactive) {
4596
+ if (selfhostedActive) {
4597
+ return { transport: "relay-frames", client: "frames", mode: "read-only" };
4598
+ }
4599
+ return { transport: "vnc-ws", client: "novnc", mode: interactive ? "interactive" : "read-only" };
4600
+ }
4582
4601
  async function mintDesktopStream(services, input) {
4583
4602
  const { db, settings, bus } = services;
4584
4603
  const { accountId, workspaceId, session } = input;
@@ -4883,7 +4902,7 @@ function viewerIdAsUuid(rawViewerId) {
4883
4902
  }
4884
4903
 
4885
4904
  // src/routes/sessions.ts
4886
- import { settingsWithEnabledCapabilityMcpServers } from "@opengeni/core";
4905
+ import { settingsWithEnabledCapabilityMcpServers, settingsWithSessionMcpServerMetadata } from "@opengeni/core";
4887
4906
  import {
4888
4907
  normalizeResources,
4889
4908
  validateFileResources,
@@ -5138,9 +5157,16 @@ function registerSessionRoutes(app, deps) {
5138
5157
  await requireAccessGrant12(c, deps, workspaceId, "sessions:read");
5139
5158
  const sessionId = c.req.param("sessionId");
5140
5159
  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));
5160
+ const after = eventSequence(c.req.query("after"), 0);
5161
+ const before = optionalEventSequence(c.req.query("before"));
5162
+ const compact = compactEvents(c.req.query("compact"));
5163
+ const limit = eventListLimit(c.req.query("limit"), compact ? 5e3 : 2e3);
5164
+ const events = await listSessionEvents3(db, workspaceId, sessionId, {
5165
+ after,
5166
+ ...before !== void 0 ? { before } : {},
5167
+ limit
5168
+ });
5169
+ return c.json(compact ? coalesceSessionEventDeltas(events) : events);
5144
5170
  });
5145
5171
  app.get("/v1/workspaces/:workspaceId/sessions/:sessionId/events/stream", async (c) => {
5146
5172
  const workspaceId = c.req.param("workspaceId");
@@ -5166,14 +5192,17 @@ function registerSessionRoutes(app, deps) {
5166
5192
  const existing = await requireQueuedTurnForApi(db, workspaceId, sessionId, turnId);
5167
5193
  const payload = UpdateSessionTurnRequest.parse(await c.req.json());
5168
5194
  assertConfiguredModel(settings, payload.model);
5169
- const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
5195
+ const session = await requireSession2(db, workspaceId, sessionId);
5196
+ const runtimeSettings = settingsWithSessionMcpServerMetadata(
5197
+ await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings),
5198
+ session.mcpServers
5199
+ );
5170
5200
  const resources = payload.resources !== void 0 ? normalizeResources(payload.resources) : existing.resources;
5171
5201
  const tools = payload.tools !== void 0 ? validateToolRefs(payload.tools, runtimeSettings) : existing.tools;
5172
5202
  if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
5173
5203
  throw new HTTPException15(503, { message: "object storage is not configured" });
5174
5204
  }
5175
5205
  await validateFileResources(db, workspaceId, resources);
5176
- const session = await requireSession2(db, workspaceId, sessionId);
5177
5206
  await validateGitHubRepositorySelection(db, workspaceId, [...session.resources, ...resources]);
5178
5207
  const turn = await updateQueuedSessionTurn(db, workspaceId, turnId, {
5179
5208
  ...payload.prompt !== void 0 ? { prompt: payload.prompt.trim() } : {},
@@ -5234,6 +5263,7 @@ function registerSessionRoutes(app, deps) {
5234
5263
  toolsProvided: userMessagePayloadHasOwnProperty(rawEvent, "tools"),
5235
5264
  model: event.payload.model ?? null,
5236
5265
  reasoningEffort: event.payload.reasoningEffort ?? null,
5266
+ mcpCredentialUpdates: event.payload.mcpCredentialUpdates ?? [],
5237
5267
  ...event.clientEventId ? { clientEventId: event.clientEventId } : {}
5238
5268
  });
5239
5269
  return c.json(accepted2, 202);
@@ -5361,19 +5391,13 @@ function registerSessionRoutes(app, deps) {
5361
5391
  } : {}
5362
5392
  });
5363
5393
  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
- }
5394
+ if (capabilities.DesktopStream.transport !== null) {
5395
+ const activeSandbox = session.activeSandboxId ? await getSandbox2(db, workspaceId, session.activeSandboxId) : null;
5396
+ const wire = resolveActiveDesktopTransport(activeSandbox?.kind === "selfhosted", settings.sandboxDesktopInteractive !== false);
5397
+ responseCapabilities = {
5398
+ ...capabilities,
5399
+ DesktopStream: { ...capabilities.DesktopStream, ...wire }
5400
+ };
5377
5401
  }
5378
5402
  return c.json(responseCapabilities);
5379
5403
  });
@@ -5497,8 +5521,13 @@ function registerSessionRoutes(app, deps) {
5497
5521
  streamToken: stream?.token ?? null,
5498
5522
  streamExpiresAt: stream?.expiresAt ?? null,
5499
5523
  resolution: stream?.resolution ?? null,
5500
- transport: stream ? "vnc-ws" : null,
5501
- client: stream ? "novnc" : null,
5524
+ // Transport MUST match where the pixels were minted: a selfhosted-active box
5525
+ // serves the RELAY framebuffer (relay-frames/frames), a Modal box serves noVNC
5526
+ // (vnc-ws/novnc). Hardcoding vnc-ws here handed a machine's relay URL to the
5527
+ // noVNC renderer (and vice-versa on the swap-away case) → "closed before it
5528
+ // opened". Key off the SAME selfhostedActive the mint routed on.
5529
+ transport: stream ? selfhostedActive ? "relay-frames" : "vnc-ws" : null,
5530
+ client: stream ? selfhostedActive ? "frames" : "novnc" : null,
5502
5531
  // The REAL PTY terminal address (pty-ws), null when degraded.
5503
5532
  terminalUrl: terminal?.url ?? null,
5504
5533
  terminalToken: terminal?.token ?? null,
@@ -5727,6 +5756,33 @@ function registerSessionRoutes(app, deps) {
5727
5756
  return c.body(null, 204);
5728
5757
  });
5729
5758
  }
5759
+ function eventListLimit(raw, max = 2e3) {
5760
+ const limit = Number(raw ?? 500);
5761
+ if (!Number.isFinite(limit)) {
5762
+ return 500;
5763
+ }
5764
+ return Math.min(max, Math.max(1, Math.floor(limit)));
5765
+ }
5766
+ function compactEvents(raw) {
5767
+ return raw === "1" || raw === "true";
5768
+ }
5769
+ function eventSequence(raw, fallback) {
5770
+ const sequence = Number(raw ?? fallback);
5771
+ if (!Number.isFinite(sequence)) {
5772
+ return fallback;
5773
+ }
5774
+ return Math.floor(sequence);
5775
+ }
5776
+ function optionalEventSequence(raw) {
5777
+ if (raw === void 0) {
5778
+ return void 0;
5779
+ }
5780
+ const sequence = Number(raw);
5781
+ if (!Number.isFinite(sequence)) {
5782
+ return void 0;
5783
+ }
5784
+ return Math.floor(sequence);
5785
+ }
5730
5786
  function hasOwnProperty(value, key) {
5731
5787
  return Boolean(value && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, key));
5732
5788
  }
@@ -6328,4 +6384,4 @@ export {
6328
6384
  withDefaultEnabledCapabilityMcpTools,
6329
6385
  workflowIdForSession3 as workflowIdForSession
6330
6386
  };
6331
- //# sourceMappingURL=chunk-XSYUDIX3.js.map
6387
+ //# sourceMappingURL=chunk-I2EDWHVF.js.map