@opengeni/api-router 0.15.4 → 0.15.6

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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createAppComposition,
3
3
  startSlackInteractionPump
4
- } from "./chunk-ICRZC3JH.js";
4
+ } from "./chunk-MN7BW3UV.js";
5
5
 
6
6
  // src/index.ts
7
7
  import {
@@ -2,11 +2,13 @@ import { type Settings } from "@opengeni/config";
2
2
  import type { Session } from "@opengeni/contracts";
3
3
  import { type Database, type LeaseSnapshot } from "@opengeni/db";
4
4
  import { type EventBus } from "@opengeni/events";
5
+ import { type Observability } from "@opengeni/observability";
5
6
  import { SandboxChannelAService, type RoutingSandboxSession } from "@opengeni/runtime/sandbox";
6
7
  export type ChannelAServices = {
7
8
  db: Database;
8
9
  settings: Settings;
9
10
  bus: EventBus;
11
+ observability?: Observability | undefined;
10
12
  };
11
13
  export type ChannelAContext = {
12
14
  accountId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/api-router",
3
- "version": "0.15.4",
3
+ "version": "0.15.6",
4
4
  "description": "OpenGeni HTTP surface: the Hono adapter/router (createApp), routes, MCP HTTP transport, and HTTP access adapters over @opengeni/core. An engine-distribution surface — its runtime closure includes engine-internal packages.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -46,14 +46,14 @@
46
46
  "@opengeni/codex": "^0.2.9",
47
47
  "@opengeni/config": "^0.9.1",
48
48
  "@opengeni/contracts": "^0.28.1",
49
- "@opengeni/core": "^0.16.1",
50
- "@opengeni/db": "^0.18.1",
51
- "@opengeni/documents": "^0.2.63",
52
- "@opengeni/events": "^0.3.54",
49
+ "@opengeni/core": "^0.16.3",
50
+ "@opengeni/db": "^0.19.0",
51
+ "@opengeni/documents": "^0.2.64",
52
+ "@opengeni/events": "^0.3.55",
53
53
  "@opengeni/github": "^0.4.13",
54
54
  "@opengeni/network": "^0.1.1",
55
- "@opengeni/observability": "^0.3.0",
56
- "@opengeni/runtime": "^0.15.3",
55
+ "@opengeni/observability": "^0.4.0",
56
+ "@opengeni/runtime": "^0.16.0",
57
57
  "@opengeni/storage": "^0.2.50",
58
58
  "@temporalio/client": "^1.17.0",
59
59
  "better-auth": "^1.6.14",
@@ -179,6 +179,7 @@ type SessionRouteDeps = ApiRouteDeps & Pick<ViewerServices, "establishSandboxSes
179
179
 
180
180
  export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
181
181
  const { settings, db, bus, workflowClient, objectStorage } = deps;
182
+ const channelAServices = { db, settings, bus, observability: deps.observability };
182
183
  const workspaceCaptureManifestCache = new WorkspaceCaptureManifestCache();
183
184
  const ptyIdentity = (pty: SandboxOpenPtySessionRow): SandboxPtyProcessIdentity => ({
184
185
  leaseId: pty.leaseId,
@@ -2047,54 +2048,42 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
2047
2048
  app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/fs/list", async (c) => {
2048
2049
  const ctx = await channelAPreamble(c, "files:read");
2049
2050
  const req = await parseChannelABody(c, FsListRequest);
2050
- const out = await withChannelA({ db, settings, bus }, ctx, ({ service }) =>
2051
- service.fsList(req),
2052
- );
2051
+ const out = await withChannelA(channelAServices, ctx, ({ service }) => service.fsList(req));
2053
2052
  return c.json(out);
2054
2053
  });
2055
2054
 
2056
2055
  app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/fs/read", async (c) => {
2057
2056
  const ctx = await channelAPreamble(c, "files:read");
2058
2057
  const req = await parseChannelABody(c, FsReadRequest);
2059
- const out = await withChannelA({ db, settings, bus }, ctx, ({ service }) =>
2060
- service.fsRead(req),
2061
- );
2058
+ const out = await withChannelA(channelAServices, ctx, ({ service }) => service.fsRead(req));
2062
2059
  return c.json(out);
2063
2060
  });
2064
2061
 
2065
2062
  app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/fs/write", async (c) => {
2066
2063
  const ctx = await channelAPreamble(c, "files:write");
2067
2064
  const req = await parseChannelABody(c, FsWriteRequest);
2068
- const out = await withChannelA({ db, settings, bus }, ctx, ({ service }) =>
2069
- service.fsWrite(req),
2070
- );
2065
+ const out = await withChannelA(channelAServices, ctx, ({ service }) => service.fsWrite(req));
2071
2066
  return c.json(out);
2072
2067
  });
2073
2068
 
2074
2069
  app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/fs/delete", async (c) => {
2075
2070
  const ctx = await channelAPreamble(c, "files:write");
2076
2071
  const req = await parseChannelABody(c, FsDeleteRequest);
2077
- const out = await withChannelA({ db, settings, bus }, ctx, ({ service }) =>
2078
- service.fsDelete(req),
2079
- );
2072
+ const out = await withChannelA(channelAServices, ctx, ({ service }) => service.fsDelete(req));
2080
2073
  return c.json(out);
2081
2074
  });
2082
2075
 
2083
2076
  app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/fs/move", async (c) => {
2084
2077
  const ctx = await channelAPreamble(c, "files:write");
2085
2078
  const req = await parseChannelABody(c, FsMoveRequest);
2086
- const out = await withChannelA({ db, settings, bus }, ctx, ({ service }) =>
2087
- service.fsMove(req),
2088
- );
2079
+ const out = await withChannelA(channelAServices, ctx, ({ service }) => service.fsMove(req));
2089
2080
  return c.json(out);
2090
2081
  });
2091
2082
 
2092
2083
  app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/fs/mkdir", async (c) => {
2093
2084
  const ctx = await channelAPreamble(c, "files:write");
2094
2085
  const req = await parseChannelABody(c, FsMkdirRequest);
2095
- const out = await withChannelA({ db, settings, bus }, ctx, ({ service }) =>
2096
- service.fsMkdir(req),
2097
- );
2086
+ const out = await withChannelA(channelAServices, ctx, ({ service }) => service.fsMkdir(req));
2098
2087
  return c.json(out);
2099
2088
  });
2100
2089
 
@@ -2102,36 +2091,28 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
2102
2091
  app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/git/status", async (c) => {
2103
2092
  const ctx = await channelAPreamble(c, "files:read");
2104
2093
  const req = await parseChannelABody(c, GitStatusRequest);
2105
- const out = await withChannelA({ db, settings, bus }, ctx, ({ service }) =>
2106
- service.gitStatus(req),
2107
- );
2094
+ const out = await withChannelA(channelAServices, ctx, ({ service }) => service.gitStatus(req));
2108
2095
  return c.json(out);
2109
2096
  });
2110
2097
 
2111
2098
  app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/git/diff", async (c) => {
2112
2099
  const ctx = await channelAPreamble(c, "files:read");
2113
2100
  const req = await parseChannelABody(c, GitDiffRequest);
2114
- const out = await withChannelA({ db, settings, bus }, ctx, ({ service }) =>
2115
- service.gitDiff(req),
2116
- );
2101
+ const out = await withChannelA(channelAServices, ctx, ({ service }) => service.gitDiff(req));
2117
2102
  return c.json(out);
2118
2103
  });
2119
2104
 
2120
2105
  app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/git/log", async (c) => {
2121
2106
  const ctx = await channelAPreamble(c, "files:read");
2122
2107
  const req = await parseChannelABody(c, GitLogRequest);
2123
- const out = await withChannelA({ db, settings, bus }, ctx, ({ service }) =>
2124
- service.gitLog(req),
2125
- );
2108
+ const out = await withChannelA(channelAServices, ctx, ({ service }) => service.gitLog(req));
2126
2109
  return c.json(out);
2127
2110
  });
2128
2111
 
2129
2112
  app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/git/show", async (c) => {
2130
2113
  const ctx = await channelAPreamble(c, "files:read");
2131
2114
  const req = await parseChannelABody(c, GitShowRequest);
2132
- const out = await withChannelA({ db, settings, bus }, ctx, ({ service }) =>
2133
- service.gitShow(req),
2134
- );
2115
+ const out = await withChannelA(channelAServices, ctx, ({ service }) => service.gitShow(req));
2135
2116
  return c.json(out);
2136
2117
  });
2137
2118
 
@@ -2196,7 +2177,7 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
2196
2177
  app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/terminal/exec", async (c) => {
2197
2178
  const ctx = await channelAPreamble(c, "terminal:attach");
2198
2179
  const req = await parseChannelABody(c, TerminalExecRequest);
2199
- const out = await withChannelA({ db, settings, bus }, ctx, ({ service }) =>
2180
+ const out = await withChannelA(channelAServices, ctx, ({ service }) =>
2200
2181
  service.terminalExec(req),
2201
2182
  );
2202
2183
  return c.json(out);
@@ -2213,7 +2194,7 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
2213
2194
  });
2214
2195
  }
2215
2196
  const ptyId = crypto.randomUUID();
2216
- const out = await withChannelA({ db, settings, bus }, ctx, async (handle) => {
2197
+ const out = await withChannelA(channelAServices, ctx, async (handle) => {
2217
2198
  if (!handle.lease) {
2218
2199
  throw new HTTPException(409, {
2219
2200
  message: "durable interactive terminals require a session-home provider lease",
@@ -2324,7 +2305,7 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
2324
2305
  throw new HTTPException(404, { message: "pty not found or closed" });
2325
2306
  }
2326
2307
  let seq = 1;
2327
- await withChannelA({ db, settings, bus }, ctx, async (handle) => {
2308
+ await withChannelA(channelAServices, ctx, async (handle) => {
2328
2309
  await adoptPtyProcess(ctx, handle, pty);
2329
2310
  let output: string;
2330
2311
  try {
@@ -2387,7 +2368,7 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
2387
2368
  if (!pty) {
2388
2369
  throw new HTTPException(404, { message: "pty not found or closed" });
2389
2370
  }
2390
- await withChannelA({ db, settings, bus }, ctx, async (handle) => {
2371
+ await withChannelA(channelAServices, ctx, async (handle) => {
2391
2372
  await adoptPtyProcess(ctx, handle, pty);
2392
2373
  await handle.service.ptyResize(req, pty.execSessionId);
2393
2374
  const updated = await updatePtySessionActivity(db, {
@@ -2418,7 +2399,7 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
2418
2399
  });
2419
2400
  // Idempotent: closing an already-closed/absent PTY is a 204 no-op.
2420
2401
  if (pty) {
2421
- await withChannelA({ db, settings, bus }, ctx, async (handle) => {
2402
+ await withChannelA(channelAServices, ctx, async (handle) => {
2422
2403
  await adoptPtyProcess(ctx, handle, pty);
2423
2404
  await handle.service.ptyClose(req, pty.execSessionId);
2424
2405
  const terminal = await getRetainedProcess(db, {
@@ -40,6 +40,7 @@ import {
40
40
  type LeaseSnapshot,
41
41
  } from "@opengeni/db";
42
42
  import { appendAndPublishEvents, type EventBus } from "@opengeni/events";
43
+ import { sandboxOperationMetricObserver, type Observability } from "@opengeni/observability";
43
44
  import { HTTPException } from "hono/http-exception";
44
45
 
45
46
  import {
@@ -67,6 +68,7 @@ export type ChannelAServices = {
67
68
  db: Database;
68
69
  settings: Settings;
69
70
  bus: EventBus;
71
+ observability?: Observability | undefined;
70
72
  };
71
73
 
72
74
  export type ChannelAContext = {
@@ -103,6 +105,9 @@ export async function withChannelA<T>(
103
105
  fn: (handle: ChannelAHandle) => Promise<T>,
104
106
  ): Promise<T> {
105
107
  const { db, settings, bus } = services;
108
+ const onSandboxOperation = services.observability
109
+ ? sandboxOperationMetricObserver(services.observability)
110
+ : undefined;
106
111
  const { accountId, workspaceId, session } = ctx;
107
112
 
108
113
  if (session.sandboxBackend === "none") {
@@ -204,7 +209,7 @@ export async function withChannelA<T>(
204
209
  backendId: "selfhosted",
205
210
  };
206
211
  const routed = wrapChannelABoxWithRouting(
207
- { db, settings, bus },
212
+ { db, settings, bus, ...(onSandboxOperation ? { onSandboxOperation } : {}) },
208
213
  {
209
214
  accountId,
210
215
  workspaceId,
@@ -354,7 +359,7 @@ export async function withChannelA<T>(
354
359
  // routing may be dormant, but its direct mutation admission is mandatory for
355
360
  // every persistable provider write.
356
361
  const routed = wrapChannelABoxWithRouting(
357
- { db, settings, bus },
362
+ { db, settings, bus, ...(onSandboxOperation ? { onSandboxOperation } : {}) },
358
363
  {
359
364
  accountId,
360
365
  workspaceId,