@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
@@ -5,8 +5,10 @@ import {
5
5
  import { type ApiRouteDeps, requireAccessGrant, TranscriptionServiceError } from "@opengeni/core";
6
6
  import { getWorkspace } from "@opengeni/db";
7
7
  import type { Hono } from "hono";
8
+ import { registerResumableTranscriptionRoutes } from "./transcription-recordings";
8
9
 
9
10
  export function registerTranscriptionRoutes(app: Hono, deps: ApiRouteDeps): void {
11
+ registerResumableTranscriptionRoutes(app, deps);
10
12
  app.post("/v1/workspaces/:workspaceId/transcriptions", async (c) => {
11
13
  const workspaceId = c.req.param("workspaceId");
12
14
  const grant = await requireAccessGrant(c, deps, workspaceId, "sessions:create");
@@ -10,8 +10,8 @@
10
10
  // to, the `server_id` for the response `aud`, and the presented `auth_token`);
11
11
  // 2. VALIDATES the bearer with verifyEnrollmentBearer (HMAC, via
12
12
  // resolveEnrollmentSigningSecret) — an invalid/expired/forged bearer is denied;
13
- // 3. confirms the enrollment is still ACTIVE in the DB (a revoked machine is
14
- // denied even with a still-unexpired bearer);
13
+ // 3. confirms the enrollment is still ACTIVE in the DB at the exact credential
14
+ // generation (a revoked or re-enrolled machine denies an old bearer);
15
15
  // 4. signs a NATS user JWT granting pub/sub ONLY `agent.<ws>.>` + `_INBOX.>`
16
16
  // (deny-all-else by an allow-list) and returns it inside a signed
17
17
  // authorization-response JWT.
@@ -48,6 +48,8 @@ import { observabilityEventLogger } from "../observability";
48
48
 
49
49
  /** The NATS subject nats-server publishes authorization requests on (ADR-26). */
50
50
  export const AUTH_CALLOUT_SUBJECT = "$SYS.REQ.USER.AUTH";
51
+ /** Keep live NATS credentials short-lived while never outliving the bearer. */
52
+ export const NATS_USER_JWT_TTL_SECONDS = 5 * 60;
51
53
 
52
54
  export interface AuthCalloutDeps {
53
55
  db: Database;
@@ -123,13 +125,23 @@ export async function handleAuthorizationRequest(
123
125
  // Belt-and-braces: the bearer's agentId/enrollmentId must match the row we found.
124
126
  // (verifyEnrollmentBearer already binds them; this guards a future schema where
125
127
  // agentId != enrollmentId.)
126
- if (enrollment.id !== claims.enrollmentId) {
128
+ if (
129
+ enrollment.workspaceId !== claims.workspaceId ||
130
+ enrollment.id !== claims.enrollmentId ||
131
+ enrollment.id !== claims.agentId ||
132
+ claims.agentId !== claims.enrollmentId ||
133
+ claims.subjectPrefix !== `agent.${claims.workspaceId}.${claims.agentId}`
134
+ ) {
127
135
  return deny("enrollment identity mismatch");
128
136
  }
137
+ if (enrollment.credentialGeneration !== claims.credentialGeneration) {
138
+ return deny("enrollment credential generation mismatch");
139
+ }
129
140
 
130
141
  // GRANT: a user JWT scoped to ONLY this workspace's agent subtree + the reply
131
142
  // inbox. This allow-list IS the per-workspace isolation boundary.
132
143
  const permissions = workspaceAgentPermissions(claims.workspaceId);
144
+ const nowSeconds = Math.floor(Date.now() / 1000);
133
145
  const userJwt = mintUserJwt({
134
146
  userPublicKey: decoded.userNkey,
135
147
  accountSeed: deps.callout.accountSeed,
@@ -142,7 +154,7 @@ export async function handleAuthorizationRequest(
142
154
  audienceAccount: deps.callout.accountName,
143
155
  // Tie the credential's life to the bearer's remaining life: a revoked/expired
144
156
  // enrollment cannot outlive its bearer at the NATS layer either.
145
- expiresAtSeconds: claims.exp,
157
+ expiresAtSeconds: Math.min(claims.exp, nowSeconds + NATS_USER_JWT_TTL_SECONDS),
146
158
  });
147
159
  const response = mintAuthResponse({
148
160
  userPublicKey: decoded.userNkey,
@@ -30,6 +30,7 @@ import type { Session } from "@opengeni/contracts";
30
30
  import {
31
31
  acquireLease,
32
32
  getSandboxSessionEnvelope,
33
+ getEnrollment,
33
34
  getSandbox,
34
35
  loadWorkspaceEnvironmentForRun,
35
36
  markWarmLeaseInstanceLost,
@@ -49,6 +50,7 @@ import {
49
50
  isProviderSandboxNotFoundError,
50
51
  SandboxChannelAService,
51
52
  NatsControlRpc,
53
+ NatsOpStreamTransport,
52
54
  ChannelAConflictError,
53
55
  ChannelANotFoundError,
54
56
  ChannelAUnsupportedError,
@@ -90,6 +92,97 @@ export type ChannelAHandle = {
90
92
  requestId: string;
91
93
  };
92
94
 
95
+ /**
96
+ * Provider handles are lightweight references to a lease-owned sandbox, but
97
+ * reconstructing one is not free: Modal resume-by-id plus its first command can
98
+ * dominate a small Git/files read. Workspace panels issue several independent
99
+ * Channel-A requests together, so reuse the exact fenced handle briefly instead
100
+ * of making every request reattach to the same warm instance.
101
+ *
102
+ * The key includes the session, lease epoch, and immutable provider instance id.
103
+ * A rotation can therefore never inherit an old handle. Entries are bounded and
104
+ * expire opportunistically; eviction only drops local references and never
105
+ * terminates the lease-owned sandbox.
106
+ */
107
+ const CHANNEL_A_HANDLE_CACHE_TTL_MS = 300_000;
108
+ const CHANNEL_A_HANDLE_CACHE_MAX_ENTRIES = 64;
109
+ type CachedEstablishedHandle = {
110
+ promise: Promise<EstablishedSandboxSession>;
111
+ lastUsedAt: number;
112
+ };
113
+ const establishedHandleCache = new Map<string, CachedEstablishedHandle>();
114
+
115
+ function establishedHandleCacheKey(
116
+ workspaceId: string,
117
+ sessionId: string,
118
+ lease: LeaseSnapshot,
119
+ ): string {
120
+ return [workspaceId, sessionId, lease.leaseEpoch, lease.instanceId ?? ""].join("\u0000");
121
+ }
122
+
123
+ function pruneEstablishedHandleCache(now: number): void {
124
+ for (const [key, entry] of establishedHandleCache) {
125
+ if (now - entry.lastUsedAt > CHANNEL_A_HANDLE_CACHE_TTL_MS) {
126
+ establishedHandleCache.delete(key);
127
+ }
128
+ }
129
+ while (establishedHandleCache.size >= CHANNEL_A_HANDLE_CACHE_MAX_ENTRIES) {
130
+ const oldestKey = establishedHandleCache.keys().next().value as string | undefined;
131
+ if (oldestKey === undefined) break;
132
+ establishedHandleCache.delete(oldestKey);
133
+ }
134
+ }
135
+
136
+ async function establishCachedHandle(
137
+ key: string,
138
+ establish: () => Promise<EstablishedSandboxSession>,
139
+ ): Promise<EstablishedSandboxSession> {
140
+ const now = Date.now();
141
+ pruneEstablishedHandleCache(now);
142
+ const cached = establishedHandleCache.get(key);
143
+ if (cached) {
144
+ cached.lastUsedAt = now;
145
+ // Refresh insertion order so the bounded map evicts the least-recently used
146
+ // exact lease identity first.
147
+ establishedHandleCache.delete(key);
148
+ establishedHandleCache.set(key, cached);
149
+ return await cached.promise;
150
+ }
151
+
152
+ const promise = establish();
153
+ const entry: CachedEstablishedHandle = { promise, lastUsedAt: now };
154
+ establishedHandleCache.set(key, entry);
155
+ try {
156
+ return await promise;
157
+ } catch (error) {
158
+ if (establishedHandleCache.get(key) === entry) establishedHandleCache.delete(key);
159
+ throw error;
160
+ }
161
+ }
162
+
163
+ /** Reuse the exact lease-fenced provider handle across API-direct surfaces.
164
+ * Stream capability negotiation and the first Files/Changes reads commonly run
165
+ * back-to-back; sharing this handle avoids paying the same Modal resume twice. */
166
+ export async function establishCachedChannelAHandle(
167
+ workspaceId: string,
168
+ sessionId: string,
169
+ lease: LeaseSnapshot,
170
+ establish: () => Promise<EstablishedSandboxSession>,
171
+ ): Promise<EstablishedSandboxSession> {
172
+ return await establishCachedHandle(
173
+ establishedHandleCacheKey(workspaceId, sessionId, lease),
174
+ establish,
175
+ );
176
+ }
177
+
178
+ function rememberEstablishedHandle(key: string, established: EstablishedSandboxSession): void {
179
+ pruneEstablishedHandleCache(Date.now());
180
+ establishedHandleCache.set(key, {
181
+ promise: Promise.resolve(established),
182
+ lastUsedAt: Date.now(),
183
+ });
184
+ }
185
+
93
186
  /**
94
187
  * Run a Channel-A op against a live box, API-direct. Acquires an exact direct holder
95
188
  * (warming the box when cold), resumes by id, builds the service, runs `fn`, and
@@ -169,7 +262,12 @@ export async function withChannelA<T>(
169
262
  leaseEpoch: lease?.leaseEpoch ?? session.activeEpoch,
170
263
  emit,
171
264
  });
172
- return await fn({ service, lease, routingSession, requestId });
265
+ const result = await fn({ service, lease, routingSession, requestId });
266
+ // The direct request has accepted the result in memory. Finalize every
267
+ // Connected Machine backend the routing proxy reached so a mid-request
268
+ // route transition cannot leave completed output retained until TTL.
269
+ await routingSession.finalizeOpStreamOps().catch(() => undefined);
270
+ return result;
173
271
  };
174
272
 
175
273
  // A machine-targeted top-level session has an honest selfhosted HOME label.
@@ -190,6 +288,7 @@ export async function withChannelA<T>(
190
288
  message: "machine-home session points to an unavailable Connected Machine",
191
289
  });
192
290
  }
291
+ const enrollment = await getEnrollment(db, workspaceId, sandbox.enrollmentId);
193
292
  const built = await buildSelfhostedBackendSession({
194
293
  workspaceId,
195
294
  agentId: sandbox.enrollmentId,
@@ -200,6 +299,17 @@ export async function withChannelA<T>(
200
299
  workingDir: pointer.workingDir,
201
300
  timeoutMs: settings.sandboxSelfhostedControlTimeoutMs,
202
301
  execTimeoutMs: settings.sandboxSelfhostedExecTimeoutMs,
302
+ ...(settings.agentOpStreamEnabled === true &&
303
+ enrollment?.opStream === true &&
304
+ bus.getOpStreamConnection
305
+ ? {
306
+ opStream: {
307
+ transport: new NatsOpStreamTransport(
308
+ async () => bus.getOpStreamConnection?.() ?? null,
309
+ ),
310
+ },
311
+ }
312
+ : {}),
203
313
  });
204
314
  established = {
205
315
  client: built.client,
@@ -271,6 +381,7 @@ export async function withChannelA<T>(
271
381
 
272
382
  let established: EstablishedSandboxSession | undefined;
273
383
  let leaseSnapshot: LeaseSnapshot = acquired.lease;
384
+ let establishedCacheKey: string | null = null;
274
385
 
275
386
  try {
276
387
  const envelope = await getSandboxSessionEnvelope(db, workspaceId, session.id);
@@ -303,6 +414,8 @@ export async function withChannelA<T>(
303
414
  });
304
415
  established = result.established;
305
416
  leaseSnapshot = result.lease;
417
+ establishedCacheKey = establishedHandleCacheKey(workspaceId, session.id, leaseSnapshot);
418
+ rememberEstablishedHandle(establishedCacheKey, established);
306
419
  } catch (error) {
307
420
  throw new HTTPException(409, {
308
421
  message: `sandbox not available (${error instanceof Error ? error.message : "spawn failed"})`,
@@ -323,17 +436,21 @@ export async function withChannelA<T>(
323
436
  });
324
437
  }
325
438
  leaseSnapshot = live;
439
+ establishedCacheKey = establishedHandleCacheKey(workspaceId, session.id, live);
326
440
  try {
327
- established = await establishSandboxSessionFromEnvelope(settings, live.resumeState, {
328
- sessionId: session.id,
329
- recovery: "resume-only",
330
- backendOverride: session.sandboxBackend,
331
- environment,
332
- });
441
+ established = await establishCachedChannelAHandle(workspaceId, session.id, live, () =>
442
+ establishSandboxSessionFromEnvelope(settings, live.resumeState, {
443
+ sessionId: session.id,
444
+ recovery: "resume-only",
445
+ backendOverride: session.sandboxBackend,
446
+ environment,
447
+ }),
448
+ );
333
449
  } catch (error) {
334
450
  if (!isProviderSandboxNotFoundError(session.sandboxBackend, error)) {
335
451
  throw error;
336
452
  }
453
+ establishedHandleCache.delete(establishedCacheKey);
337
454
  const marked = await markWarmLeaseInstanceLost(db, {
338
455
  accountId,
339
456
  workspaceId,
@@ -75,8 +75,9 @@ export const DEVICE_POLL_INTERVAL_SECONDS = 5;
75
75
  // box) caused a self-hosted agent to drop PERMANENTLY one hour after connecting: the
76
76
  // bearer expired and the auth-callout rejected every reconnect ("re-enroll may be
77
77
  // required"). A long-lived bearer is safe because the auth-callout RE-CHECKS the
78
- // enrollment status on every (re)connect (auth-callout.ts) — a revoked machine is
79
- // denied regardless of bearer life exactly as the long-lived relay token relies on.
78
+ // enrollment status AND credential generation on every (re)connect
79
+ // (auth-callout.ts) a revoked machine or an old pre-re-enrollment bearer is denied
80
+ // regardless of bearer life. The short NATS user-JWT cap bounds already-live access.
80
81
  export const ENROLLMENT_BEARER_TTL_SECONDS = 30 * 24 * 3600;
81
82
  // The relay PRODUCER token (the `ogr_` token; M8b) is ENROLLMENT-scoped,
82
83
  // NOT per-stream: the agent presents it on every channel registration for the life
@@ -370,6 +371,7 @@ export async function exchangeEnrollToken(
370
371
  secret,
371
372
  workspaceId: claims.workspaceId,
372
373
  agentId: enrollment.id,
374
+ credentialGeneration: enrollment.credentialGeneration,
373
375
  consentedScreenControl: enrollment.allowScreenControl,
374
376
  });
375
377
  return { ok: true, credentials };
@@ -430,6 +432,7 @@ export async function pollDeviceEnrollment(
430
432
  secret,
431
433
  workspaceId: request.workspaceId,
432
434
  agentId: enrollment.id,
435
+ credentialGeneration: enrollment.credentialGeneration,
433
436
  consentedScreenControl: enrollment.allowScreenControl,
434
437
  });
435
438
 
@@ -455,7 +458,13 @@ export async function pollDeviceEnrollment(
455
458
  * bearer so an agent using it as the connect-token credential works uniformly. */
456
459
  async function buildEnrollmentCredentials(
457
460
  services: EnrollmentServices,
458
- input: { secret: string; workspaceId: string; agentId: string; consentedScreenControl: boolean },
461
+ input: {
462
+ secret: string;
463
+ workspaceId: string;
464
+ agentId: string;
465
+ credentialGeneration: number;
466
+ consentedScreenControl: boolean;
467
+ },
459
468
  ): Promise<EnrollmentCredentialsResponse> {
460
469
  const { settings } = services;
461
470
  // The control-plane subject prefix the agent subscribes to: agent.<ws>.<id>.
@@ -466,6 +475,7 @@ async function buildEnrollmentCredentials(
466
475
  workspaceId: input.workspaceId,
467
476
  agentId: input.agentId,
468
477
  enrollmentId: input.agentId,
478
+ credentialGeneration: input.credentialGeneration,
469
479
  subjectPrefix,
470
480
  exp,
471
481
  });
@@ -219,7 +219,7 @@ export async function listMachines(
219
219
  // onto the machines (no N+1). Each machine is probed for liveness.
220
220
  const [sandboxes, enrollments, metricsByEnrollment] = await Promise.all([
221
221
  listSandboxes(db, workspaceId),
222
- listEnrollments(db, workspaceId),
222
+ listEnrollments(db, workspaceId, { status: "active" }),
223
223
  readMachineMetricsLatestForWorkspace(db, workspaceId),
224
224
  ]);
225
225
  const enrollmentById = new Map(enrollments.map((e) => [e.id, e]));
@@ -74,6 +74,7 @@ import {
74
74
  } from "@opengeni/runtime/sandbox";
75
75
  import { relayConfigFromSettings } from "@opengeni/core";
76
76
  import { establishApiSandboxSpawner } from "./rematerialize";
77
+ import { establishCachedChannelAHandle } from "./channel-a";
77
78
 
78
79
  /** The minimal services a viewer op needs: the DB + settings (lease cadence +
79
80
  * the sandbox client construction the leaf reads from settings). The bus is
@@ -730,18 +731,22 @@ export async function mintDesktopStream(
730
731
  // SAME stable run-env the turn declares, so a later turn finds no env delta.
731
732
  const environment = await sessionAttachEnvironment(services, workspaceId, session);
732
733
  try {
734
+ const establish = () =>
735
+ (services.establishSandboxSession ?? establishSandboxSessionFromEnvelope)(
736
+ settings,
737
+ envelope,
738
+ {
739
+ sessionId: session.id,
740
+ recovery: "resume-only",
741
+ backendOverride: session.sandboxBackend,
742
+ environment,
743
+ },
744
+ );
733
745
  established = input.establish
734
746
  ? await input.establish(envelope)
735
- : await (services.establishSandboxSession ?? establishSandboxSessionFromEnvelope)(
736
- settings,
737
- envelope,
738
- {
739
- sessionId: session.id,
740
- recovery: "resume-only",
741
- backendOverride: session.sandboxBackend,
742
- environment,
743
- },
744
- );
747
+ : services.establishSandboxSession
748
+ ? await establish()
749
+ : await establishCachedChannelAHandle(workspaceId, session.id, lease, establish);
745
750
  } catch (error) {
746
751
  await retireMissingWarmLease(services, { accountId, workspaceId, session, lease }, error);
747
752
  return null;
@@ -955,18 +960,22 @@ export async function mintTerminalStream(
955
960
  // declares, so a later turn finds no manifest-env delta.
956
961
  const environment = await sessionAttachEnvironment(services, workspaceId, session);
957
962
  try {
963
+ const establish = () =>
964
+ (services.establishSandboxSession ?? establishSandboxSessionFromEnvelope)(
965
+ settings,
966
+ envelope,
967
+ {
968
+ sessionId: session.id,
969
+ recovery: "resume-only",
970
+ backendOverride: session.sandboxBackend,
971
+ environment,
972
+ },
973
+ );
958
974
  established = input.establish
959
975
  ? await input.establish(envelope)
960
- : await (services.establishSandboxSession ?? establishSandboxSessionFromEnvelope)(
961
- settings,
962
- envelope,
963
- {
964
- sessionId: session.id,
965
- recovery: "resume-only",
966
- backendOverride: session.sandboxBackend,
967
- environment,
968
- },
969
- );
976
+ : services.establishSandboxSession
977
+ ? await establish()
978
+ : await establishCachedChannelAHandle(workspaceId, session.id, lease, establish);
970
979
  } catch (error) {
971
980
  await retireMissingWarmLease(services, { accountId, workspaceId, session, lease }, error);
972
981
  return null;
@@ -13,13 +13,14 @@ export function createAzureOpenAiTranscriptionProvider(input: {
13
13
  const url = `${input.endpoint}/openai/deployments/${encodeURIComponent(input.deployment)}/audio/transcriptions?api-version=${encodeURIComponent(input.apiVersion)}`;
14
14
  return {
15
15
  id: "azure-openai",
16
+ supportsServerDeadline: true,
16
17
  available: () => Boolean(input.apiKey || input.adToken),
17
- async transcribe({ audio, mimeType, filename, signal }) {
18
+ async transcribe({ audio, mimeType, filename, requestId, signal }) {
18
19
  const form = new FormData();
19
20
  form.append("file", new Blob([Uint8Array.from(audio).buffer], { type: mimeType }), filename);
20
21
  const headers: Record<string, string> = input.apiKey
21
- ? { "api-key": input.apiKey }
22
- : { Authorization: `Bearer ${input.adToken}` };
22
+ ? { "api-key": input.apiKey, "x-opengeni-request-id": requestId }
23
+ : { Authorization: `Bearer ${input.adToken}`, "x-opengeni-request-id": requestId };
23
24
  let response: Response;
24
25
  try {
25
26
  response = await fetchImpl(url, {
@@ -36,9 +36,10 @@ export function createCodexSubscriptionTranscriptionProvider(input: {
36
36
  });
37
37
  return {
38
38
  id: "codex-subscription",
39
+ supportsServerDeadline: true,
39
40
  experimental: true,
40
41
  available: probe,
41
- async transcribe({ audio, mimeType, filename, workspaceId, signal }) {
42
+ async transcribe({ audio, mimeType, filename, workspaceId, requestId, signal }) {
42
43
  const account = (await listCodexAccountStatuses(input.db, workspaceId)).find(
43
44
  (candidate) => candidate.isActive && candidate.status === "active",
44
45
  );
@@ -73,6 +74,8 @@ export function createCodexSubscriptionTranscriptionProvider(input: {
73
74
  originator: CODEX_ORIGINATOR,
74
75
  "User-Agent": `${CODEX_ORIGINATOR}/${CODEX_CLIENT_VERSION}`,
75
76
  version: CODEX_CLIENT_VERSION,
77
+ // Observability only; the upstream API is not treated as idempotent.
78
+ "x-opengeni-request-id": requestId,
76
79
  },
77
80
  body: form,
78
81
  ...(signal ? { signal } : {}),
@@ -9,8 +9,9 @@ export function createOpenAiTranscriptionProvider(input: {
9
9
  const fetchImpl = input.fetch ?? fetch;
10
10
  return {
11
11
  id: "openai",
12
+ supportsServerDeadline: true,
12
13
  available: () => true,
13
- async transcribe({ audio, mimeType, filename, signal }) {
14
+ async transcribe({ audio, mimeType, filename, requestId, signal }) {
14
15
  const form = new FormData();
15
16
  form.append("file", audioBlob(audio, mimeType), filename);
16
17
  form.append("model", input.model);
@@ -18,7 +19,11 @@ export function createOpenAiTranscriptionProvider(input: {
18
19
  try {
19
20
  response = await fetchImpl(`${input.baseUrl}/audio/transcriptions`, {
20
21
  method: "POST",
21
- headers: { Authorization: `Bearer ${input.apiKey}` },
22
+ headers: {
23
+ Authorization: `Bearer ${input.apiKey}`,
24
+ // Observability only; the upstream API is not treated as idempotent.
25
+ "x-opengeni-request-id": requestId,
26
+ },
22
27
  body: form,
23
28
  ...(signal ? { signal } : {}),
24
29
  });