@opengeni/api-router 0.21.14 → 0.23.1

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 (54) hide show
  1. package/dist/app.d.ts +1 -0
  2. package/dist/app.js +1 -1
  3. package/dist/auth/managed-auth.d.ts +0 -30
  4. package/dist/{chunk-R5PDSH2A.js → chunk-T4T2PGU4.js} +3989 -1356
  5. package/dist/chunk-T4T2PGU4.js.map +1 -0
  6. package/dist/http/sse.d.ts +2 -0
  7. package/dist/index.js +29 -9
  8. package/dist/index.js.map +1 -1
  9. package/dist/integrations/oauth-client.d.ts +8 -0
  10. package/dist/integrations/slack-interactions.d.ts +7 -1
  11. package/dist/mcp/receipts.d.ts +28 -0
  12. package/dist/mcp/scheduled-task-view.d.ts +350 -0
  13. package/dist/mcp/toolspace.d.ts +9 -0
  14. package/dist/routes/transcription-recordings.d.ts +3 -0
  15. package/dist/sandbox/auth-callout.d.ts +2 -0
  16. package/dist/sandbox/channel-a.d.ts +5 -1
  17. package/dist/transcription/segmenter.d.ts +10 -0
  18. package/dist/transcription/service.d.ts +5 -0
  19. package/package.json +12 -12
  20. package/src/app.ts +39 -6
  21. package/src/auth/managed-auth.ts +0 -16
  22. package/src/http/sse.ts +101 -6
  23. package/src/index.ts +28 -3
  24. package/src/integrations/oauth-client.ts +36 -56
  25. package/src/integrations/slack-interactions.ts +123 -15
  26. package/src/mcp/documents.ts +42 -25
  27. package/src/mcp/receipts.ts +95 -0
  28. package/src/mcp/scheduled-task-view.ts +608 -0
  29. package/src/mcp/server.ts +812 -182
  30. package/src/mcp/toolspace.ts +75 -71
  31. package/src/observability.ts +3 -3
  32. package/src/routes/api-keys.ts +7 -1
  33. package/src/routes/codex.ts +7 -4
  34. package/src/routes/connections.ts +74 -3
  35. package/src/routes/enrollments.ts +54 -12
  36. package/src/routes/environments.ts +60 -11
  37. package/src/routes/files.ts +175 -65
  38. package/src/routes/install.ts +31 -1
  39. package/src/routes/machines.ts +1 -1
  40. package/src/routes/scheduled-tasks.ts +39 -14
  41. package/src/routes/sessions.ts +77 -12
  42. package/src/routes/transcription-recordings.ts +754 -0
  43. package/src/routes/transcriptions.ts +2 -0
  44. package/src/sandbox/auth-callout.ts +16 -4
  45. package/src/sandbox/channel-a.ts +124 -7
  46. package/src/sandbox/enrollment.ts +13 -3
  47. package/src/sandbox/machines.ts +1 -1
  48. package/src/sandbox/viewer.ts +29 -20
  49. package/src/transcription/providers/azure-openai.ts +4 -3
  50. package/src/transcription/providers/codex-subscription.ts +4 -1
  51. package/src/transcription/providers/openai.ts +7 -2
  52. package/src/transcription/segmenter.ts +260 -0
  53. package/src/transcription/service.ts +111 -10
  54. package/dist/chunk-R5PDSH2A.js.map +0 -1
@@ -0,0 +1,350 @@
1
+ import type { ScheduledTask, ScheduledTaskScheduleSpec } from "@opengeni/contracts";
2
+ export declare const SCHEDULED_TASK_MCP_MAX_BYTES: number;
3
+ export declare const SCHEDULED_TASK_NAME_MAX_BYTES = 512;
4
+ export declare const SCHEDULED_TASK_PROMPT_MAX_BYTES: number;
5
+ export declare const SCHEDULED_TASK_SCHEDULE_FIELD_MAX_BYTES = 256;
6
+ type Utf8Projection = {
7
+ value: string;
8
+ originalBytes: number;
9
+ deliveredBytes: number;
10
+ truncated: boolean;
11
+ };
12
+ export type ScheduledTaskUtf8ProjectionFact = Omit<Utf8Projection, "value">;
13
+ type ScheduledTaskScheduleProjection = {
14
+ schedule: Extract<ScheduledTaskScheduleSpec, {
15
+ type: "once";
16
+ }>;
17
+ projection: {
18
+ type: "once";
19
+ truncated: boolean;
20
+ fields: {
21
+ runAt: ScheduledTaskUtf8ProjectionFact;
22
+ timeZone: ScheduledTaskUtf8ProjectionFact;
23
+ };
24
+ };
25
+ } | {
26
+ schedule: Extract<ScheduledTaskScheduleSpec, {
27
+ type: "interval";
28
+ }>;
29
+ projection: {
30
+ type: "interval";
31
+ truncated: boolean;
32
+ fields: {
33
+ startAt: ScheduledTaskUtf8ProjectionFact | null;
34
+ endAt: ScheduledTaskUtf8ProjectionFact | null;
35
+ };
36
+ };
37
+ } | {
38
+ schedule: Extract<ScheduledTaskScheduleSpec, {
39
+ type: "calendar";
40
+ }>;
41
+ projection: {
42
+ type: "calendar";
43
+ truncated: boolean;
44
+ fields: {
45
+ timeZone: ScheduledTaskUtf8ProjectionFact;
46
+ };
47
+ };
48
+ };
49
+ export declare function projectScheduledTaskUtf8(value: string, maxBytes: number): Utf8Projection;
50
+ export declare function projectScheduledTaskSchedule(schedule: ScheduledTaskScheduleSpec): ScheduledTaskScheduleProjection;
51
+ export declare function scheduledTaskMcpSummary(task: ScheduledTask): {
52
+ id: string;
53
+ name: string;
54
+ status: "active" | "paused";
55
+ schedule: {
56
+ type: "once";
57
+ runAt: string;
58
+ timeZone: string;
59
+ } | {
60
+ type: "interval";
61
+ everySeconds: number;
62
+ startAt?: string | undefined;
63
+ endAt?: string | undefined;
64
+ } | {
65
+ type: "calendar";
66
+ timeZone: string;
67
+ hour: number;
68
+ minute: number;
69
+ daysOfWeek?: ("FRIDAY" | "MONDAY" | "SATURDAY" | "SUNDAY" | "THURSDAY" | "TUESDAY" | "WEDNESDAY")[] | undefined;
70
+ };
71
+ runMode: "existing_session" | "new_session_per_run" | "reusable_session";
72
+ overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
73
+ targetSessionId: string | null;
74
+ reusableSessionId: string | null;
75
+ variableSetId: string | null;
76
+ rigId: string | null;
77
+ createdAt: string;
78
+ updatedAt: string;
79
+ configuration: {
80
+ model: string | null;
81
+ reasoningEffort: "high" | "low" | "max" | "medium" | "minimal" | "none" | "xhigh" | null;
82
+ sandboxBackend: "blaxel" | "cloudflare" | "daytona" | "docker" | "e2b" | "local" | "modal" | "none" | "runloop" | "selfhosted" | "vercel" | null;
83
+ hasGoal: boolean;
84
+ promptBytes: number;
85
+ resourceCount: number;
86
+ toolCount: number;
87
+ metadataKeyCount: number;
88
+ taskMetadataKeyCount: number;
89
+ };
90
+ projection: {
91
+ bounded: boolean;
92
+ name: ScheduledTaskUtf8ProjectionFact;
93
+ schedule: {
94
+ type: "once";
95
+ truncated: boolean;
96
+ fields: {
97
+ runAt: ScheduledTaskUtf8ProjectionFact;
98
+ timeZone: ScheduledTaskUtf8ProjectionFact;
99
+ };
100
+ } | {
101
+ type: "interval";
102
+ truncated: boolean;
103
+ fields: {
104
+ startAt: ScheduledTaskUtf8ProjectionFact | null;
105
+ endAt: ScheduledTaskUtf8ProjectionFact | null;
106
+ };
107
+ } | {
108
+ type: "calendar";
109
+ truncated: boolean;
110
+ fields: {
111
+ timeZone: ScheduledTaskUtf8ProjectionFact;
112
+ };
113
+ };
114
+ model: ScheduledTaskUtf8ProjectionFact | null;
115
+ createdAt: ScheduledTaskUtf8ProjectionFact;
116
+ updatedAt: ScheduledTaskUtf8ProjectionFact;
117
+ bytes: number;
118
+ maxBytes: number;
119
+ };
120
+ };
121
+ export declare function boundScheduledTaskDetailMcp(task: ScheduledTask): {
122
+ id: string;
123
+ name: string;
124
+ status: "active" | "paused";
125
+ schedule: {
126
+ type: "once";
127
+ runAt: string;
128
+ timeZone: string;
129
+ } | {
130
+ type: "interval";
131
+ everySeconds: number;
132
+ startAt?: string | undefined;
133
+ endAt?: string | undefined;
134
+ } | {
135
+ type: "calendar";
136
+ timeZone: string;
137
+ hour: number;
138
+ minute: number;
139
+ daysOfWeek?: ("FRIDAY" | "MONDAY" | "SATURDAY" | "SUNDAY" | "THURSDAY" | "TUESDAY" | "WEDNESDAY")[] | undefined;
140
+ };
141
+ runMode: "existing_session" | "new_session_per_run" | "reusable_session";
142
+ overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
143
+ targetSessionId: string | null;
144
+ reusableSessionId: string | null;
145
+ variableSetId: string | null;
146
+ rigId: string | null;
147
+ createdAt: string;
148
+ updatedAt: string;
149
+ configuration: {
150
+ model: string | null;
151
+ reasoningEffort: "high" | "low" | "max" | "medium" | "minimal" | "none" | "xhigh" | null;
152
+ sandboxBackend: "blaxel" | "cloudflare" | "daytona" | "docker" | "e2b" | "local" | "modal" | "none" | "runloop" | "selfhosted" | "vercel" | null;
153
+ hasGoal: boolean;
154
+ promptBytes: number;
155
+ resourceCount: number;
156
+ toolCount: number;
157
+ metadataKeyCount: number;
158
+ taskMetadataKeyCount: number;
159
+ };
160
+ projection: {
161
+ bounded: boolean;
162
+ name: ScheduledTaskUtf8ProjectionFact;
163
+ schedule: {
164
+ type: "once";
165
+ truncated: boolean;
166
+ fields: {
167
+ runAt: ScheduledTaskUtf8ProjectionFact;
168
+ timeZone: ScheduledTaskUtf8ProjectionFact;
169
+ };
170
+ } | {
171
+ type: "interval";
172
+ truncated: boolean;
173
+ fields: {
174
+ startAt: ScheduledTaskUtf8ProjectionFact | null;
175
+ endAt: ScheduledTaskUtf8ProjectionFact | null;
176
+ };
177
+ } | {
178
+ type: "calendar";
179
+ truncated: boolean;
180
+ fields: {
181
+ timeZone: ScheduledTaskUtf8ProjectionFact;
182
+ };
183
+ };
184
+ model: ScheduledTaskUtf8ProjectionFact | null;
185
+ createdAt: ScheduledTaskUtf8ProjectionFact;
186
+ updatedAt: ScheduledTaskUtf8ProjectionFact;
187
+ bytes: number;
188
+ maxBytes: number;
189
+ };
190
+ entity: {
191
+ agentConfig: {
192
+ prompt: string;
193
+ resources: Record<string, unknown>[];
194
+ tools: Record<string, unknown>[];
195
+ metadataKeys: string[];
196
+ model: string | null;
197
+ reasoningEffort: "high" | "low" | "max" | "medium" | "minimal" | "none" | "xhigh" | null;
198
+ sandboxBackend: "blaxel" | "cloudflare" | "daytona" | "docker" | "e2b" | "local" | "modal" | "none" | "runloop" | "selfhosted" | "vercel" | null;
199
+ goal: {
200
+ text: string;
201
+ successCriteria: string | null;
202
+ maxAutoContinuations: number | null;
203
+ } | null;
204
+ };
205
+ taskMetadataKeys: string[];
206
+ };
207
+ detailProjection: {
208
+ bounded: boolean;
209
+ fullEntityAvailableViaRest: boolean;
210
+ reducedForBytes: boolean;
211
+ terminal: boolean;
212
+ prompt: ScheduledTaskUtf8ProjectionFact;
213
+ goalText: ScheduledTaskUtf8ProjectionFact | null;
214
+ goalSuccessCriteria: ScheduledTaskUtf8ProjectionFact | null;
215
+ resources: {
216
+ originalCount: number;
217
+ deliveredCount: number;
218
+ originalBytes: number;
219
+ truncatedIdentityFieldCount: number;
220
+ };
221
+ tools: {
222
+ originalCount: number;
223
+ deliveredCount: number;
224
+ originalBytes: number;
225
+ truncatedIdentityFieldCount: number;
226
+ };
227
+ metadata: {
228
+ originalKeyCount: number;
229
+ deliveredKeyCount: number;
230
+ originalBytes: number;
231
+ truncatedKeyCount: number;
232
+ valuesIncluded: boolean;
233
+ };
234
+ taskMetadata: {
235
+ originalKeyCount: number;
236
+ deliveredKeyCount: number;
237
+ originalBytes: number;
238
+ truncatedKeyCount: number;
239
+ valuesIncluded: boolean;
240
+ };
241
+ bytes: number;
242
+ maxBytes: number;
243
+ };
244
+ };
245
+ export declare function boundScheduledTaskMcpPage(input: {
246
+ tasks: ScheduledTask[];
247
+ limit: number;
248
+ offset: number;
249
+ sourceHasMore: boolean;
250
+ maxBytes?: number;
251
+ }): {
252
+ tasks: {
253
+ id: string;
254
+ name: string;
255
+ status: "active" | "paused";
256
+ schedule: {
257
+ type: "once";
258
+ runAt: string;
259
+ timeZone: string;
260
+ } | {
261
+ type: "interval";
262
+ everySeconds: number;
263
+ startAt?: string | undefined;
264
+ endAt?: string | undefined;
265
+ } | {
266
+ type: "calendar";
267
+ timeZone: string;
268
+ hour: number;
269
+ minute: number;
270
+ daysOfWeek?: ("FRIDAY" | "MONDAY" | "SATURDAY" | "SUNDAY" | "THURSDAY" | "TUESDAY" | "WEDNESDAY")[] | undefined;
271
+ };
272
+ runMode: "existing_session" | "new_session_per_run" | "reusable_session";
273
+ overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
274
+ targetSessionId: string | null;
275
+ reusableSessionId: string | null;
276
+ variableSetId: string | null;
277
+ rigId: string | null;
278
+ createdAt: string;
279
+ updatedAt: string;
280
+ configuration: {
281
+ model: string | null;
282
+ reasoningEffort: "high" | "low" | "max" | "medium" | "minimal" | "none" | "xhigh" | null;
283
+ sandboxBackend: "blaxel" | "cloudflare" | "daytona" | "docker" | "e2b" | "local" | "modal" | "none" | "runloop" | "selfhosted" | "vercel" | null;
284
+ hasGoal: boolean;
285
+ promptBytes: number;
286
+ resourceCount: number;
287
+ toolCount: number;
288
+ metadataKeyCount: number;
289
+ taskMetadataKeyCount: number;
290
+ };
291
+ projection: {
292
+ bounded: boolean;
293
+ name: ScheduledTaskUtf8ProjectionFact;
294
+ schedule: {
295
+ type: "once";
296
+ truncated: boolean;
297
+ fields: {
298
+ runAt: ScheduledTaskUtf8ProjectionFact;
299
+ timeZone: ScheduledTaskUtf8ProjectionFact;
300
+ };
301
+ } | {
302
+ type: "interval";
303
+ truncated: boolean;
304
+ fields: {
305
+ startAt: ScheduledTaskUtf8ProjectionFact | null;
306
+ endAt: ScheduledTaskUtf8ProjectionFact | null;
307
+ };
308
+ } | {
309
+ type: "calendar";
310
+ truncated: boolean;
311
+ fields: {
312
+ timeZone: ScheduledTaskUtf8ProjectionFact;
313
+ };
314
+ };
315
+ model: ScheduledTaskUtf8ProjectionFact | null;
316
+ createdAt: ScheduledTaskUtf8ProjectionFact;
317
+ updatedAt: ScheduledTaskUtf8ProjectionFact;
318
+ bytes: number;
319
+ maxBytes: number;
320
+ };
321
+ }[];
322
+ page: {
323
+ limit: number;
324
+ offset: number;
325
+ hasMore: boolean;
326
+ nextOffset: number | null;
327
+ };
328
+ projection: {
329
+ bounded: boolean;
330
+ rowsDroppedForBytes: boolean;
331
+ droppedRowCount: number;
332
+ droppedRows: {
333
+ id: string;
334
+ nextAction: {
335
+ tool: string;
336
+ arguments: {
337
+ id: string;
338
+ includeEntity: boolean;
339
+ };
340
+ };
341
+ }[];
342
+ sourceRowsConsumed: number;
343
+ rowsReturned: number;
344
+ terminal: boolean;
345
+ reason?: string;
346
+ bytes: number;
347
+ maxBytes: number;
348
+ };
349
+ };
350
+ export {};
@@ -49,6 +49,15 @@ export declare function prepareToolspaceMcpSurface(input: {
49
49
  deps: ApiRouteDeps;
50
50
  grant: AccessGrant;
51
51
  }): Promise<ToolspaceMcpSurface | null>;
52
+ export type ToolspacePublicErrorFields = {
53
+ errorClass: "ToolspaceOperationError";
54
+ errorCode: ToolspacePublicFailureCode;
55
+ status?: number;
56
+ origin: "toolspace";
57
+ };
58
+ type ToolspacePublicFailureCode = "toolspace_operation_failed" | "tool_list_too_large" | "tool_result_too_large";
59
+ /** Allowlisted projection for public telemetry; product data stays exact. */
60
+ export declare function toolspacePublicErrorFields(error: unknown, errorCode?: ToolspacePublicFailureCode): ToolspacePublicErrorFields;
52
61
  export declare function toolspaceCanProxyServerId(serverId: string): boolean;
53
62
  export declare function connectionBrokerFetch(baseFetch: FetchLike, input: {
54
63
  deps: ApiRouteDeps;
@@ -0,0 +1,3 @@
1
+ import { type ApiRouteDeps } from "@opengeni/core";
2
+ import type { Hono } from "hono";
3
+ export declare function registerResumableTranscriptionRoutes(app: Hono, deps: ApiRouteDeps): void;
@@ -4,6 +4,8 @@ import { type ResponderConnection } from "@opengeni/events";
4
4
  import type { Observability } from "@opengeni/observability";
5
5
  /** The NATS subject nats-server publishes authorization requests on (ADR-26). */
6
6
  export declare const AUTH_CALLOUT_SUBJECT = "$SYS.REQ.USER.AUTH";
7
+ /** Keep live NATS credentials short-lived while never outliving the bearer. */
8
+ export declare const NATS_USER_JWT_TTL_SECONDS: number;
7
9
  export interface AuthCalloutDeps {
8
10
  db: Database;
9
11
  settings: Settings;
@@ -3,7 +3,7 @@ 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
5
  import { type Observability } from "@opengeni/observability";
6
- import { SandboxChannelAService, type RoutingSandboxSession } from "@opengeni/runtime/sandbox";
6
+ import { SandboxChannelAService, type EstablishedSandboxSession, type RoutingSandboxSession } from "@opengeni/runtime/sandbox";
7
7
  export type ChannelAServices = {
8
8
  db: Database;
9
9
  settings: Settings;
@@ -24,6 +24,10 @@ export type ChannelAHandle = {
24
24
  routingSession: RoutingSandboxSession;
25
25
  requestId: string;
26
26
  };
27
+ /** Reuse the exact lease-fenced provider handle across API-direct surfaces.
28
+ * Stream capability negotiation and the first Files/Changes reads commonly run
29
+ * back-to-back; sharing this handle avoids paying the same Modal resume twice. */
30
+ export declare function establishCachedChannelAHandle(workspaceId: string, sessionId: string, lease: LeaseSnapshot, establish: () => Promise<EstablishedSandboxSession>): Promise<EstablishedSandboxSession>;
27
31
  /**
28
32
  * Run a Channel-A op against a live box, API-direct. Acquires an exact direct holder
29
33
  * (warming the box when cold), resumes by id, builds the service, runs `fn`, and
@@ -0,0 +1,10 @@
1
+ import type { TranscriptionSegmenter } from "@opengeni/core";
2
+ export declare class TranscriptionSegmenterError extends Error {
3
+ readonly code: "unavailable" | "invalid_audio" | "cancelled" | "too_large" | "unknown";
4
+ readonly retryable: boolean;
5
+ readonly name = "TranscriptionSegmenterError";
6
+ constructor(message: string, code: "unavailable" | "invalid_audio" | "cancelled" | "too_large" | "unknown", retryable: boolean);
7
+ }
8
+ export declare function createFfmpegTranscriptionSegmenter(input: {
9
+ ffmpegPath: string;
10
+ }): TranscriptionSegmenter;
@@ -7,4 +7,9 @@ export declare function createTranscriptionService(input: {
7
7
  fetch?: typeof fetch;
8
8
  codexFetch?: typeof fetch;
9
9
  probeCodex?: (context?: TranscriptionAvailabilityContext) => boolean | Promise<boolean>;
10
+ /** Test seam for exercising timeout and late-completion behavior quickly. */
11
+ providerRequestTimeoutMilliseconds?: number;
12
+ /** Test seam for evaluating persisted absolute deadlines after delayed setup. */
13
+ now?: () => Date;
10
14
  }): TranscriptionService;
15
+ export declare function remainingTranscriptionProviderRequestMilliseconds(providerDeadlineAt: Date, now: Date): number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/api-router",
3
- "version": "0.21.14",
3
+ "version": "0.23.1",
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": {
@@ -43,18 +43,18 @@
43
43
  "@hono/zod-validator": "^0.7.6",
44
44
  "@modelcontextprotocol/sdk": "^1.29.0",
45
45
  "@opengeni/agent-proto": "^0.3.0",
46
- "@opengeni/codex": "^0.2.11",
47
- "@opengeni/config": "^0.10.14",
48
- "@opengeni/contracts": "^0.38.3",
49
- "@opengeni/core": "^0.20.16",
50
- "@opengeni/db": "^0.27.11",
51
- "@opengeni/documents": "^0.5.7",
52
- "@opengeni/events": "^0.3.77",
53
- "@opengeni/github": "^0.4.30",
46
+ "@opengeni/codex": "^0.2.13",
47
+ "@opengeni/config": "^0.12.1",
48
+ "@opengeni/contracts": "^0.40.0",
49
+ "@opengeni/core": "^0.21.10",
50
+ "@opengeni/db": "^0.28.9",
51
+ "@opengeni/documents": "^0.5.18",
52
+ "@opengeni/events": "^0.3.88",
53
+ "@opengeni/github": "^0.4.38",
54
54
  "@opengeni/network": "^0.2.0",
55
- "@opengeni/observability": "^0.4.16",
56
- "@opengeni/runtime": "^0.18.15",
57
- "@opengeni/storage": "^0.2.67",
55
+ "@opengeni/observability": "^0.5.6",
56
+ "@opengeni/runtime": "^0.18.24",
57
+ "@opengeni/storage": "^0.2.75",
58
58
  "@temporalio/client": "^1.17.0",
59
59
  "better-auth": "^1.6.14",
60
60
  "hono": "^4.12.18",
package/src/app.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  OPENGENI_CORRELATION_HEADER,
14
14
  resolveWorkspaceMemoryEnabled,
15
15
  VOICE_INPUT_ACCEPTED_MIME_TYPES,
16
+ TRANSCRIPTION_RECORDING_PROVIDER_SEGMENT_SECONDS,
16
17
  type AccessGrant,
17
18
  type ErrorCode,
18
19
  } from "@opengeni/contracts";
@@ -77,6 +78,7 @@ import { registerInsightsRoutes } from "./routes/insights";
77
78
  import { registerTranscriptionRoutes } from "./routes/transcriptions";
78
79
  import { projectClientModel } from "./model-catalog";
79
80
  import { createTranscriptionService } from "./transcription/service";
81
+ import { createFfmpegTranscriptionSegmenter } from "./transcription/segmenter";
80
82
  import { registerSlackInteractionRoutes } from "./integrations/slack-interactions";
81
83
 
82
84
  export type {
@@ -103,8 +105,15 @@ export { replaySessionEvents, sseSessionStream, sseWorkspaceControlStream } from
103
105
  export const API_MAX_REQUEST_BODY_BYTES = 8 * 1024 * 1024;
104
106
 
105
107
  /** Effective Hono bodyLimit — API JSON ceiling or voice multipart + multipart overhead. */
106
- export function apiRequestBodyLimitBytes(settings: { voiceInputMaxSizeBytes: number }): number {
107
- return Math.max(API_MAX_REQUEST_BODY_BYTES, settings.voiceInputMaxSizeBytes + 64 * 1024);
108
+ export function apiRequestBodyLimitBytes(settings: {
109
+ voiceInputMaxSizeBytes: number;
110
+ voiceInputResumableMaxChunkSizeBytes?: number;
111
+ }): number {
112
+ return Math.max(
113
+ API_MAX_REQUEST_BODY_BYTES,
114
+ settings.voiceInputMaxSizeBytes + 64 * 1024,
115
+ (settings.voiceInputResumableMaxChunkSizeBytes ?? 0) + 64 * 1024,
116
+ );
108
117
  }
109
118
  const API_PUBLIC_ERROR_MESSAGE_MAX_BYTES = 512;
110
119
 
@@ -204,6 +213,12 @@ export function createAppComposition(deps: AppDependencies): {
204
213
  ...(deps.codexFetch ? { codexFetch: deps.codexFetch } : {}),
205
214
  })
206
215
  : deps.transcription;
216
+ const transcriptionSegmenter =
217
+ deps.transcriptionSegmenter === undefined
218
+ ? createFfmpegTranscriptionSegmenter({
219
+ ffmpegPath: deps.settings.voiceInputFfmpegPath,
220
+ })
221
+ : deps.transcriptionSegmenter;
207
222
  const routeDeps: ApiRouteDeps = {
208
223
  ...deps,
209
224
  observability,
@@ -214,6 +229,7 @@ export function createAppComposition(deps: AppDependencies): {
214
229
  documentIndexer,
215
230
  getDocumentServices,
216
231
  transcription,
232
+ transcriptionSegmenter,
217
233
  ...(sandboxClient ? { sandboxClient } : {}),
218
234
  resumeBoxById,
219
235
  };
@@ -291,7 +307,6 @@ export function createAppComposition(deps: AppDependencies): {
291
307
  const start = performance.now();
292
308
  const span = observability.startSpan(`HTTP ${c.req.method} ${route}`, {
293
309
  "http.request.method": c.req.method,
294
- "url.path": url.pathname,
295
310
  "opengeni.route": route,
296
311
  });
297
312
  try {
@@ -350,7 +365,7 @@ export function createAppComposition(deps: AppDependencies): {
350
365
  spanId: span.spanId,
351
366
  correlationId,
352
367
  errorCode,
353
- errorClass: error instanceof Error ? error.name : "NonErrorThrown",
368
+ errorClass: "HttpOperationError",
354
369
  });
355
370
  throw error;
356
371
  }
@@ -440,6 +455,24 @@ export function createAppComposition(deps: AppDependencies): {
440
455
  maxDurationSeconds: deps.settings.voiceInputMaxDurationSeconds,
441
456
  maxSizeBytes: deps.settings.voiceInputMaxSizeBytes,
442
457
  acceptedMimeTypes: [...VOICE_INPUT_ACCEPTED_MIME_TYPES],
458
+ ...(deps.settings.voiceInputResumableEnabled &&
459
+ objectStorage &&
460
+ transcription &&
461
+ transcriptionSegmenter &&
462
+ (await transcription.available()) &&
463
+ (await transcriptionSegmenter.available())
464
+ ? {
465
+ resumable: {
466
+ maxDurationSeconds: deps.settings.voiceInputResumableMaxDurationSeconds,
467
+ maxSizeBytes: deps.settings.voiceInputResumableMaxSizeBytes,
468
+ maxChunkSizeBytes: deps.settings.voiceInputResumableMaxChunkSizeBytes,
469
+ providerSegmentSeconds: Math.min(
470
+ TRANSCRIPTION_RECORDING_PROVIDER_SEGMENT_SECONDS,
471
+ deps.settings.voiceInputMaxDurationSeconds,
472
+ ),
473
+ },
474
+ }
475
+ : {}),
443
476
  },
444
477
  productAccessMode: deps.settings.productAccessMode,
445
478
  auth: clientAuthConfig(deps.settings),
@@ -803,12 +836,12 @@ async function runReadinessChecks<const Checks extends Readonly<Record<string, R
803
836
  try {
804
837
  await withTimeout(Promise.resolve().then(check), timeoutMs);
805
838
  return [name, { ok: true }] as const;
806
- } catch (error) {
839
+ } catch {
807
840
  return [
808
841
  name,
809
842
  {
810
843
  ok: false,
811
- error: error instanceof Error ? error.message : String(error),
844
+ error: "dependency_unavailable",
812
845
  },
813
846
  ] as const;
814
847
  }
@@ -151,22 +151,6 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
151
151
  }) as ManagedAuth;
152
152
  }
153
153
 
154
- export async function managedSessionAccessContext(
155
- auth: ManagedAuth,
156
- db: Database,
157
- headers: Headers,
158
- ) {
159
- const session = await auth.api.getSession({ headers });
160
- if (!session?.user) {
161
- return null;
162
- }
163
- return await ensureManagedAccessForUser(db, {
164
- userId: session.user.id,
165
- email: session.user.email,
166
- name: session.user.name,
167
- });
168
- }
169
-
170
154
  function betterAuthBaseUrl(settings: Settings) {
171
155
  const allowedHosts = splitCsv(settings.betterAuthAllowedHosts);
172
156
  if (allowedHosts.length === 0) {