@opengeni/api-router 1.3.0 → 1.4.2

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 (42) hide show
  1. package/dist/access-grant-rls.d.ts +7 -0
  2. package/dist/app.d.ts +2 -2
  3. package/dist/app.js +1 -1
  4. package/dist/azure-live.d.ts +28 -0
  5. package/dist/{chunk-IZEGLLKZ.js → chunk-F3LQ6VWR.js} +1334 -486
  6. package/dist/{chunk-IZEGLLKZ.js.map → chunk-F3LQ6VWR.js.map} +1 -1
  7. package/dist/codemode.d.ts +3 -3
  8. package/dist/http/api-error.d.ts +14 -0
  9. package/dist/index.js +1 -1
  10. package/dist/integrations/oauth-profiles.d.ts +3 -1
  11. package/dist/routes/sessions.d.ts +2 -0
  12. package/dist/transcription/billing-refusal.d.ts +8 -0
  13. package/dist/transcription/normalize.d.ts +23 -0
  14. package/dist/transcription/providers/azure-mai.d.ts +12 -0
  15. package/dist/transcription/providers/azure-openai.d.ts +4 -0
  16. package/dist/transcription/providers/openai.d.ts +16 -0
  17. package/dist/transcription/service.d.ts +10 -1
  18. package/dist/transcription/wav-duration.d.ts +8 -0
  19. package/package.json +20 -20
  20. package/src/access-grant-rls.ts +18 -10
  21. package/src/app.ts +62 -8
  22. package/src/azure-live.ts +103 -0
  23. package/src/codemode.ts +3 -5
  24. package/src/gateway-realtime.ts +11 -7
  25. package/src/http/api-error.ts +36 -1
  26. package/src/integrations/oauth-client.ts +9 -17
  27. package/src/integrations/oauth-profiles.ts +17 -6
  28. package/src/routes/editable-artifacts.ts +8 -0
  29. package/src/routes/session-artifact-associations.ts +80 -6
  30. package/src/routes/sessions.ts +263 -66
  31. package/src/routes/transcription-recordings.ts +71 -4
  32. package/src/routes/transcriptions.ts +26 -2
  33. package/src/routes/workspaces.ts +49 -7
  34. package/src/sandbox/channel-a.ts +22 -0
  35. package/src/transcription/billing-refusal.ts +14 -0
  36. package/src/transcription/normalize.ts +77 -0
  37. package/src/transcription/providers/azure-mai.ts +98 -0
  38. package/src/transcription/providers/azure-openai.ts +16 -7
  39. package/src/transcription/providers/openai.ts +79 -5
  40. package/src/transcription/segmenter.ts +7 -1
  41. package/src/transcription/service.ts +215 -10
  42. package/src/transcription/wav-duration.ts +39 -0
@@ -6517,8 +6517,8 @@ import {
6517
6517
  canonicalPublicOrigin as canonicalPublicOrigin3,
6518
6518
  configuredAllowedReasoningEfforts,
6519
6519
  resolveFirstPartyMcpToolPolicy as resolveFirstPartyMcpToolPolicy2,
6520
- resolveVoiceInputProviderRegistry as resolveVoiceInputProviderRegistry2,
6521
- UnsupportedLatencyModeError as UnsupportedLatencyModeError2
6520
+ UnsupportedLatencyModeError as UnsupportedLatencyModeError2,
6521
+ voiceInputPricingIssues
6522
6522
  } from "@opengeni/config";
6523
6523
  import {
6524
6524
  AGENT_CAPABILITY_IDS,
@@ -6532,6 +6532,7 @@ import {
6532
6532
  resolveWorkspaceMemoryEnabled,
6533
6533
  resolveWorkspaceMemoryPromptMode,
6534
6534
  VOICE_INPUT_ACCEPTED_MIME_TYPES as VOICE_INPUT_ACCEPTED_MIME_TYPES2,
6535
+ WorkspaceVoiceInputSettings as WorkspaceVoiceInputSettings3,
6535
6536
  TRANSCRIPTION_RECORDING_PROVIDER_SEGMENT_SECONDS as TRANSCRIPTION_RECORDING_PROVIDER_SEGMENT_SECONDS2,
6536
6537
  ToolGatewayApprovalRequest as ToolGatewayApprovalRequest2,
6537
6538
  ToolGatewayCallRequest as ToolGatewayCallRequest2
@@ -6611,7 +6612,11 @@ import {
6611
6612
  ModelUnavailableError,
6612
6613
  ScheduledTaskTargetAccessChange
6613
6614
  } from "@opengeni/contracts";
6614
- import { isDatabaseConnectionLoss, WorkspaceControlBusyError } from "@opengeni/db";
6615
+ import {
6616
+ isDatabaseConnectionLoss,
6617
+ nestedPostgresSqlState as nestedPostgresSqlState4,
6618
+ WorkspaceControlBusyError
6619
+ } from "@opengeni/db";
6615
6620
  import { HTTPException as HTTPException20 } from "hono/http-exception";
6616
6621
  var ApiHttpError = class extends HTTPException20 {
6617
6622
  code;
@@ -6678,6 +6683,21 @@ function databaseUnavailableHttpError(error, method) {
6678
6683
  details: { code: DATABASE_UNAVAILABLE_ERROR_DETAIL_CODE }
6679
6684
  });
6680
6685
  }
6686
+ var DATABASE_CONTENTION_ERROR_DETAIL_CODE = "DATABASE_CONTENTION";
6687
+ function databaseContentionHttpError(error, method, options = {}) {
6688
+ if (error instanceof ApiHttpError) return null;
6689
+ if (error instanceof HTTPException20 && error.status < 500) return null;
6690
+ const sqlState = nestedPostgresSqlState4(error);
6691
+ if (sqlState !== "40P01" && sqlState !== "40001") return null;
6692
+ const safeMethod = method === "GET" || method === "HEAD" || method === "OPTIONS";
6693
+ return new ApiHttpError(503, {
6694
+ code: "upstream_unavailable",
6695
+ message: "Opengeni hit transient database contention. Retry shortly.",
6696
+ retryable: true,
6697
+ outcomeUnknown: options.outcomeUnknown ?? !safeMethod,
6698
+ details: { code: DATABASE_CONTENTION_ERROR_DETAIL_CODE, sqlState }
6699
+ });
6700
+ }
6681
6701
  function agentConfigHttpError(error) {
6682
6702
  const cause = error instanceof AgentConfigError ? error : error instanceof HTTPException20 && error.cause instanceof AgentConfigError ? error.cause : null;
6683
6703
  if (!cause) return null;
@@ -7065,8 +7085,8 @@ import {
7065
7085
  CodexCompactionV2ProviderLockedError,
7066
7086
  ManagedAuthActorLeaseOutcomeUnknownError,
7067
7087
  hasPermission as hasPermission34,
7068
- requireAccessGrant as requireAccessGrant54,
7069
- requireAccessGrantAuthorization as requireAccessGrantAuthorization40,
7088
+ requireAccessGrant as requireAccessGrant53,
7089
+ requireAccessGrantAuthorization as requireAccessGrantAuthorization41,
7070
7090
  accountScopedApiKeyWorkspaceAuthority as accountScopedApiKeyWorkspaceAuthority10,
7071
7091
  requireAccessContext as requireAccessContext15,
7072
7092
  requirePermission as requirePermission13,
@@ -8515,7 +8535,7 @@ function startManagedSignInNotificationDelivery(db, transport) {
8515
8535
  if (active) return;
8516
8536
  active = true;
8517
8537
  try {
8518
- for (let count = 0; count < 10; count++)
8538
+ for (let count2 = 0; count2 < 10; count2++)
8519
8539
  if (await deliverManagedSignInNotification(db, transport) === "outcome_unknown") break;
8520
8540
  } finally {
8521
8541
  active = false;
@@ -10277,9 +10297,9 @@ function dispositionFilenameParameters(filename) {
10277
10297
  const cleaned = Array.from(
10278
10298
  filename.replace(/[\u0000-\u001f\u007f/\\]+/g, "_").replace(/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g, "_").trim()
10279
10299
  ).slice(0, DISPOSITION_FILENAME_MAX_CHARS).join("");
10280
- const ascii2 = cleaned.replace(/[^A-Za-z0-9._ -]+/g, "_").replace(/\s+/g, " ").trim().slice(0, DISPOSITION_FILENAME_MAX_CHARS) || "download";
10281
- if (!cleaned || ascii2 === cleaned) return `filename="${ascii2}"`;
10282
- return `filename="${ascii2}"; filename*=UTF-8''${encodeExtendedParameterValue(cleaned)}`;
10300
+ const ascii3 = cleaned.replace(/[^A-Za-z0-9._ -]+/g, "_").replace(/\s+/g, " ").trim().slice(0, DISPOSITION_FILENAME_MAX_CHARS) || "download";
10301
+ if (!cleaned || ascii3 === cleaned) return `filename="${ascii3}"`;
10302
+ return `filename="${ascii3}"; filename*=UTF-8''${encodeExtendedParameterValue(cleaned)}`;
10283
10303
  }
10284
10304
  function userContentDispositionHeaders(contentType, filename) {
10285
10305
  if (!isActiveUserContentType(contentType)) return {};
@@ -10817,13 +10837,18 @@ import {
10817
10837
  import {
10818
10838
  freezeAgentLearningPolicy,
10819
10839
  withSessionRlsActorContext,
10820
- withCreditDebitAttribution
10840
+ withCreditDebitAttribution,
10841
+ currentCreditDebitAttribution
10821
10842
  } from "@opengeni/db";
10822
10843
  import { HTTPException as HTTPException21 } from "hono/http-exception";
10844
+ function creditDebitAttributionForGrant(grant) {
10845
+ if (grant.principalKind === "agent_attempt") return currentCreditDebitAttribution();
10846
+ return grant.principalKind === "human_session" ? { kind: "human", initiatingHumanSubjectId: grant.subjectId } : ["service", "api_key", "configured_key", "mcp_gateway"].includes(grant.principalKind ?? "") ? { kind: "service" } : { kind: "unknown" };
10847
+ }
10823
10848
  async function withAccessGrantSessionRlsContext(deps, grant, fn) {
10824
10849
  if (grant.principalKind !== "agent_attempt") {
10825
10850
  return await withCreditDebitAttribution(
10826
- grant.principalKind === "human_session" ? { kind: "human", initiatingHumanSubjectId: grant.subjectId } : grant.principalKind === "service" || grant.principalKind === "api_key" || grant.principalKind === "configured_key" || grant.principalKind === "mcp_gateway" ? { kind: "service" } : { kind: "unknown" },
10851
+ creditDebitAttributionForGrant(grant),
10827
10852
  () => withSessionRlsActorContext({ subjectId: grant.subjectId }, fn)
10828
10853
  );
10829
10854
  }
@@ -11270,6 +11295,13 @@ function registerEditableArtifactRoutes(app, deps) {
11270
11295
  const body3 = await parseBoundedMaterializationRequest(context);
11271
11296
  const scope = editableArtifactScope({ accountId: grant.accountId, workspaceId });
11272
11297
  const actor = editableArtifactActorForGrant(grant, body3.replicaId);
11298
+ if (!deps.settings.artifactMaterializerDeployed) {
11299
+ throw new ApiHttpError(503, {
11300
+ code: "upstream_unavailable",
11301
+ message: "File export is not available on this deployment.",
11302
+ retryable: false
11303
+ });
11304
+ }
11273
11305
  try {
11274
11306
  const result = await requireEditableArtifactExports(deps).enqueueMaterialization({
11275
11307
  scope,
@@ -11815,7 +11847,7 @@ import {
11815
11847
  confirmLegacyInstruction,
11816
11848
  getKnowledgeEntry,
11817
11849
  listKnowledgeEntries,
11818
- nestedPostgresSqlState as nestedPostgresSqlState4,
11850
+ nestedPostgresSqlState as nestedPostgresSqlState5,
11819
11851
  saveKnowledgeEntry,
11820
11852
  saveAgentInstruction,
11821
11853
  getAgentInstruction,
@@ -11861,7 +11893,7 @@ function registerKnowledgeEntryTools(server, deps, grant, sessionId) {
11861
11893
  ]
11862
11894
  };
11863
11895
  }
11864
- const state = nestedPostgresSqlState4(error);
11896
+ const state = nestedPostgresSqlState5(error);
11865
11897
  const message = surface === "instruction" ? state === "40001" ? "This instruction changed. Read its current content and baseline, then retry the same intended edit against that exact state." : state === "23505" ? "This instruction operation ID was already used with different input. Reuse it only for an exact retry." : state === "42501" ? "The instruction change is unavailable in this task's scope or Agent learning policy. Off disables authoring, not the task. Do not ask for approval to continue." : state === "22023" || state === "23514" ? `Invalid instruction change. Read the current instruction, use append for a new rule, or edit with one localized exact oldText match to update or remove text. Agents cannot replace the complete instruction. Each supplied text and the resulting instruction must stay within ${WORKSPACE_INSTRUCTION_POLICY_CONTENT_MAX_CHARS} characters, the same limit as the human editor.` : "Workspace instructions are temporarily unavailable. Retry the same operation ID only if the prior change may have succeeded." : state === "40001" ? "This entry changed. Read its current revision and retry your correction with the current version." : state === "23505" ? "This operation ID was already used with different input. Reuse it only for an exact retry." : state === "42501" ? "The entry, reference, or write is unavailable in this task's scope and Agent learning policy. Off disables saving, not your task. Do not ask for an approval to continue." : state === "22023" || state === "23514" ? "Invalid Knowledge entry or relationship. Check the referenced IDs and revision before retrying." : "Knowledge is temporarily unavailable. Your task can continue; retry the same operation ID if saving may have succeeded.";
11866
11898
  return {
11867
11899
  isError: true,
@@ -12406,7 +12438,7 @@ import { HTTPException as HTTPException22 } from "hono/http-exception";
12406
12438
  import {
12407
12439
  getScheduledScopedRigVersionMetadata,
12408
12440
  loadWorkspaceEnvironmentForRun,
12409
- nestedPostgresSqlState as nestedPostgresSqlState5
12441
+ nestedPostgresSqlState as nestedPostgresSqlState6
12410
12442
  } from "@opengeni/db";
12411
12443
  async function loadSessionAttachVariableSetValues(db, settings, input) {
12412
12444
  const { accountId, workspaceId, session, subjectId } = input;
@@ -12456,7 +12488,7 @@ async function loadSessionAttachVariableSetValues(db, settings, input) {
12456
12488
  variableSetId,
12457
12489
  source
12458
12490
  });
12459
- if (nestedPostgresSqlState5(error) === "42501") {
12491
+ if (nestedPostgresSqlState6(error) === "42501") {
12460
12492
  throw new ApiHttpError(403, {
12461
12493
  code: "forbidden",
12462
12494
  message: source === "sandbox_environment_default" ? "A default Variable Set of this session's Sandbox Environment is not available to this session." : "A Variable Set selected for this session is not available to this session.",
@@ -12470,7 +12502,7 @@ async function loadSessionAttachVariableSetValues(db, settings, input) {
12470
12502
  return values;
12471
12503
  }
12472
12504
  function logAttachFailure(message, error, context) {
12473
- const sqlState = nestedPostgresSqlState5(error);
12505
+ const sqlState = nestedPostgresSqlState6(error);
12474
12506
  console.warn(`[session-attach] ${message}`, {
12475
12507
  ...context,
12476
12508
  ...sqlState ? { sqlState } : {},
@@ -12498,6 +12530,7 @@ import {
12498
12530
  SandboxResumeIdentityMismatchError,
12499
12531
  SandboxResumeIdentityUnavailableError,
12500
12532
  RoutingActiveRouteChangedError,
12533
+ RoutingMutationOutputRejectedError,
12501
12534
  RoutingWorkspaceRootChangedError,
12502
12535
  SelfhostedWorkspaceRootChangedError,
12503
12536
  ChannelAConflictError,
@@ -13594,6 +13627,20 @@ function mapChannelAError(error, waitSignal) {
13594
13627
  });
13595
13628
  if (error instanceof SandboxResumeIdentityMismatchError || error instanceof SandboxResumeIdentityUnavailableError)
13596
13629
  return new HTTPException22(409, { message: error.message });
13630
+ if (error instanceof RoutingMutationOutputRejectedError) {
13631
+ const revoked = error.reasonCode === "authority_revoked";
13632
+ return new ApiHttpError(revoked ? 403 : 409, {
13633
+ code: revoked ? "forbidden" : "conflict",
13634
+ message: error.message,
13635
+ retryable: false,
13636
+ outcomeUnknown: false,
13637
+ details: {
13638
+ code: error.code,
13639
+ reasonCode: error.reasonCode,
13640
+ physicalOutcome: "resolved"
13641
+ }
13642
+ });
13643
+ }
13597
13644
  if (error instanceof RoutingActiveRouteChangedError || error instanceof RoutingWorkspaceRootChangedError || error instanceof SelfhostedWorkspaceRootChangedError || error instanceof ChannelAFileSystemRouteChangedError)
13598
13645
  return new ApiHttpError(409, {
13599
13646
  code: "conflict",
@@ -13660,6 +13707,13 @@ function channelAOperationFailureDiagnostic(error, waitSignal) {
13660
13707
  errorCode: "sandbox_channel_a_cancelled"
13661
13708
  };
13662
13709
  }
13710
+ if (error instanceof RoutingMutationOutputRejectedError) {
13711
+ return {
13712
+ reason: "request_rejected",
13713
+ status: error.reasonCode === "authority_revoked" ? 403 : 409,
13714
+ errorCode: "sandbox_channel_a_operation_failed"
13715
+ };
13716
+ }
13663
13717
  if (error instanceof SandboxProviderReadLockUnavailableError) {
13664
13718
  return {
13665
13719
  reason: "provider_read_busy",
@@ -15507,8 +15561,8 @@ function tokenHash(value) {
15507
15561
  function mcpOAuthRegistrationClientKey(c, trust) {
15508
15562
  return `mcp-oauth-registration:${trustedRequestSourceRateLimitKey(c, trust)}`;
15509
15563
  }
15510
- function expiresIn(seconds) {
15511
- return new Date(Date.now() + seconds * 1e3);
15564
+ function expiresIn(seconds2) {
15565
+ return new Date(Date.now() + seconds2 * 1e3);
15512
15566
  }
15513
15567
  function authorizationRedirect(redirectUri, params) {
15514
15568
  const url = new URL(redirectUri);
@@ -16872,7 +16926,10 @@ import {
16872
16926
  } from "@opengeni/db/organization-integration-policy";
16873
16927
  import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
16874
16928
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
16875
- import { parseIntegrationsOauthClientsJson } from "@opengeni/config";
16929
+ import {
16930
+ findIntegrationsOauthClient,
16931
+ parseIntegrationsOauthClientsJson
16932
+ } from "@opengeni/config";
16876
16933
  import {
16877
16934
  OAuthStartResponse,
16878
16935
  selectCanonicalPersonalSlackConnection
@@ -17002,7 +17059,11 @@ import {
17002
17059
  import { OPENGENI_PERSONAL_SLACK_MCP_URL } from "@opengeni/contracts";
17003
17060
  import { HTTPException as HTTPException31 } from "hono/http-exception";
17004
17061
  import { z as z25 } from "zod";
17005
- import { GMAIL_REST_MCP_TOOLS, gmailToolSupportsScopes } from "@opengeni/runtime/gmail-rest-mcp";
17062
+ import {
17063
+ GMAIL_REST_MCP_TOOLS,
17064
+ gmailToolAvailableOnDeployment,
17065
+ gmailToolSupportsScopes
17066
+ } from "@opengeni/runtime/gmail-rest-mcp";
17006
17067
 
17007
17068
  // src/integrations/provider-domain.ts
17008
17069
  import { HTTPException as HTTPException30 } from "hono/http-exception";
@@ -17137,10 +17198,12 @@ var OFFICIAL_GMAIL_PROFILE = {
17137
17198
  }
17138
17199
  return { gmailEmail: payload.emailAddress };
17139
17200
  },
17140
- toolsForScopes: (scopes) => {
17141
- return GMAIL_REST_MCP_TOOLS.filter((tool) => gmailToolSupportsScopes(tool.name, scopes)).map(
17142
- ({ name, description }) => ({ name, ...description ? { description } : {} })
17143
- );
17201
+ toolsForScopes: (scopes, deployment) => {
17202
+ return GMAIL_REST_MCP_TOOLS.filter(
17203
+ (tool) => gmailToolSupportsScopes(tool.name, scopes) && gmailToolAvailableOnDeployment(tool.name, {
17204
+ watchTopicName: deployment.gmailWatchTopicName
17205
+ })
17206
+ ).map(({ name, description }) => ({ name, ...description ? { description } : {} }));
17144
17207
  }
17145
17208
  }
17146
17209
  };
@@ -18472,7 +18535,6 @@ function operatorClientForAs(settings, as) {
18472
18535
  };
18473
18536
  }
18474
18537
  function operatorClientEntryFor(settings, candidates) {
18475
- const normalizedCandidates = new Set(candidates.map(normalizedIssuerKey));
18476
18538
  const candidateOrigins = candidates.flatMap((candidate) => {
18477
18539
  try {
18478
18540
  return [new URL(candidate).origin];
@@ -18489,22 +18551,10 @@ function operatorClientEntryFor(settings, candidates) {
18489
18551
  return resolved;
18490
18552
  }
18491
18553
  }
18492
- const configured2 = parseIntegrationsOauthClientsJson(settings.integrationsOauthClientsJson);
18493
- const exactKeys = uniqueStrings(
18494
- candidates.flatMap((candidate) => [candidate, normalizedIssuerKey(candidate)])
18554
+ return findIntegrationsOauthClient(
18555
+ parseIntegrationsOauthClientsJson(settings.integrationsOauthClientsJson),
18556
+ candidates
18495
18557
  );
18496
- for (const key of exactKeys) {
18497
- const entry2 = configured2[key];
18498
- if (entry2) {
18499
- return entry2;
18500
- }
18501
- }
18502
- for (const [key, entry2] of Object.entries(configured2)) {
18503
- if (normalizedCandidates.has(normalizedIssuerKey(key))) {
18504
- return entry2;
18505
- }
18506
- }
18507
- return null;
18508
18558
  }
18509
18559
  function gmailOAuthClientConfigured(settings) {
18510
18560
  const entry2 = operatorClientEntryFor(settings, ["https://accounts.google.com"]);
@@ -19122,7 +19172,8 @@ async function verifyMcpToolsListNonFatal(observability, settings, state, token,
19122
19172
  grantRejected = local.isRejectedGrant?.(payload) ?? false;
19123
19173
  providerIdentity = local.validateIdentity(payload);
19124
19174
  const verifiedTools = local.toolsForScopes(
19125
- grantedScopes(token.scopeText, state.authorizeScopes, profile)
19175
+ grantedScopes(token.scopeText, state.authorizeScopes, profile),
19176
+ settings
19126
19177
  );
19127
19178
  if (local.required && verifiedTools.length === 0)
19128
19179
  throw new Error("Connector verification did not report an authorized tool scope");
@@ -20502,15 +20553,15 @@ import { COMMAND_OUTPUT_MAX_BYTES } from "@opengeni/db/session-background-comman
20502
20553
  var COMMAND_READ_MAX_WAIT_SECONDS = 50;
20503
20554
  var COMMAND_WAIT_DEFAULT_SECONDS = 45;
20504
20555
  async function readCommandWithWait(input) {
20505
- const seconds = input.waitSeconds ?? 0;
20506
- if (!Number.isInteger(seconds) || seconds < 0 || seconds > COMMAND_READ_MAX_WAIT_SECONDS) {
20556
+ const seconds2 = input.waitSeconds ?? 0;
20557
+ if (!Number.isInteger(seconds2) || seconds2 < 0 || seconds2 > COMMAND_READ_MAX_WAIT_SECONDS) {
20507
20558
  throw new Error("waitSeconds must be between 0 and 50");
20508
20559
  }
20509
20560
  if (input.maxOutputBytes !== void 0 && (!Number.isInteger(input.maxOutputBytes) || input.maxOutputBytes < 4 || input.maxOutputBytes > COMMAND_OUTPUT_MAX_BYTES)) {
20510
20561
  throw new Error("maxOutputBytes must be between 4 and 65536");
20511
20562
  }
20512
20563
  const started2 = Date.now();
20513
- const deadline = started2 + seconds * 1e3;
20564
+ const deadline = started2 + seconds2 * 1e3;
20514
20565
  let unsubscribe;
20515
20566
  let wake;
20516
20567
  let changed2 = false;
@@ -20521,7 +20572,7 @@ async function readCommandWithWait(input) {
20521
20572
  };
20522
20573
  input.signal?.addEventListener("abort", notify);
20523
20574
  try {
20524
- if (seconds > 0) {
20575
+ if (seconds2 > 0) {
20525
20576
  try {
20526
20577
  unsubscribe = await input.subscribe((events) => {
20527
20578
  if (events.some(
@@ -20541,7 +20592,7 @@ async function readCommandWithWait(input) {
20541
20592
  return {
20542
20593
  ...result,
20543
20594
  waitedMs: Date.now() - started2,
20544
- timedOut: seconds > 0 && expired && !result.terminal && result.chunks.length === 0 && !result.hasMore,
20595
+ timedOut: seconds2 > 0 && expired && !result.terminal && result.chunks.length === 0 && !result.hasMore,
20545
20596
  aborted: input.signal?.aborted ?? false,
20546
20597
  liveFanout
20547
20598
  };
@@ -22021,7 +22072,7 @@ function buildScheduledTaskDetailMcp(task, budget, reducedForBytes) {
22021
22072
  deliveredCount: resources.length,
22022
22073
  originalBytes: jsonBytes(task.agentConfig.resources),
22023
22074
  truncatedIdentityFieldCount: resourceProjections.reduce(
22024
- (count, projection) => count + projection.truncatedFieldCount,
22075
+ (count2, projection) => count2 + projection.truncatedFieldCount,
22025
22076
  0
22026
22077
  )
22027
22078
  },
@@ -22030,7 +22081,7 @@ function buildScheduledTaskDetailMcp(task, budget, reducedForBytes) {
22030
22081
  deliveredCount: tools.length,
22031
22082
  originalBytes: jsonBytes(task.agentConfig.tools),
22032
22083
  truncatedIdentityFieldCount: toolProjections.reduce(
22033
- (count, projection) => count + projection.truncatedFieldCount,
22084
+ (count2, projection) => count2 + projection.truncatedFieldCount,
22034
22085
  0
22035
22086
  )
22036
22087
  },
@@ -25033,9 +25084,9 @@ function slackRetryAfterMs(response) {
25033
25084
  if (response.status !== 429) return null;
25034
25085
  const raw = response.headers.get("retry-after");
25035
25086
  if (!raw || !/^\d+$/.test(raw)) return null;
25036
- const seconds = Number(raw);
25037
- if (!Number.isSafeInteger(seconds) || seconds < 1) return null;
25038
- return Math.min(seconds, 3600) * 1e3;
25087
+ const seconds2 = Number(raw);
25088
+ if (!Number.isSafeInteger(seconds2) || seconds2 < 1) return null;
25089
+ return Math.min(seconds2, 3600) * 1e3;
25039
25090
  }
25040
25091
  function assertOpenGeniSlackBotScopes(grantedScopes2) {
25041
25092
  const policy = evaluateOpenGeniSlackBotScopes(grantedScopes2);
@@ -25613,11 +25664,11 @@ function nextSlackFilesListPage(payload, requested, returnedFileCount) {
25613
25664
  if (responseMetadata && Object.hasOwn(responseMetadata, "next_cursor")) {
25614
25665
  throw new SlackBotProviderError("invalid_files_paging");
25615
25666
  }
25616
- const count = paging?.count;
25667
+ const count2 = paging?.count;
25617
25668
  const total = paging?.total;
25618
25669
  const page2 = paging?.page;
25619
25670
  const pages = paging?.pages;
25620
- if (!positiveSafeInt(count) || !nonNegativeSafeInt(total) || !positiveSafeInt(page2) || !nonNegativeSafeInt(pages) || count !== requested.count || page2 !== requested.page || returnedFileCount > count || pages !== (total === 0 ? 0 : Math.ceil(total / count)) || page2 > Math.max(1, pages)) {
25671
+ if (!positiveSafeInt(count2) || !nonNegativeSafeInt(total) || !positiveSafeInt(page2) || !nonNegativeSafeInt(pages) || count2 !== requested.count || page2 !== requested.page || returnedFileCount > count2 || pages !== (total === 0 ? 0 : Math.ceil(total / count2)) || page2 > Math.max(1, pages)) {
25621
25672
  throw new SlackBotProviderError("invalid_files_paging");
25622
25673
  }
25623
25674
  if (page2 >= pages) return null;
@@ -74235,7 +74286,7 @@ import {
74235
74286
  getOrganizationIntegrationPolicy,
74236
74287
  updateOrganizationIntegrationPolicy
74237
74288
  } from "@opengeni/db/organization-integration-policy";
74238
- import { nestedPostgresSqlState as nestedPostgresSqlState6 } from "@opengeni/db";
74289
+ import { nestedPostgresSqlState as nestedPostgresSqlState7 } from "@opengeni/db";
74239
74290
  import { HTTPException as HTTPException46 } from "hono/http-exception";
74240
74291
  import { z as z32 } from "zod";
74241
74292
  async function authorizeOrganizationIntegrationAdministration(context, deps, accountId, mutation) {
@@ -74264,7 +74315,7 @@ async function policyResponse(readOrWrite) {
74264
74315
  return await readOrWrite();
74265
74316
  } catch (error) {
74266
74317
  if (error instanceof HTTPException46) throw error;
74267
- const state = nestedPostgresSqlState6(error);
74318
+ const state = nestedPostgresSqlState7(error);
74268
74319
  if (state === "42501") {
74269
74320
  throw new HTTPException46(403, { message: "Organization administration required" });
74270
74321
  }
@@ -81187,7 +81238,7 @@ import {
81187
81238
  listAgentLearningOverrides,
81188
81239
  listKnowledgeEntryHistory,
81189
81240
  listKnowledgeReviewBatches,
81190
- nestedPostgresSqlState as nestedPostgresSqlState7,
81241
+ nestedPostgresSqlState as nestedPostgresSqlState8,
81191
81242
  restoreKnowledgeEntry,
81192
81243
  reviewKnowledgeEntry,
81193
81244
  saveAgentLearningSettings,
@@ -81241,7 +81292,7 @@ function knowledgeHttpError(error) {
81241
81292
  throw new HTTPException57(422, {
81242
81293
  message: error.issues[0]?.message ?? "Invalid Knowledge request"
81243
81294
  });
81244
- const state = nestedPostgresSqlState7(error);
81295
+ const state = nestedPostgresSqlState8(error);
81245
81296
  if (state === "40001" || state === "23505")
81246
81297
  throw new HTTPException57(409, { message: "This item changed. Refresh it and try again." });
81247
81298
  if (state === "42501")
@@ -83461,12 +83512,12 @@ function creditScopeMetadata(scope) {
83461
83512
  }
83462
83513
  function creditScopeFromMetadata(metadata) {
83463
83514
  if (!metadata || !Object.keys(metadata).some((key) => key.startsWith(prefix))) return void 0;
83464
- const count = Number(metadata[countKey]);
83465
- if (!Number.isInteger(count) || count < 1 || count > 24) {
83515
+ const count2 = Number(metadata[countKey]);
83516
+ if (!Number.isInteger(count2) || count2 < 1 || count2 > 24) {
83466
83517
  throw new Error("Invalid promotional credit snapshot");
83467
83518
  }
83468
83519
  let encoded = "";
83469
- for (let index = 0; index < count; index++) {
83520
+ for (let index = 0; index < count2; index++) {
83470
83521
  const chunk = metadata[`${prefix}${index}`];
83471
83522
  if (!chunk) throw new Error("Incomplete promotional credit snapshot");
83472
83523
  encoded += chunk;
@@ -95517,7 +95568,7 @@ import {
95517
95568
  getPrReviewAppRegistrationSecret,
95518
95569
  listPrReviewAppRegistrations as listPrReviewAppRegistrations3,
95519
95570
  listPrReviewRepositoryBindings as listPrReviewRepositoryBindings2,
95520
- nestedPostgresSqlState as nestedPostgresSqlState9,
95571
+ nestedPostgresSqlState as nestedPostgresSqlState10,
95521
95572
  recordAuditEvent as recordAuditEvent12,
95522
95573
  updatePrReviewAppRegistration,
95523
95574
  updatePrReviewRepositoryBinding
@@ -95733,7 +95784,7 @@ import {
95733
95784
  getAutomationSourceSecret as getAutomationSourceSecret2,
95734
95785
  listPrReviewAppRegistrations as listPrReviewAppRegistrations2,
95735
95786
  listPrReviewRepositoryBindings,
95736
- nestedPostgresSqlState as nestedPostgresSqlState8,
95787
+ nestedPostgresSqlState as nestedPostgresSqlState9,
95737
95788
  PrReviewDispatchAuthorityError,
95738
95789
  recordAuditEvent as recordAuditEvent11,
95739
95790
  resolveManagedGitHubPrReviewRoute,
@@ -96774,7 +96825,7 @@ function registerPrReviewGitHubRoutes(app, deps) {
96774
96825
  if (error instanceof PrReviewDispatchAuthorityError) {
96775
96826
  throw new HTTPException76(409, { message: error.message });
96776
96827
  }
96777
- if (nestedPostgresSqlState8(error) === "23505") {
96828
+ if (nestedPostgresSqlState9(error) === "23505") {
96778
96829
  throw new HTTPException76(409, {
96779
96830
  message: "One of these repositories is already connected to Opengeni Lens in another workspace"
96780
96831
  });
@@ -97519,7 +97570,7 @@ async function mapPrReviewUniqueConflict(operation, message) {
97519
97570
  try {
97520
97571
  return await operation;
97521
97572
  } catch (error) {
97522
- if (nestedPostgresSqlState9(error) === "23505") {
97573
+ if (nestedPostgresSqlState10(error) === "23505") {
97523
97574
  throw new HTTPException77(409, { message });
97524
97575
  }
97525
97576
  throw error;
@@ -100182,219 +100233,10 @@ function registerScheduledTaskRoutes(app, deps) {
100182
100233
  });
100183
100234
  }
100184
100235
 
100185
- // src/routes/sessions.ts
100186
- import { getRetainedProviderCommand } from "@opengeni/db/retained-provider-commands";
100187
- import { assertGoalResumeAllowed as assertGoalResumeAllowed2, GoalResumeBlockedError } from "@opengeni/core";
100188
- import { UnsupportedLatencyModeError } from "@opengeni/config";
100189
- import { searchSessionMessagesForSubject, SessionMessageSearchCursorError } from "@opengeni/db";
100190
- import { listSessionEventSlices } from "@opengeni/db/session-event-slices";
100191
- import * as sessionPreviewSchema from "@opengeni/db/schema";
100192
- import { SessionMessageSearchRequest } from "@opengeni/contracts";
100193
- import { scheduledSessionIds } from "@opengeni/db";
100194
- import { isVerifiedDelegatedHumanAuthorization as isVerifiedDelegatedHumanAuthorization4, withSiteSessionOrigin as withSiteSessionOrigin3 } from "@opengeni/core";
100195
- import { freezeSessionRealtimeConnectionAccounts } from "@opengeni/core";
100196
- import { SandboxRecoveryRequest } from "@opengeni/contracts";
100197
- import { getManagedHumanSandboxRecovery, consentManagedHumanSandboxRecovery } from "@opengeni/core";
100198
- import { SandboxRecoveryConflictError } from "@opengeni/db";
100199
- import { getSessionCodexAccounts } from "@opengeni/db";
100200
- import { getToolActionReview, getToolReviewDetailsPage } from "@opengeni/db";
100201
- import {
100202
- AcknowledgeStreamRequest,
100203
- ApplySessionGoalRevisionRequest,
100204
- ListSessionGoalRevisionsQuery,
100205
- RejectSessionGoalRevisionRequest,
100206
- RollbackSessionGoalRevisionRequest,
100207
- ActivateCodexRealtimeConnectionRequest,
100208
- AttachViewerRequest,
100209
- BeginSessionRealtimeRequest,
100210
- ClearSessionContextRequest,
100211
- CodexRealtimeWebrtcRequest,
100212
- GatewayRealtimeConnectRequest,
100213
- ClientSessionEvent,
100214
- CompactSessionContextRequest,
100215
- CreateSessionRequest as CreateSessionRequest2,
100216
- DeleteSessionQueueItemRequest,
100217
- EditSessionQueueItemRequest,
100218
- EndSessionRealtimeRequest,
100219
- FsDeleteRequest,
100220
- FsListBatchRequest,
100221
- FsListRequest,
100222
- FsMkdirRequest,
100223
- FsMoveRequest,
100224
- FsReadRequest,
100225
- FsWriteRequest,
100226
- ForkSessionRequest,
100227
- HumanInputRequestStatus,
100228
- GitDiffRequest,
100229
- GitReadBatchRequest,
100230
- GitLogRequest,
100231
- GitShowRequest,
100232
- GitStatusRequest,
100233
- MoveSessionQueueItemRequest,
100234
- PtyCloseRequest,
100235
- PtyOpenRequest,
100236
- PtyResizeRequest,
100237
- PtyWriteRequest,
100238
- PublishSandboxFileArtifactRequest,
100239
- RenewSessionRealtimeRequest,
100240
- SyncSessionRealtimeLedgerRequest,
100241
- SessionControlRequest,
100242
- SessionRetryRequest,
100243
- SESSION_EVENT_RAW_DELTA_TYPES as SESSION_EVENT_RAW_DELTA_TYPES2,
100244
- SessionEventPayloadMode as SessionEventPayloadMode2,
100245
- SessionEventReadDirection as SessionEventReadDirection2,
100246
- SessionEventReadMode as SessionEventReadMode2,
100247
- SessionEventLatestClass as SessionEventLatestClass2,
100248
- SessionEventResultMode as SessionEventResultMode2,
100249
- SessionScopeSubjectId,
100250
- SessionEventSemanticClass as SessionEventSemanticClass2,
100251
- SessionEventType as SessionEventType2,
100252
- SessionMcpServerId,
100253
- SaveNewSessionDraftRequest,
100254
- compactSessionEventResult as compactSessionEventResult3,
100255
- sessionEventLatestClassToSemanticClass as sessionEventLatestClassToSemanticClass2,
100256
- SaveComposerDraftRequest,
100257
- SubmitComposerDraftRequest,
100258
- SteerSessionQueueItemRequest,
100259
- SteerSessionMessageRequest,
100260
- TerminalExecRequest,
100261
- UpdateSessionChannelRequest,
100262
- UpdateSessionAttentionRequest,
100263
- UpdateSessionArchiveRequest,
100264
- UpdateSessionPinRequest,
100265
- UpdateSessionGoalRequest,
100266
- UpdateSessionMcpApprovalPolicyRequest,
100267
- UpdateSessionRequest,
100268
- UpdateSessionVariableSetsRequest,
100269
- UpdateSessionVisibilityRequest,
100270
- UpdateSessionToolPolicyRequest,
100271
- UpdateSessionAgentRequest,
100272
- AgentConfigError as AgentConfigError2,
100273
- ViewerHeartbeatRequest,
100274
- WORKSPACE_CONTROL_ACTOR_MAX_BYTES,
100275
- WORK_CLAIM_CANONICAL_KEY_MAX_BYTES as WORK_CLAIM_CANONICAL_KEY_MAX_BYTES2,
100276
- WORK_CLAIM_DISCOVERY_LIMIT as WORK_CLAIM_DISCOVERY_LIMIT2,
100277
- WORK_CLAIM_NAMESPACE_MAX_BYTES as WORK_CLAIM_NAMESPACE_MAX_BYTES2,
100278
- WORK_DISCOVERY_QUERY_MAX_CHARS as WORK_DISCOVERY_QUERY_MAX_CHARS2,
100279
- WORK_DISCOVERY_RECENT_HOURS_MAX as WORK_DISCOVERY_RECENT_HOURS_MAX2,
100280
- WorkClaimSubjectFilter as WorkClaimSubjectFilterSchema,
100281
- WorkClaimSubjectType as WorkClaimSubjectType2,
100282
- normalizeWorkClaimCanonicalKey,
100283
- normalizeWorkClaimNamespace,
100284
- workspaceControlUtf8Bytes
100285
- } from "@opengeni/contracts";
100286
- import { streamTokenDegraded } from "@opengeni/config";
100287
- import {
100288
- recordAuditEvent as recordAuditEvent13,
100289
- acceptSessionApprovalDecision as acceptSessionApprovalDecision2,
100290
- acceptSessionHumanInputResponse as acceptSessionHumanInputResponse2,
100291
- clearSessionGoal,
100292
- clearSessionContext,
100293
- getOpenPtySession,
100294
- getEnrollment as getEnrollment2,
100295
- getLiveEnrollmentConnection as getLiveEnrollmentConnection7,
100296
- getRetainedProcess,
100297
- getSandbox as getSandbox4,
100298
- getSession as getSession9,
100299
- readActiveSandbox as readActiveSandbox3,
100300
- deleteSessionTreeIfQuiescent,
100301
- getSessionEvent,
100302
- getSessionForSubject as getSessionForSubject2,
100303
- getSessionGoal as getSessionGoal2,
100304
- getLatestSessionModelContext,
100305
- getSessionHumanInputRequest,
100306
- getSessionGoalWithContinuation,
100307
- getSessionGoalRevision,
100308
- getSessionQueueSnapshot as getSessionQueueSnapshot2,
100309
- getStreamAcknowledgment,
100310
- insertPtySession,
100311
- listSessionEventPage as listSessionEventPage3,
100312
- listSessionHumanInputRequests,
100313
- listSessionGoalRevisionPage,
100314
- listSessionGoalRevisions,
100315
- rejectSessionGoalRevisionWithEvent,
100316
- listSessionIdsInGroup,
100317
- listSessionDiscoverySummaries as listSessionDiscoverySummaries2,
100318
- listSessionDiscoveryAncestorPaths,
100319
- listSessionsForSubject,
100320
- listSessionEntriesForSubject,
100321
- getLatestStartedSessionTurn,
100322
- listSessionTurns,
100323
- projectEffectiveControlForRelatedAccess as projectEffectiveControlForRelatedAccess2,
100324
- projectSessionForRelatedAccess as projectSessionForRelatedAccess2,
100325
- recordStreamAcknowledgment,
100326
- requestSessionCompaction,
100327
- switchSessionCodexAccount,
100328
- setSessionChannel as setSessionChannel2,
100329
- updateSessionVariableSets,
100330
- ChannelNotFoundError,
100331
- setSessionAttention,
100332
- setSessionArchive,
100333
- setSessionPin,
100334
- SessionAttentionVersionConflictError,
100335
- SessionArchiveVersionConflictError,
100336
- SessionPinVersionConflictError,
100337
- SessionPinAccessError,
100338
- SessionListAccessError,
100339
- SessionListCursorError,
100340
- SessionListCursorExpiredError,
100341
- decodeSessionListCursor,
100342
- revokeViewer,
100343
- setSessionGoalStatusWithEvent as setSessionGoalStatusWithEvent2,
100344
- upsertSessionGoalWithEvent as upsertSessionGoalWithEvent2,
100345
- updatePtySessionActivity,
100346
- QueueCommandConflictError,
100347
- beginSessionRealtimeInTransaction,
100348
- activateSessionRealtimeConnectionInTransaction,
100349
- claimSessionRealtimeConnectionInTransaction,
100350
- completeSessionRealtimeConnectionInTransaction,
100351
- endSessionRealtimeInTransaction,
100352
- failSessionRealtimeConnectionInTransaction,
100353
- NewSessionDraftConflictError,
100354
- SessionCommandIdempotencyError,
100355
- SessionControlConflictError,
100356
- SessionRealtimeConflictError,
100357
- SessionToolPolicyVersionConflictError,
100358
- SessionContextBusyError,
100359
- SessionVariableSetSelectionUnavailableError,
100360
- SessionCreateConnectionSelectionUnavailableError as SessionCreateConnectionSelectionUnavailableError2,
100361
- workspaceControlRequestLockTimeoutMs,
100362
- SessionTenancyAccessError,
100363
- SessionTenancyConflictError,
100364
- SessionTenancyInvalidRequestError,
100365
- SessionTenancyNotActivatedError,
100366
- HumanInputResponseValidationError as HumanInputResponseValidationError2,
100367
- SkillHumanResponseError,
100368
- latestWorkspaceCapture,
100369
- sessionLatestWorkspaceCapture,
100370
- renewSessionRealtimeInTransaction,
100371
- syncSessionRealtimeLedgerInTransaction,
100372
- withWorkspaceSessionActivityRls,
100373
- withWorkspaceRls,
100374
- workspaceCaptureAtRevision
100375
- } from "@opengeni/db";
100376
- import {
100377
- backgroundCommandActivityForSessions,
100378
- listSessionBackgroundCommands,
100379
- requestSessionBackgroundCommandCancellation
100380
- } from "@opengeni/db/session-background-commands";
100381
- import {
100382
- appendAndPublishEvents as appendAndPublishEvents3,
100383
- boundSessionEventHttpPage,
100384
- coalesceSessionEventDeltasWithCoverage as coalesceSessionEventDeltasWithCoverage2,
100385
- publishDurableSessionEvents as publishDurableSessionEvents3
100386
- } from "@opengeni/events";
100387
-
100388
- // src/gateway-realtime.ts
100389
- import {
100390
- VERCEL_AI_GATEWAY_AI_SDK_BASE_URL,
100391
- VERCEL_AI_GATEWAY_BASE_URL,
100392
- resolveAiGatewayRealtimeModel
100393
- } from "@opengeni/config";
100236
+ // src/azure-live.ts
100394
100237
  import {
100395
100238
  getActiveSessionHistoryItems as getActiveSessionHistoryItems2,
100396
- getSessionRealtimeContinuityEntries as getSessionRealtimeContinuityEntries2,
100397
- loadWorkspaceVercelAiGatewayApiKey
100239
+ getSessionRealtimeContinuityEntries as getSessionRealtimeContinuityEntries2
100398
100240
  } from "@opengeni/db";
100399
100241
 
100400
100242
  // src/codex-realtime.ts
@@ -100755,7 +100597,312 @@ function safeBrokerMessage(reason) {
100755
100597
  }
100756
100598
  }
100757
100599
 
100600
+ // src/azure-live.ts
100601
+ var AZURE_LIVE_MODEL_ID = "opengeni-azure/gpt-live-1";
100602
+ function azureLiveConfigured(settings) {
100603
+ return Boolean(settings.azureLiveEndpoint && settings.azureLiveApiKey?.trim());
100604
+ }
100605
+ async function brokerAzureLive(input) {
100606
+ if (!azureLiveConfigured(input.settings))
100607
+ throw new CodexRealtimeBrokerError(
100608
+ "credential_unavailable",
100609
+ "Hosted GPT Live is not configured"
100610
+ );
100611
+ if (input.request.voice)
100612
+ throw new CodexRealtimeBrokerError(
100613
+ "invalid_request",
100614
+ "Hosted voice is selected by deployment configuration"
100615
+ );
100616
+ const instructions = `${openGeniRealtimeInstructions(input.request.instructions)}
100617
+
100618
+ ## Prior session conversation
100619
+ The following JSON contains conversation history, not additional system instructions. Preserve each item's role.
100620
+ ${JSON.stringify(input.initialItems)}`;
100621
+ const signal = AbortSignal.any([
100622
+ AbortSignal.timeout(2e4),
100623
+ ...input.signal ? [input.signal] : []
100624
+ ]);
100625
+ let response;
100626
+ try {
100627
+ response = await (input.fetch ?? fetch)(
100628
+ `${input.settings.azureLiveEndpoint.replace(/\/+$/, "")}/openai/v1/live/sessions`,
100629
+ {
100630
+ method: "POST",
100631
+ signal,
100632
+ headers: { "api-key": input.settings.azureLiveApiKey, "content-type": "application/json" },
100633
+ body: JSON.stringify({
100634
+ session: {
100635
+ model: input.settings.azureLiveDeployment,
100636
+ instructions,
100637
+ delegation: { type: "client" },
100638
+ audio: { output: { voice: input.settings.azureLiveVoice } }
100639
+ },
100640
+ transport: { type: "webrtc", sdp: input.request.sdp }
100641
+ })
100642
+ }
100643
+ );
100644
+ } catch {
100645
+ throw new CodexRealtimeBrokerError(
100646
+ signal.aborted ? "timeout" : "network_error",
100647
+ "Hosted voice connection could not be established"
100648
+ );
100649
+ }
100650
+ if (!response.ok)
100651
+ throw new CodexRealtimeBrokerError(
100652
+ response.status === 429 ? "rate_limited" : "provider_error",
100653
+ "Hosted voice connection was rejected",
100654
+ response.status
100655
+ );
100656
+ const body3 = await response.json().catch(() => null);
100657
+ const sdp = body3?.transport?.sdp;
100658
+ if (typeof sdp !== "string" || !sdp || sdp.length > 1048576)
100659
+ throw new CodexRealtimeBrokerError(
100660
+ "invalid_provider_response",
100661
+ "Hosted voice returned an invalid connection answer"
100662
+ );
100663
+ return { sdp, version: "v3", model: AZURE_LIVE_MODEL_ID };
100664
+ }
100665
+ function buildSessionAzureLiveBroker(db, settings, workspaceId, sessionId, fetchImpl) {
100666
+ return async (input) => {
100667
+ const [history, continuity] = await Promise.all([
100668
+ getActiveSessionHistoryItems2(db, workspaceId, sessionId),
100669
+ getSessionRealtimeContinuityEntries2(db, workspaceId, sessionId)
100670
+ ]);
100671
+ return brokerAzureLive({
100672
+ settings,
100673
+ ...input,
100674
+ initialItems: projectSessionRealtimeInitialItems(history, continuity),
100675
+ ...fetchImpl ? { fetch: fetchImpl } : {}
100676
+ });
100677
+ };
100678
+ }
100679
+
100680
+ // src/routes/sessions.ts
100681
+ import { getRetainedProviderCommand } from "@opengeni/db/retained-provider-commands";
100682
+ import { assertGoalResumeAllowed as assertGoalResumeAllowed2, GoalResumeBlockedError } from "@opengeni/core";
100683
+ import { UnsupportedLatencyModeError } from "@opengeni/config";
100684
+ import { searchSessionMessagesForSubject, SessionMessageSearchCursorError } from "@opengeni/db";
100685
+ import { listSessionEventSlices } from "@opengeni/db/session-event-slices";
100686
+ import * as sessionPreviewSchema from "@opengeni/db/schema";
100687
+ import { SessionMessageSearchRequest } from "@opengeni/contracts";
100688
+ import { requireWorkspace as requireWorkspace3, scheduledSessionIds } from "@opengeni/db";
100689
+ import { isVerifiedDelegatedHumanAuthorization as isVerifiedDelegatedHumanAuthorization4, withSiteSessionOrigin as withSiteSessionOrigin3 } from "@opengeni/core";
100690
+ import { freezeSessionRealtimeConnectionAccounts } from "@opengeni/core";
100691
+ import {
100692
+ createRealtimeVoiceBilling,
100693
+ RealtimeVoiceUnavailableError,
100694
+ TranscriptionBillingRefusedError
100695
+ } from "@opengeni/core";
100696
+
100697
+ // src/transcription/billing-refusal.ts
100698
+ function transcriptionBillingRefusal(c, error) {
100699
+ return c.json({ code: error.code, message: error.message, ...error.details }, error.status);
100700
+ }
100701
+
100702
+ // src/routes/sessions.ts
100703
+ import { SandboxRecoveryRequest } from "@opengeni/contracts";
100704
+ import { getManagedHumanSandboxRecovery, consentManagedHumanSandboxRecovery } from "@opengeni/core";
100705
+ import { SandboxRecoveryConflictError } from "@opengeni/db";
100706
+ import { getSessionCodexAccounts } from "@opengeni/db";
100707
+ import { getToolActionReview, getToolReviewDetailsPage } from "@opengeni/db";
100708
+ import {
100709
+ AcknowledgeStreamRequest,
100710
+ ApplySessionGoalRevisionRequest,
100711
+ ListSessionGoalRevisionsQuery,
100712
+ RejectSessionGoalRevisionRequest,
100713
+ RollbackSessionGoalRevisionRequest,
100714
+ ActivateCodexRealtimeConnectionRequest,
100715
+ AttachViewerRequest,
100716
+ BeginSessionRealtimeRequest,
100717
+ ClearSessionContextRequest,
100718
+ CodexRealtimeWebrtcRequest,
100719
+ GatewayRealtimeConnectRequest,
100720
+ ClientSessionEvent,
100721
+ CompactSessionContextRequest,
100722
+ CreateSessionRequest as CreateSessionRequest2,
100723
+ DeleteSessionQueueItemRequest,
100724
+ EditSessionQueueItemRequest,
100725
+ EndSessionRealtimeRequest,
100726
+ FsDeleteRequest,
100727
+ FsListBatchRequest,
100728
+ FsListRequest,
100729
+ FsMkdirRequest,
100730
+ FsMoveRequest,
100731
+ FsReadRequest,
100732
+ FsWriteRequest,
100733
+ ForkSessionRequest,
100734
+ HumanInputRequestStatus,
100735
+ GitDiffRequest,
100736
+ GitReadBatchRequest,
100737
+ GitLogRequest,
100738
+ GitShowRequest,
100739
+ GitStatusRequest,
100740
+ MoveSessionQueueItemRequest,
100741
+ PtyCloseRequest,
100742
+ PtyOpenRequest,
100743
+ PtyResizeRequest,
100744
+ PtyWriteRequest,
100745
+ PublishSandboxFileArtifactRequest,
100746
+ RenewSessionRealtimeRequest,
100747
+ SyncSessionRealtimeLedgerRequest,
100748
+ SessionControlRequest,
100749
+ SessionRetryRequest,
100750
+ SESSION_EVENT_RAW_DELTA_TYPES as SESSION_EVENT_RAW_DELTA_TYPES2,
100751
+ SessionEventPayloadMode as SessionEventPayloadMode2,
100752
+ SessionEventReadDirection as SessionEventReadDirection2,
100753
+ SessionEventReadMode as SessionEventReadMode2,
100754
+ SessionEventLatestClass as SessionEventLatestClass2,
100755
+ SessionEventResultMode as SessionEventResultMode2,
100756
+ SessionScopeSubjectId,
100757
+ SessionEventSemanticClass as SessionEventSemanticClass2,
100758
+ SessionEventType as SessionEventType2,
100759
+ SessionMcpServerId,
100760
+ SaveNewSessionDraftRequest,
100761
+ compactSessionEventResult as compactSessionEventResult3,
100762
+ sessionEventLatestClassToSemanticClass as sessionEventLatestClassToSemanticClass2,
100763
+ SaveComposerDraftRequest,
100764
+ SubmitComposerDraftRequest,
100765
+ SteerSessionQueueItemRequest,
100766
+ SteerSessionMessageRequest,
100767
+ TerminalExecRequest,
100768
+ UpdateSessionChannelRequest,
100769
+ UpdateSessionAttentionRequest,
100770
+ UpdateSessionArchiveRequest,
100771
+ UpdateSessionPinRequest,
100772
+ UpdateSessionGoalRequest,
100773
+ UpdateSessionMcpApprovalPolicyRequest,
100774
+ UpdateSessionRequest,
100775
+ UpdateSessionVariableSetsRequest,
100776
+ UpdateSessionVisibilityRequest,
100777
+ UpdateSessionToolPolicyRequest,
100778
+ UpdateSessionAgentRequest,
100779
+ AgentConfigError as AgentConfigError2,
100780
+ ViewerHeartbeatRequest,
100781
+ WORKSPACE_CONTROL_ACTOR_MAX_BYTES,
100782
+ WORK_CLAIM_CANONICAL_KEY_MAX_BYTES as WORK_CLAIM_CANONICAL_KEY_MAX_BYTES2,
100783
+ WORK_CLAIM_DISCOVERY_LIMIT as WORK_CLAIM_DISCOVERY_LIMIT2,
100784
+ WORK_CLAIM_NAMESPACE_MAX_BYTES as WORK_CLAIM_NAMESPACE_MAX_BYTES2,
100785
+ WORK_DISCOVERY_QUERY_MAX_CHARS as WORK_DISCOVERY_QUERY_MAX_CHARS2,
100786
+ WORK_DISCOVERY_RECENT_HOURS_MAX as WORK_DISCOVERY_RECENT_HOURS_MAX2,
100787
+ WorkClaimSubjectFilter as WorkClaimSubjectFilterSchema,
100788
+ WorkClaimSubjectType as WorkClaimSubjectType2,
100789
+ normalizeWorkClaimCanonicalKey,
100790
+ normalizeWorkClaimNamespace,
100791
+ workspaceControlUtf8Bytes
100792
+ } from "@opengeni/contracts";
100793
+ import { streamTokenDegraded } from "@opengeni/config";
100794
+ import {
100795
+ recordAuditEvent as recordAuditEvent13,
100796
+ acceptSessionApprovalDecision as acceptSessionApprovalDecision2,
100797
+ acceptSessionHumanInputResponse as acceptSessionHumanInputResponse2,
100798
+ clearSessionGoal,
100799
+ clearSessionContext,
100800
+ getOpenPtySession,
100801
+ getEnrollment as getEnrollment2,
100802
+ getLiveEnrollmentConnection as getLiveEnrollmentConnection7,
100803
+ getRetainedProcess,
100804
+ getSandbox as getSandbox4,
100805
+ getSession as getSession9,
100806
+ readActiveSandbox as readActiveSandbox3,
100807
+ deleteSessionTreeIfQuiescent,
100808
+ getSessionEvent,
100809
+ getSessionForSubject as getSessionForSubject2,
100810
+ getSessionGoal as getSessionGoal2,
100811
+ getLatestSessionModelContext,
100812
+ getSessionHumanInputRequest,
100813
+ getSessionGoalWithContinuation,
100814
+ getSessionGoalRevision,
100815
+ getSessionQueueSnapshot as getSessionQueueSnapshot2,
100816
+ getStreamAcknowledgment,
100817
+ insertPtySession,
100818
+ listSessionEventPage as listSessionEventPage3,
100819
+ listSessionHumanInputRequests,
100820
+ listSessionGoalRevisionPage,
100821
+ listSessionGoalRevisions,
100822
+ rejectSessionGoalRevisionWithEvent,
100823
+ listSessionIdsInGroup,
100824
+ listSessionDiscoverySummaries as listSessionDiscoverySummaries2,
100825
+ listSessionDiscoveryAncestorPaths,
100826
+ listSessionsForSubject,
100827
+ listSessionEntriesForSubject,
100828
+ getLatestStartedSessionTurn,
100829
+ listSessionTurns,
100830
+ projectEffectiveControlForRelatedAccess as projectEffectiveControlForRelatedAccess2,
100831
+ projectSessionForRelatedAccess as projectSessionForRelatedAccess2,
100832
+ recordStreamAcknowledgment,
100833
+ requestSessionCompaction,
100834
+ switchSessionCodexAccount,
100835
+ setSessionChannel as setSessionChannel2,
100836
+ updateSessionVariableSets,
100837
+ ChannelNotFoundError,
100838
+ setSessionAttention,
100839
+ setSessionArchive,
100840
+ setSessionPin,
100841
+ SessionAttentionVersionConflictError,
100842
+ SessionArchiveVersionConflictError,
100843
+ SessionPinVersionConflictError,
100844
+ SessionPinAccessError,
100845
+ SessionListAccessError,
100846
+ SessionListCursorError,
100847
+ SessionListCursorExpiredError,
100848
+ decodeSessionListCursor,
100849
+ revokeViewer,
100850
+ setSessionGoalStatusWithEvent as setSessionGoalStatusWithEvent2,
100851
+ upsertSessionGoalWithEvent as upsertSessionGoalWithEvent2,
100852
+ updatePtySessionActivity,
100853
+ QueueCommandConflictError,
100854
+ beginSessionRealtimeInTransaction,
100855
+ activateSessionRealtimeConnectionInTransaction,
100856
+ claimSessionRealtimeConnectionInTransaction,
100857
+ completeSessionRealtimeConnectionInTransaction,
100858
+ endSessionRealtimeInTransaction,
100859
+ failSessionRealtimeConnectionInTransaction,
100860
+ NewSessionDraftConflictError,
100861
+ SessionCommandIdempotencyError,
100862
+ SessionControlConflictError,
100863
+ SessionRealtimeConflictError,
100864
+ SessionToolPolicyVersionConflictError,
100865
+ SessionContextBusyError,
100866
+ SessionVariableSetSelectionUnavailableError,
100867
+ SessionCreateConnectionSelectionUnavailableError as SessionCreateConnectionSelectionUnavailableError2,
100868
+ workspaceControlRequestLockTimeoutMs,
100869
+ SessionTenancyAccessError,
100870
+ SessionTenancyConflictError,
100871
+ SessionTenancyInvalidRequestError,
100872
+ SessionTenancyNotActivatedError,
100873
+ HumanInputResponseValidationError as HumanInputResponseValidationError2,
100874
+ SkillHumanResponseError,
100875
+ latestWorkspaceCapture,
100876
+ sessionLatestWorkspaceCapture,
100877
+ renewSessionRealtimeInTransaction,
100878
+ syncSessionRealtimeLedgerInTransaction,
100879
+ withWorkspaceSessionActivityRls,
100880
+ withWorkspaceRls,
100881
+ workspaceCaptureAtRevision
100882
+ } from "@opengeni/db";
100883
+ import {
100884
+ backgroundCommandActivityForSessions,
100885
+ listSessionBackgroundCommands,
100886
+ requestSessionBackgroundCommandCancellation
100887
+ } from "@opengeni/db/session-background-commands";
100888
+ import {
100889
+ appendAndPublishEvents as appendAndPublishEvents3,
100890
+ boundSessionEventHttpPage,
100891
+ coalesceSessionEventDeltasWithCoverage as coalesceSessionEventDeltasWithCoverage2,
100892
+ publishDurableSessionEvents as publishDurableSessionEvents3
100893
+ } from "@opengeni/events";
100894
+
100758
100895
  // src/gateway-realtime.ts
100896
+ import {
100897
+ VERCEL_AI_GATEWAY_AI_SDK_BASE_URL,
100898
+ VERCEL_AI_GATEWAY_BASE_URL,
100899
+ resolveAiGatewayRealtimeModel
100900
+ } from "@opengeni/config";
100901
+ import {
100902
+ getActiveSessionHistoryItems as getActiveSessionHistoryItems3,
100903
+ getSessionRealtimeContinuityEntries as getSessionRealtimeContinuityEntries3,
100904
+ loadWorkspaceVercelAiGatewayApiKey
100905
+ } from "@opengeni/db";
100759
100906
  var GatewayRealtimeBrokerError = class extends Error {
100760
100907
  constructor(code, message, providerStatus = null) {
100761
100908
  super(message);
@@ -100780,10 +100927,11 @@ async function createGatewayRealtimeConnectionSecret(input) {
100780
100927
  );
100781
100928
  }
100782
100929
  const [history, continuity, minted] = await Promise.all([
100783
- getActiveSessionHistoryItems2(input.db, input.workspaceId, input.sessionId),
100784
- getSessionRealtimeContinuityEntries2(input.db, input.workspaceId, input.sessionId),
100930
+ getActiveSessionHistoryItems3(input.db, input.workspaceId, input.sessionId),
100931
+ getSessionRealtimeContinuityEntries3(input.db, input.workspaceId, input.sessionId),
100785
100932
  mintGatewayClientSecret({
100786
100933
  apiKey,
100934
+ source: resolved.source,
100787
100935
  upstreamModelId: resolved.upstreamModelId,
100788
100936
  fetchImpl: input.fetchImpl ?? fetch
100789
100937
  })
@@ -100812,13 +100960,14 @@ async function mintGatewayClientSecret(input) {
100812
100960
  } catch {
100813
100961
  throw new GatewayRealtimeBrokerError(
100814
100962
  "provider_error",
100815
- "AI Gateway realtime token request failed"
100963
+ "The voice provider could not be reached. Try again."
100816
100964
  );
100817
100965
  }
100818
100966
  if (!response.ok) {
100967
+ const rejected = response.status === 401 || response.status === 403;
100819
100968
  throw new GatewayRealtimeBrokerError(
100820
- response.status === 401 || response.status === 403 ? "credential_unavailable" : "provider_error",
100821
- response.status === 401 || response.status === 403 ? "AI Gateway credentials were rejected" : "AI Gateway realtime token request failed",
100969
+ rejected ? "credential_unavailable" : "provider_error",
100970
+ rejected ? input.source === "managed" ? "Opengeni voice is temporarily unavailable. Try another voice model." : "Your AI Gateway key was rejected. Update it in workspace settings." : "The voice provider could not start a call. Try again.",
100822
100971
  response.status
100823
100972
  );
100824
100973
  }
@@ -100844,8 +100993,8 @@ async function mintGatewayClientSecret(input) {
100844
100993
  // src/xai-realtime.ts
100845
100994
  import { SUPERGROK_REALTIME_MODEL_ID } from "@opengeni/config";
100846
100995
  import {
100847
- getActiveSessionHistoryItems as getActiveSessionHistoryItems3,
100848
- getSessionRealtimeContinuityEntries as getSessionRealtimeContinuityEntries3,
100996
+ getActiveSessionHistoryItems as getActiveSessionHistoryItems4,
100997
+ getSessionRealtimeContinuityEntries as getSessionRealtimeContinuityEntries4,
100849
100998
  getXaiSessionAccountPin,
100850
100999
  resolveXaiProviderAccountAuthoritySnapshotForAcceptance as resolveXaiProviderAccountAuthoritySnapshotForAcceptance3,
100851
101000
  setXaiSessionAccountPin
@@ -101054,8 +101203,8 @@ async function createXaiRealtimeConnectionSecret(input) {
101054
101203
  );
101055
101204
  }
101056
101205
  const [history, continuity, minted] = await Promise.all([
101057
- getActiveSessionHistoryItems3(input.db, input.workspaceId, input.sessionId),
101058
- getSessionRealtimeContinuityEntries3(input.db, input.workspaceId, input.sessionId),
101206
+ getActiveSessionHistoryItems4(input.db, input.workspaceId, input.sessionId),
101207
+ getSessionRealtimeContinuityEntries4(input.db, input.workspaceId, input.sessionId),
101059
101208
  mintXaiClientSecret({
101060
101209
  auth,
101061
101210
  fetchImpl: input.fetchImpl ?? fetch
@@ -101495,6 +101644,46 @@ function registerSessionRoutes(app, deps) {
101495
101644
  observability: deps.observability
101496
101645
  };
101497
101646
  const workspaceCaptureManifestCache = new WorkspaceCaptureManifestCache();
101647
+ const realtimeVoiceBilling = createRealtimeVoiceBilling({ db, settings });
101648
+ const realtimeVoiceRefusalResponse = (c, error) => {
101649
+ if (error instanceof TranscriptionBillingRefusedError) {
101650
+ return transcriptionBillingRefusal(c, error);
101651
+ }
101652
+ if (error instanceof RealtimeVoiceUnavailableError) {
101653
+ return c.json(
101654
+ {
101655
+ error: {
101656
+ status: 409,
101657
+ code: "realtime_voice_unavailable",
101658
+ message: error.message,
101659
+ retryable: false,
101660
+ details: { reason: error.code }
101661
+ }
101662
+ },
101663
+ 409
101664
+ );
101665
+ }
101666
+ return null;
101667
+ };
101668
+ const settleRealtimeVoice = async (input) => {
101669
+ try {
101670
+ return await realtimeVoiceBilling.settle({
101671
+ workspaceId: input.workspaceId,
101672
+ sessionId: input.sessionId,
101673
+ realtimeId: input.realtimeId,
101674
+ callerSubjectId: input.grant.subjectId,
101675
+ attribution: input.attribution
101676
+ });
101677
+ } catch (error) {
101678
+ console.error("realtime voice settlement failed", {
101679
+ workspaceId: input.workspaceId,
101680
+ sessionId: input.sessionId,
101681
+ realtimeId: input.realtimeId,
101682
+ error: error instanceof Error ? error.message : String(error)
101683
+ });
101684
+ return null;
101685
+ }
101686
+ };
101498
101687
  const withSiteCommandOrigin = (c, workspaceId, run) => withOptionalSiteCommandOrigin(
101499
101688
  db,
101500
101689
  workspaceId,
@@ -102143,18 +102332,26 @@ function registerSessionRoutes(app, deps) {
102143
102332
  if (!session) {
102144
102333
  throw new HTTPException88(404, { message: "session not found" });
102145
102334
  }
102146
- const activity = await backgroundCommandActivityForSessions(db, {
102147
- accountId: grant.accountId,
102148
- workspaceId,
102149
- sessionIds: [sessionId]
102150
- });
102151
- const scheduleTargets = hasPermission23(grant.permissions, "scheduled_tasks:run") && hasPermission23(grant.permissions, "sessions:control") ? await scheduledSessionIds(db, workspaceId, [sessionId]) : /* @__PURE__ */ new Set();
102335
+ const [activity, scheduleTargets, policy] = await Promise.all([
102336
+ backgroundCommandActivityForSessions(db, {
102337
+ accountId: grant.accountId,
102338
+ workspaceId,
102339
+ sessionIds: [sessionId]
102340
+ }),
102341
+ hasPermission23(grant.permissions, "scheduled_tasks:run") && hasPermission23(grant.permissions, "sessions:control") ? scheduledSessionIds(db, workspaceId, [sessionId]) : Promise.resolve(/* @__PURE__ */ new Set()),
102342
+ loadEffectivePolicyContext(deps, workspaceId, grant.subjectId, [session])
102343
+ ]);
102152
102344
  return c.json(
102153
- await withEffectivePolicy(deps, workspaceId, grant.subjectId, {
102154
- ...session,
102155
- hasSchedules: scheduleTargets.has(sessionId),
102156
- ...activity.get(sessionId) ? { backgroundCommandActivity: activity.get(sessionId) } : {}
102157
- })
102345
+ sessionWithEffectiveToolPolicy2(
102346
+ {
102347
+ ...session,
102348
+ hasSchedules: scheduleTargets.has(sessionId),
102349
+ ...activity.get(sessionId) ? { backgroundCommandActivity: activity.get(sessionId) } : {}
102350
+ },
102351
+ policy.workspaceServerIds,
102352
+ policy.workspaceDefaultServerIds,
102353
+ policy.effectiveToolsContext
102354
+ )
102158
102355
  );
102159
102356
  });
102160
102357
  app.get("/v1/workspaces/:workspaceId/sessions/:sessionId/model-context", async (c) => {
@@ -102298,6 +102495,18 @@ function registerSessionRoutes(app, deps) {
102298
102495
  message: "invalid session realtime request"
102299
102496
  });
102300
102497
  }
102498
+ try {
102499
+ await realtimeVoiceBilling.admit({
102500
+ accountId: grant.accountId,
102501
+ workspaceId,
102502
+ model: parsed.data.model,
102503
+ attribution: creditDebitAttributionForGrant(grant)
102504
+ });
102505
+ } catch (error) {
102506
+ const refused = realtimeVoiceRefusalResponse(c, error);
102507
+ if (refused) return refused;
102508
+ throw error;
102509
+ }
102301
102510
  try {
102302
102511
  const result = await withWorkspaceSessionActivityRls(db, workspaceId, async (scopedDb) => {
102303
102512
  const accounts = await freezeSessionRealtimeConnectionAccounts({
@@ -102342,6 +102551,14 @@ function registerSessionRoutes(app, deps) {
102342
102551
  });
102343
102552
  }
102344
102553
  try {
102554
+ const settlement = await settleRealtimeVoice({
102555
+ grant,
102556
+ attribution: creditDebitAttributionForGrant(grant),
102557
+ workspaceId,
102558
+ sessionId,
102559
+ realtimeId
102560
+ });
102561
+ const stop = settlement?.stop ?? null;
102345
102562
  const result = await withWorkspaceSessionActivityRls(
102346
102563
  db,
102347
102564
  workspaceId,
@@ -102350,12 +102567,17 @@ function registerSessionRoutes(app, deps) {
102350
102567
  sessionId,
102351
102568
  realtimeId,
102352
102569
  ownerSubjectId: grant.subjectId,
102353
- ...parsed.data
102570
+ ...parsed.data,
102571
+ extendLease: stop === null
102354
102572
  })
102355
102573
  );
102356
102574
  await publishRealtimeMutation(grant.accountId, workspaceId, sessionId, result);
102357
102575
  c.header("cache-control", "private, no-store");
102358
- return c.json({ mode: result.mode, replay: result.replay });
102576
+ return c.json({
102577
+ mode: result.mode,
102578
+ replay: result.replay,
102579
+ ...stop && result.mode.state === "active" ? { stop: { code: stop.code, message: stop.message } } : {}
102580
+ });
102359
102581
  } catch (error) {
102360
102582
  throw sessionRealtimeHttpError(error);
102361
102583
  }
@@ -102390,6 +102612,13 @@ function registerSessionRoutes(app, deps) {
102390
102612
  })
102391
102613
  );
102392
102614
  await publishRealtimeMutation(grant.accountId, workspaceId, sessionId, result);
102615
+ await settleRealtimeVoice({
102616
+ grant,
102617
+ attribution: creditDebitAttributionForGrant(grant),
102618
+ workspaceId,
102619
+ sessionId,
102620
+ realtimeId
102621
+ });
102393
102622
  c.header("cache-control", "private, no-store");
102394
102623
  return c.json({ mode: result.mode, replay: result.replay });
102395
102624
  } catch (error) {
@@ -102425,21 +102654,31 @@ function registerSessionRoutes(app, deps) {
102425
102654
  const claim = await withWorkspaceRls(
102426
102655
  db,
102427
102656
  workspaceId,
102428
- async (scopedDb) => scopedDb.transaction(
102429
- async (tx) => claimSessionRealtimeConnectionInTransaction(tx, {
102430
- workspaceId,
102431
- sessionId,
102432
- realtimeId,
102433
- operationId,
102434
- ownerSubjectId: grant.subjectId,
102435
- browserInstanceId,
102436
- ownerKey,
102437
- expectedVersion,
102438
- expectedConnectionEpoch,
102439
- rotate,
102440
- promotionMode: browserActivation === "required" ? "staged" : "legacy"
102441
- })
102442
- )
102657
+ async (scopedDb) => scopedDb.transaction(async (tx) => {
102658
+ const claimed = await claimSessionRealtimeConnectionInTransaction(
102659
+ tx,
102660
+ {
102661
+ workspaceId,
102662
+ sessionId,
102663
+ realtimeId,
102664
+ operationId,
102665
+ ownerSubjectId: grant.subjectId,
102666
+ browserInstanceId,
102667
+ ownerKey,
102668
+ expectedVersion,
102669
+ expectedConnectionEpoch,
102670
+ rotate,
102671
+ promotionMode: browserActivation === "required" ? "staged" : "legacy"
102672
+ }
102673
+ );
102674
+ if (claimed.mode.model !== "gpt-live-1-boulder-alpha" && claimed.mode.model !== AZURE_LIVE_MODEL_ID) {
102675
+ throw new CodexRealtimeBrokerError(
102676
+ "invalid_request",
102677
+ "This voice model does not use WebRTC negotiation"
102678
+ );
102679
+ }
102680
+ return claimed;
102681
+ })
102443
102682
  );
102444
102683
  if (claim.replay) {
102445
102684
  if (claim.connection.state !== "ready" && claim.connection.state !== "active" || !claim.connection.sdpAnswer) {
@@ -102470,7 +102709,7 @@ function registerSessionRoutes(app, deps) {
102470
102709
  return c.json({
102471
102710
  sdp: claim.connection.sdpAnswer,
102472
102711
  version: "v3",
102473
- model: "gpt-live-1-boulder-alpha",
102712
+ model: claim.mode.model,
102474
102713
  connectionId: claim.connection.id,
102475
102714
  connectionEpoch: claim.connection.connectionEpoch,
102476
102715
  startupFenceSequence: claim.connection.startupFenceSequence,
@@ -102478,13 +102717,34 @@ function registerSessionRoutes(app, deps) {
102478
102717
  replay: true
102479
102718
  });
102480
102719
  }
102481
- const broker = buildSessionCodexRealtimeBroker(
102482
- db,
102483
- settings,
102484
- workspaceId,
102485
- sessionId,
102486
- deps.codexFetch
102487
- );
102720
+ try {
102721
+ await realtimeVoiceBilling.admit({
102722
+ accountId: grant.accountId,
102723
+ workspaceId,
102724
+ model: claim.mode.model,
102725
+ attribution: creditDebitAttributionForGrant(grant)
102726
+ });
102727
+ } catch (error) {
102728
+ const refused = realtimeVoiceRefusalResponse(c, error);
102729
+ if (!refused) throw error;
102730
+ await withWorkspaceRls(
102731
+ db,
102732
+ workspaceId,
102733
+ async (scopedDb) => scopedDb.transaction(
102734
+ async (tx) => failSessionRealtimeConnectionInTransaction(tx, {
102735
+ workspaceId,
102736
+ sessionId,
102737
+ realtimeId,
102738
+ connectionId: claim.connection.id,
102739
+ operationId,
102740
+ connectionEpoch: claim.connection.connectionEpoch,
102741
+ failureCode: "billing_refused"
102742
+ })
102743
+ )
102744
+ ).catch(() => void 0);
102745
+ return refused;
102746
+ }
102747
+ const broker = (claim.mode.model === AZURE_LIVE_MODEL_ID ? buildSessionAzureLiveBroker : buildSessionCodexRealtimeBroker)(db, settings, workspaceId, sessionId, deps.codexFetch);
102488
102748
  try {
102489
102749
  const answer = await broker({
102490
102750
  request: providerRequest,
@@ -102524,6 +102784,13 @@ function registerSessionRoutes(app, deps) {
102524
102784
  })
102525
102785
  )
102526
102786
  ) : null;
102787
+ await settleRealtimeVoice({
102788
+ grant,
102789
+ attribution: creditDebitAttributionForGrant(grant),
102790
+ workspaceId,
102791
+ sessionId,
102792
+ realtimeId
102793
+ });
102527
102794
  return c.json({
102528
102795
  ...answer,
102529
102796
  connectionId: completed.connection.id,
@@ -102626,6 +102893,12 @@ function registerSessionRoutes(app, deps) {
102626
102893
  "Realtime Gateway tokens are single-use; reconnect with a new operation"
102627
102894
  );
102628
102895
  }
102896
+ await realtimeVoiceBilling.admit({
102897
+ accountId: grant.accountId,
102898
+ workspaceId,
102899
+ model: claim.mode.model,
102900
+ attribution: creditDebitAttributionForGrant(grant)
102901
+ });
102629
102902
  const secret = await createGatewayRealtimeConnectionSecret({
102630
102903
  db,
102631
102904
  settings,
@@ -102651,6 +102924,13 @@ function registerSessionRoutes(app, deps) {
102651
102924
  )
102652
102925
  );
102653
102926
  connectionCompleted = true;
102927
+ await settleRealtimeVoice({
102928
+ grant,
102929
+ attribution: creditDebitAttributionForGrant(grant),
102930
+ workspaceId,
102931
+ sessionId,
102932
+ realtimeId
102933
+ });
102654
102934
  return c.json({
102655
102935
  ...secret,
102656
102936
  connectionId: completed.connection.id,
@@ -102673,11 +102953,13 @@ function registerSessionRoutes(app, deps) {
102673
102953
  connectionId: claimed.connection.id,
102674
102954
  operationId,
102675
102955
  connectionEpoch: claimed.connection.connectionEpoch,
102676
- failureCode: error instanceof GatewayRealtimeBrokerError ? error.code : "gateway_error"
102956
+ failureCode: error instanceof GatewayRealtimeBrokerError ? error.code : error instanceof TranscriptionBillingRefusedError || error instanceof RealtimeVoiceUnavailableError ? "billing_refused" : "gateway_error"
102677
102957
  })
102678
102958
  )
102679
102959
  ).catch(() => void 0);
102680
102960
  }
102961
+ const refused = realtimeVoiceRefusalResponse(c, error);
102962
+ if (refused) return refused;
102681
102963
  if (error instanceof SessionRealtimeConflictError) throw sessionRealtimeHttpError(error);
102682
102964
  if (!(error instanceof GatewayRealtimeBrokerError)) throw error;
102683
102965
  const status = error.code === "credential_unavailable" ? 409 : 502;
@@ -102883,23 +103165,31 @@ function registerSessionRoutes(app, deps) {
102883
103165
  });
102884
103166
  }
102885
103167
  try {
102886
- const result = await withWorkspaceSessionActivityRls(db, workspaceId, async (scopedDb) => {
102887
- await beforeCommit?.(scopedDb);
102888
- return syncSessionRealtimeLedgerInTransaction(
102889
- scopedDb,
102890
- {
102891
- workspaceId,
102892
- sessionId,
102893
- realtimeId,
102894
- ownerSubjectId: grant.subjectId,
102895
- ...parsed.data,
102896
- controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs()
102897
- },
102898
- captureLinked ? {
102899
- afterDelegationAdmission: async ({ turnId }) => captureLinked(scopedDb, sessionId, turnId)
102900
- } : {}
102901
- );
102902
- });
103168
+ const result = await withWorkspaceSessionActivityRls(
103169
+ db,
103170
+ workspaceId,
103171
+ async (scopedDb) => {
103172
+ await beforeCommit?.(scopedDb);
103173
+ return syncSessionRealtimeLedgerInTransaction(
103174
+ scopedDb,
103175
+ {
103176
+ workspaceId,
103177
+ sessionId,
103178
+ realtimeId,
103179
+ ownerSubjectId: grant.subjectId,
103180
+ ...parsed.data,
103181
+ controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs()
103182
+ },
103183
+ captureLinked ? {
103184
+ afterDelegationAdmission: async ({ turnId }) => captureLinked(scopedDb, sessionId, turnId)
103185
+ } : {}
103186
+ );
103187
+ },
103188
+ void 0,
103189
+ // External reauthorization takes the organization-membership lock,
103190
+ // which precedes the tenancy fence and the canonical session prefix.
103191
+ Boolean(beforeCommit)
103192
+ );
102903
103193
  await publishRealtimeMutation(grant.accountId, workspaceId, sessionId, result);
102904
103194
  c.header("cache-control", "private, no-store");
102905
103195
  return c.json({ accepted: result.accepted, outbound: result.outbound });
@@ -105543,11 +105833,16 @@ function codexRealtimeHttpFailure(error) {
105543
105833
  return { status: 409, code: "conflict", retryable: false };
105544
105834
  }
105545
105835
  }
105836
+ var SESSION_REALTIME_USER_MESSAGES = {
105837
+ REALTIME_ACTIVE: "Voice is already on for this session in another tab or window. End it there, or try again in a minute.",
105838
+ CONTROL_NOT_ACTIVE: "Resume this session to start voice.",
105839
+ SESSION_CANCELLED: "This session was cancelled, so voice can't start."
105840
+ };
105546
105841
  function sessionRealtimeHttpError(error) {
105547
105842
  if (error instanceof HTTPException88) return error;
105548
105843
  if (error instanceof SessionRealtimeConflictError) {
105549
105844
  return new HTTPException88(error.code === "REALTIME_NOT_FOUND" ? 404 : 409, {
105550
- message: error.message,
105845
+ message: SESSION_REALTIME_USER_MESSAGES[error.code] ?? error.message,
105551
105846
  cause: error
105552
105847
  });
105553
105848
  }
@@ -106325,9 +106620,17 @@ function commandConflictResponse(c, error) {
106325
106620
  throw error;
106326
106621
  }
106327
106622
  async function loadEffectivePolicyContext(deps, workspaceId, subjectId, sessions) {
106623
+ const workspaceRead = requireWorkspace3(deps.db, workspaceId);
106624
+ void workspaceRead.catch(() => void 0);
106328
106625
  const [policy, effectiveToolsContext] = await Promise.all([
106329
- workspaceSessionToolPolicyContext(deps.db, workspaceId, deps.settings, subjectId),
106330
- workspaceSessionEffectiveToolsContext2(deps, workspaceId, subjectId, sessions)
106626
+ workspaceSessionToolPolicyContext(
106627
+ deps.db,
106628
+ workspaceId,
106629
+ deps.settings,
106630
+ subjectId,
106631
+ workspaceRead
106632
+ ),
106633
+ workspaceSessionEffectiveToolsContext2(deps, workspaceId, subjectId, sessions, workspaceRead)
106331
106634
  ]);
106332
106635
  return { ...policy, effectiveToolsContext };
106333
106636
  }
@@ -106798,6 +107101,11 @@ function socialHttpException(error) {
106798
107101
  }
106799
107102
 
106800
107103
  // src/routes/workspaces.ts
107104
+ import {
107105
+ createRealtimeVoiceBilling as createRealtimeVoiceBilling2,
107106
+ deploymentRealtimeVoice,
107107
+ realtimeVoiceOfferProblem
107108
+ } from "@opengeni/core";
106801
107109
  import { withDirectModelProviders } from "@opengeni/config";
106802
107110
  import { listConnectionsMetadata as listConnectionsMetadata6 } from "@opengeni/db";
106803
107111
  import {
@@ -106866,9 +107174,9 @@ import {
106866
107174
  replayWorkspaceGatewayCustomModelCreate,
106867
107175
  replayWorkspaceOpenRouterCustomModelCreate,
106868
107176
  listWorkspacesForSubject as listWorkspacesForSubject2,
106869
- nestedPostgresSqlState as nestedPostgresSqlState11,
107177
+ nestedPostgresSqlState as nestedPostgresSqlState12,
106870
107178
  removeWorkspaceMember,
106871
- requireWorkspace as requireWorkspace3,
107179
+ requireWorkspace as requireWorkspace4,
106872
107180
  updateWorkspaceSettings,
106873
107181
  getRig,
106874
107182
  setWorkspaceDefaultRig,
@@ -106917,7 +107225,7 @@ import {
106917
107225
  } from "@opengeni/core";
106918
107226
 
106919
107227
  // src/workspace-deletion.ts
106920
- import { deleteWorkspaceIfQuiescent, nestedPostgresSqlState as nestedPostgresSqlState10 } from "@opengeni/db";
107228
+ import { deleteWorkspaceIfQuiescent, nestedPostgresSqlState as nestedPostgresSqlState11 } from "@opengeni/db";
106921
107229
  import { HTTPException as HTTPException91 } from "hono/http-exception";
106922
107230
 
106923
107231
  // src/workspace-delete-observability.ts
@@ -106959,13 +107267,13 @@ function recordWorkspaceDeleteObservation(observability, identity2, observation)
106959
107267
  labels: { outcome: observation.outcome }
106960
107268
  });
106961
107269
  }
106962
- for (const [kind, count] of Object.entries(observation.inventory ?? {})) {
107270
+ for (const [kind, count2] of Object.entries(observation.inventory ?? {})) {
106963
107271
  observability.observeHistogram({
106964
107272
  name: "opengeni_workspace_delete_inventory_rows",
106965
107273
  help: "Rows or live owners observed by workspace deletion preflight inventory class.",
106966
107274
  labels: { kind },
106967
107275
  buckets: INVENTORY_BUCKETS,
106968
- value: Math.max(0, Math.floor(count ?? 0))
107276
+ value: Math.max(0, Math.floor(count2 ?? 0))
106969
107277
  });
106970
107278
  }
106971
107279
  observability.info("Workspace deletion phase", {
@@ -107007,7 +107315,7 @@ async function deleteWorkspaceForRequest(deps, input) {
107007
107315
  ...observer ? { observer } : {}
107008
107316
  });
107009
107317
  } catch (error) {
107010
- const state = nestedPostgresSqlState10(error);
107318
+ const state = nestedPostgresSqlState11(error);
107011
107319
  if (state === "42501") {
107012
107320
  throw new HTTPException91(403, { message: "organization administration is not authorized" });
107013
107321
  }
@@ -107161,7 +107469,7 @@ function registerWorkspaceRoutes(app, deps) {
107161
107469
  ];
107162
107470
  if (readableWorkspaceIds.length > 0) {
107163
107471
  const workspaces = await Promise.all(
107164
- readableWorkspaceIds.map((workspaceId) => requireWorkspace3(deps.db, workspaceId))
107472
+ readableWorkspaceIds.map((workspaceId) => requireWorkspace4(deps.db, workspaceId))
107165
107473
  );
107166
107474
  return c.json(workspaces.map((workspace) => Workspace.parse(workspace)));
107167
107475
  }
@@ -107278,7 +107586,7 @@ function registerWorkspaceRoutes(app, deps) {
107278
107586
  app.get("/v1/workspaces/:workspaceId", async (c) => {
107279
107587
  const workspaceId = c.req.param("workspaceId");
107280
107588
  await requireAccessGrant39(c, deps, workspaceId, "workspace:read");
107281
- return c.json(Workspace.parse(await requireWorkspace3(deps.db, workspaceId)));
107589
+ return c.json(Workspace.parse(await requireWorkspace4(deps.db, workspaceId)));
107282
107590
  });
107283
107591
  app.patch("/v1/workspaces/:workspaceId", async (c) => {
107284
107592
  const workspaceId = c.req.param("workspaceId");
@@ -107340,14 +107648,14 @@ function registerWorkspaceRoutes(app, deps) {
107340
107648
  deps.db,
107341
107649
  workspaceId,
107342
107650
  parsed.data,
107343
- { requireWorkspace: requireWorkspace3, updateWorkspaceSettings },
107651
+ { requireWorkspace: requireWorkspace4, updateWorkspaceSettings },
107344
107652
  {
107345
107653
  controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs2()
107346
107654
  }
107347
107655
  );
107348
107656
  return c.json(Workspace.parse(workspace));
107349
107657
  } catch (error) {
107350
- if (nestedPostgresSqlState11(error) === "0A000") {
107658
+ if (nestedPostgresSqlState12(error) === "0A000") {
107351
107659
  throw new HTTPException92(409, {
107352
107660
  message: "Memory settings moved to Settings > Agent learning. Refresh the app to change them."
107353
107661
  });
@@ -107543,7 +107851,7 @@ function registerWorkspaceRoutes(app, deps) {
107543
107851
  if (error instanceof WorkspaceGatewayCustomModelLimitError || error instanceof WorkspaceGatewayCustomModelHistoryLimitError) {
107544
107852
  throw new HTTPException92(422, { message: error.message });
107545
107853
  }
107546
- if (nestedPostgresSqlState11(error) === "23505") {
107854
+ if (nestedPostgresSqlState12(error) === "23505") {
107547
107855
  throw new HTTPException92(422, {
107548
107856
  message: "Gateway custom model already exists"
107549
107857
  });
@@ -107672,7 +107980,7 @@ function registerWorkspaceRoutes(app, deps) {
107672
107980
  if (error instanceof WorkspaceOpenRouterCustomModelLimitError || error instanceof WorkspaceOpenRouterCustomModelHistoryLimitError) {
107673
107981
  throw new HTTPException92(422, { message: error.message });
107674
107982
  }
107675
- if (nestedPostgresSqlState11(error) === "23505") {
107983
+ if (nestedPostgresSqlState12(error) === "23505") {
107676
107984
  throw new HTTPException92(422, {
107677
107985
  message: "OpenRouter custom model already exists"
107678
107986
  });
@@ -107735,16 +108043,37 @@ function registerWorkspaceRoutes(app, deps) {
107735
108043
  return credentialReady ? { available: true, unavailableReason: null } : { available: false, unavailableReason: credentialReason };
107736
108044
  };
107737
108045
  const gatewayModels = Object.values(AI_GATEWAY_REALTIME_MODELS);
107738
- const models = [
107739
- ...gatewayModels.map((model, index) => ({
108046
+ const managedCandidates = [
108047
+ {
108048
+ id: AZURE_LIVE_MODEL_ID,
108049
+ label: "GPT Live 1",
108050
+ description: "Realtime voice with session delegation"
108051
+ },
108052
+ ...gatewayModels.map((model) => ({
107740
108053
  id: model.managedModelId,
107741
108054
  label: model.label,
108055
+ description: model.description
108056
+ }))
108057
+ ].filter((model) => {
108058
+ const voice = deploymentRealtimeVoice(deps.settings, model.id);
108059
+ return voice !== null && realtimeVoiceOfferProblem(deps.settings, voice) === null;
108060
+ }).filter(
108061
+ (model, _index, offered) => offered.some((candidate) => candidate.id === AZURE_LIVE_MODEL_ID) ? model.id === AZURE_LIVE_MODEL_ID : true
108062
+ );
108063
+ const creditStanding = managedCandidates.length > 0 ? await createRealtimeVoiceBilling2({
108064
+ db: deps.db,
108065
+ settings: deps.settings
108066
+ }).creditStanding(grant.accountId) : "none";
108067
+ const hasCredits = creditStanding === "spendable";
108068
+ const models = [
108069
+ ...managedCandidates.map((model, index) => ({
108070
+ ...model,
107742
108071
  provider: "OpenGeni",
107743
- description: model.description,
107744
- ...availability(
107745
- Boolean(deps.settings.vercelAiGatewayApiKey),
107746
- "Opengeni Gateway voice is not configured"
107747
- ),
108072
+ ...hasCredits ? { available: true, unavailableReason: null, unavailableCode: null } : {
108073
+ available: false,
108074
+ unavailableReason: creditStanding === "promotional_only" ? "Promotional credits don't cover live voice. Add credits to use it." : "Add Opengeni credits to use live voice",
108075
+ unavailableCode: "insufficient_credits"
108076
+ },
107748
108077
  recommended: index === 0
107749
108078
  })),
107750
108079
  {
@@ -108090,7 +108419,7 @@ function rethrowWorkspaceMemberCandidateError(error) {
108090
108419
  message: "the workspace must keep at least one administrator"
108091
108420
  });
108092
108421
  }
108093
- const sqlState = nestedPostgresSqlState11(error);
108422
+ const sqlState = nestedPostgresSqlState12(error);
108094
108423
  if (sqlState === "P0002") {
108095
108424
  throw new HTTPException92(404, { message: "workspace not found" });
108096
108425
  }
@@ -108155,7 +108484,7 @@ import {
108155
108484
  getWorkspace as getWorkspace6,
108156
108485
  getWorkspaceUsage,
108157
108486
  grantWorkspaceCredits,
108158
- nestedPostgresSqlState as nestedPostgresSqlState12,
108487
+ nestedPostgresSqlState as nestedPostgresSqlState13,
108159
108488
  setMemberAllowance,
108160
108489
  setWorkspaceAllowance,
108161
108490
  withAccountRls as withAccountRls3,
@@ -108290,7 +108619,7 @@ async function lifecycle2(operation) {
108290
108619
  } catch (error) {
108291
108620
  if (error instanceof UsageAllowanceVersionConflictError)
108292
108621
  throw new HTTPException93(409, { message: error.message });
108293
- const state = nestedPostgresSqlState12(error);
108622
+ const state = nestedPostgresSqlState13(error);
108294
108623
  if (state === "40001" || state === "23505")
108295
108624
  throw new HTTPException93(409, { message: "Allowance version or operation conflicts" });
108296
108625
  if (state === "42501")
@@ -110041,7 +110370,7 @@ import {
110041
110370
  } from "@opengeni/core";
110042
110371
  import {
110043
110372
  availableSlackBotConnectionMetadata as availableSlackBotConnectionMetadata2,
110044
- nestedPostgresSqlState as nestedPostgresSqlState13,
110373
+ nestedPostgresSqlState as nestedPostgresSqlState14,
110045
110374
  readOrganizationSlackBotAccess,
110046
110375
  setOrganizationSlackBotAccess,
110047
110376
  sharedOrganizationSlackBots as sharedOrganizationSlackBots2
@@ -110071,7 +110400,7 @@ function registerSlackBotAccessRoutes(app, deps) {
110071
110400
  )
110072
110401
  );
110073
110402
  } catch (error) {
110074
- if (nestedPostgresSqlState13(error) === "42501")
110403
+ if (nestedPostgresSqlState14(error) === "42501")
110075
110404
  throw new HTTPException99(404, { message: "Local bot installation unavailable" });
110076
110405
  throw error;
110077
110406
  }
@@ -110125,7 +110454,7 @@ function registerSlackBotAccessRoutes(app, deps) {
110125
110454
  return context.json(OpenGeniSlackBotOrganizationAccess.parse(result));
110126
110455
  } catch (error) {
110127
110456
  if (error instanceof HTTPException99) throw error;
110128
- if (nestedPostgresSqlState13(error) === "42501")
110457
+ if (nestedPostgresSqlState14(error) === "42501")
110129
110458
  throw new HTTPException99(403, {
110130
110459
  message: "Organization administrator and a verified local bot installation required"
110131
110460
  });
@@ -110322,7 +110651,7 @@ import {
110322
110651
  getWorkspaceArtifact as getWorkspaceArtifact3,
110323
110652
  getWorkspaceArtifactContentRef as getWorkspaceArtifactContentRef3,
110324
110653
  listWorkspaceArtifacts as listWorkspaceArtifacts2,
110325
- nestedPostgresSqlState as nestedPostgresSqlState14,
110654
+ nestedPostgresSqlState as nestedPostgresSqlState15,
110326
110655
  publishWorkspaceArtifactVersion as publishWorkspaceArtifactVersion2,
110327
110656
  rollbackWorkspaceArtifact as rollbackWorkspaceArtifact2,
110328
110657
  setWorkspaceArtifactStatus as setWorkspaceArtifactStatus2,
@@ -110369,7 +110698,7 @@ function workspaceArtifactErrorResponse(context, error) {
110369
110698
  422
110370
110699
  );
110371
110700
  }
110372
- if (nestedPostgresSqlState14(error) === "23505") {
110701
+ if (nestedPostgresSqlState15(error) === "23505") {
110373
110702
  return context.json(
110374
110703
  {
110375
110704
  code: "WORKSPACE_ARTIFACT_CONFLICT",
@@ -110753,7 +111082,7 @@ import {
110753
111082
  getGeneratedImageArtifact as getGeneratedImageArtifact2,
110754
111083
  getSessionAuthorityProjection as getSessionAuthorityProjection3,
110755
111084
  listArtifactCatalogCandidates,
110756
- nestedPostgresSqlState as nestedPostgresSqlState15,
111085
+ nestedPostgresSqlState as nestedPostgresSqlState16,
110757
111086
  updateArtifactPin,
110758
111087
  withSessionRlsActorContext as withSessionRlsActorContext9
110759
111088
  } from "@opengeni/db";
@@ -110888,7 +111217,7 @@ function registerArtifactCatalogRoutes(app, deps) {
110888
111217
  }
110889
111218
  );
110890
111219
  } catch (error) {
110891
- if (nestedPostgresSqlState15(error) === "42501")
111220
+ if (nestedPostgresSqlState16(error) === "42501")
110892
111221
  throw new HTTPException103(404, { message: "Artifact not found" });
110893
111222
  throw error;
110894
111223
  }
@@ -111549,7 +111878,11 @@ import {
111549
111878
  resolveWorkspaceVoiceInputEnabled as resolveWorkspaceVoiceInputEnabled2,
111550
111879
  WorkspaceVoiceInputSettings as WorkspaceVoiceInputSettings2
111551
111880
  } from "@opengeni/contracts";
111552
- import { requireAccessGrant as requireAccessGrant50, TranscriptionServiceError as TranscriptionServiceError2 } from "@opengeni/core";
111881
+ import {
111882
+ requireAccessGrant as requireAccessGrant50,
111883
+ TranscriptionBillingRefusedError as TranscriptionBillingRefusedError3,
111884
+ TranscriptionServiceError as TranscriptionServiceError2
111885
+ } from "@opengeni/core";
111553
111886
  import { getWorkspace as getWorkspace10 } from "@opengeni/db";
111554
111887
 
111555
111888
  // src/routes/transcription-recordings.ts
@@ -111567,6 +111900,7 @@ import {
111567
111900
  normalizeMimeType,
111568
111901
  requireAccessGrant as requireAccessGrant49,
111569
111902
  TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS,
111903
+ TranscriptionBillingRefusedError as TranscriptionBillingRefusedError2,
111570
111904
  TranscriptionServiceError
111571
111905
  } from "@opengeni/core";
111572
111906
  import { retryWhileMissing as retryWhileMissing7 } from "@opengeni/storage";
@@ -111598,6 +111932,38 @@ import {
111598
111932
  } from "@opengeni/db";
111599
111933
  import { getWorkspace as getWorkspace9 } from "@opengeni/db";
111600
111934
 
111935
+ // src/transcription/wav-duration.ts
111936
+ function wavDurationSeconds(bytes) {
111937
+ if (bytes.byteLength < 12) return null;
111938
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
111939
+ if (ascii2(bytes, 0) !== "RIFF" || ascii2(bytes, 8) !== "WAVE") return null;
111940
+ let offset = 12;
111941
+ let byteRate = null;
111942
+ while (offset + 8 <= bytes.byteLength) {
111943
+ const id = ascii2(bytes, offset);
111944
+ const size = view.getUint32(offset + 4, true);
111945
+ const body3 = offset + 8;
111946
+ if (id === "fmt " && body3 + 12 <= bytes.byteLength) {
111947
+ byteRate = view.getUint32(body3 + 8, true);
111948
+ } else if (id === "data") {
111949
+ if (!byteRate) return null;
111950
+ const available = bytes.byteLength - body3;
111951
+ const dataBytes = size === 0 || size === 4294967295 ? available : Math.min(size, available);
111952
+ return dataBytes / byteRate;
111953
+ }
111954
+ offset = body3 + size + size % 2;
111955
+ }
111956
+ return null;
111957
+ }
111958
+ function ascii2(bytes, offset) {
111959
+ return String.fromCharCode(
111960
+ bytes[offset] ?? 0,
111961
+ bytes[offset + 1] ?? 0,
111962
+ bytes[offset + 2] ?? 0,
111963
+ bytes[offset + 3] ?? 0
111964
+ );
111965
+ }
111966
+
111601
111967
  // src/transcription/segmenter.ts
111602
111968
  import { spawn } from "child_process";
111603
111969
  import { createWriteStream } from "fs";
@@ -111629,7 +111995,7 @@ function createFfmpegTranscriptionSegmenter(input) {
111629
111995
  true
111630
111996
  );
111631
111997
  }
111632
- if (!Number.isSafeInteger(request.providerSegmentSeconds) || request.providerSegmentSeconds <= 0 || !Number.isSafeInteger(request.totalDurationMilliseconds) || request.totalDurationMilliseconds <= 0) {
111998
+ if (!Number.isSafeInteger(request.providerSegmentSeconds) || request.providerSegmentSeconds <= 0 || !Number.isSafeInteger(request.totalDurationMilliseconds) || request.totalDurationMilliseconds <= 0 || request.maxDecodeSeconds !== void 0 && (!Number.isSafeInteger(request.maxDecodeSeconds) || request.maxDecodeSeconds <= 0)) {
111633
111999
  throw new TranscriptionSegmenterError(
111634
112000
  "Audio segmentation bounds are invalid",
111635
112001
  "invalid_audio",
@@ -111661,6 +112027,8 @@ function createFfmpegTranscriptionSegmenter(input) {
111661
112027
  "-y",
111662
112028
  "-i",
111663
112029
  inputPath,
112030
+ // Output-side ceiling: audio past it is never decoded or written.
112031
+ ...request.maxDecodeSeconds !== void 0 ? ["-t", String(request.maxDecodeSeconds)] : [],
111664
112032
  "-map",
111665
112033
  "0:a:0",
111666
112034
  "-vn",
@@ -112123,7 +112491,9 @@ function registerResumableTranscriptionRoutes(app, deps) {
112123
112491
  const service = deps.transcription;
112124
112492
  if (!deps.objectStorage || !service || !await service.available({
112125
112493
  workspaceId: authority.workspaceId,
112126
- subjectId: authority.subjectId
112494
+ subjectId: authority.subjectId,
112495
+ preferredProvider: authority.voiceInput?.preferredProvider,
112496
+ fallbackEnabled: authority.voiceInput?.fallbackEnabled
112127
112497
  })) {
112128
112498
  return c.json({ code: "unavailable" }, 503);
112129
112499
  }
@@ -112166,6 +112536,31 @@ function registerResumableTranscriptionRoutes(app, deps) {
112166
112536
  false
112167
112537
  );
112168
112538
  }
112539
+ const segmentProviderId = claim.segment.providerId && claim.segment.providerId !== "host" ? claim.segment.providerId : null;
112540
+ if (segmentProviderId && service.admit) {
112541
+ try {
112542
+ await service.admit({
112543
+ providerId: segmentProviderId,
112544
+ accountId: authority.accountId,
112545
+ workspaceId: authority.workspaceId,
112546
+ attribution: authority.attribution
112547
+ });
112548
+ } catch (error) {
112549
+ if (!(error instanceof TranscriptionBillingRefusedError2)) throw error;
112550
+ await failTranscriptionRecordingSegment(deps.db, {
112551
+ workspaceId: authority.workspaceId,
112552
+ subjectId: authority.subjectId,
112553
+ recordingId: uuidParam3(c, "recordingId"),
112554
+ segmentNumber,
112555
+ attemptId,
112556
+ fallbackProviderId: null,
112557
+ // The real refusal, so a reloaded recording still explains it.
112558
+ errorCode: error.code,
112559
+ retryable: true
112560
+ }).catch(() => null);
112561
+ return transcriptionBillingRefusal(c, error);
112562
+ }
112563
+ }
112169
112564
  const providerStartedAt = /* @__PURE__ */ new Date();
112170
112565
  const providerDeadlineAt = new Date(
112171
112566
  providerStartedAt.getTime() + TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS
@@ -112188,7 +112583,17 @@ function registerResumableTranscriptionRoutes(app, deps) {
112188
112583
  durationSeconds: claim.segment.durationMilliseconds / 1e3,
112189
112584
  requestId: attemptId,
112190
112585
  providerDeadlineAt,
112191
- ...claim.segment.providerId && claim.segment.providerId !== "host" ? { providerId: claim.segment.providerId } : {}
112586
+ deferBillingSettlement: true,
112587
+ ...segmentProviderId ? { providerId: segmentProviderId } : {},
112588
+ billing: {
112589
+ // One settlement per prepared segment of this exact recording,
112590
+ // however many provider attempts it takes. The recording id is
112591
+ // client-chosen, so the unit id also binds its server creation
112592
+ // time and the server-produced segment bytes.
112593
+ sourceId: `${claim.recording.recording.id}:${Date.parse(claim.recording.recording.createdAt)}:${segmentNumber}:${claim.segment.sha256}`,
112594
+ attribution: authority.attribution,
112595
+ trustedDurationSeconds: wavDurationSeconds(stored.bytes) ?? void 0
112596
+ }
112192
112597
  });
112193
112598
  const completed = await completeTranscriptionRecordingSegment(deps.db, {
112194
112599
  workspaceId: authority.workspaceId,
@@ -112200,6 +112605,7 @@ function registerResumableTranscriptionRoutes(app, deps) {
112200
112605
  languages: result.languages,
112201
112606
  providerId: claim.segment.providerId ?? result.providerId
112202
112607
  });
112608
+ await result.settleBilling?.().catch(() => void 0);
112203
112609
  return c.json(await cleanupTerminalObjects(deps, authority, completed));
112204
112610
  } catch (error) {
112205
112611
  if (authority && attemptId && segmentNumber !== null) {
@@ -112223,6 +112629,9 @@ function registerResumableTranscriptionRoutes(app, deps) {
112223
112629
  errorCode: failure2.code,
112224
112630
  retryable: failure2.retryable
112225
112631
  }).catch(() => null);
112632
+ if (error instanceof TranscriptionBillingRefusedError2) {
112633
+ return transcriptionBillingRefusal(c, error);
112634
+ }
112226
112635
  if (persisted) return c.json(persisted);
112227
112636
  }
112228
112637
  return routeError2(c, error);
@@ -112269,7 +112678,8 @@ async function requireRecordingAuthority(c, deps, requirePolicy) {
112269
112678
  voiceInput: WorkspaceVoiceInputSettings.safeParse(workspace?.settings.voiceInput).data,
112270
112679
  accountId: grant.accountId,
112271
112680
  workspaceId,
112272
- subjectId: grant.subjectId
112681
+ subjectId: grant.subjectId,
112682
+ attribution: creditDebitAttributionForGrant(grant)
112273
112683
  };
112274
112684
  }
112275
112685
  async function resumableAvailable(deps, workspaceId) {
@@ -112340,6 +112750,9 @@ function processingFailure(error) {
112340
112750
  if (error instanceof TranscriptionSegmenterError) {
112341
112751
  return { code: error.code, retryable: error.retryable };
112342
112752
  }
112753
+ if (error instanceof TranscriptionBillingRefusedError2) {
112754
+ return { code: error.code, retryable: true };
112755
+ }
112343
112756
  if (error instanceof TranscriptionServiceError) {
112344
112757
  return {
112345
112758
  code: error.code,
@@ -112355,6 +112768,9 @@ function routeError2(c, error) {
112355
112768
  if (error instanceof TranscriptionRecordingNotFoundError) {
112356
112769
  return c.json({ code: "not_found" }, 404);
112357
112770
  }
112771
+ if (error instanceof TranscriptionBillingRefusedError2) {
112772
+ return transcriptionBillingRefusal(c, error);
112773
+ }
112358
112774
  if (error instanceof TranscriptionRecordingConflictError) {
112359
112775
  return c.json({ code: "conflict" }, 409);
112360
112776
  }
@@ -112478,12 +112894,21 @@ function registerTranscriptionRoutes(app, deps) {
112478
112894
  }
112479
112895
  const preferences = WorkspaceVoiceInputSettings2.safeParse(workspace.settings.voiceInput).data;
112480
112896
  const service = deps.transcription;
112481
- if (!service || !await service.available({ workspaceId, subjectId: grant.subjectId })) {
112897
+ if (!service || !await service.available({
112898
+ workspaceId,
112899
+ subjectId: grant.subjectId,
112900
+ preferredProvider: preferences?.preferredProvider,
112901
+ fallbackEnabled: preferences?.fallbackEnabled
112902
+ })) {
112482
112903
  return c.json({ code: "unavailable" }, 503);
112483
112904
  }
112484
112905
  try {
112485
112906
  const body3 = await audioRequest(c.req.raw, service.limits().maxSizeBytes);
112486
112907
  const result = await service.transcribe({
112908
+ billing: {
112909
+ sourceId: crypto.randomUUID(),
112910
+ attribution: creditDebitAttributionForGrant(grant)
112911
+ },
112487
112912
  preferredProvider: preferences?.preferredProvider,
112488
112913
  fallbackEnabled: preferences?.fallbackEnabled,
112489
112914
  workspaceId,
@@ -112504,6 +112929,9 @@ function registerTranscriptionRoutes(app, deps) {
112504
112929
  if (error instanceof TranscriptionServiceError2) {
112505
112930
  return c.json({ code: error.code }, error.status);
112506
112931
  }
112932
+ if (error instanceof TranscriptionBillingRefusedError3) {
112933
+ return transcriptionBillingRefusal(c, error);
112934
+ }
112507
112935
  if (isAbort3(error)) return c.json({ code: "cancelled" }, 499);
112508
112936
  return c.json({ code: "unknown" }, 500);
112509
112937
  }
@@ -112609,17 +113037,23 @@ function isAbort3(error) {
112609
113037
 
112610
113038
  // src/routes/session-artifact-associations.ts
112611
113039
  import {
112612
- requireAccessGrant as requireAccessGrant51,
113040
+ fileOwnerContextForAccess as fileOwnerContextForAccess6,
113041
+ requireAccessGrantAuthorization as requireAccessGrantAuthorization37,
112613
113042
  requirePermission as requirePermission12,
112614
113043
  requireSessionAuthorization as requireSessionAuthorization15,
112615
113044
  SessionAuthorizationDeniedError as SessionAuthorizationDeniedError15,
112616
113045
  SessionAuthorizationUnavailableError as SessionAuthorizationUnavailableError13
112617
113046
  } from "@opengeni/core";
112618
113047
  import {
113048
+ getGeneratedImageArtifact as getGeneratedImageArtifact3,
113049
+ getGeneratedVideoArtifact as getGeneratedVideoArtifact2,
113050
+ getRetainedFileArtifact as getRetainedFileArtifact2,
112619
113051
  hasEditableArtifactSessionLink as hasEditableArtifactSessionLink2,
112620
113052
  hasWorkspaceArtifactSessionLink,
113053
+ listArtifactCatalogCandidates as listArtifactCatalogCandidates2,
112621
113054
  transactionallyAuthorizeEditableArtifactActor,
112622
- withRlsContext as withRlsContext3
113055
+ withRlsContext as withRlsContext3,
113056
+ withSessionRlsActorContext as withSessionRlsActorContext10
112623
113057
  } from "@opengeni/db";
112624
113058
  import { HTTPException as HTTPException107 } from "hono/http-exception";
112625
113059
  import { z as z55 } from "zod";
@@ -112630,12 +113064,18 @@ function registerSessionArtifactAssociationRoutes(app, deps) {
112630
113064
  "/v1/workspaces/:workspaceId/sessions/:sessionId/artifact-associations/:artifactId",
112631
113065
  async (context) => {
112632
113066
  const workspaceId = context.req.param("workspaceId");
112633
- const grant = await requireAccessGrant51(context, deps, workspaceId, "artifacts:read");
112634
- requirePermission12(grant, "sessions:read");
112635
113067
  const sessionId = context.req.param("sessionId");
112636
113068
  const artifactId2 = context.req.param("artifactId");
112637
- const kind = EditableArtifactId.safeParse(artifactId2).success ? "editable" : SessionId2.safeParse(artifactId2).success ? "site" : null;
112638
113069
  const requestedKind = context.req.query("kind");
113070
+ const kind = EditableArtifactId.safeParse(artifactId2).success ? "editable" : SessionId2.safeParse(artifactId2).success ? requestedKind === "retained" ? "retained" : "site" : null;
113071
+ const authorization = await requireAccessGrantAuthorization37(
113072
+ context,
113073
+ deps,
113074
+ workspaceId,
113075
+ kind === "retained" ? "files:read" : "artifacts:read"
113076
+ );
113077
+ const grant = authorization.grant;
113078
+ requirePermission12(grant, "sessions:read");
112639
113079
  if (!SessionId2.safeParse(sessionId).success || !kind || requestedKind !== void 0 && requestedKind !== kind) {
112640
113080
  throw new HTTPException107(404, { message: "Artifact association not found" });
112641
113081
  }
@@ -112647,7 +113087,18 @@ function registerSessionArtifactAssociationRoutes(app, deps) {
112647
113087
  surface: "http"
112648
113088
  });
112649
113089
  const scope = { accountId: grant.accountId, workspaceId };
112650
- const linked = kind === "editable" ? await hasEditableArtifactSessionLink2(deps.db, scope, sessionId, artifactId2) : await hasWorkspaceArtifactSessionLink(deps.db, workspaceId, sessionId, artifactId2);
113090
+ const linked = kind === "editable" ? await hasEditableArtifactSessionLink2(deps.db, scope, sessionId, artifactId2) : kind === "retained" ? await hasRetainedArtifactSessionLink(
113091
+ deps,
113092
+ authorization,
113093
+ workspaceId,
113094
+ sessionId,
113095
+ artifactId2.toLowerCase()
113096
+ ) : await hasWorkspaceArtifactSessionLink(
113097
+ deps.db,
113098
+ workspaceId,
113099
+ sessionId,
113100
+ artifactId2
113101
+ );
112651
113102
  if (!linked) throw new HTTPException107(404, { message: "Artifact association not found" });
112652
113103
  if (kind === "editable") {
112653
113104
  const decision = await withRlsContext3(
@@ -112679,6 +113130,38 @@ function registerSessionArtifactAssociationRoutes(app, deps) {
112679
113130
  }
112680
113131
  );
112681
113132
  }
113133
+ async function hasRetainedArtifactSessionLink(deps, authorization, workspaceId, sessionId, artifactId2) {
113134
+ const image = await getGeneratedImageArtifact3(deps.db, workspaceId, artifactId2);
113135
+ if (image) return image.sessionId === sessionId;
113136
+ const video = await getGeneratedVideoArtifact2(deps.db, workspaceId, artifactId2);
113137
+ if (video) return !video.artifact.deletedAt && video.artifact.sessionId === sessionId;
113138
+ const file = await getRetainedFileArtifact2(deps.db, workspaceId, artifactId2);
113139
+ if (!file) return false;
113140
+ const grant = authorization.grant;
113141
+ const actor = await fileOwnerContextForAccess6(deps, authorization, "files:read");
113142
+ const candidates = await withSessionRlsActorContext10(
113143
+ actor,
113144
+ () => listArtifactCatalogCandidates2(
113145
+ deps.db,
113146
+ { accountId: grant.accountId, workspaceId },
113147
+ {
113148
+ query: {
113149
+ sourceSessionId: sessionId,
113150
+ q: file.file.filename.slice(0, 200),
113151
+ sort: "newest",
113152
+ status: "active",
113153
+ limit: 100
113154
+ },
113155
+ kinds: ["file", "image"],
113156
+ snapshotAt: (/* @__PURE__ */ new Date()).toISOString(),
113157
+ limit: 201
113158
+ }
113159
+ )
113160
+ );
113161
+ return candidates.some(
113162
+ (candidate) => candidate.origin === "sandbox_file" && candidate.id === artifactId2
113163
+ );
113164
+ }
112682
113165
 
112683
113166
  // src/routes/video-generation.ts
112684
113167
  import {
@@ -112689,7 +113172,7 @@ import {
112689
113172
  } from "@opengeni/contracts";
112690
113173
  import {
112691
113174
  getVideoGenerationOperationSummary,
112692
- withSessionRlsActorContext as withSessionRlsActorContext10,
113175
+ withSessionRlsActorContext as withSessionRlsActorContext11,
112693
113176
  getWorkspaceVercelAiGatewayConnectionMetadata,
112694
113177
  getWorkspaceVideoGenerationPolicy,
112695
113178
  updateWorkspaceVideoGenerationPolicy,
@@ -112697,9 +113180,9 @@ import {
112697
113180
  workspaceXaiSubscriptionActive as workspaceXaiSubscriptionActive2
112698
113181
  } from "@opengeni/db";
112699
113182
  import {
112700
- requireAccessGrant as requireAccessGrant52,
112701
- requireAccessGrantAuthorization as requireAccessGrantAuthorization37,
112702
- fileOwnerContextForAccess as fileOwnerContextForAccess6,
113183
+ requireAccessGrant as requireAccessGrant51,
113184
+ requireAccessGrantAuthorization as requireAccessGrantAuthorization38,
113185
+ fileOwnerContextForAccess as fileOwnerContextForAccess7,
112703
113186
  requireWorkspaceSettingsGrant as requireWorkspaceSettingsGrant6,
112704
113187
  VIDEO_GENERATION_MODEL_CATALOG,
112705
113188
  videoGenerationModelSupportsFundingSource,
@@ -112709,7 +113192,7 @@ import { HTTPException as HTTPException108 } from "hono/http-exception";
112709
113192
  function registerVideoGenerationRoutes(app, deps) {
112710
113193
  app.get("/v1/workspaces/:workspaceId/video-generation", async (c) => {
112711
113194
  const workspaceId = c.req.param("workspaceId");
112712
- const grant = await requireAccessGrant52(c, deps, workspaceId, "workspace:read");
113195
+ const grant = await requireAccessGrant51(c, deps, workspaceId, "workspace:read");
112713
113196
  const [policy, connection, supergrokConfigured] = await Promise.all([
112714
113197
  getWorkspaceVideoGenerationPolicy(deps.db, workspaceId),
112715
113198
  getWorkspaceVercelAiGatewayConnectionMetadata(deps.db, workspaceId),
@@ -112783,9 +113266,9 @@ function registerVideoGenerationRoutes(app, deps) {
112783
113266
  });
112784
113267
  app.get("/v1/workspaces/:workspaceId/video-generation/operations/:operationId", async (c) => {
112785
113268
  const workspaceId = c.req.param("workspaceId");
112786
- const access = await requireAccessGrantAuthorization37(c, deps, workspaceId, "workspace:read");
112787
- const actor = await fileOwnerContextForAccess6(deps, access, "workspace:read");
112788
- const summary = await withSessionRlsActorContext10(
113269
+ const access = await requireAccessGrantAuthorization38(c, deps, workspaceId, "workspace:read");
113270
+ const actor = await fileOwnerContextForAccess7(deps, access, "workspace:read");
113271
+ const summary = await withSessionRlsActorContext11(
112789
113272
  actor,
112790
113273
  () => getVideoGenerationOperationSummary(deps.db, workspaceId, c.req.param("operationId"))
112791
113274
  );
@@ -113983,7 +114466,7 @@ function registerManagedSignInMethodRoutes(app, deps) {
113983
114466
  select provider_id as provider, (provider_id <> 'credential' or length(password)>0) as usable from auth_identities where user_id=${actor.authUserId}`
113984
114467
  );
113985
114468
  const connected = (provider) => accounts.some((a) => a.provider === provider && a.usable) && projection.loginBindings.some((b) => b.providerId === provider && b.status === "active");
113986
- const count = ["credential", "google", "github"].filter(
114469
+ const count2 = ["credential", "google", "github"].filter(
113987
114470
  (p) => connected(p) && configured(deps, p)
113988
114471
  ).length;
113989
114472
  return context.json(
@@ -113996,7 +114479,7 @@ function registerManagedSignInMethodRoutes(app, deps) {
113996
114479
  provider,
113997
114480
  connected: connected(provider),
113998
114481
  available: configured(deps, provider),
113999
- canDisconnect: provider !== "credential" && connected(provider) && count - (configured(deps, provider) ? 1 : 0) > 0,
114482
+ canDisconnect: provider !== "credential" && connected(provider) && count2 - (configured(deps, provider) ? 1 : 0) > 0,
114000
114483
  implicitRelinkingSuppressed: !connected(provider) && projection.loginBindings.some(
114001
114484
  (b) => b.providerId === provider && b.status !== "active"
114002
114485
  )
@@ -114454,11 +114937,11 @@ import {
114454
114937
  getOrganizationPrivateSessionSettings,
114455
114938
  getSelfOrganizationInvitation,
114456
114939
  getOrganizationRetentionPolicy,
114457
- requireWorkspace as requireWorkspace4,
114940
+ requireWorkspace as requireWorkspace5,
114458
114941
  listOrganizationAdministrationMembers,
114459
114942
  listOrganizationInvitations,
114460
114943
  listSelfOrganizationInvitations,
114461
- nestedPostgresSqlState as nestedPostgresSqlState16,
114944
+ nestedPostgresSqlState as nestedPostgresSqlState17,
114462
114945
  prepareOrganizationUserSetupDelivery,
114463
114946
  revokeOrganizationInvitation,
114464
114947
  settleOrganizationUserSetupDelivery,
@@ -114507,7 +114990,7 @@ async function requireOrganizationKeyWorkspaceDeletion(context, deps, organizati
114507
114990
  message: "deleting an organization workspace requires an organization owner session or an organization API key with workspace:admin"
114508
114991
  });
114509
114992
  }
114510
- const workspace = await requireWorkspace4(deps.db, workspaceId).catch(() => null);
114993
+ const workspace = await requireWorkspace5(deps.db, workspaceId).catch(() => null);
114511
114994
  if (!workspace || workspace.accountId !== organizationId2) {
114512
114995
  throw new HTTPException112(404, { message: "workspace not found" });
114513
114996
  }
@@ -114575,7 +115058,7 @@ function parseId(schema, value, label) {
114575
115058
  return parsed.data;
114576
115059
  }
114577
115060
  function rethrowMembershipError(error, resourceLimitMessage) {
114578
- const sqlState = nestedPostgresSqlState16(error);
115061
+ const sqlState = nestedPostgresSqlState17(error);
114579
115062
  if (sqlState === "54000" && resourceLimitMessage) {
114580
115063
  throw new HTTPException112(409, {
114581
115064
  message: resourceLimitMessage
@@ -114681,7 +115164,7 @@ function registerOrganizationMembershipRoutes(app, deps) {
114681
115164
  })
114682
115165
  );
114683
115166
  } catch (error) {
114684
- if (nestedPostgresSqlState16(error) === "42501") {
115167
+ if (nestedPostgresSqlState17(error) === "42501") {
114685
115168
  throw new HTTPException112(403, {
114686
115169
  message: "organization membership is not active"
114687
115170
  });
@@ -115321,7 +115804,7 @@ import {
115321
115804
  organizationWorkspaceInScope as organizationWorkspaceInScope4,
115322
115805
  hasPermission as hasPermission31,
115323
115806
  requireAccessContext as requireAccessContext13,
115324
- requireAccessGrantAuthorization as requireAccessGrantAuthorization38,
115807
+ requireAccessGrantAuthorization as requireAccessGrantAuthorization39,
115325
115808
  requireSessionAuthorizationListScope as requireSessionAuthorizationListScope3,
115326
115809
  SessionAuthorizationDeniedError as SessionAuthorizationDeniedError16,
115327
115810
  SessionAuthorizationUnavailableError as SessionAuthorizationUnavailableError14,
@@ -115474,7 +115957,7 @@ function registerOrganizationSessionRoutes(app, deps) {
115474
115957
  async function listWorkspaceSessionPage(c, deps, input) {
115475
115958
  let authorization;
115476
115959
  try {
115477
- authorization = await requireAccessGrantAuthorization38(
115960
+ authorization = await requireAccessGrantAuthorization39(
115478
115961
  c,
115479
115962
  deps,
115480
115963
  input.workspace.id,
@@ -115824,7 +116307,7 @@ import {
115824
116307
  completeSelfServiceOrganizationSetup,
115825
116308
  completeOrganizationUserSetup,
115826
116309
  getSelfServiceOrganizationOnboardingState,
115827
- nestedPostgresSqlState as nestedPostgresSqlState17,
116310
+ nestedPostgresSqlState as nestedPostgresSqlState18,
115828
116311
  preflightOrganizationUserSetup,
115829
116312
  previewOrganizationUserSetup,
115830
116313
  recordOrganizationSignupUseCase
@@ -115882,14 +116365,14 @@ function registerManagedOnboardingRoutes(app, deps, options = {}) {
115882
116365
  useCase: parsed.data.useCase
115883
116366
  }).catch((error) => {
115884
116367
  console.warn("[api] could not record the signup use case", {
115885
- sqlState: nestedPostgresSqlState17(error) ?? null
116368
+ sqlState: nestedPostgresSqlState18(error) ?? null
115886
116369
  });
115887
116370
  });
115888
116371
  }
115889
116372
  return context.json(completed);
115890
116373
  } catch (error) {
115891
116374
  recordOrganizationSetupOutcome(deps.observability, "failed");
115892
- const sqlState = nestedPostgresSqlState17(error);
116375
+ const sqlState = nestedPostgresSqlState18(error);
115893
116376
  if (sqlState === "22023") {
115894
116377
  throw new HTTPException115(422, {
115895
116378
  message: "invalid organization setup request"
@@ -115969,7 +116452,7 @@ function registerManagedOnboardingRoutes(app, deps, options = {}) {
115969
116452
  )
115970
116453
  );
115971
116454
  } catch (error) {
115972
- const sqlState = nestedPostgresSqlState17(error);
116455
+ const sqlState = nestedPostgresSqlState18(error);
115973
116456
  if (sqlState === "22023") {
115974
116457
  throw new HTTPException115(422, {
115975
116458
  message: "invalid account setup request"
@@ -116084,13 +116567,13 @@ import {
116084
116567
  import {
116085
116568
  issueManagedHumanUserResourceGrant,
116086
116569
  listManagedHumanUserResourceAuthorities,
116087
- requireAccessGrantAuthorization as requireAccessGrantAuthorization39,
116570
+ requireAccessGrantAuthorization as requireAccessGrantAuthorization40,
116088
116571
  revokeManagedHumanUserResourceGrant,
116089
116572
  SessionAuthorizationDeniedError as SessionAuthorizationDeniedError17,
116090
116573
  SessionAuthorizationUnavailableError as SessionAuthorizationUnavailableError15,
116091
116574
  SessionTenancyManagedHumanRequiredError as SessionTenancyManagedHumanRequiredError3
116092
116575
  } from "@opengeni/core";
116093
- import { nestedPostgresSqlState as nestedPostgresSqlState18, SessionTenancyNotActivatedError as SessionTenancyNotActivatedError3 } from "@opengeni/db";
116576
+ import { nestedPostgresSqlState as nestedPostgresSqlState19, SessionTenancyNotActivatedError as SessionTenancyNotActivatedError3 } from "@opengeni/db";
116094
116577
  import { HTTPException as HTTPException116 } from "hono/http-exception";
116095
116578
  import { z as z62 } from "zod";
116096
116579
  async function body2(context, schema) {
@@ -116105,10 +116588,10 @@ function lifecycleError(error) {
116105
116588
  if (error instanceof SessionAuthorizationUnavailableError15) {
116106
116589
  throw new HTTPException116(503, { message: "session authorization unavailable" });
116107
116590
  }
116108
- if (nestedPostgresSqlState18(error) === "42501") {
116591
+ if (nestedPostgresSqlState19(error) === "42501") {
116109
116592
  throw new HTTPException116(403, { message: "user-resource authority denied" });
116110
116593
  }
116111
- if (nestedPostgresSqlState18(error) === "22023") {
116594
+ if (nestedPostgresSqlState19(error) === "22023") {
116112
116595
  throw new HTTPException116(422, { message: "invalid user-resource request" });
116113
116596
  }
116114
116597
  throw error;
@@ -116124,7 +116607,7 @@ function registerUserResourceAuthorityRoutes(app, deps) {
116124
116607
  });
116125
116608
  if (!query.success) throw new HTTPException116(422, { message: "explicit scope=user required" });
116126
116609
  const workspaceId = context.req.param("workspaceId");
116127
- const access = await requireAccessGrantAuthorization39(
116610
+ const access = await requireAccessGrantAuthorization40(
116128
116611
  context,
116129
116612
  deps,
116130
116613
  workspaceId,
@@ -116152,7 +116635,7 @@ function registerUserResourceAuthorityRoutes(app, deps) {
116152
116635
  const authorityId = z62.string().uuid().safeParse(context.req.param("authorityId"));
116153
116636
  if (!authorityId.success) throw new HTTPException116(422, { message: "invalid authority id" });
116154
116637
  const request = await body2(context, IssueUserResourceGrantRequest);
116155
- const access = await requireAccessGrantAuthorization39(
116638
+ const access = await requireAccessGrantAuthorization40(
116156
116639
  context,
116157
116640
  deps,
116158
116641
  workspaceId,
@@ -116182,7 +116665,7 @@ function registerUserResourceAuthorityRoutes(app, deps) {
116182
116665
  const grantId = z62.string().uuid().safeParse(context.req.param("grantId"));
116183
116666
  if (!grantId.success) throw new HTTPException116(422, { message: "invalid grant id" });
116184
116667
  const workspaceId = context.req.param("workspaceId");
116185
- const access = await requireAccessGrantAuthorization39(
116668
+ const access = await requireAccessGrantAuthorization40(
116186
116669
  context,
116187
116670
  deps,
116188
116671
  workspaceId,
@@ -116202,28 +116685,94 @@ function registerUserResourceAuthorityRoutes(app, deps) {
116202
116685
  }
116203
116686
 
116204
116687
  // src/transcription/service.ts
116205
- import { resolveVoiceInputProviderRegistry } from "@opengeni/config";
116688
+ import {
116689
+ resolveVoiceInputProviderRegistry,
116690
+ voiceInputCreditBillingActive
116691
+ } from "@opengeni/config";
116206
116692
  import { VOICE_INPUT_ACCEPTED_MIME_TYPES } from "@opengeni/contracts";
116693
+ import { voiceTranscriptionSettlementKeys } from "@opengeni/db";
116207
116694
  import {
116695
+ createVoiceInputBilling,
116208
116696
  filenameForMimeType,
116209
116697
  isAcceptedMimeType as isAcceptedMimeType2,
116210
116698
  normalizeMimeType as normalizeMimeType2,
116211
116699
  TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS as TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS2,
116212
- TranscriptionServiceError as TranscriptionServiceError6
116700
+ TranscriptionServiceError as TranscriptionServiceError7
116213
116701
  } from "@opengeni/core";
116214
116702
 
116215
- // src/transcription/providers/openai.ts
116703
+ // src/transcription/normalize.ts
116216
116704
  import { TranscriptionServiceError as TranscriptionServiceError3 } from "@opengeni/core";
116705
+ function createFfmpegAudioNormalizer(input) {
116706
+ const segmenter = createFfmpegTranscriptionSegmenter({ ffmpegPath: input.ffmpegPath });
116707
+ return async ({ audio, mimeType, maxDurationSeconds, signal }) => {
116708
+ if (!Number.isFinite(maxDurationSeconds) || maxDurationSeconds <= 0) {
116709
+ throw new TranscriptionServiceError3({
116710
+ code: "unavailable",
116711
+ message: "Voice input duration limit is not configured."
116712
+ });
116713
+ }
116714
+ const decodeSeconds = Math.ceil(maxDurationSeconds) + 1;
116715
+ const segmentSeconds = decodeSeconds + 1;
116716
+ let normalized;
116717
+ try {
116718
+ for await (const segment of segmenter.segment({
116719
+ sourceMimeType: mimeType,
116720
+ chunks: async function* () {
116721
+ yield audio;
116722
+ }(),
116723
+ providerSegmentSeconds: segmentSeconds,
116724
+ totalDurationMilliseconds: segmentSeconds * 1e3,
116725
+ maxDecodeSeconds: decodeSeconds,
116726
+ ...signal ? { signal } : {}
116727
+ })) {
116728
+ if (normalized) {
116729
+ throw new TranscriptionServiceError3({ code: "too_large", message: "Audio is too long." });
116730
+ }
116731
+ normalized = segment.bytes;
116732
+ }
116733
+ } catch (error) {
116734
+ if (error instanceof TranscriptionSegmenterError) {
116735
+ throw new TranscriptionServiceError3({
116736
+ code: error.code === "unknown" ? "invalid_audio" : error.code,
116737
+ message: "Audio could not be prepared for transcription.",
116738
+ retryable: error.retryable
116739
+ });
116740
+ }
116741
+ throw error;
116742
+ }
116743
+ const durationSeconds = normalized ? wavDurationSeconds(normalized) : null;
116744
+ if (!normalized || durationSeconds === null || !(durationSeconds > 0)) {
116745
+ throw new TranscriptionServiceError3({
116746
+ code: "invalid_audio",
116747
+ message: "Audio could not be decoded."
116748
+ });
116749
+ }
116750
+ if (durationSeconds > decodeSeconds - 0.02) {
116751
+ throw new TranscriptionServiceError3({ code: "too_large", message: "Audio is too long." });
116752
+ }
116753
+ return { bytes: normalized, durationSeconds };
116754
+ };
116755
+ }
116756
+
116757
+ // src/transcription/providers/openai.ts
116758
+ import {
116759
+ voiceInputModelReportsDurationOnly
116760
+ } from "@opengeni/config";
116761
+ import { TranscriptionServiceError as TranscriptionServiceError4 } from "@opengeni/core";
116217
116762
  function createOpenAiTranscriptionProvider(input) {
116218
116763
  const fetchImpl = input.fetch ?? fetch;
116219
116764
  return {
116220
116765
  id: "openai",
116221
116766
  supportsServerDeadline: true,
116767
+ deploymentFunded: { model: input.model, pricing: input.pricing ?? null },
116222
116768
  available: () => true,
116223
116769
  async transcribe({ audio, mimeType, filename, requestId, signal }) {
116224
116770
  const form = new FormData();
116225
116771
  form.append("file", audioBlob(audio, mimeType), filename);
116226
116772
  form.append("model", input.model);
116773
+ if (voiceInputModelReportsDurationOnly(input.model)) {
116774
+ form.append("response_format", "verbose_json");
116775
+ }
116227
116776
  let response;
116228
116777
  try {
116229
116778
  response = await fetchImpl(`${input.baseUrl}/audio/transcriptions`, {
@@ -116241,50 +116790,89 @@ function createOpenAiTranscriptionProvider(input) {
116241
116790
  }
116242
116791
  if (!response.ok) throw responseError(response.status);
116243
116792
  const body3 = await response.json().catch(() => null);
116244
- if (!body3 || typeof body3.text !== "string") {
116245
- throw new TranscriptionServiceError3({
116793
+ const parsed = parseTranscriptionResponseBody(body3);
116794
+ if (!parsed) {
116795
+ throw new TranscriptionServiceError4({
116246
116796
  code: "provider",
116247
116797
  message: "Invalid transcription response."
116248
116798
  });
116249
116799
  }
116250
- return {
116251
- text: body3.text,
116252
- languages: typeof body3.language === "string" && body3.language ? [body3.language] : []
116253
- };
116800
+ return parsed;
116254
116801
  }
116255
116802
  };
116256
116803
  }
116804
+ function parseTranscriptionResponseBody(body3) {
116805
+ if (!body3 || typeof body3 !== "object") return null;
116806
+ const record6 = body3;
116807
+ if (typeof record6.text !== "string") return null;
116808
+ const languages = typeof record6.language === "string" && record6.language ? [record6.language] : Array.isArray(record6.languages) ? record6.languages.flatMap((language) => {
116809
+ const code = typeof language === "string" ? language : language?.code;
116810
+ return typeof code === "string" && code ? [code] : [];
116811
+ }) : [];
116812
+ return { text: record6.text, languages, usage: parseTranscriptionUsage(record6) };
116813
+ }
116814
+ function parseTranscriptionUsage(body3) {
116815
+ const usage = body3.usage;
116816
+ if (usage && typeof usage === "object") {
116817
+ const record6 = usage;
116818
+ if (record6.type === "tokens") {
116819
+ const inputTokens = count(record6.input_tokens);
116820
+ const outputTokens = count(record6.output_tokens);
116821
+ if (inputTokens !== null && outputTokens !== null) {
116822
+ const details = record6.input_token_details && typeof record6.input_token_details === "object" ? record6.input_token_details : {};
116823
+ return {
116824
+ kind: "tokens",
116825
+ inputTokens,
116826
+ audioInputTokens: count(details.audio_tokens) ?? 0,
116827
+ textInputTokens: count(details.text_tokens) ?? 0,
116828
+ outputTokens
116829
+ };
116830
+ }
116831
+ }
116832
+ if (record6.type === "duration" && seconds(record6.seconds) !== null) {
116833
+ return { kind: "duration", seconds: seconds(record6.seconds) };
116834
+ }
116835
+ }
116836
+ const duration = seconds(body3.duration);
116837
+ return duration === null ? null : { kind: "duration", seconds: duration };
116838
+ }
116839
+ function count(value) {
116840
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
116841
+ }
116842
+ function seconds(value) {
116843
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value < 86400 ? value : null;
116844
+ }
116257
116845
  function audioBlob(audio, mimeType) {
116258
116846
  return new Blob([Uint8Array.from(audio).buffer], { type: mimeType });
116259
116847
  }
116260
116848
  function responseError(status) {
116261
116849
  if (status === 401 || status === 403) {
116262
- return new TranscriptionServiceError3({
116850
+ return new TranscriptionServiceError4({
116263
116851
  fallbackSafe: true,
116264
116852
  code: "unavailable",
116265
116853
  message: "Transcription is unavailable."
116266
116854
  });
116267
116855
  }
116268
116856
  if (status === 413) {
116269
- return new TranscriptionServiceError3({
116857
+ return new TranscriptionServiceError4({
116270
116858
  code: "too_large",
116271
116859
  message: "Audio is too large."
116272
116860
  });
116273
116861
  }
116274
116862
  if (status === 400 || status === 422) {
116275
- return new TranscriptionServiceError3({
116863
+ return new TranscriptionServiceError4({
116276
116864
  code: "invalid_audio",
116277
116865
  message: "Audio could not be transcribed."
116278
116866
  });
116279
116867
  }
116280
116868
  if (status === 408 || status === 504) {
116281
- return new TranscriptionServiceError3({
116869
+ return new TranscriptionServiceError4({
116282
116870
  code: "timeout",
116283
116871
  message: "Transcription timed out.",
116284
116872
  retryable: true
116285
116873
  });
116286
116874
  }
116287
- return new TranscriptionServiceError3({
116875
+ return new TranscriptionServiceError4({
116288
116876
  code: status >= 500 ? "unavailable" : "provider",
116289
116877
  message: "Transcription provider failed.",
116290
116878
  retryable: status >= 500
@@ -116292,29 +116880,105 @@ function responseError(status) {
116292
116880
  }
116293
116881
  function fetchError(error) {
116294
116882
  if (error instanceof DOMException && error.name === "AbortError") {
116295
- return new TranscriptionServiceError3({
116883
+ return new TranscriptionServiceError4({
116296
116884
  code: "cancelled",
116297
116885
  message: "Transcription was cancelled."
116298
116886
  });
116299
116887
  }
116300
- return new TranscriptionServiceError3({
116888
+ return new TranscriptionServiceError4({
116301
116889
  code: "network",
116302
116890
  message: "Transcription provider is unreachable.",
116303
116891
  retryable: true
116304
116892
  });
116305
116893
  }
116306
116894
 
116895
+ // src/transcription/providers/azure-mai.ts
116896
+ function createMaiTranscriptionProvider(input) {
116897
+ const fetchImpl = input.fetch ?? fetch;
116898
+ const normalize = createFfmpegAudioNormalizer({ ffmpegPath: input.ffmpegPath });
116899
+ return {
116900
+ id: "azure-mai",
116901
+ supportsServerDeadline: true,
116902
+ deploymentFunded: { model: input.model, pricing: input.pricing ?? null },
116903
+ available: () => Boolean(input.apiKey),
116904
+ async transcribe({ audio, mimeType, filename, requestId, signal }) {
116905
+ let bytes = audio;
116906
+ let contentType = mimeType;
116907
+ let name = filename;
116908
+ if (!["audio/wav", "audio/x-wav", "audio/mpeg", "audio/flac"].includes(mimeType)) {
116909
+ const normalized = await normalize({
116910
+ audio,
116911
+ mimeType,
116912
+ maxDurationSeconds: 600,
116913
+ ...signal ? { signal } : {}
116914
+ });
116915
+ bytes = normalized.bytes;
116916
+ contentType = "audio/wav";
116917
+ name = "audio.wav";
116918
+ }
116919
+ const form = new FormData();
116920
+ form.append("audio", new Blob([Uint8Array.from(bytes).buffer], { type: contentType }), name);
116921
+ form.append(
116922
+ "definition",
116923
+ JSON.stringify({ enhancedMode: { enabled: true, model: input.model } })
116924
+ );
116925
+ let response;
116926
+ try {
116927
+ response = await fetchImpl(
116928
+ `${input.endpoint.replace(/\/+$/, "")}/speechtotext/transcriptions:transcribe?api-version=${encodeURIComponent(input.apiVersion)}`,
116929
+ {
116930
+ method: "POST",
116931
+ headers: {
116932
+ "Ocp-Apim-Subscription-Key": input.apiKey,
116933
+ "x-opengeni-request-id": requestId
116934
+ },
116935
+ body: form,
116936
+ ...signal ? { signal } : {}
116937
+ }
116938
+ );
116939
+ } catch (error) {
116940
+ throw fetchError(error);
116941
+ }
116942
+ if (!response.ok) throw responseError(response.status);
116943
+ const body3 = await response.json().catch(() => null);
116944
+ if (!body3 || !Array.isArray(body3.combinedPhrases) || !body3.combinedPhrases.every(
116945
+ (phrase) => typeof phrase === "object" && phrase !== null && "text" in phrase && typeof phrase.text === "string"
116946
+ )) {
116947
+ throw responseError(502);
116948
+ }
116949
+ return {
116950
+ usage: typeof body3.durationMilliseconds === "number" && Number.isFinite(body3.durationMilliseconds) && body3.durationMilliseconds >= 0 ? { kind: "duration", seconds: body3.durationMilliseconds / 1e3 } : null,
116951
+ text: body3.combinedPhrases.map((phrase) => phrase.text).join("\n"),
116952
+ languages: [
116953
+ ...new Set(
116954
+ (Array.isArray(body3.phrases) ? body3.phrases : []).flatMap(
116955
+ (phrase) => typeof phrase?.locale === "string" && phrase.locale ? [phrase.locale] : []
116956
+ )
116957
+ )
116958
+ ]
116959
+ };
116960
+ }
116961
+ };
116962
+ }
116963
+
116307
116964
  // src/transcription/providers/azure-openai.ts
116965
+ import { voiceInputModelReportsDurationOnly as voiceInputModelReportsDurationOnly2 } from "@opengeni/config";
116308
116966
  function createAzureOpenAiTranscriptionProvider(input) {
116309
116967
  const fetchImpl = input.fetch ?? fetch;
116310
- const url = `${input.endpoint}/openai/deployments/${encodeURIComponent(input.deployment)}/audio/transcriptions?api-version=${encodeURIComponent(input.apiVersion)}`;
116968
+ const model = input.model ?? input.deployment;
116969
+ const endpoint = input.endpoint.replace(/\/+$/, "");
116970
+ const url = `${endpoint}/openai/deployments/${encodeURIComponent(input.deployment)}/audio/transcriptions?api-version=${encodeURIComponent(input.apiVersion)}`;
116311
116971
  return {
116312
116972
  id: "azure-openai",
116313
116973
  supportsServerDeadline: true,
116974
+ deploymentFunded: { model, pricing: input.pricing ?? null },
116314
116975
  available: () => Boolean(input.apiKey || input.adToken),
116315
116976
  async transcribe({ audio, mimeType, filename, requestId, signal }) {
116316
116977
  const form = new FormData();
116317
116978
  form.append("file", new Blob([Uint8Array.from(audio).buffer], { type: mimeType }), filename);
116979
+ if (voiceInputModelReportsDurationOnly2(model)) {
116980
+ form.append("response_format", "verbose_json");
116981
+ }
116318
116982
  const headers = input.apiKey ? { "api-key": input.apiKey, "x-opengeni-request-id": requestId } : { Authorization: `Bearer ${input.adToken}`, "x-opengeni-request-id": requestId };
116319
116983
  let response;
116320
116984
  try {
@@ -116329,13 +116993,11 @@ function createAzureOpenAiTranscriptionProvider(input) {
116329
116993
  }
116330
116994
  if (!response.ok) throw responseError(response.status);
116331
116995
  const body3 = await response.json().catch(() => null);
116332
- if (!body3 || typeof body3.text !== "string") {
116996
+ const parsed = parseTranscriptionResponseBody(body3);
116997
+ if (!parsed) {
116333
116998
  throw responseError(502);
116334
116999
  }
116335
- return {
116336
- text: body3.text,
116337
- languages: typeof body3.language === "string" && body3.language ? [body3.language] : []
116338
- };
117000
+ return parsed;
116339
117001
  }
116340
117002
  };
116341
117003
  }
@@ -116343,7 +117005,7 @@ function createAzureOpenAiTranscriptionProvider(input) {
116343
117005
  // src/transcription/providers/codex-subscription.ts
116344
117006
  import { CODEX_CLIENT_VERSION as CODEX_CLIENT_VERSION4, CODEX_ORIGINATOR } from "@opengeni/codex/constants";
116345
117007
  import {
116346
- TranscriptionServiceError as TranscriptionServiceError4
117008
+ TranscriptionServiceError as TranscriptionServiceError5
116347
117009
  } from "@opengeni/core";
116348
117010
  import { buildCodexTokenResolver as buildCodexTokenResolver4, listCodexAccountStatuses as listCodexAccountStatuses3 } from "@opengeni/db";
116349
117011
  var TRANSCRIBE_URL = "https://chatgpt.com/backend-api/transcribe";
@@ -116369,7 +117031,7 @@ function createCodexSubscriptionTranscriptionProvider(input) {
116369
117031
  (candidate) => candidate.isActive && candidate.status === "active"
116370
117032
  );
116371
117033
  if (!account) {
116372
- throw new TranscriptionServiceError4({
117034
+ throw new TranscriptionServiceError5({
116373
117035
  fallbackSafe: true,
116374
117036
  code: "unavailable",
116375
117037
  message: "Transcription is unavailable."
@@ -116380,7 +117042,7 @@ function createCodexSubscriptionTranscriptionProvider(input) {
116380
117042
  try {
116381
117043
  token = await resolver.getToken();
116382
117044
  } catch {
116383
- throw new TranscriptionServiceError4({
117045
+ throw new TranscriptionServiceError5({
116384
117046
  fallbackSafe: true,
116385
117047
  code: "unavailable",
116386
117048
  message: "Transcription is unavailable."
@@ -116431,7 +117093,7 @@ function createCodexSubscriptionTranscriptionProvider(input) {
116431
117093
 
116432
117094
  // src/transcription/providers/xai-subscription.ts
116433
117095
  import {
116434
- TranscriptionServiceError as TranscriptionServiceError5
117096
+ TranscriptionServiceError as TranscriptionServiceError6
116435
117097
  } from "@opengeni/core";
116436
117098
  import { workspaceXaiSubscriptionActive as workspaceXaiSubscriptionActive3 } from "@opengeni/db";
116437
117099
  import {
@@ -116480,7 +117142,7 @@ function createXaiSubscriptionTranscriptionProvider(input) {
116480
117142
  ...input.fetch ? { fetch: input.fetch } : {}
116481
117143
  });
116482
117144
  } catch {
116483
- throw new TranscriptionServiceError5({
117145
+ throw new TranscriptionServiceError6({
116484
117146
  code: "unavailable",
116485
117147
  fallbackSafe: true,
116486
117148
  message: "Transcription is unavailable."
@@ -116511,7 +117173,7 @@ function createXaiSubscriptionTranscriptionProvider(input) {
116511
117173
  try {
116512
117174
  return await (refresh ? auth.context.refresh() : auth.context.getToken());
116513
117175
  } catch {
116514
- throw new TranscriptionServiceError5({
117176
+ throw new TranscriptionServiceError6({
116515
117177
  code: "unavailable",
116516
117178
  message: "Reconnect the SuperGrok account to use transcription.",
116517
117179
  fallbackSafe: true
@@ -116528,7 +117190,7 @@ function createXaiSubscriptionTranscriptionProvider(input) {
116528
117190
  response = await request(token.accessToken);
116529
117191
  }
116530
117192
  } catch (error) {
116531
- if (error instanceof TranscriptionServiceError5) throw error;
117193
+ if (error instanceof TranscriptionServiceError6) throw error;
116532
117194
  throw fetchError(error);
116533
117195
  }
116534
117196
  if (!response.ok) throw responseError(response.status);
@@ -116581,6 +117243,12 @@ function createTranscriptionService(input) {
116581
117243
  ...config,
116582
117244
  ...input.fetch ? { fetch: input.fetch } : {}
116583
117245
  });
117246
+ case "azure-mai":
117247
+ return createMaiTranscriptionProvider({
117248
+ ...config,
117249
+ ffmpegPath: input.settings.voiceInputFfmpegPath,
117250
+ ...input.fetch ? { fetch: input.fetch } : {}
117251
+ });
116584
117252
  case "azure-openai":
116585
117253
  return createAzureOpenAiTranscriptionProvider({
116586
117254
  ...config,
@@ -116611,32 +117279,113 @@ function createTranscriptionService(input) {
116611
117279
  };
116612
117280
  const providerRequestTimeoutMilliseconds = input.providerRequestTimeoutMilliseconds ?? TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS2;
116613
117281
  const now = input.now ?? (() => /* @__PURE__ */ new Date());
117282
+ const log = input.log ?? ((message, attributes) => {
117283
+ console.error(message, attributes);
117284
+ });
117285
+ const billing = input.billing ?? createVoiceInputBilling({
117286
+ db: input.db,
117287
+ settings: input.settings,
117288
+ onReconcileError: (error, scope) => {
117289
+ log("Voice input charge reconciliation failed", {
117290
+ ...scope,
117291
+ errorClass: error instanceof Error ? error.name : "UnknownError",
117292
+ error: boundedErrorMessage(error)
117293
+ });
117294
+ }
117295
+ });
117296
+ let normalizer = input.normalizeAudio;
117297
+ const normalizeAudio = (request) => {
117298
+ normalizer ??= createFfmpegAudioNormalizer({ ffmpegPath: input.settings.voiceInputFfmpegPath });
117299
+ return normalizer(request);
117300
+ };
117301
+ const retryDelays = input.settlementRetryDelaysMilliseconds ?? [2e3, 15e3, 6e4];
117302
+ const settleWithoutFailing = async (settlement) => {
117303
+ try {
117304
+ return (await billing.settle(settlement)).creditCostMicros;
117305
+ } catch (error) {
117306
+ reportUnsettled(settlement, error, 0, retryDelays.length > 0 && !deterministic(error));
117307
+ if (!deterministic(error)) scheduleSettlementRetry(settlement, 0);
117308
+ return 0;
117309
+ }
117310
+ };
117311
+ const deterministic = (error) => error instanceof TranscriptionServiceError7;
117312
+ const scheduleSettlementRetry = (settlement, attempt) => {
117313
+ const delay = retryDelays[attempt];
117314
+ if (delay === void 0) return;
117315
+ const timer = setTimeout(() => {
117316
+ void billing.settle(settlement).then(
117317
+ () => {
117318
+ log("Voice input charge settled on retry", {
117319
+ ...settlementAttributes(settlement),
117320
+ attempt: attempt + 1
117321
+ });
117322
+ },
117323
+ (error) => {
117324
+ const more = attempt + 1 < retryDelays.length;
117325
+ reportUnsettled(settlement, error, attempt + 1, more);
117326
+ if (more) scheduleSettlementRetry(settlement, attempt + 1);
117327
+ }
117328
+ );
117329
+ }, delay);
117330
+ timer.unref?.();
117331
+ };
117332
+ const reportUnsettled = (settlement, error, attempt, willRetry) => {
117333
+ log(
117334
+ willRetry ? "Voice input charge is unsettled after provider success; retrying" : "Voice input charge is UNSETTLED after provider success; manual reconciliation needed unless its usage receipt committed",
117335
+ {
117336
+ ...settlementAttributes(settlement),
117337
+ attempt,
117338
+ willRetry,
117339
+ errorClass: error instanceof Error ? error.name : "UnknownError",
117340
+ error: boundedErrorMessage(error)
117341
+ }
117342
+ );
117343
+ };
117344
+ const creditBillingActive = voiceInputCreditBillingActive(input.settings);
117345
+ const chargeable = (provider) => creditBillingActive && provider.deploymentFunded !== void 0;
116614
117346
  return {
116615
117347
  limits: () => limits,
116616
117348
  async available(context) {
116617
- return (await Promise.all(providers.map((provider) => provider.available(context)))).some(
116618
- Boolean
117349
+ return Boolean(
117350
+ await firstAvailable(orderedProviders(providers, context ?? {}), context ?? {})
116619
117351
  );
116620
117352
  },
117353
+ async availableProviderIds(context) {
117354
+ const available = await Promise.all(
117355
+ providers.map(async (provider) => {
117356
+ try {
117357
+ return await provider.available(context) ? provider.id : null;
117358
+ } catch {
117359
+ return null;
117360
+ }
117361
+ })
117362
+ );
117363
+ return available.filter((id) => id !== null);
117364
+ },
116621
117365
  async selectProvider(context) {
116622
117366
  return (await firstAvailable(orderedProviders(providers, context), context))?.id ?? null;
116623
117367
  },
117368
+ async admit({ providerId, accountId, workspaceId, attribution }) {
117369
+ const provider = providers.find((candidate) => candidate.id === providerId);
117370
+ if (!provider || !chargeable(provider)) return;
117371
+ await billing.admit({ accountId, workspaceId, attribution });
117372
+ },
116624
117373
  async transcribe(request) {
116625
117374
  const mimeType = normalizeMimeType2(request.mimeType);
116626
117375
  if (!isAcceptedMimeType2(mimeType, limits.acceptedMimeTypes)) {
116627
- throw new TranscriptionServiceError6({
117376
+ throw new TranscriptionServiceError7({
116628
117377
  code: "not_supported",
116629
117378
  message: "Unsupported audio format."
116630
117379
  });
116631
117380
  }
116632
117381
  if (request.audio.byteLength > limits.maxSizeBytes) {
116633
- throw new TranscriptionServiceError6({
117382
+ throw new TranscriptionServiceError7({
116634
117383
  code: "too_large",
116635
117384
  message: "Audio is too large."
116636
117385
  });
116637
117386
  }
116638
117387
  if (request.durationSeconds !== void 0 && (!Number.isFinite(request.durationSeconds) || request.durationSeconds < 0 || request.durationSeconds > limits.maxDurationSeconds)) {
116639
- throw new TranscriptionServiceError6({
117388
+ throw new TranscriptionServiceError7({
116640
117389
  code: "invalid_audio",
116641
117390
  message: "Invalid audio duration."
116642
117391
  });
@@ -116646,22 +117395,51 @@ function createTranscriptionService(input) {
116646
117395
  subjectId: request.subjectId
116647
117396
  }) : await firstAvailable(orderedProviders(providers, request), request);
116648
117397
  if (!provider) {
116649
- throw new TranscriptionServiceError6({
117398
+ throw new TranscriptionServiceError7({
116650
117399
  fallbackSafe: true,
116651
117400
  code: "unavailable",
116652
117401
  message: "Transcription is unavailable."
116653
117402
  });
116654
117403
  }
116655
117404
  if (provider.supportsServerDeadline !== true) {
116656
- throw new TranscriptionServiceError6({
117405
+ throw new TranscriptionServiceError7({
116657
117406
  code: "unavailable",
116658
117407
  message: "Transcription provider does not support bounded requests."
116659
117408
  });
116660
117409
  }
117410
+ if (chargeable(provider)) {
117411
+ if (!provider.deploymentFunded?.pricing || !request.billing) {
117412
+ throw new TranscriptionServiceError7({
117413
+ code: "unavailable",
117414
+ message: "Transcription billing is not configured."
117415
+ });
117416
+ }
117417
+ await billing.admit({
117418
+ accountId: request.accountId,
117419
+ workspaceId: request.workspaceId,
117420
+ attribution: request.billing.attribution
117421
+ });
117422
+ }
117423
+ let audio = request.audio;
117424
+ let providerMimeType = mimeType;
117425
+ let billingContext = request.billing;
117426
+ let audioSeconds = request.durationSeconds ?? 0;
117427
+ if (chargeable(provider) && billingContext?.trustedDurationSeconds === void 0) {
117428
+ const normalized = await normalizeAudio({
117429
+ audio,
117430
+ mimeType,
117431
+ maxDurationSeconds: limits.maxDurationSeconds,
117432
+ ...request.signal ? { signal: request.signal } : {}
117433
+ });
117434
+ audio = normalized.bytes;
117435
+ providerMimeType = "audio/wav";
117436
+ audioSeconds = normalized.durationSeconds;
117437
+ billingContext = { ...billingContext, trustedDurationSeconds: normalized.durationSeconds };
117438
+ }
116661
117439
  const startedAt = performance.now();
116662
117440
  const remainingMilliseconds = request.providerDeadlineAt ? remainingTranscriptionProviderRequestMilliseconds(request.providerDeadlineAt, now()) : providerRequestTimeoutMilliseconds;
116663
117441
  if (request.providerDeadlineAt && (!Number.isFinite(remainingMilliseconds) || remainingMilliseconds <= 0)) {
116664
- throw new TranscriptionServiceError6({
117442
+ throw new TranscriptionServiceError7({
116665
117443
  code: "timeout",
116666
117444
  message: "Transcription provider deadline expired.",
116667
117445
  retryable: true
@@ -116671,9 +117449,9 @@ function createTranscriptionService(input) {
116671
117449
  let result;
116672
117450
  try {
116673
117451
  result = await provider.transcribe({
116674
- audio: request.audio,
116675
- mimeType,
116676
- filename: filenameForMimeType(mimeType),
117452
+ audio,
117453
+ mimeType: providerMimeType,
117454
+ filename: filenameForMimeType(providerMimeType),
116677
117455
  workspaceId: request.workspaceId,
116678
117456
  accountId: request.accountId,
116679
117457
  subjectId: request.subjectId,
@@ -116681,7 +117459,7 @@ function createTranscriptionService(input) {
116681
117459
  signal: deadline.signal
116682
117460
  });
116683
117461
  if (deadline.timedOut && !request.signal?.aborted) {
116684
- throw new TranscriptionServiceError6({
117462
+ throw new TranscriptionServiceError7({
116685
117463
  code: "timeout",
116686
117464
  message: "Transcription provider timed out.",
116687
117465
  retryable: true
@@ -116689,13 +117467,13 @@ function createTranscriptionService(input) {
116689
117467
  }
116690
117468
  } catch (error) {
116691
117469
  if (deadline.timedOut && !request.signal?.aborted) {
116692
- throw new TranscriptionServiceError6({
117470
+ throw new TranscriptionServiceError7({
116693
117471
  code: "timeout",
116694
117472
  message: "Transcription provider timed out.",
116695
117473
  retryable: true
116696
117474
  });
116697
117475
  }
116698
- if (error instanceof TranscriptionServiceError6 && error.fallbackSafe && !request.providerId && request.fallbackEnabled !== false && !request.signal?.aborted) {
117476
+ if (error instanceof TranscriptionServiceError7 && error.fallbackSafe && !request.providerId && request.fallbackEnabled !== false && !request.signal?.aborted) {
116699
117477
  const excludedProviders = [...request.excludedProviders ?? [], provider.id];
116700
117478
  if (await firstAvailable(
116701
117479
  orderedProviders(providers, { ...request, excludedProviders }),
@@ -116708,11 +117486,30 @@ function createTranscriptionService(input) {
116708
117486
  } finally {
116709
117487
  deadline.dispose();
116710
117488
  }
117489
+ const latencyMs = Math.round(performance.now() - startedAt);
117490
+ let creditCostMicros = 0;
117491
+ const settle = async () => {
117492
+ if (chargeable(provider) && provider.deploymentFunded?.pricing && billingContext) {
117493
+ creditCostMicros = await settleWithoutFailing({
117494
+ accountId: request.accountId,
117495
+ workspaceId: request.workspaceId,
117496
+ providerId: provider.id,
117497
+ model: provider.deploymentFunded.model,
117498
+ pricing: provider.deploymentFunded.pricing,
117499
+ usage: result.usage ?? null,
117500
+ billing: billingContext
117501
+ });
117502
+ }
117503
+ };
117504
+ if (!request.deferBillingSettlement) await settle();
116711
117505
  return {
116712
- ...result,
117506
+ text: result.text,
117507
+ languages: result.languages,
116713
117508
  providerId: provider.id,
116714
- audioSeconds: request.durationSeconds ?? 0,
116715
- latencyMs: Math.round(performance.now() - startedAt)
117509
+ audioSeconds,
117510
+ latencyMs,
117511
+ creditCostMicros,
117512
+ ...request.deferBillingSettlement ? { settleBilling: settle } : {}
116716
117513
  };
116717
117514
  }
116718
117515
  };
@@ -116767,6 +117564,23 @@ async function exactAvailable(providers, providerId, context) {
116767
117564
  const provider = providers.find((candidate) => candidate.id === providerId);
116768
117565
  return provider && await provider.available(context) ? provider : null;
116769
117566
  }
117567
+ function settlementAttributes(settlement) {
117568
+ const keys2 = voiceTranscriptionSettlementKeys({
117569
+ workspaceId: settlement.workspaceId,
117570
+ sourceId: settlement.billing.sourceId
117571
+ });
117572
+ return {
117573
+ accountId: settlement.accountId,
117574
+ workspaceId: settlement.workspaceId,
117575
+ providerId: settlement.providerId,
117576
+ sourceId: settlement.billing.sourceId,
117577
+ usageIdempotencyKey: keys2.usageIdempotencyKey,
117578
+ debitIdempotencyKey: keys2.debitIdempotencyKey
117579
+ };
117580
+ }
117581
+ function boundedErrorMessage(error) {
117582
+ return (error instanceof Error ? error.message : String(error)).slice(0, 500);
117583
+ }
116770
117584
 
116771
117585
  // src/integrations/slack-tool-review.ts
116772
117586
  function slackToolReviewCanDecide(review) {
@@ -116936,7 +117750,7 @@ import {
116936
117750
  getActorNewSessionModelChoice,
116937
117751
  hasPermission as hasPermission33,
116938
117752
  requireAccessContext as requireAccessContext14,
116939
- requireAccessGrant as requireAccessGrant53,
117753
+ requireAccessGrant as requireAccessGrant52,
116940
117754
  requireSessionAuthorizationListScope as requireSessionAuthorizationListScope4,
116941
117755
  resolveWorkspaceCatalogSettings as resolveWorkspaceCatalogSettings9
116942
117756
  } from "@opengeni/core";
@@ -118103,7 +118917,7 @@ function registerSlackInteractionRoutes(app, deps) {
118103
118917
  );
118104
118918
  app.get("/v1/workspaces/:workspaceId/members/access-requests/slack", async (c) => {
118105
118919
  const workspaceId = c.req.param("workspaceId");
118106
- await requireAccessGrant53(c, deps, workspaceId, "members:manage");
118920
+ await requireAccessGrant52(c, deps, workspaceId, "members:manage");
118107
118921
  const workspace = await getWorkspace11(deps.db, workspaceId);
118108
118922
  const requests = await listPendingSlackUserLinkAccessRequests(deps.db, workspaceId);
118109
118923
  return c.json(
@@ -118119,7 +118933,7 @@ function registerSlackInteractionRoutes(app, deps) {
118119
118933
  "/v1/workspaces/:workspaceId/members/access-requests/slack/:requestId/approve",
118120
118934
  async (c) => {
118121
118935
  const workspaceId = c.req.param("workspaceId");
118122
- const grant = await requireAccessGrant53(c, deps, workspaceId, "members:manage");
118936
+ const grant = await requireAccessGrant52(c, deps, workspaceId, "members:manage");
118123
118937
  const payload = await parseRequestJson(c, ApproveSlackUserLinkAccessRequest);
118124
118938
  const requestId = c.req.param("requestId");
118125
118939
  const requestTeam = await slackUserLinkAccessRequestTeam(deps.db, {
@@ -118159,7 +118973,7 @@ function registerSlackInteractionRoutes(app, deps) {
118159
118973
  "/v1/workspaces/:workspaceId/members/access-requests/slack/:requestId/deny",
118160
118974
  async (c) => {
118161
118975
  const workspaceId = c.req.param("workspaceId");
118162
- const grant = await requireAccessGrant53(c, deps, workspaceId, "members:manage");
118976
+ const grant = await requireAccessGrant52(c, deps, workspaceId, "members:manage");
118163
118977
  const payload = await parseRequestJson(c, SlackUserLinkAccessMutationRequest);
118164
118978
  try {
118165
118979
  const request = await denySlackUserLinkAccessRequest(deps.db, {
@@ -118182,7 +118996,7 @@ function registerSlackInteractionRoutes(app, deps) {
118182
118996
  );
118183
118997
  app.post("/v1/workspaces/:workspaceId/integrations/slack/user-links", async (c) => {
118184
118998
  const workspaceId = c.req.param("workspaceId");
118185
- const grant = await requireAccessGrant53(c, deps, workspaceId, "sessions:create");
118999
+ const grant = await requireAccessGrant52(c, deps, workspaceId, "sessions:create");
118186
119000
  const body3 = record5(await c.req.json().catch(() => null));
118187
119001
  const linkToken = boundedString(body3?.linkToken, 2048);
118188
119002
  const signingSecret = deps.settings.slackSigningSecret;
@@ -118238,7 +119052,7 @@ function registerSlackInteractionRoutes(app, deps) {
118238
119052
  "/v1/workspaces/:workspaceId/integrations/slack/user-links/:slackUserId",
118239
119053
  async (c) => {
118240
119054
  const workspaceId = c.req.param("workspaceId");
118241
- await requireAccessGrant53(c, deps, workspaceId, "connections:write");
119055
+ await requireAccessGrant52(c, deps, workspaceId, "connections:write");
118242
119056
  const connectionId = boundedString(c.req.query("connectionId"), 64);
118243
119057
  if (!connectionId) throw new HTTPException117(400, { message: "connectionId is required" });
118244
119058
  return c.json({
@@ -118253,7 +119067,7 @@ function registerSlackInteractionRoutes(app, deps) {
118253
119067
  );
118254
119068
  app.get("/v1/workspaces/:workspaceId/integrations/slack/channel-routes", async (c) => {
118255
119069
  const workspaceId = c.req.param("workspaceId");
118256
- const grant = await requireAccessGrant53(c, deps, workspaceId, "workspace:admin");
119070
+ const grant = await requireAccessGrant52(c, deps, workspaceId, "workspace:admin");
118257
119071
  const connectionId = boundedString(c.req.query("connectionId"), 64);
118258
119072
  if (!connectionId) throw new HTTPException117(400, { message: "connectionId is required" });
118259
119073
  const routes = await listSlackChannelRoutes(
@@ -118284,7 +119098,7 @@ function registerSlackInteractionRoutes(app, deps) {
118284
119098
  });
118285
119099
  app.put("/v1/workspaces/:workspaceId/integrations/slack/channel-routes", async (c) => {
118286
119100
  const workspaceId = c.req.param("workspaceId");
118287
- const grant = await requireAccessGrant53(c, deps, workspaceId, "workspace:admin");
119101
+ const grant = await requireAccessGrant52(c, deps, workspaceId, "workspace:admin");
118288
119102
  const payload = await parseRequestJson(c, UpdateSlackChannelRoutesRequest);
118289
119103
  const home = { accountId: grant.accountId, workspaceId };
118290
119104
  const installation = await listSlackInstallationBindings2(deps.db, home);
@@ -118339,7 +119153,7 @@ function registerSlackInteractionRoutes(app, deps) {
118339
119153
  });
118340
119154
  app.get("/v1/workspaces/:workspaceId/integrations/slack/reaction-channels", async (c) => {
118341
119155
  const workspaceId = c.req.param("workspaceId");
118342
- const grant = await requireAccessGrant53(c, deps, workspaceId, "workspace:admin");
119156
+ const grant = await requireAccessGrant52(c, deps, workspaceId, "workspace:admin");
118343
119157
  const connectionId = boundedString(c.req.query("connectionId"), 64);
118344
119158
  if (!connectionId) throw new HTTPException117(400, { message: "connectionId is required" });
118345
119159
  const cursor = boundedString(c.req.query("cursor"), 1024);
@@ -122244,10 +123058,23 @@ function createAppComposition(deps) {
122244
123058
  }, MANAGED_AUTH_REAPER_INTERVAL_MS);
122245
123059
  timer.unref?.();
122246
123060
  }
123061
+ if (deps.transcription === void 0) {
123062
+ for (const issue of voiceInputPricingIssues(deps.settings)) {
123063
+ observability.error(
123064
+ `Voice input provider ${issue.providerId} is unavailable: ${issue.message}`,
123065
+ {
123066
+ providerId: issue.providerId,
123067
+ env: issue.env,
123068
+ reason: issue.reason
123069
+ }
123070
+ );
123071
+ }
123072
+ }
122247
123073
  const transcription = deps.transcription === void 0 ? createTranscriptionService({
122248
123074
  settings: deps.settings,
122249
123075
  db: deps.db,
122250
- ...deps.codexFetch ? { codexFetch: deps.codexFetch } : {}
123076
+ ...deps.codexFetch ? { codexFetch: deps.codexFetch } : {},
123077
+ log: (message, attributes) => observability.error(message, attributes)
122251
123078
  }) : deps.transcription;
122252
123079
  const transcriptionSegmenter = deps.transcriptionSegmenter === void 0 ? createFfmpegTranscriptionSegmenter({
122253
123080
  ffmpegPath: deps.settings.voiceInputFfmpegPath
@@ -122355,8 +123182,14 @@ function createAppComposition(deps) {
122355
123182
  "X-OpenGeni-Access-Key",
122356
123183
  "X-OpenGeni-Api-Contract",
122357
123184
  "X-OpenGeni-Actor-Epoch",
123185
+ // Resumable voice-input chunk integrity/timing (cross-origin embeds).
123186
+ "X-OpenGeni-Chunk-Duration-Milliseconds",
123187
+ "X-OpenGeni-Chunk-Sha256",
123188
+ "X-OpenGeni-Chunk-Start-Milliseconds",
122358
123189
  "X-OpenGeni-Correlation-Id",
122359
123190
  "X-OpenGeni-Session-Csrf",
123191
+ // Session scope for a session proxy; the API itself ignores it.
123192
+ "X-OpenGeni-Session-Id",
122360
123193
  "X-OpenGeni-Site-Id",
122361
123194
  "X-OpenGeni-Site-Version",
122362
123195
  "X-OpenGeni-Subject",
@@ -122803,6 +123636,8 @@ function createAppComposition(deps) {
122803
123636
  creditsAvailable: false
122804
123637
  });
122805
123638
  let modelSelectionForbidden = false;
123639
+ let voiceInputAvailable = false;
123640
+ let voiceInputProviders = [];
122806
123641
  const requestedWorkspaceId = c.req.query("workspaceId");
122807
123642
  if (requestedWorkspaceId !== void 0 && requestedWorkspaceId.trim() === "") {
122808
123643
  throw new HTTPException118(422, { message: "workspaceId must not be empty" });
@@ -122821,7 +123656,7 @@ function createAppComposition(deps) {
122821
123656
  }
122822
123657
  const workspaceId = requestedWorkspaceId ?? context?.defaultWorkspaceId;
122823
123658
  if (context && workspaceId) {
122824
- const grant = await requireAccessGrant54(c, routeDeps, workspaceId);
123659
+ const grant = await requireAccessGrant53(c, routeDeps, workspaceId);
122825
123660
  await withAccessGrantSessionRlsContext(routeDeps, grant, async () => {
122826
123661
  catalogSettings = (await resolveWorkspaceCatalogSettings10(deps.db, deps.settings, {
122827
123662
  accountId: grant.accountId,
@@ -122838,6 +123673,19 @@ function createAppComposition(deps) {
122838
123673
  selections = [];
122839
123674
  }
122840
123675
  const workspace = await getWorkspace12(deps.db, workspaceId);
123676
+ const voicePreferences = WorkspaceVoiceInputSettings3.safeParse(
123677
+ workspace?.settings.voiceInput
123678
+ ).data;
123679
+ const voiceContext = {
123680
+ workspaceId,
123681
+ subjectId: grant.subjectId,
123682
+ preferredProvider: voicePreferences?.preferredProvider,
123683
+ fallbackEnabled: voicePreferences?.fallbackEnabled
123684
+ };
123685
+ if (hasPermission34(grant.permissions, "sessions:create") && transcription) {
123686
+ voiceInputProviders = await transcription.availableProviderIds?.(voiceContext) ?? [];
123687
+ voiceInputAvailable = voiceInputProviders.length > 0 && await Promise.resolve(transcription.available(voiceContext)).catch(() => false);
123688
+ }
122841
123689
  defaultSelection = await resolveDefaultSessionModelForSelections2(deps.db, {
122842
123690
  settings: catalogSettings,
122843
123691
  accountId: grant.accountId,
@@ -122893,14 +123741,12 @@ function createAppComposition(deps) {
122893
123741
  maxSizeBytes: objectStorage?.maxSinglePutSizeBytes ?? 5e9
122894
123742
  },
122895
123743
  voiceInput: {
122896
- providers: resolveVoiceInputProviderRegistry2(deps.settings).map(
122897
- (provider) => provider.id
122898
- ),
122899
- available: await transcription?.available() ?? false,
123744
+ providers: voiceInputProviders,
123745
+ available: voiceInputAvailable,
122900
123746
  maxDurationSeconds: deps.settings.voiceInputMaxDurationSeconds,
122901
123747
  maxSizeBytes: deps.settings.voiceInputMaxSizeBytes,
122902
123748
  acceptedMimeTypes: [...VOICE_INPUT_ACCEPTED_MIME_TYPES2],
122903
- ...deps.settings.voiceInputResumableEnabled && objectStorage && transcription && transcriptionSegmenter && await transcription.available() && await transcriptionSegmenter.available() ? {
123749
+ ...deps.settings.voiceInputResumableEnabled && objectStorage && transcription && transcriptionSegmenter && voiceInputAvailable && await transcriptionSegmenter.available() ? {
122904
123750
  resumable: {
122905
123751
  maxDurationSeconds: deps.settings.voiceInputResumableMaxDurationSeconds,
122906
123752
  maxSizeBytes: deps.settings.voiceInputResumableMaxSizeBytes,
@@ -123009,7 +123855,7 @@ function createAppComposition(deps) {
123009
123855
  app.use("/v1/workspaces/:workspaceId/*", async (c, next) => {
123010
123856
  const workspaceId = c.req.param("workspaceId");
123011
123857
  if (workspaceRequestRequiresCodexAccountPrevalidation(c.req.raw)) {
123012
- const grant2 = await requireAccessGrant54(c, routeDeps, workspaceId);
123858
+ const grant2 = await requireAccessGrant53(c, routeDeps, workspaceId);
123013
123859
  await validateCodexAccountTarget(c.req.raw);
123014
123860
  await withAccessGrantSessionRlsContext(routeDeps, grant2, next);
123015
123861
  return;
@@ -123019,7 +123865,7 @@ function createAppComposition(deps) {
123019
123865
  return;
123020
123866
  }
123021
123867
  const permission = c.req.method === "PUT" && /^\/v1\/workspaces\/[^/]+\/artifact-catalog\/[^/]+\/[^/]+\/pin$/.test(c.req.path) ? "artifacts:publish" : void 0;
123022
- const grant = await requireAccessGrant54(c, routeDeps, workspaceId, permission);
123868
+ const grant = await requireAccessGrant53(c, routeDeps, workspaceId, permission);
123023
123869
  await withAccessGrantSessionRlsContext(routeDeps, grant, next);
123024
123870
  });
123025
123871
  app.all("/v1/workspaces/:workspaceId/mcp", async (c) => {
@@ -123174,7 +124020,7 @@ function createAppComposition(deps) {
123174
124020
  });
123175
124021
  app.get("/v1/workspaces/:workspaceId/tools/catalog", async (c) => {
123176
124022
  const workspaceId = c.req.param("workspaceId");
123177
- const authorization = await requireAccessGrantAuthorization40(
124023
+ const authorization = await requireAccessGrantAuthorization41(
123178
124024
  c,
123179
124025
  routeDeps,
123180
124026
  workspaceId,
@@ -123190,7 +124036,7 @@ function createAppComposition(deps) {
123190
124036
  });
123191
124037
  app.post("/v1/workspaces/:workspaceId/tools/calls", async (c) => {
123192
124038
  const workspaceId = c.req.param("workspaceId");
123193
- const authorization = await requireAccessGrantAuthorization40(
124039
+ const authorization = await requireAccessGrantAuthorization41(
123194
124040
  c,
123195
124041
  routeDeps,
123196
124042
  workspaceId,
@@ -123219,7 +124065,7 @@ function createAppComposition(deps) {
123219
124065
  });
123220
124066
  app.post("/v1/workspaces/:workspaceId/tools/approvals", async (c) => {
123221
124067
  const workspaceId = c.req.param("workspaceId");
123222
- const authorization = await requireAccessGrantAuthorization40(
124068
+ const authorization = await requireAccessGrantAuthorization41(
123223
124069
  c,
123224
124070
  routeDeps,
123225
124071
  workspaceId,
@@ -123248,7 +124094,7 @@ function createAppComposition(deps) {
123248
124094
  });
123249
124095
  app.get("/v1/workspaces/:workspaceId/tools/declarations", async (c) => {
123250
124096
  const workspaceId = c.req.param("workspaceId");
123251
- const authorization = await requireAccessGrantAuthorization40(
124097
+ const authorization = await requireAccessGrantAuthorization41(
123252
124098
  c,
123253
124099
  routeDeps,
123254
124100
  workspaceId,
@@ -123264,7 +124110,7 @@ function createAppComposition(deps) {
123264
124110
  });
123265
124111
  app.all("/v1/workspaces/:workspaceId/codemode/sdk/*", async (c) => {
123266
124112
  const workspaceId = c.req.param("workspaceId");
123267
- const grant = await requireAccessGrant54(c, routeDeps, workspaceId);
124113
+ const grant = await requireAccessGrant53(c, routeDeps, workspaceId);
123268
124114
  const url = new URL(c.req.url);
123269
124115
  const prefix2 = `/v1/workspaces/${workspaceId}/codemode/sdk`;
123270
124116
  let forwarded;
@@ -123282,7 +124128,7 @@ function createAppComposition(deps) {
123282
124128
  });
123283
124129
  app.get("/v1/workspaces/:workspaceId/codemode/catalog", async (c) => {
123284
124130
  const workspaceId = c.req.param("workspaceId");
123285
- const grant = await requireAccessGrant54(c, routeDeps, workspaceId);
124131
+ const grant = await requireAccessGrant53(c, routeDeps, workspaceId);
123286
124132
  if (!isCodemodeGrant(grant)) {
123287
124133
  throw new HTTPException118(403, { message: "Codemode access denied" });
123288
124134
  }
@@ -123294,7 +124140,7 @@ function createAppComposition(deps) {
123294
124140
  });
123295
124141
  app.post("/v1/workspaces/:workspaceId/codemode/calls", async (c) => {
123296
124142
  const workspaceId = c.req.param("workspaceId");
123297
- const grant = await requireAccessGrant54(c, routeDeps, workspaceId);
124143
+ const grant = await requireAccessGrant53(c, routeDeps, workspaceId);
123298
124144
  if (!isCodemodeGrant(grant)) {
123299
124145
  throw new HTTPException118(403, { message: "Codemode access denied" });
123300
124146
  }
@@ -123314,7 +124160,7 @@ function createAppComposition(deps) {
123314
124160
  });
123315
124161
  app.get("/v1/workspaces/:workspaceId/codemode/calls/:operationId", async (c) => {
123316
124162
  const workspaceId = c.req.param("workspaceId");
123317
- const grant = await requireAccessGrant54(c, routeDeps, workspaceId);
124163
+ const grant = await requireAccessGrant53(c, routeDeps, workspaceId);
123318
124164
  if (!isCodemodeGrant(grant)) {
123319
124165
  throw new HTTPException118(403, { message: "Codemode access denied" });
123320
124166
  }
@@ -123439,13 +124285,13 @@ function createAppComposition(deps) {
123439
124285
  latencyMode: rawError.latencyMode,
123440
124286
  allowedLatencyModes: [...rawError.allowedLatencyModes]
123441
124287
  }
123442
- }) : null) ?? requestBodyValidationHttpError(rawError) ?? invalidPathIdentifierHttpError(rawError, new URL(c.req.url).pathname) ?? databaseUnavailableHttpError(rawError, c.req.method) ?? rawError;
124288
+ }) : null) ?? requestBodyValidationHttpError(rawError) ?? invalidPathIdentifierHttpError(rawError, new URL(c.req.url).pathname) ?? databaseUnavailableHttpError(rawError, c.req.method) ?? databaseContentionHttpError(rawError, c.req.method) ?? rawError;
123443
124289
  const compactionLock = codexCompactionV2ProviderLockedError(error);
123444
124290
  const apiError = error instanceof ApiHttpError ? error : null;
123445
124291
  const status = compactionLock ? 422 : httpStatusForError(error);
123446
124292
  const code = compactionLock ? compactionLock.code : apiError?.code ?? errorCodeForStatus(status);
123447
124293
  if (status >= 500) logHttpFailure(c, status, code, rawError);
123448
- if (apiError?.details?.code === DATABASE_UNAVAILABLE_ERROR_DETAIL_CODE) {
124294
+ if (apiError?.details?.code === DATABASE_UNAVAILABLE_ERROR_DETAIL_CODE || apiError?.details?.code === DATABASE_CONTENTION_ERROR_DETAIL_CODE) {
123449
124295
  c.header("retry-after", "1");
123450
124296
  }
123451
124297
  const envelope = ErrorEnvelope.parse({
@@ -123511,7 +124357,7 @@ async function validateCodexAccountTarget(request) {
123511
124357
  }
123512
124358
  }
123513
124359
  async function requireMcpAccessGrantAuthorization(c, deps, workspaceId) {
123514
- const authorization = await requireAccessGrantAuthorization40(c, deps, workspaceId);
124360
+ const authorization = await requireAccessGrantAuthorization41(c, deps, workspaceId);
123515
124361
  const grant = authorization.grant;
123516
124362
  if (hasPermission34(grant.permissions, "workspace:read")) {
123517
124363
  return authorization;
@@ -123666,6 +124512,8 @@ function assertConfiguredCodemodeSessionProxyPath(session, path, method) {
123666
124512
  }
123667
124513
  }
123668
124514
  function codemodeHttpError(error) {
124515
+ const contention = databaseContentionHttpError(error, "POST", { outcomeUnknown: false });
124516
+ if (contention) return contention;
123669
124517
  if (error instanceof SiteSessionPathError) {
123670
124518
  return new HTTPException118(404, { message: error.message, cause: error });
123671
124519
  }
@@ -124905,4 +125753,4 @@ export {
124905
125753
  withDefaultEnabledCapabilityMcpTools,
124906
125754
  workflowIdForSession4 as workflowIdForSession
124907
125755
  };
124908
- //# sourceMappingURL=chunk-IZEGLLKZ.js.map
125756
+ //# sourceMappingURL=chunk-F3LQ6VWR.js.map