@opengeni/core 1.4.0 → 1.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -845,7 +845,11 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
845
845
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
846
846
  import { isDeepStrictEqual } from "util";
847
847
  import { withLockedCapabilityInstallation } from "@opengeni/db/capability-reconciliation";
848
- import { environmentsEncryptionKeyBytes } from "@opengeni/config";
848
+ import {
849
+ environmentsEncryptionKeyBytes,
850
+ findIntegrationsOauthClient,
851
+ parseIntegrationsOauthClientsJson
852
+ } from "@opengeni/config";
849
853
  import { pinnedFetch } from "@opengeni/network";
850
854
  import {
851
855
  CapabilityCatalogItem,
@@ -2027,6 +2031,7 @@ async function buildCapabilityCatalog(input) {
2027
2031
  listEnabledMcpCapabilityServers(input.db, input.workspaceId)
2028
2032
  ]);
2029
2033
  const runnableCapabilityIds = new Set(runnableMcpServers.map((server) => server.capabilityId));
2034
+ const operatorOAuthClientConfigured = operatorOAuthClientResolver(input.settings);
2030
2035
  const catalogInstallations = capabilityInstallations.filter(
2031
2036
  (installation) => installation.kind === "mcp"
2032
2037
  );
@@ -2064,13 +2069,33 @@ async function buildCapabilityCatalog(input) {
2064
2069
  notes: "This installed connection is unavailable. Review its connection settings."
2065
2070
  }
2066
2071
  } : projected;
2067
- return applyCapabilityLifecycle(runtimeProjected);
2072
+ return applyCapabilityLifecycle(
2073
+ applyOperatorOAuthClientRequirement(runtimeProjected, operatorOAuthClientConfigured)
2074
+ );
2068
2075
  }).sort(compareCatalogItems);
2069
2076
  return {
2070
2077
  items,
2071
2078
  installations: catalogInstallations
2072
2079
  };
2073
2080
  }
2081
+ function operatorOAuthClientResolver(settings) {
2082
+ let configured = {};
2083
+ try {
2084
+ configured = parseIntegrationsOauthClientsJson(settings.integrationsOauthClientsJson);
2085
+ } catch {
2086
+ }
2087
+ return (issuer) => findIntegrationsOauthClient(configured, [issuer]) !== null;
2088
+ }
2089
+ function applyOperatorOAuthClientRequirement(item, configured) {
2090
+ if (item.kind !== "mcp") return item;
2091
+ const requirement = item.metadata.oauthClientRequirement;
2092
+ const issuer = requirement && typeof requirement === "object" && !Array.isArray(requirement) ? requirement.issuer : void 0;
2093
+ if (typeof issuer !== "string" || !issuer) return item;
2094
+ return {
2095
+ ...item,
2096
+ runtime: { ...item.runtime, operatorOAuthClient: { configured: configured(issuer) } }
2097
+ };
2098
+ }
2074
2099
  async function createCatalogItem(input) {
2075
2100
  const id = input.payload.id?.trim() || generatedCapabilityId(input.payload);
2076
2101
  if (id.startsWith("skill:")) {
@@ -6707,15 +6732,13 @@ import {
6707
6732
  import {
6708
6733
  listConnectionsMetadata as listConnectionsMetadata3,
6709
6734
  getDeploymentModelCatalog,
6710
- listWorkspaceProviderCustomModels,
6735
+ listWorkspaceProviderCustomModelsByKind,
6711
6736
  getWorkspaceProviderCustomModelForExecution,
6712
6737
  lockActiveWorkspaceProviderCustomModelForAdmission,
6713
6738
  getWorkspaceGatewayCustomModelForExecution,
6714
6739
  getWorkspaceOpenRouterCustomModelForExecution,
6715
6740
  getOrganizationModelProviderCustomModelForExecution,
6716
- listWorkspaceGatewayCustomModels,
6717
- listWorkspaceOpenRouterCustomModels,
6718
- listOrganizationModelProviderCustomModelsForWorkspace,
6741
+ getOrganizationModelProviderCatalogForWorkspace,
6719
6742
  lockActiveOrganizationModelProviderCustomModelForAdmission
6720
6743
  } from "@opengeni/db";
6721
6744
  function isWorkspaceGatewayCustomModelId(settings, modelId) {
@@ -6830,26 +6853,28 @@ async function resolveWorkspaceCatalogSettings(db, envSettings, input) {
6830
6853
  const retainedOrganizationOpenRouterUpstreamModelIds = retainedProductModelIds.flatMap(
6831
6854
  (productModelId) => productModelId?.startsWith(ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX) ? [productModelId.slice(ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX.length)] : []
6832
6855
  );
6856
+ const workspaceCustomModelKinds = [
6857
+ "vercel_gateway",
6858
+ "openrouter",
6859
+ ...supportsOrganizationProviderReads ? CLAUDE_CONNECTION_KINDS.filter(
6860
+ (kind) => kind !== "claude_subscription" || envSettings.claudeSubscriptionEnabled
6861
+ ) : []
6862
+ ];
6833
6863
  const [
6834
6864
  resolved,
6835
- activeGatewayCustomModels,
6836
- activeOpenRouterCustomModels,
6865
+ workspaceCustomModels,
6837
6866
  retainedGatewayCustomModels,
6838
6867
  retainedOpenRouterCustomModels,
6839
- organizationGatewayCustomModels,
6840
- organizationOpenRouterCustomModels,
6868
+ organizationProviders,
6841
6869
  retainedOrganizationGatewayCustomModels,
6842
6870
  retainedOrganizationOpenRouterCustomModels,
6843
6871
  directConnections
6844
6872
  ] = await Promise.all([
6845
6873
  resolveCatalogSettings(db, envSettings),
6846
- listWorkspaceGatewayCustomModels(db, {
6874
+ listWorkspaceProviderCustomModelsByKind(db, {
6847
6875
  accountId: input.accountId,
6848
- workspaceId: input.workspaceId
6849
- }),
6850
- listWorkspaceOpenRouterCustomModels(db, {
6851
- accountId: input.accountId,
6852
- workspaceId: input.workspaceId
6876
+ workspaceId: input.workspaceId,
6877
+ providerKinds: workspaceCustomModelKinds
6853
6878
  }),
6854
6879
  Promise.all(
6855
6880
  [...new Set(retainedGatewayUpstreamModelIds)].map(
@@ -6869,16 +6894,11 @@ async function resolveWorkspaceCatalogSettings(db, envSettings, input) {
6869
6894
  })
6870
6895
  )
6871
6896
  ),
6872
- supportsOrganizationProviderReads ? listOrganizationModelProviderCustomModelsForWorkspace(db, {
6873
- accountId: input.accountId,
6874
- workspaceId: input.workspaceId,
6875
- providerKind: "vercel_gateway"
6876
- }) : Promise.resolve([]),
6877
- supportsOrganizationProviderReads ? listOrganizationModelProviderCustomModelsForWorkspace(db, {
6897
+ supportsOrganizationProviderReads ? getOrganizationModelProviderCatalogForWorkspace(db, {
6878
6898
  accountId: input.accountId,
6879
6899
  workspaceId: input.workspaceId,
6880
- providerKind: "openrouter"
6881
- }) : Promise.resolve([]),
6900
+ providerKinds: ["vercel_gateway", "openrouter", ...CLAUDE_CONNECTION_KINDS]
6901
+ }) : null,
6882
6902
  supportsOrganizationProviderReads ? Promise.all(
6883
6903
  [...new Set(retainedOrganizationGatewayUpstreamModelIds)].map(
6884
6904
  async (upstreamModelId) => await getOrganizationModelProviderCustomModelForExecution(db, {
@@ -6913,29 +6933,25 @@ async function resolveWorkspaceCatalogSettings(db, envSettings, input) {
6913
6933
  return customModels;
6914
6934
  };
6915
6935
  const gatewayCustomModels = includeRetainedModels(
6916
- activeGatewayCustomModels,
6936
+ workspaceCustomModels.vercel_gateway,
6917
6937
  retainedGatewayCustomModels
6918
6938
  );
6919
6939
  const openRouterCustomModels = includeRetainedModels(
6920
- activeOpenRouterCustomModels,
6940
+ workspaceCustomModels.openrouter,
6921
6941
  retainedOpenRouterCustomModels
6922
6942
  );
6923
6943
  const organizationGatewayModels = includeRetainedModels(
6924
- organizationGatewayCustomModels,
6944
+ organizationProviders?.vercel_gateway.models ?? [],
6925
6945
  retainedOrganizationGatewayCustomModels
6926
6946
  );
6927
6947
  const organizationOpenRouterModels = includeRetainedModels(
6928
- organizationOpenRouterCustomModels,
6948
+ organizationProviders?.openrouter.models ?? [],
6929
6949
  retainedOrganizationOpenRouterCustomModels
6930
6950
  );
6931
6951
  const claudeConnections = {};
6932
- if (supportsOrganizationProviderReads)
6952
+ if (organizationProviders)
6933
6953
  for (const kind of CLAUDE_CONNECTION_KINDS) {
6934
- const models = await listOrganizationModelProviderCustomModelsForWorkspace(db, {
6935
- accountId: input.accountId,
6936
- workspaceId: input.workspaceId,
6937
- providerKind: kind
6938
- });
6954
+ const models = [...organizationProviders[kind].models];
6939
6955
  const prefix = claudeProviderId(kind) + "/";
6940
6956
  for (const productId of retainedProductModelIds)
6941
6957
  if (productId?.startsWith(prefix)) {
@@ -6953,11 +6969,7 @@ async function resolveWorkspaceCatalogSettings(db, envSettings, input) {
6953
6969
  if (supportsOrganizationProviderReads)
6954
6970
  for (const kind of CLAUDE_CONNECTION_KINDS) {
6955
6971
  if (kind === "claude_subscription" && !resolved.settings.claudeSubscriptionEnabled) continue;
6956
- const models = await listWorkspaceProviderCustomModels(db, {
6957
- accountId: input.accountId,
6958
- workspaceId: input.workspaceId,
6959
- providerKind: kind
6960
- });
6972
+ const models = [...workspaceCustomModels[kind]];
6961
6973
  const prefix = claudeProviderId(kind, "workspace") + "/";
6962
6974
  for (const productId of retainedProductModelIds) {
6963
6975
  if (!productId?.startsWith(prefix)) continue;
@@ -7267,15 +7279,11 @@ import {
7267
7279
  getWorkspace as getWorkspace2,
7268
7280
  getWorkspaceConnectionModelRestrictions,
7269
7281
  getWorkspaceModelPolicy,
7270
- getWorkspaceProviderApiKeyConnectionMetadata,
7271
- listWorkspaceProviderCustomModels as listWorkspaceProviderCustomModels2,
7272
- listOrganizationModelProviderCustomModelsForWorkspace as listOrganizationModelProviderCustomModelsForWorkspace2,
7273
- listWorkspaceGatewayCustomModels as listWorkspaceGatewayCustomModels2,
7274
- listWorkspaceOpenRouterCustomModels as listWorkspaceOpenRouterCustomModels2,
7275
- organizationModelProviderConnectionActiveForWorkspace,
7282
+ getOrganizationModelProviderCatalogForWorkspace as getOrganizationModelProviderCatalogForWorkspace2,
7283
+ listConnectionsMetadata as listConnectionsMetadata4,
7284
+ listWorkspaceProviderCustomModelsByKind as listWorkspaceProviderCustomModelsByKind2,
7276
7285
  workspaceCodexSubscriptionActive,
7277
- workspaceOpenRouterConnectionActive,
7278
- workspaceVercelAiGatewayConnectionActive,
7286
+ workspaceProviderApiKeyConnectionMetadataFromConnections,
7279
7287
  getBillingBalance,
7280
7288
  spendableCreditMicros,
7281
7289
  workspaceXaiSubscriptionActive,
@@ -7592,84 +7600,55 @@ async function loadWorkspaceClaudeSubscriptionReadiness(db, settings, context) {
7592
7600
  }
7593
7601
  async function loadWorkspaceModelSelectionInput(db, settings, context, options = {}) {
7594
7602
  const { accountId, workspaceId } = context;
7595
- const restrictionsRead = (async () => connectionRestrictionsAndXaiReadiness(db, settings, context))();
7596
- const claudePoolRead = (async () => loadWorkspaceClaudeSubscriptionReadiness(db, settings, context))();
7603
+ const providerKinds = [
7604
+ "vercel_gateway",
7605
+ "openrouter",
7606
+ ...CLAUDE_CONNECTION_KINDS2.filter(
7607
+ (kind) => kind !== "claude_subscription" || settings.claudeSubscriptionEnabled
7608
+ )
7609
+ ];
7597
7610
  const inputRead = (async () => Promise.all([
7598
- restrictionsRead,
7599
- claudePoolRead,
7611
+ connectionRestrictionsAndXaiReadiness(db, settings, context),
7612
+ loadWorkspaceClaudeSubscriptionReadiness(db, settings, context),
7600
7613
  getWorkspaceModelPolicy(db, workspaceId),
7601
7614
  workspaceCodexSubscriptionActive(db, settings, workspaceId),
7602
- options.observeAvailability === false ? Promise.resolve({}) : loadWorkspaceCodexModelAvailability(db, settings, workspaceId),
7603
- workspaceVercelAiGatewayConnectionActive(db, workspaceId),
7604
- listWorkspaceGatewayCustomModels2(db, { accountId, workspaceId }),
7605
- workspaceOpenRouterConnectionActive(db, workspaceId),
7606
- listWorkspaceOpenRouterCustomModels2(db, { accountId, workspaceId }),
7607
- organizationModelProviderConnectionActiveForWorkspace(db, {
7608
- accountId,
7609
- workspaceId,
7610
- providerKind: "vercel_gateway"
7611
- }),
7612
- organizationModelProviderConnectionActiveForWorkspace(db, {
7613
- accountId,
7614
- workspaceId,
7615
- providerKind: "openrouter"
7616
- }),
7617
- listOrganizationModelProviderCustomModelsForWorkspace2(db, {
7618
- accountId,
7619
- workspaceId,
7620
- providerKind: "vercel_gateway"
7621
- }),
7622
- listOrganizationModelProviderCustomModelsForWorkspace2(db, {
7615
+ options.observeAvailability === false ? Promise.resolve({}) : loadWorkspaceCodexModelAvailability(db, settings, workspaceId)
7616
+ ]))();
7617
+ if (typeof db.rollback === "function") await inputRead;
7618
+ const catalogRead = (async () => Promise.all([
7619
+ listConnectionsMetadata4(db, workspaceId, null),
7620
+ listWorkspaceProviderCustomModelsByKind2(db, { accountId, workspaceId, providerKinds }),
7621
+ getOrganizationModelProviderCatalogForWorkspace2(db, {
7623
7622
  accountId,
7624
7623
  workspaceId,
7625
- providerKind: "openrouter"
7624
+ providerKinds
7626
7625
  })
7627
7626
  ]))();
7628
- const claudeConnections = {};
7629
- const workspaceClaudeConnections = {};
7630
- if (typeof db.rollback === "function") await inputRead;
7631
- const catalogRead = (async () => Promise.all(
7632
- CLAUDE_CONNECTION_KINDS2.map(async (kind) => {
7633
- if (kind === "claude_subscription" && !settings.claudeSubscriptionEnabled) return;
7634
- const [active, models, metadata, workspaceModels] = await Promise.all([
7635
- kind === "claude_subscription" ? claudePoolRead.then((pool) => pool.organization) : organizationModelProviderConnectionActiveForWorkspace(db, {
7636
- accountId,
7637
- workspaceId,
7638
- providerKind: kind
7639
- }),
7640
- listOrganizationModelProviderCustomModelsForWorkspace2(db, {
7641
- accountId,
7642
- workspaceId,
7643
- providerKind: kind
7644
- }),
7645
- kind === "claude_subscription" ? Promise.resolve(null) : getWorkspaceProviderApiKeyConnectionMetadata(db, workspaceId, kind),
7646
- listWorkspaceProviderCustomModels2(db, { accountId, workspaceId, providerKind: kind })
7647
- ]);
7648
- claudeConnections[kind] = { active, models };
7649
- workspaceClaudeConnections[kind] = {
7650
- active: kind === "claude_subscription" ? (await claudePoolRead).workspace : metadata !== null,
7651
- models: workspaceModels
7652
- };
7653
- })
7654
- ))();
7655
7627
  const [inputResult, catalogResult] = await Promise.allSettled([inputRead, catalogRead]);
7656
7628
  if (inputResult.status === "rejected") throw inputResult.reason;
7657
7629
  if (catalogResult.status === "rejected") throw catalogResult.reason;
7658
7630
  const [
7659
7631
  { restrictions: connectionModelRestrictions, xaiSubscriptionActive },
7660
- _claudePool,
7632
+ claudePool,
7661
7633
  policy,
7662
7634
  codexSubscriptionActive,
7663
- observations,
7664
- workspaceGatewayConnectionActive,
7665
- workspaceGatewayCustomModels,
7666
- openRouterConnectionActive,
7667
- workspaceOpenRouterCustomModels,
7668
- organizationGatewayConnectionActive,
7669
- organizationOpenRouterConnectionActive,
7670
- organizationGatewayCustomModels,
7671
- organizationOpenRouterCustomModels
7635
+ observations
7672
7636
  ] = inputResult.value;
7637
+ const [workspaceConnections, workspaceCustomModels, organizationProviders] = catalogResult.value;
7638
+ const workspaceConnectionActive = (kind) => workspaceProviderApiKeyConnectionMetadataFromConnections(workspaceConnections, kind) !== null;
7639
+ const claudeConnections = {};
7640
+ const workspaceClaudeConnections = {};
7641
+ for (const kind of CLAUDE_CONNECTION_KINDS2) {
7642
+ if (kind === "claude_subscription" && !settings.claudeSubscriptionEnabled) continue;
7643
+ claudeConnections[kind] = {
7644
+ active: kind === "claude_subscription" ? claudePool.organization : organizationProviders[kind].active,
7645
+ models: organizationProviders[kind].models
7646
+ };
7647
+ workspaceClaudeConnections[kind] = {
7648
+ active: kind === "claude_subscription" ? claudePool.workspace : workspaceConnectionActive(kind),
7649
+ models: workspaceCustomModels[kind]
7650
+ };
7651
+ }
7673
7652
  return {
7674
7653
  claudeConnections,
7675
7654
  workspaceClaudeConnections,
@@ -7679,14 +7658,14 @@ async function loadWorkspaceModelSelectionInput(db, settings, context, options =
7679
7658
  codexSubscriptionActive,
7680
7659
  observations,
7681
7660
  xaiSubscriptionActive,
7682
- workspaceGatewayConnectionActive,
7683
- workspaceGatewayCustomModels,
7684
- workspaceOpenRouterConnectionActive: openRouterConnectionActive,
7685
- workspaceOpenRouterCustomModels,
7686
- organizationGatewayConnectionActive,
7687
- organizationOpenRouterConnectionActive,
7688
- organizationGatewayCustomModels,
7689
- organizationOpenRouterCustomModels
7661
+ workspaceGatewayConnectionActive: workspaceConnectionActive("vercel_gateway"),
7662
+ workspaceGatewayCustomModels: workspaceCustomModels.vercel_gateway,
7663
+ workspaceOpenRouterConnectionActive: workspaceConnectionActive("openrouter"),
7664
+ workspaceOpenRouterCustomModels: workspaceCustomModels.openrouter,
7665
+ organizationGatewayConnectionActive: organizationProviders.vercel_gateway.active,
7666
+ organizationOpenRouterConnectionActive: organizationProviders.openrouter.active,
7667
+ organizationGatewayCustomModels: organizationProviders.vercel_gateway.models,
7668
+ organizationOpenRouterCustomModels: organizationProviders.openrouter.models
7690
7669
  };
7691
7670
  }
7692
7671
  async function resolveCallerWorkspaceModelSelections(db, settings, context, options = {}) {
@@ -7969,12 +7948,12 @@ function resolveTurnToolPolicy(input) {
7969
7948
  defaultMcpServerIds: scheduledIds ?? input.defaultMcpServerIds ?? []
7970
7949
  });
7971
7950
  }
7972
- async function workspaceSessionToolPolicyContext(db, workspaceId, settings, subjectId) {
7951
+ async function workspaceSessionToolPolicyContext(db, workspaceId, settings, subjectId, workspaceRead) {
7973
7952
  const [runtimeSettings, workspace] = await Promise.all([
7974
7953
  settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings, {
7975
7954
  ...subjectId ? { subjectId } : {}
7976
7955
  }),
7977
- requireWorkspace3(db, workspaceId)
7956
+ workspaceRead ?? requireWorkspace3(db, workspaceId)
7978
7957
  ]);
7979
7958
  return {
7980
7959
  workspaceServerIds: sortedIds(runtimeSettings.mcpServers.map((server) => server.id)),
@@ -8010,7 +7989,7 @@ function workspaceSessionToolPolicyDefaultServerIdsFor(runtimeMcpServers, worksp
8010
7989
  ...configured.inheritConnectedMcpServers ? availableDefaults.filter((id) => !["opengeni", "files", "docs"].includes(id)) : []
8011
7990
  ]);
8012
7991
  }
8013
- async function workspaceSessionEffectiveToolsContext(deps, workspaceId, subjectId, sessions) {
7992
+ async function workspaceSessionEffectiveToolsContext(deps, workspaceId, subjectId, sessions, workspaceRead) {
8014
7993
  const configured = sessions.filter((session) => session.agent != null);
8015
7994
  const baseline = {
8016
7995
  settings: deps.settings,
@@ -8020,7 +7999,7 @@ async function workspaceSessionEffectiveToolsContext(deps, workspaceId, subjectI
8020
7999
  };
8021
8000
  if (configured.length === 0) return baseline;
8022
8001
  const [workspace, catalog, descriptors, sandboxes, routerHistory] = await Promise.all([
8023
- requireWorkspace3(deps.db, workspaceId),
8002
+ workspaceRead ?? requireWorkspace3(deps.db, workspaceId),
8024
8003
  resolveWorkspaceCatalogSettings(deps.db, deps.settings, {
8025
8004
  accountId: configured[0].accountId,
8026
8005
  workspaceId,
@@ -9952,7 +9931,12 @@ async function postUserMessageTurn(input) {
9952
9931
  }
9953
9932
  } : {},
9954
9933
  controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs()
9955
- })
9934
+ }),
9935
+ void 0,
9936
+ "shared",
9937
+ // Linked-actor capture reauthorizes under the organization-membership
9938
+ // lock; take it before the tenancy fence and the canonical prefix.
9939
+ Boolean(input.captureTurnAuthority)
9956
9940
  )
9957
9941
  );
9958
9942
  } catch (error) {
@@ -10342,24 +10326,29 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
10342
10326
  let retainedKeyedShellModel = null;
10343
10327
  let retainedKeyedShellReasoningEffort = null;
10344
10328
  if (payload.idempotencyKey && (effectiveVisibility !== "user_private" || replayManagedHumanSubjectId !== null)) {
10329
+ const createIdempotencyKey = payload.idempotencyKey;
10345
10330
  try {
10346
- const initializedReplay = await getInitializedSessionCreateReplay(db, {
10347
- bundledSkillIds,
10348
- accountId: grant.accountId,
10349
- workspaceId,
10350
- subjectId: replayManagedHumanSubjectId ?? grant.subjectId,
10351
- ...replayManagedHumanSubjectId ? { activeManagedHumanSubjectId: replayManagedHumanSubjectId } : {},
10352
- createIdempotencyKey: payload.idempotencyKey,
10353
- selectedInstalledSkillIds: payload.installedSkillIds ?? [],
10354
- initialAgentLearning: payload.agentLearning,
10355
- ...sessionScope,
10356
- ...payload.requestedSessionId ? { requestedSessionId: payload.requestedSessionId } : {},
10357
- visibility: effectiveVisibility,
10358
- variableSetIds: payload.variableSetIds ?? [],
10359
- initialPersonalResourceAttachmentIntent: payload.personalResourceAttachment ?? null,
10360
- deferInitialTurn: payload.startMode === "realtime",
10361
- agentConfig
10362
- });
10331
+ const initializedReplay = await measureSessionStartPhase(
10332
+ unresolvedDeps.observability,
10333
+ "idempotent_replay",
10334
+ () => getInitializedSessionCreateReplay(db, {
10335
+ bundledSkillIds,
10336
+ accountId: grant.accountId,
10337
+ workspaceId,
10338
+ subjectId: replayManagedHumanSubjectId ?? grant.subjectId,
10339
+ ...replayManagedHumanSubjectId ? { activeManagedHumanSubjectId: replayManagedHumanSubjectId } : {},
10340
+ createIdempotencyKey,
10341
+ selectedInstalledSkillIds: payload.installedSkillIds ?? [],
10342
+ initialAgentLearning: payload.agentLearning,
10343
+ ...sessionScope,
10344
+ ...payload.requestedSessionId ? { requestedSessionId: payload.requestedSessionId } : {},
10345
+ visibility: effectiveVisibility,
10346
+ variableSetIds: payload.variableSetIds ?? [],
10347
+ initialPersonalResourceAttachmentIntent: payload.personalResourceAttachment ?? null,
10348
+ deferInitialTurn: payload.startMode === "realtime",
10349
+ agentConfig
10350
+ })
10351
+ );
10363
10352
  if (initializedReplay) {
10364
10353
  if (initializedReplay.outcome === "denied") {
10365
10354
  throw new SessionSpawnDeniedError(SessionSpawnDenial.parse(initializedReplay.denial));
@@ -10668,40 +10657,50 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
10668
10657
  }
10669
10658
  const tools = withFirstPartyTools(selectedTools, runtimeSettings);
10670
10659
  const captureLinkedAuthority = prepareExternalLinkTurnAdmission(authorization);
10671
- await validateGitHubRepositorySelection(db, workspaceId, resources);
10672
- if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
10673
- throw new HTTPException17(503, {
10674
- message: "object storage is not configured"
10675
- });
10676
- }
10677
- const attachmentOwnerContext = authorization ? await fileOwnerContextForAccess({ db }, authorization, "sessions:create") : grant.principalKind === "agent_attempt" ? await fileOwnerContextForAgent({ db }, grant, "sessions:create") : void 0;
10678
- const attachmentOwner = attachmentOwnerContext?.privateFileOwnerSubjectId === grant.subjectId ? grant.subjectId : null;
10679
- await validateFileResources(
10680
- db,
10681
- grant.accountId,
10682
- workspaceId,
10683
- personalResourceSubjectId ?? grant.subjectId,
10684
- resources,
10685
- attachmentOwnerContext
10686
- );
10687
- const variableSets = [];
10688
- for (const variableSetId of payload.variableSetIds ?? []) {
10689
- variableSets.push(
10690
- await validateVariableSetAttachment({ settings, db }, grant, workspaceId, variableSetId)
10691
- );
10692
- }
10693
- const requestedRigId = payload.rigId === void 0 ? await getWorkspaceDefaultRigId(db, workspaceId) : payload.rigId;
10694
- let frozenRigId = null;
10695
- let frozenRigVersionId = null;
10696
- if (requestedRigId) {
10697
- const rig = await getRig2(db, grant, requestedRigId);
10698
- if (!rig || !rig.activeVersion) {
10699
- if (payload.rigId) {
10700
- throw new HTTPException17(422, {
10701
- message: rig ? `sandbox environment ${payload.rigId} has no active version to bind` : `unknown rigId: ${payload.rigId}`
10660
+ const { attachmentOwnerContext, variableSets } = await measureSessionStartPhase(
10661
+ unresolvedDeps.observability,
10662
+ "resource_validation",
10663
+ async () => {
10664
+ await validateGitHubRepositorySelection(db, workspaceId, resources);
10665
+ if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
10666
+ throw new HTTPException17(503, {
10667
+ message: "object storage is not configured"
10702
10668
  });
10703
10669
  }
10704
- } else {
10670
+ const ownerContext = authorization ? await fileOwnerContextForAccess({ db }, authorization, "sessions:create") : grant.principalKind === "agent_attempt" ? await fileOwnerContextForAgent({ db }, grant, "sessions:create") : void 0;
10671
+ await validateFileResources(
10672
+ db,
10673
+ grant.accountId,
10674
+ workspaceId,
10675
+ personalResourceSubjectId ?? grant.subjectId,
10676
+ resources,
10677
+ ownerContext
10678
+ );
10679
+ const validatedVariableSets = [];
10680
+ for (const variableSetId of payload.variableSetIds ?? []) {
10681
+ validatedVariableSets.push(
10682
+ await validateVariableSetAttachment({ settings, db }, grant, workspaceId, variableSetId)
10683
+ );
10684
+ }
10685
+ return { attachmentOwnerContext: ownerContext, variableSets: validatedVariableSets };
10686
+ }
10687
+ );
10688
+ const attachmentOwner = attachmentOwnerContext?.privateFileOwnerSubjectId === grant.subjectId ? grant.subjectId : null;
10689
+ const { frozenRigId, frozenRigVersionId } = await measureSessionStartPhase(
10690
+ unresolvedDeps.observability,
10691
+ "rig_binding",
10692
+ async () => {
10693
+ const requestedRigId = payload.rigId === void 0 ? await getWorkspaceDefaultRigId(db, workspaceId) : payload.rigId;
10694
+ if (!requestedRigId) return { frozenRigId: null, frozenRigVersionId: null };
10695
+ const rig = await getRig2(db, grant, requestedRigId);
10696
+ if (!rig || !rig.activeVersion) {
10697
+ if (payload.rigId) {
10698
+ throw new HTTPException17(422, {
10699
+ message: rig ? `sandbox environment ${payload.rigId} has no active version to bind` : `unknown rigId: ${payload.rigId}`
10700
+ });
10701
+ }
10702
+ return { frozenRigId: null, frozenRigVersionId: null };
10703
+ }
10705
10704
  for (const defaultVariableSetId of new Set(rig.activeVersion.defaultVariableSetIds)) {
10706
10705
  await validateVariableSetAttachment(
10707
10706
  { settings, db },
@@ -10710,10 +10709,12 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
10710
10709
  defaultVariableSetId
10711
10710
  );
10712
10711
  }
10713
- frozenRigId = rig.id;
10714
- frozenRigVersionId = rig.activeVersion.id;
10712
+ return {
10713
+ frozenRigId: rig.id,
10714
+ frozenRigVersionId: rig.activeVersion.id
10715
+ };
10715
10716
  }
10716
- }
10717
+ );
10717
10718
  let channelId = null;
10718
10719
  if (payload.channelId) {
10719
10720
  const channel = await getChannel(db, workspaceId, payload.channelId);
@@ -10728,19 +10729,21 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
10728
10729
  if (model === null || model === void 0) {
10729
10730
  throw new Error("effective session model unexpectedly resolved to null");
10730
10731
  }
10731
- await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model);
10732
- if (retainedKeyedShellModel === null && !parentSession) {
10733
- const selections = await resolveCallerWorkspaceModelSelections(db, settings, {
10734
- accountId: grant.accountId,
10735
- workspaceId,
10736
- subjectId: personalResourceSubjectId ?? grant.subjectId,
10737
- ...xaiProviderAccountAuthoritySnapshot ? { xaiAuthoritySnapshot: xaiProviderAccountAuthoritySnapshot } : {},
10738
- ...claudeProviderAccountAuthoritySnapshot ? { claudeAuthoritySnapshot: claudeProviderAccountAuthoritySnapshot } : {}
10739
- });
10740
- if (!admissibleWorkspaceModel(selections, model)) {
10741
- throw new HTTPException17(422, { message: `model is not selectable: ${model}` });
10732
+ await measureSessionStartPhase(unresolvedDeps.observability, "model_admission", async () => {
10733
+ await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model);
10734
+ if (retainedKeyedShellModel === null && !parentSession) {
10735
+ const selections = await resolveCallerWorkspaceModelSelections(db, settings, {
10736
+ accountId: grant.accountId,
10737
+ workspaceId,
10738
+ subjectId: personalResourceSubjectId ?? grant.subjectId,
10739
+ ...xaiProviderAccountAuthoritySnapshot ? { xaiAuthoritySnapshot: xaiProviderAccountAuthoritySnapshot } : {},
10740
+ ...claudeProviderAccountAuthoritySnapshot ? { claudeAuthoritySnapshot: claudeProviderAccountAuthoritySnapshot } : {}
10741
+ });
10742
+ if (!admissibleWorkspaceModel(selections, model)) {
10743
+ throw new HTTPException17(422, { message: `model is not selectable: ${model}` });
10744
+ }
10742
10745
  }
10743
- }
10746
+ });
10744
10747
  const inheritedReasoningEffort = parentCallingTurn?.reasoningEffort ?? parentSession?.reasoningEffort ?? resolvedDefault?.reasoningEffort ?? settings.openaiReasoningEffort;
10745
10748
  const reasoningEffort = payload.reasoningEffort ?? inheritedReasoningEffort;
10746
10749
  const latencyMode = payload.latencyMode ?? "standard";
@@ -10859,18 +10862,22 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
10859
10862
  workspace.settings
10860
10863
  )
10861
10864
  });
10862
- const { personalConnectionDelegations, mcpAccountBindings } = await freezeConnectionAccounts({
10863
- db,
10864
- accountId: grant.accountId,
10865
- workspaceId,
10866
- settings: runtimeSettings,
10867
- tools: connectionAccountTools,
10868
- resources,
10869
- source: connectionDelegationSource,
10870
- authoritySelections: payload.connectionAccounts,
10871
- googleDrivePublicationEnabled,
10872
- atlassianEnabled
10873
- });
10865
+ const { personalConnectionDelegations, mcpAccountBindings } = await measureSessionStartPhase(
10866
+ unresolvedDeps.observability,
10867
+ "connection_freeze",
10868
+ () => freezeConnectionAccounts({
10869
+ db,
10870
+ accountId: grant.accountId,
10871
+ workspaceId,
10872
+ settings: runtimeSettings,
10873
+ tools: connectionAccountTools,
10874
+ resources,
10875
+ source: connectionDelegationSource,
10876
+ authoritySelections: payload.connectionAccounts,
10877
+ googleDrivePublicationEnabled,
10878
+ atlassianEnabled
10879
+ })
10880
+ );
10874
10881
  if (effectiveGoal) {
10875
10882
  const missingGoalTools = ["goal_update", "goal_progress", "goal_complete", "goal_pause"].filter(
10876
10883
  (name) => !firstPartyMcpTools.includes(name)
@@ -12658,21 +12665,22 @@ async function searchKnowledgeEntries(db, context, input, embedder, settings) {
12658
12665
  // src/domain/voice-input-billing.ts
12659
12666
  import {
12660
12667
  calculateVoiceInputCost,
12661
- configuredStaticUsageLimits as configuredStaticUsageLimits2,
12662
- voiceInputCreditBillingActive,
12668
+ configuredStaticUsageLimits as configuredStaticUsageLimits3,
12669
+ voiceInputCreditBillingActive as voiceInputCreditBillingActive2,
12663
12670
  voiceInputPricingHasTokenRates
12664
12671
  } from "@opengeni/config";
12665
12672
  import {
12666
- applyCreditDebitAfterUse as applyCreditDebitAfterUse2,
12667
- checkWorkspaceAllowance as checkWorkspaceAllowance3,
12668
- creditDebitAttributionMetadata,
12669
- getSpendableCreditBalance as getSpendableCreditBalance3,
12673
+ applyCreditDebitAfterUse as applyCreditDebitAfterUse3,
12674
+ checkWorkspaceAllowance as checkWorkspaceAllowance4,
12675
+ creditDebitAttributionMetadata as creditDebitAttributionMetadata2,
12676
+ getSpendableCreditBalance as getSpendableCreditBalance4,
12670
12677
  listUnsettledVoiceTranscriptionCharges,
12671
- recordUsageEvent as recordUsageEvent3,
12672
- sumUsageQuantity as sumUsageQuantity3,
12678
+ recordUsageEvent as recordUsageEvent4,
12679
+ sumUsageQuantity as sumUsageQuantity4,
12673
12680
  VOICE_TRANSCRIPTION_DEBIT_TYPE,
12674
12681
  VOICE_TRANSCRIPTION_SOURCE_TYPE,
12675
- voiceTranscriptionSettlementKeys
12682
+ voiceTranscriptionSettlementKeys,
12683
+ VOICE_CREDIT_USAGE as VOICE_CREDIT_USAGE2
12676
12684
  } from "@opengeni/db";
12677
12685
 
12678
12686
  // src/transcription.ts
@@ -12764,73 +12772,347 @@ function filenameForMimeType(mimeType) {
12764
12772
  }
12765
12773
  }
12766
12774
 
12767
- // src/domain/voice-input-billing.ts
12768
- var VOICE_INPUT_DEBIT_TYPE = VOICE_TRANSCRIPTION_DEBIT_TYPE;
12769
- var VOICE_INPUT_SOURCE_TYPE = VOICE_TRANSCRIPTION_SOURCE_TYPE;
12770
- function voiceInputBillableUsage(input) {
12771
- if (input.usage?.kind === "tokens" && voiceInputPricingHasTokenRates(input.pricing)) {
12772
- return { usage: input.usage, basis: "provider_tokens" };
12775
+ // src/domain/realtime-voice-billing.ts
12776
+ import {
12777
+ AI_GATEWAY_REALTIME_MODELS,
12778
+ AZURE_LIVE_REALTIME_MODEL_ID,
12779
+ configuredStaticUsageLimits as configuredStaticUsageLimits2,
12780
+ parseRealtimeVoicePricingJson,
12781
+ parseRealtimeVoicePricingTableJson,
12782
+ realtimeVoiceMinuteCreditMicros,
12783
+ realtimeVoiceStartedMinutes,
12784
+ voiceInputCreditBillingActive
12785
+ } from "@opengeni/config";
12786
+ import {
12787
+ applyCreditDebitAfterUse as applyCreditDebitAfterUse2,
12788
+ checkWorkspaceAllowance as checkWorkspaceAllowance3,
12789
+ creditDebitAttributionMetadata,
12790
+ existingUsageEventIdempotencyKeys,
12791
+ getSpendableCreditBalance as getSpendableCreditBalance3,
12792
+ loadSessionRealtimeBillingFacts,
12793
+ recordUsageEvent as recordUsageEvent3,
12794
+ sumUsageQuantity as sumUsageQuantity3,
12795
+ VOICE_CREDIT_USAGE,
12796
+ withRlsContext
12797
+ } from "@opengeni/db";
12798
+ var REALTIME_VOICE_DEBIT_TYPE = "voice_realtime_debit";
12799
+ var REALTIME_VOICE_SOURCE_TYPE = "voice_realtime";
12800
+ var reportedPricingErrors = /* @__PURE__ */ new Set();
12801
+ function tolerantPricing(parse, fallback) {
12802
+ try {
12803
+ return parse();
12804
+ } catch (error) {
12805
+ const message = error instanceof Error ? error.message : String(error);
12806
+ if (!reportedPricingErrors.has(message)) {
12807
+ reportedPricingErrors.add(message);
12808
+ console.error(`Live voice pricing ignored: ${message}`);
12809
+ }
12810
+ return fallback;
12773
12811
  }
12774
- if (input.usage?.kind === "duration") {
12775
- return { usage: input.usage, basis: "provider_duration" };
12812
+ }
12813
+ function deploymentRealtimeVoice(settings, model) {
12814
+ const azureConfigured = Boolean(settings.azureLiveEndpoint && settings.azureLiveApiKey?.trim());
12815
+ if (model === AZURE_LIVE_REALTIME_MODEL_ID) {
12816
+ return {
12817
+ provider: "azure-live",
12818
+ configured: azureConfigured,
12819
+ pricing: tolerantPricing(
12820
+ () => parseRealtimeVoicePricingJson(
12821
+ settings.azureLivePricingJson,
12822
+ "OPENGENI_AZURE_LIVE_PRICING_JSON"
12823
+ ),
12824
+ null
12825
+ )
12826
+ };
12776
12827
  }
12777
- if (input.trustedDurationSeconds !== void 0 && Number.isFinite(input.trustedDurationSeconds) && input.trustedDurationSeconds >= 0) {
12828
+ for (const gateway of Object.values(AI_GATEWAY_REALTIME_MODELS)) {
12829
+ if (gateway.managedModelId !== model) continue;
12778
12830
  return {
12779
- usage: { kind: "duration", seconds: input.trustedDurationSeconds },
12780
- basis: "server_duration"
12831
+ provider: "ai-gateway",
12832
+ configured: Boolean(settings.vercelAiGatewayApiKey),
12833
+ pricing: tolerantPricing(
12834
+ () => parseRealtimeVoicePricingTableJson(
12835
+ settings.aiGatewayRealtimePricingJson,
12836
+ "OPENGENI_AI_GATEWAY_REALTIME_PRICING_JSON"
12837
+ ),
12838
+ {}
12839
+ )[gateway.upstreamModelId] ?? null
12781
12840
  };
12782
12841
  }
12783
- throw new TranscriptionServiceError({
12784
- code: "provider",
12785
- message: "Transcription usage was not reported."
12786
- });
12842
+ return null;
12787
12843
  }
12788
- function initiatingHuman(attribution) {
12789
- return attribution.kind === "human" || attribution.kind === "turn" ? attribution.initiatingHumanSubjectId : null;
12844
+ var RealtimeVoiceUnavailableError = class extends Error {
12845
+ constructor(code, message) {
12846
+ super(message);
12847
+ this.code = code;
12848
+ }
12849
+ name = "RealtimeVoiceUnavailableError";
12850
+ };
12851
+ function realtimeVoiceOfferProblem(settings, voice) {
12852
+ if (!voice.configured) {
12853
+ return new RealtimeVoiceUnavailableError(
12854
+ "not_configured",
12855
+ "Opengeni live voice is not configured on this deployment."
12856
+ );
12857
+ }
12858
+ if (voiceInputCreditBillingActive(settings) && !voice.pricing) {
12859
+ return new RealtimeVoiceUnavailableError(
12860
+ "pricing_unconfigured",
12861
+ "Opengeni live voice is not available on this deployment yet."
12862
+ );
12863
+ }
12864
+ return null;
12790
12865
  }
12791
- function startOfUtcMonth2() {
12792
- const now = /* @__PURE__ */ new Date();
12866
+ var REALTIME_VOICE_INSUFFICIENT_CREDITS_MESSAGE = "Live voice needs Opengeni credits. Add credits to continue.";
12867
+ function voiceCreditStanding(balance) {
12868
+ if (balance.balanceMicros > 0) return "spendable";
12869
+ return (balance.promotionalCredits ?? []).some((grant) => grant.remainingMicros > 0) ? "promotional_only" : "none";
12870
+ }
12871
+ function voiceInsufficientCreditsMessage(feature, standing) {
12872
+ return standing === "promotional_only" ? `Promotional credits don't cover ${feature.toLowerCase()}. Add credits to use it.` : `${feature} needs Opengeni credits. Add credits to continue.`;
12873
+ }
12874
+ function startOfUtcMonth2(now) {
12793
12875
  return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
12794
12876
  }
12795
- function createVoiceInputBilling(deps) {
12796
- const reconcileInterval = deps.reconcileIntervalMilliseconds ?? 5 * 6e4;
12797
- const lastReconciled = /* @__PURE__ */ new Map();
12798
- const reconcileDue = (key) => {
12799
- const now = Date.now();
12800
- const last = lastReconciled.get(key);
12801
- if (last !== void 0 && now - last < reconcileInterval) return false;
12802
- if (lastReconciled.size >= 1e4) lastReconciled.clear();
12803
- lastReconciled.set(key, now);
12804
- return true;
12805
- };
12806
- return {
12807
- async admit({ accountId, workspaceId, attribution }) {
12808
- if (!voiceInputCreditBillingActive(deps.settings)) return;
12809
- if (attribution.kind === "unknown") {
12810
- throw new TranscriptionServiceError({
12811
- code: "policy_blocked",
12812
- status: 403,
12813
- message: "Voice input payer could not be verified."
12814
- });
12815
- }
12816
- if (reconcileDue(`${accountId}:${workspaceId}`)) {
12817
- await reconcileUnsettledVoiceInputCharges(deps.db, { accountId, workspaceId }).catch(
12818
- (error) => {
12819
- deps.onReconcileError?.(error, { accountId, workspaceId });
12820
- }
12821
- );
12822
- }
12823
- const balance = await getSpendableCreditBalance3(deps.db, accountId);
12824
- if (balance.balanceMicros <= 0) {
12825
- throw new TranscriptionBillingRefusedError({
12826
- code: "insufficient_credits",
12827
- message: "Voice input needs Opengeni credits. Add credits to continue."
12828
- });
12829
- }
12830
- const refusal = await checkWorkspaceAllowance3(deps.db, {
12831
- accountId,
12832
- workspaceId,
12833
- subjectId: initiatingHuman(attribution)
12877
+ function initiatingHuman(attribution) {
12878
+ return attribution.kind === "human" || attribution.kind === "turn" ? attribution.initiatingHumanSubjectId : null;
12879
+ }
12880
+ function createRealtimeVoiceBilling(deps) {
12881
+ const billingActive = () => voiceInputCreditBillingActive(deps.settings);
12882
+ async function refusal(input) {
12883
+ const balance = await getSpendableCreditBalance3(deps.db, input.accountId, VOICE_CREDIT_USAGE);
12884
+ if (balance.balanceMicros <= 0) {
12885
+ return new TranscriptionBillingRefusedError({
12886
+ code: "insufficient_credits",
12887
+ message: voiceInsufficientCreditsMessage("Live voice", voiceCreditStanding(balance))
12888
+ });
12889
+ }
12890
+ const allowance = await checkWorkspaceAllowance3(deps.db, {
12891
+ accountId: input.accountId,
12892
+ workspaceId: input.workspaceId,
12893
+ subjectId: initiatingHuman(input.attribution)
12894
+ });
12895
+ if (allowance) {
12896
+ return new TranscriptionBillingRefusedError({
12897
+ code: allowance.code,
12898
+ message: allowance.message,
12899
+ details: {
12900
+ scope: allowance.scope,
12901
+ resetsAt: allowance.resetsAt,
12902
+ ...allowance.subjectId ? { subjectId: allowance.subjectId } : {}
12903
+ }
12904
+ });
12905
+ }
12906
+ if (deps.settings.usageLimitsMode === "static" || deps.settings.usageLimitsMode === "managed") {
12907
+ const cap = configuredStaticUsageLimits2(deps.settings).maxMonthlyCostMicrosPerAccount;
12908
+ if (cap) {
12909
+ const used = await sumUsageQuantity3(deps.db, {
12910
+ accountId: input.accountId,
12911
+ eventType: "model.cost",
12912
+ since: startOfUtcMonth2(input.now)
12913
+ });
12914
+ if (used >= cap) {
12915
+ return new TranscriptionBillingRefusedError({
12916
+ code: "monthly_model_cost_limit",
12917
+ message: "The monthly model cost limit has been reached."
12918
+ });
12919
+ }
12920
+ }
12921
+ }
12922
+ return null;
12923
+ }
12924
+ return {
12925
+ /**
12926
+ * Throws RealtimeVoiceUnavailableError when the deployment cannot offer
12927
+ * the model and TranscriptionBillingRefusedError for a credit refusal.
12928
+ * Workspace-funded models are always admitted here.
12929
+ */
12930
+ async admit(input) {
12931
+ const voice = deploymentRealtimeVoice(deps.settings, input.model);
12932
+ if (!voice) return;
12933
+ const problem = realtimeVoiceOfferProblem(deps.settings, voice);
12934
+ if (problem) throw problem;
12935
+ if (!billingActive()) return;
12936
+ const refused = await refusal({ ...input, now: input.now ?? /* @__PURE__ */ new Date() });
12937
+ if (refused) throw refused;
12938
+ },
12939
+ /** Spendable credits are positive (always true when credits are not enforced). */
12940
+ async hasSpendableCredits(accountId) {
12941
+ return await this.creditStanding(accountId) === "spendable";
12942
+ },
12943
+ /** Credit standing for live voice ("spendable" when credits are not enforced). */
12944
+ async creditStanding(accountId) {
12945
+ if (!billingActive()) return "spendable";
12946
+ return voiceCreditStanding(
12947
+ await getSpendableCreditBalance3(deps.db, accountId, VOICE_CREDIT_USAGE)
12948
+ );
12949
+ },
12950
+ /**
12951
+ * Settle every started minute observed so far for one live-voice mode and
12952
+ * report whether the call must stop. Safe to call repeatedly and
12953
+ * concurrently: receipts and debits are idempotent per (connection, minute).
12954
+ */
12955
+ async settle(input) {
12956
+ const now = input.now ?? /* @__PURE__ */ new Date();
12957
+ if (!billingActive()) return { debitedMicros: 0, stop: null };
12958
+ const facts = await loadSessionRealtimeBillingFacts(deps.db, { ...input, now });
12959
+ if (!facts) return { debitedMicros: 0, stop: null };
12960
+ const voice = deploymentRealtimeVoice(deps.settings, facts.model);
12961
+ if (!voice) return { debitedMicros: 0, stop: null };
12962
+ if (!voice.pricing) {
12963
+ return {
12964
+ debitedMicros: 0,
12965
+ stop: new RealtimeVoiceUnavailableError(
12966
+ "pricing_unconfigured",
12967
+ "Opengeni live voice is no longer available on this deployment."
12968
+ )
12969
+ };
12970
+ }
12971
+ const attribution = input.callerSubjectId === facts.ownerSubjectId ? input.attribution : { kind: "unknown" };
12972
+ const cost = realtimeVoiceMinuteCreditMicros(voice.pricing);
12973
+ const minutes = facts.connections.flatMap(
12974
+ (connection) => Array.from(
12975
+ { length: realtimeVoiceStartedMinutes(connection.startedAt, connection.observedUntil) },
12976
+ (_, index) => ({
12977
+ connectionId: connection.id,
12978
+ minute: index + 1,
12979
+ startsAt: new Date(connection.startedAt.getTime() + index * 6e4),
12980
+ key: `voice.realtime_cost:${connection.id}:${index + 1}`
12981
+ })
12982
+ )
12983
+ );
12984
+ const recorded = await existingUsageEventIdempotencyKeys(deps.db, {
12985
+ accountId: facts.accountId,
12986
+ workspaceId: facts.workspaceId,
12987
+ keys: minutes.map((minute) => minute.key)
12988
+ });
12989
+ let debitedMicros = 0;
12990
+ for (const minute of minutes) {
12991
+ if (recorded.has(minute.key)) continue;
12992
+ debitedMicros += await withRlsContext(
12993
+ deps.db,
12994
+ { accountId: facts.accountId, workspaceId: facts.workspaceId },
12995
+ async (tx) => {
12996
+ const sourceId = `${minute.connectionId}:${minute.minute}`;
12997
+ const receipt2 = await recordUsageEvent3(tx, {
12998
+ accountId: facts.accountId,
12999
+ workspaceId: facts.workspaceId,
13000
+ sessionId: facts.sessionId,
13001
+ eventType: "model.cost",
13002
+ quantity: cost.creditCostMicros,
13003
+ unit: "usd_micros",
13004
+ sourceResourceType: REALTIME_VOICE_SOURCE_TYPE,
13005
+ sourceResourceId: sourceId,
13006
+ idempotencyKey: minute.key,
13007
+ occurredAt: minute.startsAt,
13008
+ initiator: { kind: "service", subjectId: "api:voice-realtime" },
13009
+ initiatorContext: { creditDebitAttribution: attribution }
13010
+ });
13011
+ const amount = Number(receipt2.quantity);
13012
+ if (amount <= 0) return 0;
13013
+ const debit = await applyCreditDebitAfterUse2(tx, {
13014
+ accountId: facts.accountId,
13015
+ workspaceId: facts.workspaceId,
13016
+ type: REALTIME_VOICE_DEBIT_TYPE,
13017
+ amountMicros: amount,
13018
+ sourceType: REALTIME_VOICE_SOURCE_TYPE,
13019
+ sourceId,
13020
+ idempotencyKey: `credit:${REALTIME_VOICE_DEBIT_TYPE}:${sourceId}`,
13021
+ occurredAt: minute.startsAt,
13022
+ usage: VOICE_CREDIT_USAGE,
13023
+ metadata: {
13024
+ provider: voice.provider,
13025
+ model: facts.model,
13026
+ realtimeId: facts.realtimeId,
13027
+ connectionId: minute.connectionId,
13028
+ minute: minute.minute,
13029
+ basis: "server_observed_started_minute",
13030
+ providerCostMicros: cost.providerCostMicros,
13031
+ ...attribution.kind === "unknown" ? {} : creditDebitAttributionMetadata(attribution)
13032
+ }
13033
+ });
13034
+ return debit.debitedMicros;
13035
+ }
13036
+ );
13037
+ }
13038
+ const stop = facts.state === "active" ? await refusal({
13039
+ accountId: facts.accountId,
13040
+ workspaceId: facts.workspaceId,
13041
+ attribution,
13042
+ now
13043
+ }) : null;
13044
+ return { debitedMicros, stop };
13045
+ }
13046
+ };
13047
+ }
13048
+
13049
+ // src/domain/voice-input-billing.ts
13050
+ var VOICE_INPUT_DEBIT_TYPE = VOICE_TRANSCRIPTION_DEBIT_TYPE;
13051
+ var VOICE_INPUT_SOURCE_TYPE = VOICE_TRANSCRIPTION_SOURCE_TYPE;
13052
+ function voiceInputBillableUsage(input) {
13053
+ if (input.usage?.kind === "tokens" && voiceInputPricingHasTokenRates(input.pricing)) {
13054
+ return { usage: input.usage, basis: "provider_tokens" };
13055
+ }
13056
+ if (input.usage?.kind === "duration") {
13057
+ return { usage: input.usage, basis: "provider_duration" };
13058
+ }
13059
+ if (input.trustedDurationSeconds !== void 0 && Number.isFinite(input.trustedDurationSeconds) && input.trustedDurationSeconds >= 0) {
13060
+ return {
13061
+ usage: { kind: "duration", seconds: input.trustedDurationSeconds },
13062
+ basis: "server_duration"
13063
+ };
13064
+ }
13065
+ throw new TranscriptionServiceError({
13066
+ code: "provider",
13067
+ message: "Transcription usage was not reported."
13068
+ });
13069
+ }
13070
+ function initiatingHuman2(attribution) {
13071
+ return attribution.kind === "human" || attribution.kind === "turn" ? attribution.initiatingHumanSubjectId : null;
13072
+ }
13073
+ function startOfUtcMonth3() {
13074
+ const now = /* @__PURE__ */ new Date();
13075
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
13076
+ }
13077
+ function createVoiceInputBilling(deps) {
13078
+ const reconcileInterval = deps.reconcileIntervalMilliseconds ?? 5 * 6e4;
13079
+ const lastReconciled = /* @__PURE__ */ new Map();
13080
+ const reconcileDue = (key) => {
13081
+ const now = Date.now();
13082
+ const last = lastReconciled.get(key);
13083
+ if (last !== void 0 && now - last < reconcileInterval) return false;
13084
+ if (lastReconciled.size >= 1e4) lastReconciled.clear();
13085
+ lastReconciled.set(key, now);
13086
+ return true;
13087
+ };
13088
+ return {
13089
+ async admit({ accountId, workspaceId, attribution }) {
13090
+ if (!voiceInputCreditBillingActive2(deps.settings)) return;
13091
+ if (attribution.kind === "unknown") {
13092
+ throw new TranscriptionServiceError({
13093
+ code: "policy_blocked",
13094
+ status: 403,
13095
+ message: "Voice input payer could not be verified."
13096
+ });
13097
+ }
13098
+ if (reconcileDue(`${accountId}:${workspaceId}`)) {
13099
+ await reconcileUnsettledVoiceInputCharges(deps.db, { accountId, workspaceId }).catch(
13100
+ (error) => {
13101
+ deps.onReconcileError?.(error, { accountId, workspaceId });
13102
+ }
13103
+ );
13104
+ }
13105
+ const balance = await getSpendableCreditBalance4(deps.db, accountId, VOICE_CREDIT_USAGE2);
13106
+ if (balance.balanceMicros <= 0) {
13107
+ throw new TranscriptionBillingRefusedError({
13108
+ code: "insufficient_credits",
13109
+ message: voiceInsufficientCreditsMessage("Voice input", voiceCreditStanding(balance))
13110
+ });
13111
+ }
13112
+ const refusal = await checkWorkspaceAllowance4(deps.db, {
13113
+ accountId,
13114
+ workspaceId,
13115
+ subjectId: initiatingHuman2(attribution)
12834
13116
  });
12835
13117
  if (refusal) {
12836
13118
  throw new TranscriptionBillingRefusedError({
@@ -12844,12 +13126,12 @@ function createVoiceInputBilling(deps) {
12844
13126
  });
12845
13127
  }
12846
13128
  if (deps.settings.usageLimitsMode === "static" || deps.settings.usageLimitsMode === "managed") {
12847
- const cap = configuredStaticUsageLimits2(deps.settings).maxMonthlyCostMicrosPerAccount;
13129
+ const cap = configuredStaticUsageLimits3(deps.settings).maxMonthlyCostMicrosPerAccount;
12848
13130
  if (cap) {
12849
- const used = await sumUsageQuantity3(deps.db, {
13131
+ const used = await sumUsageQuantity4(deps.db, {
12850
13132
  accountId,
12851
13133
  eventType: "model.cost",
12852
- since: startOfUtcMonth2()
13134
+ since: startOfUtcMonth3()
12853
13135
  });
12854
13136
  if (used >= cap) {
12855
13137
  throw new TranscriptionBillingRefusedError({
@@ -12861,7 +13143,7 @@ function createVoiceInputBilling(deps) {
12861
13143
  }
12862
13144
  },
12863
13145
  async settle(input) {
12864
- if (!voiceInputCreditBillingActive(deps.settings)) return { creditCostMicros: 0 };
13146
+ if (!voiceInputCreditBillingActive2(deps.settings)) return { creditCostMicros: 0 };
12865
13147
  const { usage, basis } = voiceInputBillableUsage({
12866
13148
  pricing: input.pricing,
12867
13149
  usage: input.usage,
@@ -12888,7 +13170,7 @@ async function settleVoiceInputUsage(db, input) {
12888
13170
  workspaceId: input.workspaceId,
12889
13171
  sourceId: input.billing.sourceId
12890
13172
  });
12891
- const receipt2 = await recordUsageEvent3(db, {
13173
+ const receipt2 = await recordUsageEvent4(db, {
12892
13174
  accountId: input.accountId,
12893
13175
  workspaceId: input.workspaceId,
12894
13176
  eventType: "model.cost",
@@ -12903,7 +13185,7 @@ async function settleVoiceInputUsage(db, input) {
12903
13185
  });
12904
13186
  const amount = Number(receipt2.quantity);
12905
13187
  if (amount > 0) {
12906
- await applyCreditDebitAfterUse2(db, {
13188
+ await applyCreditDebitAfterUse3(db, {
12907
13189
  accountId: input.accountId,
12908
13190
  workspaceId: input.workspaceId,
12909
13191
  type: VOICE_TRANSCRIPTION_DEBIT_TYPE,
@@ -12911,6 +13193,7 @@ async function settleVoiceInputUsage(db, input) {
12911
13193
  sourceType: VOICE_TRANSCRIPTION_SOURCE_TYPE,
12912
13194
  sourceId: input.billing.sourceId,
12913
13195
  idempotencyKey: keys.debitIdempotencyKey,
13196
+ usage: VOICE_CREDIT_USAGE2,
12914
13197
  metadata: {
12915
13198
  providerId: input.providerId,
12916
13199
  model: input.model,
@@ -12922,7 +13205,7 @@ async function settleVoiceInputUsage(db, input) {
12922
13205
  textInputTokens: input.usage.textInputTokens,
12923
13206
  outputTokens: input.usage.outputTokens
12924
13207
  } : { audioMilliseconds: Math.ceil(input.usage.seconds * 1e3) },
12925
- ...attribution.kind === "unknown" ? {} : creditDebitAttributionMetadata(attribution)
13208
+ ...attribution.kind === "unknown" ? {} : creditDebitAttributionMetadata2(attribution)
12926
13209
  }
12927
13210
  });
12928
13211
  }
@@ -12935,7 +13218,7 @@ async function reconcileUnsettledVoiceInputCharges(db, input) {
12935
13218
  ...input.minAgeMilliseconds !== void 0 ? { minAgeMilliseconds: input.minAgeMilliseconds } : {}
12936
13219
  });
12937
13220
  for (const charge of pending) {
12938
- await applyCreditDebitAfterUse2(db, {
13221
+ await applyCreditDebitAfterUse3(db, {
12939
13222
  accountId: input.accountId,
12940
13223
  workspaceId: input.workspaceId,
12941
13224
  type: VOICE_TRANSCRIPTION_DEBIT_TYPE,
@@ -12946,9 +13229,10 @@ async function reconcileUnsettledVoiceInputCharges(db, input) {
12946
13229
  workspaceId: input.workspaceId,
12947
13230
  sourceId: charge.sourceId
12948
13231
  }).debitIdempotencyKey,
13232
+ usage: VOICE_CREDIT_USAGE2,
12949
13233
  metadata: {
12950
13234
  basis: "reconciled_receipt",
12951
- ...charge.attribution.kind === "unknown" ? {} : creditDebitAttributionMetadata(charge.attribution)
13235
+ ...charge.attribution.kind === "unknown" ? {} : creditDebitAttributionMetadata2(charge.attribution)
12952
13236
  }
12953
13237
  });
12954
13238
  }
@@ -12957,13 +13241,13 @@ async function reconcileUnsettledVoiceInputCharges(db, input) {
12957
13241
 
12958
13242
  // src/domain/web-search-billing.ts
12959
13243
  import {
12960
- applyCreditDebitAfterUse as applyCreditDebitAfterUse3,
12961
- checkWorkspaceAllowance as checkWorkspaceAllowance4,
13244
+ applyCreditDebitAfterUse as applyCreditDebitAfterUse4,
13245
+ checkWorkspaceAllowance as checkWorkspaceAllowance5,
12962
13246
  creditDebitAttributionForTurn as creditDebitAttributionForTurn2,
12963
- creditDebitAttributionMetadata as creditDebitAttributionMetadata2,
12964
- getSpendableCreditBalance as getSpendableCreditBalance4,
12965
- recordUsageEvent as recordUsageEvent4,
12966
- withRlsContext
13247
+ creditDebitAttributionMetadata as creditDebitAttributionMetadata3,
13248
+ getSpendableCreditBalance as getSpendableCreditBalance5,
13249
+ recordUsageEvent as recordUsageEvent5,
13250
+ withRlsContext as withRlsContext2
12967
13251
  } from "@opengeni/db";
12968
13252
  var WEB_SEARCH_DEBIT_TYPE = "web_search_debit";
12969
13253
  var WEB_SEARCH_SOURCE_TYPE = "web_search";
@@ -12995,14 +13279,14 @@ function createWebSearchBilling(deps) {
12995
13279
  async admit(scope, expectedProviderMicros) {
12996
13280
  if (!active || expectedProviderMicros <= 0) return;
12997
13281
  const attribution = await attributionFor(scope);
12998
- const balance = await getSpendableCreditBalance4(deps.db, scope.accountId);
13282
+ const balance = await getSpendableCreditBalance5(deps.db, scope.accountId);
12999
13283
  if (balance.balanceMicros <= 0) {
13000
13284
  throw new WebSearchBillingRefusedError(
13001
13285
  "insufficient_credits",
13002
13286
  "Web search needs Opengeni credits, and this account has none left."
13003
13287
  );
13004
13288
  }
13005
- const refusal = await checkWorkspaceAllowance4(deps.db, {
13289
+ const refusal = await checkWorkspaceAllowance5(deps.db, {
13006
13290
  accountId: scope.accountId,
13007
13291
  workspaceId: scope.workspaceId,
13008
13292
  subjectId: attribution.kind === "turn" ? attribution.initiatingHumanSubjectId : null
@@ -13019,7 +13303,7 @@ function createWebSearchBilling(deps) {
13019
13303
  sourceResourceType: WEB_SEARCH_SOURCE_TYPE,
13020
13304
  sourceResourceId: `${scope.attemptId}:${cost.operationId}`
13021
13305
  };
13022
- await recordUsageEvent4(deps.db, {
13306
+ await recordUsageEvent5(deps.db, {
13023
13307
  ...shared,
13024
13308
  eventType: `web_search.${cost.operation}_requests`,
13025
13309
  quantity: 1,
@@ -13028,11 +13312,11 @@ function createWebSearchBilling(deps) {
13028
13312
  });
13029
13313
  if (!active || cost.creditMicros <= 0) return;
13030
13314
  const attribution = await attributionFor(scope);
13031
- await withRlsContext(
13315
+ await withRlsContext2(
13032
13316
  deps.db,
13033
13317
  { accountId: scope.accountId, workspaceId: scope.workspaceId },
13034
13318
  async (tx) => {
13035
- await recordUsageEvent4(tx, {
13319
+ await recordUsageEvent5(tx, {
13036
13320
  ...shared,
13037
13321
  eventType: "web_search.cost",
13038
13322
  quantity: cost.creditMicros,
@@ -13041,7 +13325,7 @@ function createWebSearchBilling(deps) {
13041
13325
  initiator: { kind: "service", subjectId: WEB_SEARCH_BILLING_INITIATOR },
13042
13326
  initiatorContext: { creditDebitAttribution: attribution }
13043
13327
  });
13044
- await applyCreditDebitAfterUse3(tx, {
13328
+ await applyCreditDebitAfterUse4(tx, {
13045
13329
  accountId: scope.accountId,
13046
13330
  workspaceId: scope.workspaceId,
13047
13331
  type: WEB_SEARCH_DEBIT_TYPE,
@@ -13052,7 +13336,7 @@ function createWebSearchBilling(deps) {
13052
13336
  metadata: {
13053
13337
  // turnId lets the allowance trigger resolve the causal human
13054
13338
  // from the immutable turn receipt.
13055
- ...creditDebitAttributionMetadata2(attribution),
13339
+ ...creditDebitAttributionMetadata3(attribution),
13056
13340
  provider: cost.provider,
13057
13341
  operation: cost.operation,
13058
13342
  providerCostMicros: cost.providerMicros,
@@ -13066,241 +13350,6 @@ function createWebSearchBilling(deps) {
13066
13350
  };
13067
13351
  }
13068
13352
 
13069
- // src/domain/realtime-voice-billing.ts
13070
- import {
13071
- AI_GATEWAY_REALTIME_MODELS,
13072
- AZURE_LIVE_REALTIME_MODEL_ID,
13073
- configuredStaticUsageLimits as configuredStaticUsageLimits3,
13074
- parseRealtimeVoicePricingJson,
13075
- parseRealtimeVoicePricingTableJson,
13076
- realtimeVoiceMinuteCreditMicros,
13077
- realtimeVoiceStartedMinutes,
13078
- voiceInputCreditBillingActive as voiceInputCreditBillingActive2
13079
- } from "@opengeni/config";
13080
- import {
13081
- applyCreditDebitAfterUse as applyCreditDebitAfterUse4,
13082
- checkWorkspaceAllowance as checkWorkspaceAllowance5,
13083
- creditDebitAttributionMetadata as creditDebitAttributionMetadata3,
13084
- existingUsageEventIdempotencyKeys,
13085
- getSpendableCreditBalance as getSpendableCreditBalance5,
13086
- loadSessionRealtimeBillingFacts,
13087
- recordUsageEvent as recordUsageEvent5,
13088
- sumUsageQuantity as sumUsageQuantity4,
13089
- withRlsContext as withRlsContext2
13090
- } from "@opengeni/db";
13091
- var REALTIME_VOICE_DEBIT_TYPE = "voice_realtime_debit";
13092
- var REALTIME_VOICE_SOURCE_TYPE = "voice_realtime";
13093
- function deploymentRealtimeVoice(settings, model) {
13094
- const azureConfigured = Boolean(settings.azureLiveEndpoint && settings.azureLiveApiKey?.trim());
13095
- if (model === AZURE_LIVE_REALTIME_MODEL_ID) {
13096
- return {
13097
- provider: "azure-live",
13098
- configured: azureConfigured,
13099
- pricing: parseRealtimeVoicePricingJson(settings.azureLivePricingJson)
13100
- };
13101
- }
13102
- for (const gateway of Object.values(AI_GATEWAY_REALTIME_MODELS)) {
13103
- if (gateway.managedModelId !== model) continue;
13104
- return {
13105
- provider: "ai-gateway",
13106
- configured: Boolean(settings.vercelAiGatewayApiKey),
13107
- pricing: parseRealtimeVoicePricingTableJson(settings.aiGatewayRealtimePricingJson)[gateway.upstreamModelId] ?? null
13108
- };
13109
- }
13110
- return null;
13111
- }
13112
- var RealtimeVoiceUnavailableError = class extends Error {
13113
- constructor(code, message) {
13114
- super(message);
13115
- this.code = code;
13116
- }
13117
- name = "RealtimeVoiceUnavailableError";
13118
- };
13119
- function realtimeVoiceOfferProblem(settings, voice) {
13120
- if (!voice.configured) {
13121
- return new RealtimeVoiceUnavailableError(
13122
- "not_configured",
13123
- "Opengeni live voice is not configured on this deployment."
13124
- );
13125
- }
13126
- if (voiceInputCreditBillingActive2(settings) && !voice.pricing) {
13127
- return new RealtimeVoiceUnavailableError(
13128
- "pricing_unconfigured",
13129
- "Opengeni live voice is not available on this deployment yet."
13130
- );
13131
- }
13132
- return null;
13133
- }
13134
- var REALTIME_VOICE_INSUFFICIENT_CREDITS_MESSAGE = "Live voice needs Opengeni credits. Add credits to continue.";
13135
- function startOfUtcMonth3(now) {
13136
- return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
13137
- }
13138
- function initiatingHuman2(attribution) {
13139
- return attribution.kind === "human" || attribution.kind === "turn" ? attribution.initiatingHumanSubjectId : null;
13140
- }
13141
- function createRealtimeVoiceBilling(deps) {
13142
- const billingActive = () => voiceInputCreditBillingActive2(deps.settings);
13143
- async function refusal(input) {
13144
- const balance = await getSpendableCreditBalance5(deps.db, input.accountId);
13145
- if (balance.balanceMicros <= 0) {
13146
- return new TranscriptionBillingRefusedError({
13147
- code: "insufficient_credits",
13148
- message: REALTIME_VOICE_INSUFFICIENT_CREDITS_MESSAGE
13149
- });
13150
- }
13151
- const allowance = await checkWorkspaceAllowance5(deps.db, {
13152
- accountId: input.accountId,
13153
- workspaceId: input.workspaceId,
13154
- subjectId: initiatingHuman2(input.attribution)
13155
- });
13156
- if (allowance) {
13157
- return new TranscriptionBillingRefusedError({
13158
- code: allowance.code,
13159
- message: allowance.message,
13160
- details: {
13161
- scope: allowance.scope,
13162
- resetsAt: allowance.resetsAt,
13163
- ...allowance.subjectId ? { subjectId: allowance.subjectId } : {}
13164
- }
13165
- });
13166
- }
13167
- if (deps.settings.usageLimitsMode === "static" || deps.settings.usageLimitsMode === "managed") {
13168
- const cap = configuredStaticUsageLimits3(deps.settings).maxMonthlyCostMicrosPerAccount;
13169
- if (cap) {
13170
- const used = await sumUsageQuantity4(deps.db, {
13171
- accountId: input.accountId,
13172
- eventType: "model.cost",
13173
- since: startOfUtcMonth3(input.now)
13174
- });
13175
- if (used >= cap) {
13176
- return new TranscriptionBillingRefusedError({
13177
- code: "monthly_model_cost_limit",
13178
- message: "The monthly model cost limit has been reached."
13179
- });
13180
- }
13181
- }
13182
- }
13183
- return null;
13184
- }
13185
- return {
13186
- /**
13187
- * Throws RealtimeVoiceUnavailableError when the deployment cannot offer
13188
- * the model and TranscriptionBillingRefusedError for a credit refusal.
13189
- * Workspace-funded models are always admitted here.
13190
- */
13191
- async admit(input) {
13192
- const voice = deploymentRealtimeVoice(deps.settings, input.model);
13193
- if (!voice) return;
13194
- const problem = realtimeVoiceOfferProblem(deps.settings, voice);
13195
- if (problem) throw problem;
13196
- if (!billingActive()) return;
13197
- const refused = await refusal({ ...input, now: input.now ?? /* @__PURE__ */ new Date() });
13198
- if (refused) throw refused;
13199
- },
13200
- /** Spendable credits are positive (always true when credits are not enforced). */
13201
- async hasSpendableCredits(accountId) {
13202
- if (!billingActive()) return true;
13203
- const balance = await getSpendableCreditBalance5(deps.db, accountId);
13204
- return balance.balanceMicros > 0;
13205
- },
13206
- /**
13207
- * Settle every started minute observed so far for one live-voice mode and
13208
- * report whether the call must stop. Safe to call repeatedly and
13209
- * concurrently: receipts and debits are idempotent per (connection, minute).
13210
- */
13211
- async settle(input) {
13212
- const now = input.now ?? /* @__PURE__ */ new Date();
13213
- if (!billingActive()) return { debitedMicros: 0, stop: null };
13214
- const facts = await loadSessionRealtimeBillingFacts(deps.db, { ...input, now });
13215
- if (!facts) return { debitedMicros: 0, stop: null };
13216
- const voice = deploymentRealtimeVoice(deps.settings, facts.model);
13217
- if (!voice) return { debitedMicros: 0, stop: null };
13218
- if (!voice.pricing) {
13219
- return {
13220
- debitedMicros: 0,
13221
- stop: new RealtimeVoiceUnavailableError(
13222
- "pricing_unconfigured",
13223
- "Opengeni live voice is no longer available on this deployment."
13224
- )
13225
- };
13226
- }
13227
- const attribution = input.callerSubjectId === facts.ownerSubjectId ? input.attribution : { kind: "unknown" };
13228
- const cost = realtimeVoiceMinuteCreditMicros(voice.pricing);
13229
- const minutes = facts.connections.flatMap(
13230
- (connection) => Array.from(
13231
- { length: realtimeVoiceStartedMinutes(connection.startedAt, connection.observedUntil) },
13232
- (_, index) => ({
13233
- connectionId: connection.id,
13234
- minute: index + 1,
13235
- startsAt: new Date(connection.startedAt.getTime() + index * 6e4),
13236
- key: `voice.realtime_cost:${connection.id}:${index + 1}`
13237
- })
13238
- )
13239
- );
13240
- const recorded = await existingUsageEventIdempotencyKeys(deps.db, {
13241
- accountId: facts.accountId,
13242
- workspaceId: facts.workspaceId,
13243
- keys: minutes.map((minute) => minute.key)
13244
- });
13245
- let debitedMicros = 0;
13246
- for (const minute of minutes) {
13247
- if (recorded.has(minute.key)) continue;
13248
- debitedMicros += await withRlsContext2(
13249
- deps.db,
13250
- { accountId: facts.accountId, workspaceId: facts.workspaceId },
13251
- async (tx) => {
13252
- const sourceId = `${minute.connectionId}:${minute.minute}`;
13253
- const receipt2 = await recordUsageEvent5(tx, {
13254
- accountId: facts.accountId,
13255
- workspaceId: facts.workspaceId,
13256
- sessionId: facts.sessionId,
13257
- eventType: "model.cost",
13258
- quantity: cost.creditCostMicros,
13259
- unit: "usd_micros",
13260
- sourceResourceType: REALTIME_VOICE_SOURCE_TYPE,
13261
- sourceResourceId: sourceId,
13262
- idempotencyKey: minute.key,
13263
- occurredAt: minute.startsAt,
13264
- initiator: { kind: "service", subjectId: "api:voice-realtime" },
13265
- initiatorContext: { creditDebitAttribution: attribution }
13266
- });
13267
- const amount = Number(receipt2.quantity);
13268
- if (amount <= 0) return 0;
13269
- const debit = await applyCreditDebitAfterUse4(tx, {
13270
- accountId: facts.accountId,
13271
- workspaceId: facts.workspaceId,
13272
- type: REALTIME_VOICE_DEBIT_TYPE,
13273
- amountMicros: amount,
13274
- sourceType: REALTIME_VOICE_SOURCE_TYPE,
13275
- sourceId,
13276
- idempotencyKey: `credit:${REALTIME_VOICE_DEBIT_TYPE}:${sourceId}`,
13277
- occurredAt: minute.startsAt,
13278
- metadata: {
13279
- provider: voice.provider,
13280
- model: facts.model,
13281
- realtimeId: facts.realtimeId,
13282
- connectionId: minute.connectionId,
13283
- minute: minute.minute,
13284
- basis: "server_observed_started_minute",
13285
- providerCostMicros: cost.providerCostMicros,
13286
- ...attribution.kind === "unknown" ? {} : creditDebitAttributionMetadata3(attribution)
13287
- }
13288
- });
13289
- return debit.debitedMicros;
13290
- }
13291
- );
13292
- }
13293
- const stop = facts.state === "active" ? await refusal({
13294
- accountId: facts.accountId,
13295
- workspaceId: facts.workspaceId,
13296
- attribution,
13297
- now
13298
- }) : null;
13299
- return { debitedMicros, stop };
13300
- }
13301
- };
13302
- }
13303
-
13304
13353
  // src/workflow-wake-contract.ts
13305
13354
  var SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID = "opengeni-session-workflow-wake-dispatcher";
13306
13355
  var SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE = "sessionWorkflowWakeDispatcherWorkflow";
@@ -23243,6 +23292,7 @@ export {
23243
23292
  appendRigSetupCommand,
23244
23293
  applyCapabilityEnablement,
23245
23294
  applyGitHubRepositoryBindings,
23295
+ applyOperatorOAuthClientRequirement,
23246
23296
  applySessionAgentConfigWriteThrough,
23247
23297
  approveSkill,
23248
23298
  assertAllowedEnvironmentVariableName,
@@ -23759,7 +23809,9 @@ export {
23759
23809
  videoGenerationProviderIdempotencyKey,
23760
23810
  videoGenerationRequestDigest,
23761
23811
  visibleMcpAccountConnections,
23812
+ voiceCreditStanding,
23762
23813
  voiceInputBillableUsage,
23814
+ voiceInsufficientCreditsMessage,
23763
23815
  webSearchCreditBillingActive,
23764
23816
  withAgentConfigHttpErrors,
23765
23817
  withDefaultEnabledCapabilityMcpTools,