@opengeni/core 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 (36) hide show
  1. package/dist/application/modal-native-live-declaration-join.d.ts +37 -0
  2. package/dist/application/modal-native-original-configuration.d.ts +79 -0
  3. package/dist/domain/capabilities.d.ts +1 -0
  4. package/dist/domain/connector-tool-permissions.d.ts +1 -1
  5. package/dist/domain/realtime-voice-billing.d.ts +95 -0
  6. package/dist/domain/scheduled-task-access.d.ts +2 -2
  7. package/dist/domain/scheduled-tasks.d.ts +2 -2
  8. package/dist/domain/session-connection-accounts.d.ts +1 -1
  9. package/dist/domain/session-start-timing.d.ts +1 -1
  10. package/dist/domain/session-tool-policy.d.ts +11 -7
  11. package/dist/domain/voice-input-billing.d.ts +77 -0
  12. package/dist/domain/web-search-billing.d.ts +45 -0
  13. package/dist/goal-admission.d.ts +24 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.js +1065 -365
  16. package/dist/index.js.map +1 -1
  17. package/dist/transcription.d.ts +119 -2
  18. package/package.json +12 -12
  19. package/src/application/modal-native-live-declaration-join.ts +196 -0
  20. package/src/application/modal-native-original-configuration.ts +261 -0
  21. package/src/default-session-model.ts +58 -88
  22. package/src/domain/capabilities.ts +44 -2
  23. package/src/domain/connector-tool-permissions.ts +24 -8
  24. package/src/domain/realtime-voice-billing.ts +400 -0
  25. package/src/domain/scheduled-task-access.ts +2 -1
  26. package/src/domain/scheduled-tasks.ts +3 -2
  27. package/src/domain/session-connection-accounts.ts +7 -3
  28. package/src/domain/session-start-timing.ts +5 -0
  29. package/src/domain/session-tool-policy.ts +20 -4
  30. package/src/domain/sessions.ts +145 -98
  31. package/src/domain/voice-input-billing.ts +315 -0
  32. package/src/domain/web-search-billing.ts +154 -0
  33. package/src/index.ts +3 -0
  34. package/src/model-catalog.ts +32 -38
  35. package/src/sandbox/routing.ts +59 -25
  36. package/src/transcription.ts +122 -3
package/dist/index.js CHANGED
@@ -837,6 +837,7 @@ function expandApiIntegrationAccountRoutes(input) {
837
837
  }
838
838
 
839
839
  // src/domain/session-connection-accounts.ts
840
+ import { allowedFirstPartyMcpToolsForSession as allowedFirstPartyMcpToolsForSession4 } from "@opengeni/config";
840
841
  import { requireSession as requireSession3, requireWorkspace as requireWorkspace6 } from "@opengeni/db";
841
842
 
842
843
  // src/domain/capabilities.ts
@@ -844,7 +845,11 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
844
845
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
845
846
  import { isDeepStrictEqual } from "util";
846
847
  import { withLockedCapabilityInstallation } from "@opengeni/db/capability-reconciliation";
847
- import { environmentsEncryptionKeyBytes } from "@opengeni/config";
848
+ import {
849
+ environmentsEncryptionKeyBytes,
850
+ findIntegrationsOauthClient,
851
+ parseIntegrationsOauthClientsJson
852
+ } from "@opengeni/config";
848
853
  import { pinnedFetch } from "@opengeni/network";
849
854
  import {
850
855
  CapabilityCatalogItem,
@@ -2026,6 +2031,7 @@ async function buildCapabilityCatalog(input) {
2026
2031
  listEnabledMcpCapabilityServers(input.db, input.workspaceId)
2027
2032
  ]);
2028
2033
  const runnableCapabilityIds = new Set(runnableMcpServers.map((server) => server.capabilityId));
2034
+ const operatorOAuthClientConfigured = operatorOAuthClientResolver(input.settings);
2029
2035
  const catalogInstallations = capabilityInstallations.filter(
2030
2036
  (installation) => installation.kind === "mcp"
2031
2037
  );
@@ -2063,13 +2069,33 @@ async function buildCapabilityCatalog(input) {
2063
2069
  notes: "This installed connection is unavailable. Review its connection settings."
2064
2070
  }
2065
2071
  } : projected;
2066
- return applyCapabilityLifecycle(runtimeProjected);
2072
+ return applyCapabilityLifecycle(
2073
+ applyOperatorOAuthClientRequirement(runtimeProjected, operatorOAuthClientConfigured)
2074
+ );
2067
2075
  }).sort(compareCatalogItems);
2068
2076
  return {
2069
2077
  items,
2070
2078
  installations: catalogInstallations
2071
2079
  };
2072
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
+ }
2073
2099
  async function createCatalogItem(input) {
2074
2100
  const id = input.payload.id?.trim() || generatedCapabilityId(input.payload);
2075
2101
  if (id.startsWith("skill:")) {
@@ -4834,6 +4860,7 @@ import {
4834
4860
  ORGANIZATION_GATEWAY_MODEL_ID_PREFIX as ORGANIZATION_GATEWAY_MODEL_ID_PREFIX2,
4835
4861
  ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX as ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX2,
4836
4862
  allowedFirstPartyMcpToolsForSession as allowedFirstPartyMcpToolsForSession3,
4863
+ deploymentUnavailableFirstPartyMcpTools,
4837
4864
  resolveFirstPartyMcpToolPolicy,
4838
4865
  policyProviderIdForModel,
4839
4866
  resolveTurnExecutionPolicyV1,
@@ -5370,6 +5397,7 @@ import {
5370
5397
  readActiveSandbox,
5371
5398
  retainWorkspaceMutationProcess,
5372
5399
  SandboxRetainedProcessPromotionFencedError,
5400
+ SandboxWorkspaceMutationOutputRejectedError,
5373
5401
  retainedProcessSettlementIdentity,
5374
5402
  settleRetainedProcess,
5375
5403
  verifyDirectWorkspaceMutationSettlement,
@@ -5382,6 +5410,7 @@ import {
5382
5410
  NatsControlRpc,
5383
5411
  NatsOpStreamTransport,
5384
5412
  RoutingSandboxSession,
5413
+ RoutingMutationOutputRejectedError,
5385
5414
  resolveModalCheckpointProviderBindingForSession
5386
5415
  } from "@opengeni/runtime/sandbox";
5387
5416
  var retainWorkspaceProviderCommand = createProviderCommandRetainer(
@@ -5540,22 +5569,35 @@ function wrapChannelABoxWithRouting(services, ids, established) {
5540
5569
  throw new Error(
5541
5570
  "Outcome-unknown command settlement requires its exact retained invocation"
5542
5571
  );
5543
- await verifyDirectWorkspaceMutationSettlement(db, {
5544
- accountId: ids.accountId,
5545
- workspaceId: ids.workspaceId,
5546
- sessionId: ids.sessionId,
5547
- requestId: ids.directRequest.requestId,
5548
- holderId: ids.directRequest.holderId,
5549
- initiatorSubjectId: ids.resourceSubjectId,
5550
- sandboxGroupId: homeLease.sandboxGroupId,
5551
- expectedEpoch: backend.leaseEpoch,
5552
- expectedInstanceId: backend.providerInstanceId,
5553
- routeTargetId: exactAdmission.routeTargetId,
5554
- routeEpoch: exactAdmission.routeEpoch,
5555
- admission: exactAdmission,
5556
- operation: op,
5557
- outcome
5558
- });
5572
+ try {
5573
+ await verifyDirectWorkspaceMutationSettlement(db, {
5574
+ accountId: ids.accountId,
5575
+ workspaceId: ids.workspaceId,
5576
+ sessionId: ids.sessionId,
5577
+ requestId: ids.directRequest.requestId,
5578
+ holderId: ids.directRequest.holderId,
5579
+ initiatorSubjectId: ids.resourceSubjectId,
5580
+ sandboxGroupId: homeLease.sandboxGroupId,
5581
+ expectedEpoch: backend.leaseEpoch,
5582
+ expectedInstanceId: backend.providerInstanceId,
5583
+ routeTargetId: exactAdmission.routeTargetId,
5584
+ routeEpoch: exactAdmission.routeEpoch,
5585
+ admission: exactAdmission,
5586
+ operation: op,
5587
+ outcome
5588
+ });
5589
+ } catch (error) {
5590
+ if (error instanceof SandboxWorkspaceMutationOutputRejectedError && error.matchesPhysicalSettlement({
5591
+ accountId: ids.accountId,
5592
+ workspaceId: ids.workspaceId,
5593
+ admission: exactAdmission,
5594
+ operation: op,
5595
+ outcome
5596
+ })) {
5597
+ throw new RoutingMutationOutputRejectedError(op, error.code, { cause: error });
5598
+ }
5599
+ throw error;
5600
+ }
5559
5601
  } : void 0;
5560
5602
  const beforeProcessMutation = homeLease ? async ({
5561
5603
  op,
@@ -5578,15 +5620,28 @@ function wrapChannelABoxWithRouting(services, ids, established) {
5578
5620
  if (!admission || typeof admission !== "object" || typeof admission.id !== "string" || typeof admission.workspaceGeneration !== "number") {
5579
5621
  throw new Error("API retained-process mutation settlement lacked its exact admission");
5580
5622
  }
5581
- await verifyRetainedProcessMutationSettlement(db, {
5582
- accountId: ids.accountId,
5583
- workspaceId: ids.workspaceId,
5584
- sessionId: ids.sessionId,
5585
- processId: process.id,
5586
- admission,
5587
- operation: op,
5588
- outcome
5589
- });
5623
+ try {
5624
+ await verifyRetainedProcessMutationSettlement(db, {
5625
+ accountId: ids.accountId,
5626
+ workspaceId: ids.workspaceId,
5627
+ sessionId: ids.sessionId,
5628
+ processId: process.id,
5629
+ admission,
5630
+ operation: op,
5631
+ outcome
5632
+ });
5633
+ } catch (error) {
5634
+ if (error instanceof SandboxWorkspaceMutationOutputRejectedError && error.matchesPhysicalSettlement({
5635
+ accountId: ids.accountId,
5636
+ workspaceId: ids.workspaceId,
5637
+ admission,
5638
+ operation: op,
5639
+ outcome
5640
+ })) {
5641
+ throw new RoutingMutationOutputRejectedError(op, error.code, { cause: error });
5642
+ }
5643
+ throw error;
5644
+ }
5590
5645
  } : void 0;
5591
5646
  const settleProcess = homeLease ? async ({
5592
5647
  backend,
@@ -6677,15 +6732,13 @@ import {
6677
6732
  import {
6678
6733
  listConnectionsMetadata as listConnectionsMetadata3,
6679
6734
  getDeploymentModelCatalog,
6680
- listWorkspaceProviderCustomModels,
6735
+ listWorkspaceProviderCustomModelsByKind,
6681
6736
  getWorkspaceProviderCustomModelForExecution,
6682
6737
  lockActiveWorkspaceProviderCustomModelForAdmission,
6683
6738
  getWorkspaceGatewayCustomModelForExecution,
6684
6739
  getWorkspaceOpenRouterCustomModelForExecution,
6685
6740
  getOrganizationModelProviderCustomModelForExecution,
6686
- listWorkspaceGatewayCustomModels,
6687
- listWorkspaceOpenRouterCustomModels,
6688
- listOrganizationModelProviderCustomModelsForWorkspace,
6741
+ getOrganizationModelProviderCatalogForWorkspace,
6689
6742
  lockActiveOrganizationModelProviderCustomModelForAdmission
6690
6743
  } from "@opengeni/db";
6691
6744
  function isWorkspaceGatewayCustomModelId(settings, modelId) {
@@ -6800,26 +6853,28 @@ async function resolveWorkspaceCatalogSettings(db, envSettings, input) {
6800
6853
  const retainedOrganizationOpenRouterUpstreamModelIds = retainedProductModelIds.flatMap(
6801
6854
  (productModelId) => productModelId?.startsWith(ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX) ? [productModelId.slice(ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX.length)] : []
6802
6855
  );
6856
+ const workspaceCustomModelKinds = [
6857
+ "vercel_gateway",
6858
+ "openrouter",
6859
+ ...supportsOrganizationProviderReads ? CLAUDE_CONNECTION_KINDS.filter(
6860
+ (kind) => kind !== "claude_subscription" || envSettings.claudeSubscriptionEnabled
6861
+ ) : []
6862
+ ];
6803
6863
  const [
6804
6864
  resolved,
6805
- activeGatewayCustomModels,
6806
- activeOpenRouterCustomModels,
6865
+ workspaceCustomModels,
6807
6866
  retainedGatewayCustomModels,
6808
6867
  retainedOpenRouterCustomModels,
6809
- organizationGatewayCustomModels,
6810
- organizationOpenRouterCustomModels,
6868
+ organizationProviders,
6811
6869
  retainedOrganizationGatewayCustomModels,
6812
6870
  retainedOrganizationOpenRouterCustomModels,
6813
6871
  directConnections
6814
6872
  ] = await Promise.all([
6815
6873
  resolveCatalogSettings(db, envSettings),
6816
- listWorkspaceGatewayCustomModels(db, {
6817
- accountId: input.accountId,
6818
- workspaceId: input.workspaceId
6819
- }),
6820
- listWorkspaceOpenRouterCustomModels(db, {
6874
+ listWorkspaceProviderCustomModelsByKind(db, {
6821
6875
  accountId: input.accountId,
6822
- workspaceId: input.workspaceId
6876
+ workspaceId: input.workspaceId,
6877
+ providerKinds: workspaceCustomModelKinds
6823
6878
  }),
6824
6879
  Promise.all(
6825
6880
  [...new Set(retainedGatewayUpstreamModelIds)].map(
@@ -6839,16 +6894,11 @@ async function resolveWorkspaceCatalogSettings(db, envSettings, input) {
6839
6894
  })
6840
6895
  )
6841
6896
  ),
6842
- supportsOrganizationProviderReads ? listOrganizationModelProviderCustomModelsForWorkspace(db, {
6843
- accountId: input.accountId,
6844
- workspaceId: input.workspaceId,
6845
- providerKind: "vercel_gateway"
6846
- }) : Promise.resolve([]),
6847
- supportsOrganizationProviderReads ? listOrganizationModelProviderCustomModelsForWorkspace(db, {
6897
+ supportsOrganizationProviderReads ? getOrganizationModelProviderCatalogForWorkspace(db, {
6848
6898
  accountId: input.accountId,
6849
6899
  workspaceId: input.workspaceId,
6850
- providerKind: "openrouter"
6851
- }) : Promise.resolve([]),
6900
+ providerKinds: ["vercel_gateway", "openrouter", ...CLAUDE_CONNECTION_KINDS]
6901
+ }) : null,
6852
6902
  supportsOrganizationProviderReads ? Promise.all(
6853
6903
  [...new Set(retainedOrganizationGatewayUpstreamModelIds)].map(
6854
6904
  async (upstreamModelId) => await getOrganizationModelProviderCustomModelForExecution(db, {
@@ -6883,29 +6933,25 @@ async function resolveWorkspaceCatalogSettings(db, envSettings, input) {
6883
6933
  return customModels;
6884
6934
  };
6885
6935
  const gatewayCustomModels = includeRetainedModels(
6886
- activeGatewayCustomModels,
6936
+ workspaceCustomModels.vercel_gateway,
6887
6937
  retainedGatewayCustomModels
6888
6938
  );
6889
6939
  const openRouterCustomModels = includeRetainedModels(
6890
- activeOpenRouterCustomModels,
6940
+ workspaceCustomModels.openrouter,
6891
6941
  retainedOpenRouterCustomModels
6892
6942
  );
6893
6943
  const organizationGatewayModels = includeRetainedModels(
6894
- organizationGatewayCustomModels,
6944
+ organizationProviders?.vercel_gateway.models ?? [],
6895
6945
  retainedOrganizationGatewayCustomModels
6896
6946
  );
6897
6947
  const organizationOpenRouterModels = includeRetainedModels(
6898
- organizationOpenRouterCustomModels,
6948
+ organizationProviders?.openrouter.models ?? [],
6899
6949
  retainedOrganizationOpenRouterCustomModels
6900
6950
  );
6901
6951
  const claudeConnections = {};
6902
- if (supportsOrganizationProviderReads)
6952
+ if (organizationProviders)
6903
6953
  for (const kind of CLAUDE_CONNECTION_KINDS) {
6904
- const models = await listOrganizationModelProviderCustomModelsForWorkspace(db, {
6905
- accountId: input.accountId,
6906
- workspaceId: input.workspaceId,
6907
- providerKind: kind
6908
- });
6954
+ const models = [...organizationProviders[kind].models];
6909
6955
  const prefix = claudeProviderId(kind) + "/";
6910
6956
  for (const productId of retainedProductModelIds)
6911
6957
  if (productId?.startsWith(prefix)) {
@@ -6923,11 +6969,7 @@ async function resolveWorkspaceCatalogSettings(db, envSettings, input) {
6923
6969
  if (supportsOrganizationProviderReads)
6924
6970
  for (const kind of CLAUDE_CONNECTION_KINDS) {
6925
6971
  if (kind === "claude_subscription" && !resolved.settings.claudeSubscriptionEnabled) continue;
6926
- const models = await listWorkspaceProviderCustomModels(db, {
6927
- accountId: input.accountId,
6928
- workspaceId: input.workspaceId,
6929
- providerKind: kind
6930
- });
6972
+ const models = [...workspaceCustomModels[kind]];
6931
6973
  const prefix = claudeProviderId(kind, "workspace") + "/";
6932
6974
  for (const productId of retainedProductModelIds) {
6933
6975
  if (!productId?.startsWith(prefix)) continue;
@@ -7237,15 +7279,11 @@ import {
7237
7279
  getWorkspace as getWorkspace2,
7238
7280
  getWorkspaceConnectionModelRestrictions,
7239
7281
  getWorkspaceModelPolicy,
7240
- getWorkspaceProviderApiKeyConnectionMetadata,
7241
- listWorkspaceProviderCustomModels as listWorkspaceProviderCustomModels2,
7242
- listOrganizationModelProviderCustomModelsForWorkspace as listOrganizationModelProviderCustomModelsForWorkspace2,
7243
- listWorkspaceGatewayCustomModels as listWorkspaceGatewayCustomModels2,
7244
- listWorkspaceOpenRouterCustomModels as listWorkspaceOpenRouterCustomModels2,
7245
- organizationModelProviderConnectionActiveForWorkspace,
7282
+ getOrganizationModelProviderCatalogForWorkspace as getOrganizationModelProviderCatalogForWorkspace2,
7283
+ listConnectionsMetadata as listConnectionsMetadata4,
7284
+ listWorkspaceProviderCustomModelsByKind as listWorkspaceProviderCustomModelsByKind2,
7246
7285
  workspaceCodexSubscriptionActive,
7247
- workspaceOpenRouterConnectionActive,
7248
- workspaceVercelAiGatewayConnectionActive,
7286
+ workspaceProviderApiKeyConnectionMetadataFromConnections,
7249
7287
  getBillingBalance,
7250
7288
  spendableCreditMicros,
7251
7289
  workspaceXaiSubscriptionActive,
@@ -7562,84 +7600,55 @@ async function loadWorkspaceClaudeSubscriptionReadiness(db, settings, context) {
7562
7600
  }
7563
7601
  async function loadWorkspaceModelSelectionInput(db, settings, context, options = {}) {
7564
7602
  const { accountId, workspaceId } = context;
7565
- const restrictionsRead = (async () => connectionRestrictionsAndXaiReadiness(db, settings, context))();
7566
- 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
+ ];
7567
7610
  const inputRead = (async () => Promise.all([
7568
- restrictionsRead,
7569
- claudePoolRead,
7611
+ connectionRestrictionsAndXaiReadiness(db, settings, context),
7612
+ loadWorkspaceClaudeSubscriptionReadiness(db, settings, context),
7570
7613
  getWorkspaceModelPolicy(db, workspaceId),
7571
7614
  workspaceCodexSubscriptionActive(db, settings, workspaceId),
7572
- options.observeAvailability === false ? Promise.resolve({}) : loadWorkspaceCodexModelAvailability(db, settings, workspaceId),
7573
- workspaceVercelAiGatewayConnectionActive(db, workspaceId),
7574
- listWorkspaceGatewayCustomModels2(db, { accountId, workspaceId }),
7575
- workspaceOpenRouterConnectionActive(db, workspaceId),
7576
- listWorkspaceOpenRouterCustomModels2(db, { accountId, workspaceId }),
7577
- organizationModelProviderConnectionActiveForWorkspace(db, {
7578
- accountId,
7579
- workspaceId,
7580
- providerKind: "vercel_gateway"
7581
- }),
7582
- organizationModelProviderConnectionActiveForWorkspace(db, {
7583
- accountId,
7584
- workspaceId,
7585
- providerKind: "openrouter"
7586
- }),
7587
- listOrganizationModelProviderCustomModelsForWorkspace2(db, {
7588
- accountId,
7589
- workspaceId,
7590
- providerKind: "vercel_gateway"
7591
- }),
7592
- 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, {
7593
7622
  accountId,
7594
7623
  workspaceId,
7595
- providerKind: "openrouter"
7624
+ providerKinds
7596
7625
  })
7597
7626
  ]))();
7598
- const claudeConnections = {};
7599
- const workspaceClaudeConnections = {};
7600
- if (typeof db.rollback === "function") await inputRead;
7601
- const catalogRead = (async () => Promise.all(
7602
- CLAUDE_CONNECTION_KINDS2.map(async (kind) => {
7603
- if (kind === "claude_subscription" && !settings.claudeSubscriptionEnabled) return;
7604
- const [active, models, metadata, workspaceModels] = await Promise.all([
7605
- kind === "claude_subscription" ? claudePoolRead.then((pool) => pool.organization) : organizationModelProviderConnectionActiveForWorkspace(db, {
7606
- accountId,
7607
- workspaceId,
7608
- providerKind: kind
7609
- }),
7610
- listOrganizationModelProviderCustomModelsForWorkspace2(db, {
7611
- accountId,
7612
- workspaceId,
7613
- providerKind: kind
7614
- }),
7615
- kind === "claude_subscription" ? Promise.resolve(null) : getWorkspaceProviderApiKeyConnectionMetadata(db, workspaceId, kind),
7616
- listWorkspaceProviderCustomModels2(db, { accountId, workspaceId, providerKind: kind })
7617
- ]);
7618
- claudeConnections[kind] = { active, models };
7619
- workspaceClaudeConnections[kind] = {
7620
- active: kind === "claude_subscription" ? (await claudePoolRead).workspace : metadata !== null,
7621
- models: workspaceModels
7622
- };
7623
- })
7624
- ))();
7625
7627
  const [inputResult, catalogResult] = await Promise.allSettled([inputRead, catalogRead]);
7626
7628
  if (inputResult.status === "rejected") throw inputResult.reason;
7627
7629
  if (catalogResult.status === "rejected") throw catalogResult.reason;
7628
7630
  const [
7629
7631
  { restrictions: connectionModelRestrictions, xaiSubscriptionActive },
7630
- _claudePool,
7632
+ claudePool,
7631
7633
  policy,
7632
7634
  codexSubscriptionActive,
7633
- observations,
7634
- workspaceGatewayConnectionActive,
7635
- workspaceGatewayCustomModels,
7636
- openRouterConnectionActive,
7637
- workspaceOpenRouterCustomModels,
7638
- organizationGatewayConnectionActive,
7639
- organizationOpenRouterConnectionActive,
7640
- organizationGatewayCustomModels,
7641
- organizationOpenRouterCustomModels
7635
+ observations
7642
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
+ }
7643
7652
  return {
7644
7653
  claudeConnections,
7645
7654
  workspaceClaudeConnections,
@@ -7649,14 +7658,14 @@ async function loadWorkspaceModelSelectionInput(db, settings, context, options =
7649
7658
  codexSubscriptionActive,
7650
7659
  observations,
7651
7660
  xaiSubscriptionActive,
7652
- workspaceGatewayConnectionActive,
7653
- workspaceGatewayCustomModels,
7654
- workspaceOpenRouterConnectionActive: openRouterConnectionActive,
7655
- workspaceOpenRouterCustomModels,
7656
- organizationGatewayConnectionActive,
7657
- organizationOpenRouterConnectionActive,
7658
- organizationGatewayCustomModels,
7659
- 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
7660
7669
  };
7661
7670
  }
7662
7671
  async function resolveCallerWorkspaceModelSelections(db, settings, context, options = {}) {
@@ -7802,7 +7811,8 @@ import {
7802
7811
  allowedFirstPartyMcpToolsForSession as allowedFirstPartyMcpToolsForSession2,
7803
7812
  codeSearchDeploymentPolicy,
7804
7813
  resolveModelProviderForTurn as resolveModelProviderForTurn2,
7805
- resolveFirstPartyDelegationSecret as resolveFirstPartyDelegationSecret2
7814
+ resolveFirstPartyDelegationSecret as resolveFirstPartyDelegationSecret2,
7815
+ webSearchToolPlan
7806
7816
  } from "@opengeni/config";
7807
7817
  import {
7808
7818
  AGENT_SKILL_MANAGE_TOOL_NAMES,
@@ -7938,12 +7948,12 @@ function resolveTurnToolPolicy(input) {
7938
7948
  defaultMcpServerIds: scheduledIds ?? input.defaultMcpServerIds ?? []
7939
7949
  });
7940
7950
  }
7941
- async function workspaceSessionToolPolicyContext(db, workspaceId, settings, subjectId) {
7951
+ async function workspaceSessionToolPolicyContext(db, workspaceId, settings, subjectId, workspaceRead) {
7942
7952
  const [runtimeSettings, workspace] = await Promise.all([
7943
7953
  settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings, {
7944
7954
  ...subjectId ? { subjectId } : {}
7945
7955
  }),
7946
- requireWorkspace3(db, workspaceId)
7956
+ workspaceRead ?? requireWorkspace3(db, workspaceId)
7947
7957
  ]);
7948
7958
  return {
7949
7959
  workspaceServerIds: sortedIds(runtimeSettings.mcpServers.map((server) => server.id)),
@@ -7979,7 +7989,7 @@ function workspaceSessionToolPolicyDefaultServerIdsFor(runtimeMcpServers, worksp
7979
7989
  ...configured.inheritConnectedMcpServers ? availableDefaults.filter((id) => !["opengeni", "files", "docs"].includes(id)) : []
7980
7990
  ]);
7981
7991
  }
7982
- async function workspaceSessionEffectiveToolsContext(deps, workspaceId, subjectId, sessions) {
7992
+ async function workspaceSessionEffectiveToolsContext(deps, workspaceId, subjectId, sessions, workspaceRead) {
7983
7993
  const configured = sessions.filter((session) => session.agent != null);
7984
7994
  const baseline = {
7985
7995
  settings: deps.settings,
@@ -7989,7 +7999,7 @@ async function workspaceSessionEffectiveToolsContext(deps, workspaceId, subjectI
7989
7999
  };
7990
8000
  if (configured.length === 0) return baseline;
7991
8001
  const [workspace, catalog, descriptors, sandboxes, routerHistory] = await Promise.all([
7992
- requireWorkspace3(deps.db, workspaceId),
8002
+ workspaceRead ?? requireWorkspace3(deps.db, workspaceId),
7993
8003
  resolveWorkspaceCatalogSettings(deps.db, deps.settings, {
7994
8004
  accountId: configured[0].accountId,
7995
8005
  workspaceId,
@@ -8076,9 +8086,14 @@ function sessionEffectiveToolProjectionInput(session, toolRefs, context) {
8076
8086
  const sandboxAvailable = context !== void 0 && sandboxBackend !== "none";
8077
8087
  const supportsImages = model?.model.api === "responses" && model.model.capabilities.inputModalities.includes("image");
8078
8088
  const hostedToolNames = [];
8089
+ const webSearchPlan = context ? webSearchToolPlan(context.settings, {
8090
+ hostedWebSearch: model?.model.hostedWebSearch ?? context.settings.webSearchEnabled,
8091
+ transportHostedSearch: model?.provider.kind === "xai-subscription"
8092
+ }) : { hostedWebSearch: false, providerTools: [] };
8079
8093
  if (model?.provider.kind === "xai-subscription") {
8080
8094
  if (context?.settings.webSearchEnabled) hostedToolNames.push("web_search", "x_search");
8081
- } else if (model?.model.hostedWebSearch) hostedToolNames.push("web_search");
8095
+ } else if (model?.model.hostedWebSearch && webSearchPlan.hostedWebSearch)
8096
+ hostedToolNames.push("web_search");
8082
8097
  const mediaAttachment = context?.mediaAttachments?.get(session.id) ?? (context?.objectStorageAvailable === false ? { image: null, video: false } : void 0);
8083
8098
  const media = resolveAgentMediaToolSurface(session.agent, mediaAttachment);
8084
8099
  hostedToolNames.push(...media.hosted);
@@ -8087,7 +8102,8 @@ function sessionEffectiveToolProjectionInput(session, toolRefs, context) {
8087
8102
  "list_models",
8088
8103
  "skill_read",
8089
8104
  ...AGENT_SKILL_MANAGE_TOOL_NAMES,
8090
- ...media.runtime
8105
+ ...media.runtime,
8106
+ ...webSearchPlan.providerTools
8091
8107
  ] : [];
8092
8108
  const sandboxToolNames = sandboxAvailable ? [
8093
8109
  "exec_command",
@@ -8146,6 +8162,7 @@ function sessionEffectiveToolProjectionInput(session, toolRefs, context) {
8146
8162
  "skill_read",
8147
8163
  "request_human_input",
8148
8164
  "list_models",
8165
+ ...webSearchPlan.providerTools,
8149
8166
  ...toolRefs.some((ref) => ref.id === "opengeni" && ref.eager === true) || !progressiveDisclosure ? firstPartyMcpTools.filter((name) => !interactionNames.has(name) || !progressiveDisclosure) : [],
8150
8167
  ...!progressiveDisclosure ? runtimeToolNames : []
8151
8168
  ]);
@@ -8155,7 +8172,7 @@ function sessionEffectiveToolProjectionInput(session, toolRefs, context) {
8155
8172
  ...session.toolPolicy.mode !== "workspace_default" ? session.tools.map((tool) => tool.id) : []
8156
8173
  ]),
8157
8174
  hasSkills: context ? sessionHasSkills(session, context) : false,
8158
- webSearch: hostedToolNames.includes("web_search"),
8175
+ webSearch: hostedToolNames.includes("web_search") || webSearchPlan.providerTools.length > 0,
8159
8176
  humanInput: context?.humanInputEnabled ?? false,
8160
8177
  media: media.toolsKnown ? media.hosted.length + media.runtime.length > 0 : void 0,
8161
8178
  routerInHistory: context?.routerInHistory === true || context?.routerHistorySessionIds?.has(session.id) === true,
@@ -9914,7 +9931,12 @@ async function postUserMessageTurn(input) {
9914
9931
  }
9915
9932
  } : {},
9916
9933
  controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs()
9917
- })
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)
9918
9940
  )
9919
9941
  );
9920
9942
  } catch (error) {
@@ -10167,6 +10189,12 @@ async function withSessionCreateUsageRecording(input) {
10167
10189
  }
10168
10190
  async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspaceId, rawPayload, authorization, agentChildPresentation, requestOptions = {}) {
10169
10191
  const payload = CreateSessionRequest.parse(rawPayload);
10192
+ if (payload.firstPartyMcpTools) {
10193
+ const unavailable = deploymentUnavailableFirstPartyMcpTools(unresolvedDeps.settings);
10194
+ payload.firstPartyMcpTools = payload.firstPartyMcpTools.filter(
10195
+ (tool) => !unavailable.has(tool)
10196
+ );
10197
+ }
10170
10198
  const surface = resolveTurnSurface({
10171
10199
  grant,
10172
10200
  authorization,
@@ -10298,24 +10326,29 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
10298
10326
  let retainedKeyedShellModel = null;
10299
10327
  let retainedKeyedShellReasoningEffort = null;
10300
10328
  if (payload.idempotencyKey && (effectiveVisibility !== "user_private" || replayManagedHumanSubjectId !== null)) {
10329
+ const createIdempotencyKey = payload.idempotencyKey;
10301
10330
  try {
10302
- const initializedReplay = await getInitializedSessionCreateReplay(db, {
10303
- bundledSkillIds,
10304
- accountId: grant.accountId,
10305
- workspaceId,
10306
- subjectId: replayManagedHumanSubjectId ?? grant.subjectId,
10307
- ...replayManagedHumanSubjectId ? { activeManagedHumanSubjectId: replayManagedHumanSubjectId } : {},
10308
- createIdempotencyKey: payload.idempotencyKey,
10309
- selectedInstalledSkillIds: payload.installedSkillIds ?? [],
10310
- initialAgentLearning: payload.agentLearning,
10311
- ...sessionScope,
10312
- ...payload.requestedSessionId ? { requestedSessionId: payload.requestedSessionId } : {},
10313
- visibility: effectiveVisibility,
10314
- variableSetIds: payload.variableSetIds ?? [],
10315
- initialPersonalResourceAttachmentIntent: payload.personalResourceAttachment ?? null,
10316
- deferInitialTurn: payload.startMode === "realtime",
10317
- agentConfig
10318
- });
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
+ );
10319
10352
  if (initializedReplay) {
10320
10353
  if (initializedReplay.outcome === "denied") {
10321
10354
  throw new SessionSpawnDeniedError(SessionSpawnDenial.parse(initializedReplay.denial));
@@ -10624,40 +10657,50 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
10624
10657
  }
10625
10658
  const tools = withFirstPartyTools(selectedTools, runtimeSettings);
10626
10659
  const captureLinkedAuthority = prepareExternalLinkTurnAdmission(authorization);
10627
- await validateGitHubRepositorySelection(db, workspaceId, resources);
10628
- if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
10629
- throw new HTTPException17(503, {
10630
- message: "object storage is not configured"
10631
- });
10632
- }
10633
- const attachmentOwnerContext = authorization ? await fileOwnerContextForAccess({ db }, authorization, "sessions:create") : grant.principalKind === "agent_attempt" ? await fileOwnerContextForAgent({ db }, grant, "sessions:create") : void 0;
10634
- const attachmentOwner = attachmentOwnerContext?.privateFileOwnerSubjectId === grant.subjectId ? grant.subjectId : null;
10635
- await validateFileResources(
10636
- db,
10637
- grant.accountId,
10638
- workspaceId,
10639
- personalResourceSubjectId ?? grant.subjectId,
10640
- resources,
10641
- attachmentOwnerContext
10642
- );
10643
- const variableSets = [];
10644
- for (const variableSetId of payload.variableSetIds ?? []) {
10645
- variableSets.push(
10646
- await validateVariableSetAttachment({ settings, db }, grant, workspaceId, variableSetId)
10647
- );
10648
- }
10649
- const requestedRigId = payload.rigId === void 0 ? await getWorkspaceDefaultRigId(db, workspaceId) : payload.rigId;
10650
- let frozenRigId = null;
10651
- let frozenRigVersionId = null;
10652
- if (requestedRigId) {
10653
- const rig = await getRig2(db, grant, requestedRigId);
10654
- if (!rig || !rig.activeVersion) {
10655
- if (payload.rigId) {
10656
- throw new HTTPException17(422, {
10657
- 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"
10658
10668
  });
10659
10669
  }
10660
- } 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
+ }
10661
10704
  for (const defaultVariableSetId of new Set(rig.activeVersion.defaultVariableSetIds)) {
10662
10705
  await validateVariableSetAttachment(
10663
10706
  { settings, db },
@@ -10666,10 +10709,12 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
10666
10709
  defaultVariableSetId
10667
10710
  );
10668
10711
  }
10669
- frozenRigId = rig.id;
10670
- frozenRigVersionId = rig.activeVersion.id;
10712
+ return {
10713
+ frozenRigId: rig.id,
10714
+ frozenRigVersionId: rig.activeVersion.id
10715
+ };
10671
10716
  }
10672
- }
10717
+ );
10673
10718
  let channelId = null;
10674
10719
  if (payload.channelId) {
10675
10720
  const channel = await getChannel(db, workspaceId, payload.channelId);
@@ -10684,19 +10729,21 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
10684
10729
  if (model === null || model === void 0) {
10685
10730
  throw new Error("effective session model unexpectedly resolved to null");
10686
10731
  }
10687
- await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model);
10688
- if (retainedKeyedShellModel === null && !parentSession) {
10689
- const selections = await resolveCallerWorkspaceModelSelections(db, settings, {
10690
- accountId: grant.accountId,
10691
- workspaceId,
10692
- subjectId: personalResourceSubjectId ?? grant.subjectId,
10693
- ...xaiProviderAccountAuthoritySnapshot ? { xaiAuthoritySnapshot: xaiProviderAccountAuthoritySnapshot } : {},
10694
- ...claudeProviderAccountAuthoritySnapshot ? { claudeAuthoritySnapshot: claudeProviderAccountAuthoritySnapshot } : {}
10695
- });
10696
- if (!admissibleWorkspaceModel(selections, model)) {
10697
- 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
+ }
10698
10745
  }
10699
- }
10746
+ });
10700
10747
  const inheritedReasoningEffort = parentCallingTurn?.reasoningEffort ?? parentSession?.reasoningEffort ?? resolvedDefault?.reasoningEffort ?? settings.openaiReasoningEffort;
10701
10748
  const reasoningEffort = payload.reasoningEffort ?? inheritedReasoningEffort;
10702
10749
  const latencyMode = payload.latencyMode ?? "standard";
@@ -10815,18 +10862,22 @@ async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspa
10815
10862
  workspace.settings
10816
10863
  )
10817
10864
  });
10818
- const { personalConnectionDelegations, mcpAccountBindings } = await freezeConnectionAccounts({
10819
- db,
10820
- accountId: grant.accountId,
10821
- workspaceId,
10822
- settings: runtimeSettings,
10823
- tools: connectionAccountTools,
10824
- resources,
10825
- source: connectionDelegationSource,
10826
- authoritySelections: payload.connectionAccounts,
10827
- googleDrivePublicationEnabled,
10828
- atlassianEnabled
10829
- });
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
+ );
10830
10881
  if (effectiveGoal) {
10831
10882
  const missingGoalTools = ["goal_update", "goal_progress", "goal_complete", "goal_pause"].filter(
10832
10883
  (name) => !firstPartyMcpTools.includes(name)
@@ -11464,6 +11515,10 @@ async function acceptSessionUserMessageInFileScope(deps, grant, workspaceId, ses
11464
11515
  (await requireWorkspace5(db, workspaceId)).settings
11465
11516
  )
11466
11517
  });
11518
+ const existingEffectiveFirstPartyTools = allowedFirstPartyMcpToolsForSession3(
11519
+ settings,
11520
+ existingSession.firstPartyMcpTools
11521
+ );
11467
11522
  const { personalConnectionDelegations, mcpAccountBindings } = await freezeConnectionAccounts({
11468
11523
  db,
11469
11524
  accountId: grant.accountId,
@@ -11473,7 +11528,7 @@ async function acceptSessionUserMessageInFileScope(deps, grant, workspaceId, ses
11473
11528
  resources: [...existingSession.resources, ...requestedResources],
11474
11529
  source: connectionDelegationSource,
11475
11530
  targetSessionId: sessionId,
11476
- googleDrivePublicationEnabled: existingSession.firstPartyMcpTools.includes("editable_artifact_export") && existingSession.firstPartyMcpTools.includes("editable_artifact_export_status") && (!existingSession.firstPartyMcpPermissions?.length || existingSession.firstPartyMcpPermissions.includes("artifacts:read") && existingSession.firstPartyMcpPermissions.includes("artifacts:publish")),
11531
+ googleDrivePublicationEnabled: existingEffectiveFirstPartyTools.includes("editable_artifact_export") && existingEffectiveFirstPartyTools.includes("editable_artifact_export_status") && (!existingSession.firstPartyMcpPermissions?.length || existingSession.firstPartyMcpPermissions.includes("artifacts:read") && existingSession.firstPartyMcpPermissions.includes("artifacts:publish")),
11477
11532
  atlassianEnabled: existingSession.firstPartyMcpTools.some((tool) => tool.startsWith("atlassian_")) && (!existingSession.firstPartyMcpPermissions?.length || existingSession.firstPartyMcpPermissions.includes("connections:read")),
11478
11533
  ...input.connectionAccounts ? { authoritySelections: input.connectionAccounts } : {}
11479
11534
  });
@@ -12329,6 +12384,10 @@ async function freezeSessionRealtimeConnectionAccounts(input) {
12329
12384
  (await requireWorkspace6(db, workspaceId)).settings
12330
12385
  )
12331
12386
  });
12387
+ const effectiveFirstPartyTools = allowedFirstPartyMcpToolsForSession4(
12388
+ settings,
12389
+ session.firstPartyMcpTools
12390
+ );
12332
12391
  return await freezeConnectionAccounts({
12333
12392
  db,
12334
12393
  settings: runtimeSettings,
@@ -12338,7 +12397,7 @@ async function freezeSessionRealtimeConnectionAccounts(input) {
12338
12397
  resources: session.resources,
12339
12398
  source: personalConnectionDelegationSourceForGrant(grant),
12340
12399
  targetSessionId: sessionId,
12341
- googleDrivePublicationEnabled: session.firstPartyMcpTools.includes("editable_artifact_export") && session.firstPartyMcpTools.includes("editable_artifact_export_status") && (!session.firstPartyMcpPermissions?.length || session.firstPartyMcpPermissions.includes("artifacts:read") && session.firstPartyMcpPermissions.includes("artifacts:publish")),
12400
+ googleDrivePublicationEnabled: effectiveFirstPartyTools.includes("editable_artifact_export") && effectiveFirstPartyTools.includes("editable_artifact_export_status") && (!session.firstPartyMcpPermissions?.length || session.firstPartyMcpPermissions.includes("artifacts:read") && session.firstPartyMcpPermissions.includes("artifacts:publish")),
12342
12401
  atlassianEnabled: session.firstPartyMcpTools.some((tool) => tool.startsWith("atlassian_")) && (!session.firstPartyMcpPermissions?.length || session.firstPartyMcpPermissions.includes("connections:read"))
12343
12402
  });
12344
12403
  }
@@ -12603,109 +12662,41 @@ async function searchKnowledgeEntries(db, context, input, embedder, settings) {
12603
12662
  );
12604
12663
  }
12605
12664
 
12606
- // src/workflow-wake-contract.ts
12607
- var SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID = "opengeni-session-workflow-wake-dispatcher";
12608
- var SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE = "sessionWorkflowWakeDispatcherWorkflow";
12609
- var SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS = 1e4;
12610
- var TURN_ACTIVITY_CANCELLATION_HEARTBEAT_INTERVAL_MS = 500;
12665
+ // src/domain/voice-input-billing.ts
12666
+ import {
12667
+ calculateVoiceInputCost,
12668
+ configuredStaticUsageLimits as configuredStaticUsageLimits3,
12669
+ voiceInputCreditBillingActive as voiceInputCreditBillingActive2,
12670
+ voiceInputPricingHasTokenRates
12671
+ } from "@opengeni/config";
12672
+ import {
12673
+ applyCreditDebitAfterUse as applyCreditDebitAfterUse3,
12674
+ checkWorkspaceAllowance as checkWorkspaceAllowance4,
12675
+ creditDebitAttributionMetadata as creditDebitAttributionMetadata2,
12676
+ getSpendableCreditBalance as getSpendableCreditBalance4,
12677
+ listUnsettledVoiceTranscriptionCharges,
12678
+ recordUsageEvent as recordUsageEvent4,
12679
+ sumUsageQuantity as sumUsageQuantity4,
12680
+ VOICE_TRANSCRIPTION_DEBIT_TYPE,
12681
+ VOICE_TRANSCRIPTION_SOURCE_TYPE,
12682
+ voiceTranscriptionSettlementKeys,
12683
+ VOICE_CREDIT_USAGE as VOICE_CREDIT_USAGE2
12684
+ } from "@opengeni/db";
12611
12685
 
12612
- // src/user-presence.ts
12613
- import { recordUserActivityPresence } from "@opengeni/db";
12614
- var USER_PRESENCE_THROTTLE_MS = 6e4;
12615
- var USER_PRESENCE_FLUSH_DELAY_MS = 5e3;
12616
- var USER_PRESENCE_MAX_TRACKED = 5e4;
12617
- var USER_PRESENCE_SUBJECT = /^user:[A-Za-z0-9_-]{8,128}$/;
12618
- var PRESENCE_FLUSH_METRIC = {
12619
- name: "opengeni_user_presence_flushes_total",
12620
- help: "Batched user presence writes from this API process by outcome."
12686
+ // src/transcription.ts
12687
+ var TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS = 10 * 60 * 1e3;
12688
+ var TranscriptionBillingRefusedError = class extends Error {
12689
+ status;
12690
+ code;
12691
+ details;
12692
+ constructor(input) {
12693
+ super(input.message);
12694
+ this.name = "TranscriptionBillingRefusedError";
12695
+ this.code = input.code;
12696
+ this.status = input.code === "monthly_model_cost_limit" ? 429 : 402;
12697
+ this.details = input.details ?? {};
12698
+ }
12621
12699
  };
12622
- function createUserPresenceRecorder(options) {
12623
- const throttleMs = options.throttleMs ?? USER_PRESENCE_THROTTLE_MS;
12624
- const flushDelayMs = options.flushDelayMs ?? USER_PRESENCE_FLUSH_DELAY_MS;
12625
- const maxTracked = options.maxTracked ?? USER_PRESENCE_MAX_TRACKED;
12626
- const now = options.now ?? Date.now;
12627
- const record3 = options.record ?? recordUserActivityPresence;
12628
- const lastQueuedAt = /* @__PURE__ */ new Map();
12629
- const pending = /* @__PURE__ */ new Set();
12630
- let timer = null;
12631
- let closed = false;
12632
- let lastWarningAt = 0;
12633
- let tail = Promise.resolve();
12634
- const count = (outcome) => {
12635
- try {
12636
- options.observability?.incrementCounter({ ...PRESENCE_FLUSH_METRIC, labels: { outcome } });
12637
- } catch {
12638
- }
12639
- };
12640
- const prune = (at) => {
12641
- for (const [subject, queuedAt] of lastQueuedAt) {
12642
- if (at - queuedAt >= throttleMs && !pending.has(subject)) lastQueuedAt.delete(subject);
12643
- }
12644
- if (lastQueuedAt.size > maxTracked) {
12645
- const excess = lastQueuedAt.size - maxTracked;
12646
- let removed = 0;
12647
- for (const subject of lastQueuedAt.keys()) {
12648
- if (removed >= excess) break;
12649
- if (pending.has(subject)) continue;
12650
- lastQueuedAt.delete(subject);
12651
- removed += 1;
12652
- }
12653
- }
12654
- };
12655
- const writeBatch = async () => {
12656
- if (pending.size === 0) return;
12657
- const batch = [...pending];
12658
- pending.clear();
12659
- try {
12660
- await record3(options.db, batch);
12661
- count("ok");
12662
- } catch (error) {
12663
- for (const subject of batch) lastQueuedAt.delete(subject);
12664
- count("failed");
12665
- const at = now();
12666
- if (at - lastWarningAt >= throttleMs) {
12667
- lastWarningAt = at;
12668
- options.observability?.warn("user presence write failed", {
12669
- errorClass: error instanceof Error ? error.name : "UnknownError"
12670
- });
12671
- }
12672
- }
12673
- };
12674
- const flush = () => {
12675
- if (timer) {
12676
- clearTimeout(timer);
12677
- timer = null;
12678
- }
12679
- tail = tail.then(writeBatch, writeBatch);
12680
- return tail;
12681
- };
12682
- return {
12683
- touch(subjectId) {
12684
- if (closed || !USER_PRESENCE_SUBJECT.test(subjectId)) return;
12685
- const at = now();
12686
- const previous = lastQueuedAt.get(subjectId);
12687
- if (previous !== void 0 && at - previous < throttleMs) return;
12688
- lastQueuedAt.set(subjectId, at);
12689
- pending.add(subjectId);
12690
- if (lastQueuedAt.size > maxTracked) prune(at);
12691
- if (!timer) {
12692
- timer = setTimeout(() => {
12693
- timer = null;
12694
- void flush();
12695
- }, flushDelayMs);
12696
- timer.unref?.();
12697
- }
12698
- },
12699
- flush,
12700
- async close() {
12701
- closed = true;
12702
- await flush();
12703
- }
12704
- };
12705
- }
12706
-
12707
- // src/transcription.ts
12708
- var TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS = 10 * 60 * 1e3;
12709
12700
  var TranscriptionServiceError = class extends Error {
12710
12701
  code;
12711
12702
  status;
@@ -12781,6 +12772,685 @@ function filenameForMimeType(mimeType) {
12781
12772
  }
12782
12773
  }
12783
12774
 
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;
12811
+ }
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
+ };
12827
+ }
12828
+ for (const gateway of Object.values(AI_GATEWAY_REALTIME_MODELS)) {
12829
+ if (gateway.managedModelId !== model) continue;
12830
+ return {
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
12840
+ };
12841
+ }
12842
+ return null;
12843
+ }
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;
12865
+ }
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) {
12875
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
12876
+ }
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)
13116
+ });
13117
+ if (refusal) {
13118
+ throw new TranscriptionBillingRefusedError({
13119
+ code: refusal.code,
13120
+ message: refusal.message,
13121
+ details: {
13122
+ scope: refusal.scope,
13123
+ resetsAt: refusal.resetsAt,
13124
+ ...refusal.subjectId ? { subjectId: refusal.subjectId } : {}
13125
+ }
13126
+ });
13127
+ }
13128
+ if (deps.settings.usageLimitsMode === "static" || deps.settings.usageLimitsMode === "managed") {
13129
+ const cap = configuredStaticUsageLimits3(deps.settings).maxMonthlyCostMicrosPerAccount;
13130
+ if (cap) {
13131
+ const used = await sumUsageQuantity4(deps.db, {
13132
+ accountId,
13133
+ eventType: "model.cost",
13134
+ since: startOfUtcMonth3()
13135
+ });
13136
+ if (used >= cap) {
13137
+ throw new TranscriptionBillingRefusedError({
13138
+ code: "monthly_model_cost_limit",
13139
+ message: "The monthly model cost limit has been reached."
13140
+ });
13141
+ }
13142
+ }
13143
+ }
13144
+ },
13145
+ async settle(input) {
13146
+ if (!voiceInputCreditBillingActive2(deps.settings)) return { creditCostMicros: 0 };
13147
+ const { usage, basis } = voiceInputBillableUsage({
13148
+ pricing: input.pricing,
13149
+ usage: input.usage,
13150
+ trustedDurationSeconds: input.billing.trustedDurationSeconds
13151
+ });
13152
+ const cost = calculateVoiceInputCost(input.pricing, usage);
13153
+ return await settleVoiceInputUsage(deps.db, {
13154
+ accountId: input.accountId,
13155
+ workspaceId: input.workspaceId,
13156
+ providerId: input.providerId,
13157
+ model: input.model,
13158
+ billing: input.billing,
13159
+ usage,
13160
+ basis,
13161
+ providerCostMicros: cost.providerCostMicros,
13162
+ creditCostMicros: cost.creditCostMicros
13163
+ });
13164
+ }
13165
+ };
13166
+ }
13167
+ async function settleVoiceInputUsage(db, input) {
13168
+ const attribution = input.billing.attribution;
13169
+ const keys = voiceTranscriptionSettlementKeys({
13170
+ workspaceId: input.workspaceId,
13171
+ sourceId: input.billing.sourceId
13172
+ });
13173
+ const receipt2 = await recordUsageEvent4(db, {
13174
+ accountId: input.accountId,
13175
+ workspaceId: input.workspaceId,
13176
+ eventType: "model.cost",
13177
+ quantity: input.creditCostMicros,
13178
+ unit: "usd_micros",
13179
+ sourceResourceType: VOICE_TRANSCRIPTION_SOURCE_TYPE,
13180
+ sourceResourceId: input.billing.sourceId,
13181
+ idempotencyKey: keys.usageIdempotencyKey,
13182
+ // The API is the writer; the payer is the trusted attribution snapshot.
13183
+ initiator: { kind: "service", subjectId: "api:voice-input" },
13184
+ initiatorContext: { creditDebitAttribution: attribution }
13185
+ });
13186
+ const amount = Number(receipt2.quantity);
13187
+ if (amount > 0) {
13188
+ await applyCreditDebitAfterUse3(db, {
13189
+ accountId: input.accountId,
13190
+ workspaceId: input.workspaceId,
13191
+ type: VOICE_TRANSCRIPTION_DEBIT_TYPE,
13192
+ amountMicros: amount,
13193
+ sourceType: VOICE_TRANSCRIPTION_SOURCE_TYPE,
13194
+ sourceId: input.billing.sourceId,
13195
+ idempotencyKey: keys.debitIdempotencyKey,
13196
+ usage: VOICE_CREDIT_USAGE2,
13197
+ metadata: {
13198
+ providerId: input.providerId,
13199
+ model: input.model,
13200
+ basis: input.basis,
13201
+ providerCostMicros: input.providerCostMicros,
13202
+ ...input.usage.kind === "tokens" ? {
13203
+ inputTokens: input.usage.inputTokens,
13204
+ audioInputTokens: input.usage.audioInputTokens,
13205
+ textInputTokens: input.usage.textInputTokens,
13206
+ outputTokens: input.usage.outputTokens
13207
+ } : { audioMilliseconds: Math.ceil(input.usage.seconds * 1e3) },
13208
+ ...attribution.kind === "unknown" ? {} : creditDebitAttributionMetadata2(attribution)
13209
+ }
13210
+ });
13211
+ }
13212
+ return { creditCostMicros: amount };
13213
+ }
13214
+ async function reconcileUnsettledVoiceInputCharges(db, input) {
13215
+ const pending = await listUnsettledVoiceTranscriptionCharges(db, {
13216
+ accountId: input.accountId,
13217
+ workspaceId: input.workspaceId,
13218
+ ...input.minAgeMilliseconds !== void 0 ? { minAgeMilliseconds: input.minAgeMilliseconds } : {}
13219
+ });
13220
+ for (const charge of pending) {
13221
+ await applyCreditDebitAfterUse3(db, {
13222
+ accountId: input.accountId,
13223
+ workspaceId: input.workspaceId,
13224
+ type: VOICE_TRANSCRIPTION_DEBIT_TYPE,
13225
+ amountMicros: charge.amountMicros,
13226
+ sourceType: VOICE_TRANSCRIPTION_SOURCE_TYPE,
13227
+ sourceId: charge.sourceId,
13228
+ idempotencyKey: voiceTranscriptionSettlementKeys({
13229
+ workspaceId: input.workspaceId,
13230
+ sourceId: charge.sourceId
13231
+ }).debitIdempotencyKey,
13232
+ usage: VOICE_CREDIT_USAGE2,
13233
+ metadata: {
13234
+ basis: "reconciled_receipt",
13235
+ ...charge.attribution.kind === "unknown" ? {} : creditDebitAttributionMetadata2(charge.attribution)
13236
+ }
13237
+ });
13238
+ }
13239
+ return { reconciled: pending.length };
13240
+ }
13241
+
13242
+ // src/domain/web-search-billing.ts
13243
+ import {
13244
+ applyCreditDebitAfterUse as applyCreditDebitAfterUse4,
13245
+ checkWorkspaceAllowance as checkWorkspaceAllowance5,
13246
+ creditDebitAttributionForTurn as creditDebitAttributionForTurn2,
13247
+ creditDebitAttributionMetadata as creditDebitAttributionMetadata3,
13248
+ getSpendableCreditBalance as getSpendableCreditBalance5,
13249
+ recordUsageEvent as recordUsageEvent5,
13250
+ withRlsContext as withRlsContext2
13251
+ } from "@opengeni/db";
13252
+ var WEB_SEARCH_DEBIT_TYPE = "web_search_debit";
13253
+ var WEB_SEARCH_SOURCE_TYPE = "web_search";
13254
+ var WEB_SEARCH_BILLING_INITIATOR = "worker:web-search";
13255
+ function webSearchCreditBillingActive(settings) {
13256
+ return settings.billingMode === "stripe" || settings.usageLimitsMode === "managed";
13257
+ }
13258
+ var WebSearchBillingRefusedError = class extends Error {
13259
+ constructor(code, message) {
13260
+ super(message);
13261
+ this.code = code;
13262
+ this.name = "WebSearchBillingRefusedError";
13263
+ }
13264
+ };
13265
+ function createWebSearchBilling(deps) {
13266
+ const active = webSearchCreditBillingActive(deps.settings);
13267
+ const attributionByTurn = /* @__PURE__ */ new Map();
13268
+ const attributionFor = (scope) => {
13269
+ let attribution = attributionByTurn.get(scope.turnId);
13270
+ if (!attribution) {
13271
+ attribution = creditDebitAttributionForTurn2(deps.db, scope);
13272
+ attributionByTurn.set(scope.turnId, attribution);
13273
+ attribution.catch(() => attributionByTurn.delete(scope.turnId));
13274
+ }
13275
+ return attribution;
13276
+ };
13277
+ return {
13278
+ active,
13279
+ async admit(scope, expectedProviderMicros) {
13280
+ if (!active || expectedProviderMicros <= 0) return;
13281
+ const attribution = await attributionFor(scope);
13282
+ const balance = await getSpendableCreditBalance5(deps.db, scope.accountId);
13283
+ if (balance.balanceMicros <= 0) {
13284
+ throw new WebSearchBillingRefusedError(
13285
+ "insufficient_credits",
13286
+ "Web search needs Opengeni credits, and this account has none left."
13287
+ );
13288
+ }
13289
+ const refusal = await checkWorkspaceAllowance5(deps.db, {
13290
+ accountId: scope.accountId,
13291
+ workspaceId: scope.workspaceId,
13292
+ subjectId: attribution.kind === "turn" ? attribution.initiatingHumanSubjectId : null
13293
+ });
13294
+ if (refusal) throw new WebSearchBillingRefusedError("allowance_exhausted", refusal.message);
13295
+ },
13296
+ async settle(scope, cost) {
13297
+ const shared = {
13298
+ accountId: scope.accountId,
13299
+ workspaceId: scope.workspaceId,
13300
+ sessionId: scope.sessionId,
13301
+ turnId: scope.turnId,
13302
+ turnAttemptId: scope.attemptId,
13303
+ sourceResourceType: WEB_SEARCH_SOURCE_TYPE,
13304
+ sourceResourceId: `${scope.attemptId}:${cost.operationId}`
13305
+ };
13306
+ await recordUsageEvent5(deps.db, {
13307
+ ...shared,
13308
+ eventType: `web_search.${cost.operation}_requests`,
13309
+ quantity: 1,
13310
+ unit: "request",
13311
+ idempotencyKey: `usage:web_search.${cost.operation}_requests:${shared.sourceResourceId}`
13312
+ });
13313
+ if (!active || cost.creditMicros <= 0) return;
13314
+ const attribution = await attributionFor(scope);
13315
+ await withRlsContext2(
13316
+ deps.db,
13317
+ { accountId: scope.accountId, workspaceId: scope.workspaceId },
13318
+ async (tx) => {
13319
+ await recordUsageEvent5(tx, {
13320
+ ...shared,
13321
+ eventType: "web_search.cost",
13322
+ quantity: cost.creditMicros,
13323
+ unit: "usd_micros",
13324
+ idempotencyKey: `usage:web_search.cost:${shared.sourceResourceId}`,
13325
+ initiator: { kind: "service", subjectId: WEB_SEARCH_BILLING_INITIATOR },
13326
+ initiatorContext: { creditDebitAttribution: attribution }
13327
+ });
13328
+ await applyCreditDebitAfterUse4(tx, {
13329
+ accountId: scope.accountId,
13330
+ workspaceId: scope.workspaceId,
13331
+ type: WEB_SEARCH_DEBIT_TYPE,
13332
+ amountMicros: cost.creditMicros,
13333
+ sourceType: WEB_SEARCH_SOURCE_TYPE,
13334
+ sourceId: shared.sourceResourceId,
13335
+ idempotencyKey: `credit:${WEB_SEARCH_DEBIT_TYPE}:${shared.sourceResourceId}`,
13336
+ metadata: {
13337
+ // turnId lets the allowance trigger resolve the causal human
13338
+ // from the immutable turn receipt.
13339
+ ...creditDebitAttributionMetadata3(attribution),
13340
+ provider: cost.provider,
13341
+ operation: cost.operation,
13342
+ providerCostMicros: cost.providerMicros,
13343
+ marginBps: cost.marginBps,
13344
+ basis: cost.basis
13345
+ }
13346
+ });
13347
+ }
13348
+ );
13349
+ }
13350
+ };
13351
+ }
13352
+
13353
+ // src/workflow-wake-contract.ts
13354
+ var SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID = "opengeni-session-workflow-wake-dispatcher";
13355
+ var SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE = "sessionWorkflowWakeDispatcherWorkflow";
13356
+ var SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS = 1e4;
13357
+ var TURN_ACTIVITY_CANCELLATION_HEARTBEAT_INTERVAL_MS = 500;
13358
+
13359
+ // src/user-presence.ts
13360
+ import { recordUserActivityPresence } from "@opengeni/db";
13361
+ var USER_PRESENCE_THROTTLE_MS = 6e4;
13362
+ var USER_PRESENCE_FLUSH_DELAY_MS = 5e3;
13363
+ var USER_PRESENCE_MAX_TRACKED = 5e4;
13364
+ var USER_PRESENCE_SUBJECT = /^user:[A-Za-z0-9_-]{8,128}$/;
13365
+ var PRESENCE_FLUSH_METRIC = {
13366
+ name: "opengeni_user_presence_flushes_total",
13367
+ help: "Batched user presence writes from this API process by outcome."
13368
+ };
13369
+ function createUserPresenceRecorder(options) {
13370
+ const throttleMs = options.throttleMs ?? USER_PRESENCE_THROTTLE_MS;
13371
+ const flushDelayMs = options.flushDelayMs ?? USER_PRESENCE_FLUSH_DELAY_MS;
13372
+ const maxTracked = options.maxTracked ?? USER_PRESENCE_MAX_TRACKED;
13373
+ const now = options.now ?? Date.now;
13374
+ const record3 = options.record ?? recordUserActivityPresence;
13375
+ const lastQueuedAt = /* @__PURE__ */ new Map();
13376
+ const pending = /* @__PURE__ */ new Set();
13377
+ let timer = null;
13378
+ let closed = false;
13379
+ let lastWarningAt = 0;
13380
+ let tail = Promise.resolve();
13381
+ const count = (outcome) => {
13382
+ try {
13383
+ options.observability?.incrementCounter({ ...PRESENCE_FLUSH_METRIC, labels: { outcome } });
13384
+ } catch {
13385
+ }
13386
+ };
13387
+ const prune = (at) => {
13388
+ for (const [subject, queuedAt] of lastQueuedAt) {
13389
+ if (at - queuedAt >= throttleMs && !pending.has(subject)) lastQueuedAt.delete(subject);
13390
+ }
13391
+ if (lastQueuedAt.size > maxTracked) {
13392
+ const excess = lastQueuedAt.size - maxTracked;
13393
+ let removed = 0;
13394
+ for (const subject of lastQueuedAt.keys()) {
13395
+ if (removed >= excess) break;
13396
+ if (pending.has(subject)) continue;
13397
+ lastQueuedAt.delete(subject);
13398
+ removed += 1;
13399
+ }
13400
+ }
13401
+ };
13402
+ const writeBatch = async () => {
13403
+ if (pending.size === 0) return;
13404
+ const batch = [...pending];
13405
+ pending.clear();
13406
+ try {
13407
+ await record3(options.db, batch);
13408
+ count("ok");
13409
+ } catch (error) {
13410
+ for (const subject of batch) lastQueuedAt.delete(subject);
13411
+ count("failed");
13412
+ const at = now();
13413
+ if (at - lastWarningAt >= throttleMs) {
13414
+ lastWarningAt = at;
13415
+ options.observability?.warn("user presence write failed", {
13416
+ errorClass: error instanceof Error ? error.name : "UnknownError"
13417
+ });
13418
+ }
13419
+ }
13420
+ };
13421
+ const flush = () => {
13422
+ if (timer) {
13423
+ clearTimeout(timer);
13424
+ timer = null;
13425
+ }
13426
+ tail = tail.then(writeBatch, writeBatch);
13427
+ return tail;
13428
+ };
13429
+ return {
13430
+ touch(subjectId) {
13431
+ if (closed || !USER_PRESENCE_SUBJECT.test(subjectId)) return;
13432
+ const at = now();
13433
+ const previous = lastQueuedAt.get(subjectId);
13434
+ if (previous !== void 0 && at - previous < throttleMs) return;
13435
+ lastQueuedAt.set(subjectId, at);
13436
+ pending.add(subjectId);
13437
+ if (lastQueuedAt.size > maxTracked) prune(at);
13438
+ if (!timer) {
13439
+ timer = setTimeout(() => {
13440
+ timer = null;
13441
+ void flush();
13442
+ }, flushDelayMs);
13443
+ timer.unref?.();
13444
+ }
13445
+ },
13446
+ flush,
13447
+ async close() {
13448
+ closed = true;
13449
+ await flush();
13450
+ }
13451
+ };
13452
+ }
13453
+
12784
13454
  // src/goal-admission.ts
12785
13455
  import {
12786
13456
  isModelAvailableForNewSelection as isModelAvailableForNewSelection2,
@@ -12799,13 +13469,13 @@ import { getWorkspaceModelPolicy as getWorkspaceModelPolicy3 } from "@opengeni/d
12799
13469
  // src/billing/agent-run-admission.ts
12800
13470
  import {
12801
13471
  canonicalizeConfiguredModelId as canonicalizeConfiguredModelId4,
12802
- configuredStaticUsageLimits as configuredStaticUsageLimits2
13472
+ configuredStaticUsageLimits as configuredStaticUsageLimits4
12803
13473
  } from "@opengeni/config";
12804
13474
  import {
12805
- checkWorkspaceAllowance as checkWorkspaceAllowance3,
12806
- getSpendableCreditBalance as getSpendableCreditBalance3,
13475
+ checkWorkspaceAllowance as checkWorkspaceAllowance6,
13476
+ getSpendableCreditBalance as getSpendableCreditBalance6,
12807
13477
  isCodexBilledTurn as isCodexBilledTurn2,
12808
- sumUsageQuantity as sumUsageQuantity3
13478
+ sumUsageQuantity as sumUsageQuantity5
12809
13479
  } from "@opengeni/db";
12810
13480
  async function agentRunAdmissionDenial(services, input) {
12811
13481
  const codexBilled = await isCodexBilledTurn2({
@@ -12829,7 +13499,7 @@ async function agentRunAdmissionDenial(services, input) {
12829
13499
  });
12830
13500
  if (!decision.allowed) return "insufficient_credits";
12831
13501
  } else {
12832
- const balance = await getSpendableCreditBalance3(
13502
+ const balance = await getSpendableCreditBalance6(
12833
13503
  services.db,
12834
13504
  input.accountId,
12835
13505
  canonicalizeConfiguredModelId4(services.settings, input.model)
@@ -12838,7 +13508,7 @@ async function agentRunAdmissionDenial(services, input) {
12838
13508
  }
12839
13509
  }
12840
13510
  if (!externallyBilled) {
12841
- const refusal = await checkWorkspaceAllowance3(services.db, {
13511
+ const refusal = await checkWorkspaceAllowance6(services.db, {
12842
13512
  accountId: input.accountId,
12843
13513
  workspaceId: input.workspaceId,
12844
13514
  subjectId: input.initiatingHumanSubjectId ?? null
@@ -12848,22 +13518,22 @@ async function agentRunAdmissionDenial(services, input) {
12848
13518
  if (services.settings.usageLimitsMode !== "static" && services.settings.usageLimitsMode !== "managed") {
12849
13519
  return null;
12850
13520
  }
12851
- const limits = configuredStaticUsageLimits2(services.settings);
13521
+ const limits = configuredStaticUsageLimits4(services.settings);
12852
13522
  if (!externallyBilled && limits.maxMonthlyCostMicrosPerAccount) {
12853
- const used = await sumUsageQuantity3(services.db, {
13523
+ const used = await sumUsageQuantity5(services.db, {
12854
13524
  accountId: input.accountId,
12855
13525
  eventType: "model.cost",
12856
- since: startOfUtcMonth2()
13526
+ since: startOfUtcMonth4()
12857
13527
  });
12858
13528
  if (used >= limits.maxMonthlyCostMicrosPerAccount) {
12859
13529
  return "monthly_model_cost_limit";
12860
13530
  }
12861
13531
  }
12862
13532
  if (limits.maxMonthlyAgentRunsPerWorkspace) {
12863
- const used = await sumUsageQuantity3(services.db, {
13533
+ const used = await sumUsageQuantity5(services.db, {
12864
13534
  workspaceId: input.workspaceId,
12865
13535
  eventType: "agent_run.created",
12866
- since: startOfUtcMonth2()
13536
+ since: startOfUtcMonth4()
12867
13537
  });
12868
13538
  if (used + input.requestedAgentRuns > limits.maxMonthlyAgentRunsPerWorkspace) {
12869
13539
  return "monthly_agent_run_limit";
@@ -12871,7 +13541,7 @@ async function agentRunAdmissionDenial(services, input) {
12871
13541
  }
12872
13542
  return null;
12873
13543
  }
12874
- function startOfUtcMonth2() {
13544
+ function startOfUtcMonth4() {
12875
13545
  const now = /* @__PURE__ */ new Date();
12876
13546
  return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
12877
13547
  }
@@ -15581,7 +16251,7 @@ import {
15581
16251
  SCHEDULED_TASK_OCCURRENCE_PAYLOAD_INGRESS_HEADROOM_BYTES
15582
16252
  } from "@opengeni/contracts";
15583
16253
  import {
15584
- allowedFirstPartyMcpToolsForSession as allowedFirstPartyMcpToolsForSession4,
16254
+ allowedFirstPartyMcpToolsForSession as allowedFirstPartyMcpToolsForSession5,
15585
16255
  resolveFirstPartyMcpToolPolicy as resolveFirstPartyMcpToolPolicy2
15586
16256
  } from "@opengeni/config";
15587
16257
  import {
@@ -15689,7 +16359,7 @@ async function scheduledConnectionTools(db, workspaceId, settings, target, taskT
15689
16359
  }).toolRefs;
15690
16360
  }
15691
16361
  function scheduledConnectionSurfaceEligibility(settings, target) {
15692
- const tools = target?.firstPartyMcpTools ?? resolveFirstPartyMcpToolPolicy2(settings).default;
16362
+ const tools = allowedFirstPartyMcpToolsForSession5(settings, target?.firstPartyMcpTools);
15693
16363
  const permissions = target?.firstPartyMcpPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS3;
15694
16364
  return {
15695
16365
  googleDrivePublicationEnabled: tools.includes("editable_artifact_export") && tools.includes("editable_artifact_export_status") && permissions.includes("artifacts:read") && permissions.includes("artifacts:publish"),
@@ -16014,7 +16684,7 @@ async function frozenScheduledTaskCreatorPolicy(input) {
16014
16684
  restricted ||= Boolean(
16015
16685
  turnPolicy.kind === "valid" && turnPolicy.policy.credentialRestriction === "developer_setup" || sessionPolicy.kind === "valid" && sessionPolicy.policy.credentialRestriction === "developer_setup"
16016
16686
  );
16017
- const firstPartyMcpTools = allowedFirstPartyMcpToolsForSession4(
16687
+ const firstPartyMcpTools = allowedFirstPartyMcpToolsForSession5(
16018
16688
  input.settings,
16019
16689
  session.firstPartyMcpTools
16020
16690
  );
@@ -17315,7 +17985,7 @@ function trimmedScheduledTaskName(name) {
17315
17985
 
17316
17986
  // src/domain/scheduled-task-access.ts
17317
17987
  import {
17318
- allowedFirstPartyMcpToolsForSession as allowedFirstPartyMcpToolsForSession5,
17988
+ allowedFirstPartyMcpToolsForSession as allowedFirstPartyMcpToolsForSession6,
17319
17989
  resolveFirstPartyMcpToolPolicy as resolveFirstPartyMcpToolPolicy3
17320
17990
  } from "@opengeni/config";
17321
17991
  import {
@@ -17420,7 +18090,7 @@ function sameMembers(left, right) {
17420
18090
  function planScheduledTaskOpenGeniTools(input) {
17421
18091
  const stored = input.creatorPolicy?.firstPartyMcpTools;
17422
18092
  if (!stored) return { missing: [], policy: null };
17423
- const effective = new Set(allowedFirstPartyMcpToolsForSession5(input.settings, stored));
18093
+ const effective = new Set(allowedFirstPartyMcpToolsForSession6(input.settings, stored));
17424
18094
  const missing = resolveFirstPartyMcpToolPolicy3(input.settings).default.filter(
17425
18095
  (tool) => !effective.has(tool) && !input.leaveOut?.has(tool)
17426
18096
  );
@@ -17939,7 +18609,7 @@ function orderScheduledTaskAccessAttention(items) {
17939
18609
  }
17940
18610
 
17941
18611
  // src/domain/insights.ts
17942
- import { configuredStaticUsageLimits as configuredStaticUsageLimits3 } from "@opengeni/config";
18612
+ import { configuredStaticUsageLimits as configuredStaticUsageLimits5 } from "@opengeni/config";
17943
18613
  import {
17944
18614
  WorkspaceInsightsSnapshot
17945
18615
  } from "@opengeni/contracts";
@@ -18070,7 +18740,7 @@ function cacheHitPct(cached2, input) {
18070
18740
  function startOfUtcDay(date) {
18071
18741
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
18072
18742
  }
18073
- function startOfUtcMonth3(date) {
18743
+ function startOfUtcMonth5(date) {
18074
18744
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));
18075
18745
  }
18076
18746
  function startOfUtcYear(date) {
@@ -18099,7 +18769,7 @@ function resolveRangeWindow(range, now) {
18099
18769
  cacheSeriesLabel = "Cache hit % / UTC day";
18100
18770
  break;
18101
18771
  case "month":
18102
- since = startOfUtcMonth3(now);
18772
+ since = startOfUtcMonth5(now);
18103
18773
  rangeLabel = "This month (UTC)";
18104
18774
  priorLabel = "Prior equal window";
18105
18775
  seriesLabel = "Credit $ / UTC day";
@@ -18230,7 +18900,7 @@ async function getWorkspaceInsights(db, settings, input, observePhase) {
18230
18900
  until: window.until,
18231
18901
  priorSince: window.priorSince,
18232
18902
  priorUntil: window.priorUntil,
18233
- monthSince: startOfUtcMonth3(now),
18903
+ monthSince: startOfUtcMonth5(now),
18234
18904
  granularity: input.range === "today" ? "hour" : "day",
18235
18905
  warmGroupLimit: 24
18236
18906
  })
@@ -18473,7 +19143,7 @@ async function getWorkspaceInsights(db, settings, input, observePhase) {
18473
19143
  billing: fact ? billingPathOf(fact.billingPath) : null
18474
19144
  };
18475
19145
  });
18476
- const limits = configuredStaticUsageLimits3(settings);
19146
+ const limits = configuredStaticUsageLimits5(settings);
18477
19147
  const floor = floorRows.filter((row) => {
18478
19148
  if (!model) return true;
18479
19149
  return row.model === model;
@@ -20721,7 +21391,7 @@ async function submitComposerDraftForRequest(deps, grant, workspaceId, sessionId
20721
21391
  import { DraftTimelineAnnotations as DraftTimelineAnnotations3 } from "@opengeni/contracts";
20722
21392
  import { HTTPException as HTTPException30 } from "hono/http-exception";
20723
21393
  import {
20724
- checkWorkspaceAllowance as checkWorkspaceAllowance4,
21394
+ checkWorkspaceAllowance as checkWorkspaceAllowance7,
20725
21395
  deleteSessionQueueItemInTransaction,
20726
21396
  editQueuedTurnInTransaction,
20727
21397
  getComposerDraftInTransaction as getComposerDraftInTransaction2,
@@ -20819,7 +21489,7 @@ async function assertFreshAgentCommandAllowance(tx, deps, context, targetSession
20819
21489
  }
20820
21490
  }
20821
21491
  const frozen = await frozenInitiatorForCommandActor2(tx, context.workspaceId, agentActor(context));
20822
- const refusal = await checkWorkspaceAllowance4(tx, {
21492
+ const refusal = await checkWorkspaceAllowance7(tx, {
20823
21493
  accountId: target.accountId,
20824
21494
  workspaceId: context.workspaceId,
20825
21495
  subjectId: initiatingHumanForAllowance(frozen)
@@ -22142,7 +22812,6 @@ import { createPinnedIntegrationTransport as createPinnedIntegrationTransport2 }
22142
22812
  import {
22143
22813
  buildConnectionTokenResolver,
22144
22814
  getConnectionMetadata as getConnectionMetadata5,
22145
- listConnectionsMetadata as listConnectionsMetadata4,
22146
22815
  listEnabledMcpCapabilityServers as listEnabledMcpCapabilityServers3,
22147
22816
  listConnectorToolPermissionPolicies,
22148
22817
  listChangedConnectorToolPermissions,
@@ -22155,6 +22824,7 @@ import {
22155
22824
  import { HTTPException as HTTPException32 } from "hono/http-exception";
22156
22825
  import {
22157
22826
  GMAIL_REST_MCP_TOOLS,
22827
+ gmailToolAvailableOnDeployment,
22158
22828
  gmailToolSupportsScopes,
22159
22829
  isOfficialGmailMcpConfig,
22160
22830
  isOfficialSlackMcpConfig
@@ -22240,11 +22910,12 @@ async function resolveTarget2(input) {
22240
22910
  throw new HTTPException32(403, {
22241
22911
  message: "Only the authenticated connection owner may manage personal tool permissions"
22242
22912
  });
22243
- const visible = ref ? await listConnectionsMetadata4(
22244
- input.db,
22245
- input.workspaceId,
22246
- input.personalOwnerVerified ? input.grant.subjectId : null
22247
- ) : [];
22913
+ const source = personalConnectionDelegationSourceForGrant(input.grant);
22914
+ const visible = ref ? await visibleMcpAccountConnections(input.db, {
22915
+ accountId: input.grant.accountId,
22916
+ workspaceId: input.workspaceId,
22917
+ source: input.personalOwnerVerified && source.kind === "subject" ? source : { kind: "none" }
22918
+ }) : [];
22248
22919
  const bindings = mcpAccountBindingsFromVisibleConnections({
22249
22920
  accountId: input.grant.accountId,
22250
22921
  workspaceId: input.workspaceId,
@@ -22286,15 +22957,20 @@ async function resolveTarget2(input) {
22286
22957
  listedTools: null
22287
22958
  };
22288
22959
  }
22289
- function reviewedConnectorToolCatalog(server, grantedScopes) {
22290
- const tools = isOfficialGmailMcpConfig(server.url, server.connectionRef) ? GMAIL_REST_MCP_TOOLS.filter((tool) => gmailToolSupportsScopes(tool.name, grantedScopes)) : isOfficialSlackMcpConfig(server.url, server.connectionRef) ? slackRestMcpToolsForScopes(grantedScopes) : null;
22960
+ function reviewedConnectorToolCatalog(server, grantedScopes, deployment) {
22961
+ const tools = isOfficialGmailMcpConfig(server.url, server.connectionRef) ? GMAIL_REST_MCP_TOOLS.filter(
22962
+ (tool) => gmailToolSupportsScopes(tool.name, grantedScopes) && gmailToolAvailableOnDeployment(tool.name, {
22963
+ watchTopicName: deployment.gmailWatchTopicName
22964
+ })
22965
+ ) : isOfficialSlackMcpConfig(server.url, server.connectionRef) ? slackRestMcpToolsForScopes(grantedScopes) : null;
22291
22966
  return tools?.filter((tool) => !server.allowedTools || server.allowedTools.includes(tool.name)) ?? null;
22292
22967
  }
22293
22968
  async function listTools(input, target) {
22294
22969
  if (target.listedTools !== null) return target.listedTools;
22295
22970
  const reviewed = reviewedConnectorToolCatalog(
22296
22971
  target.server,
22297
- target.connection?.grantedScopes ?? []
22972
+ target.connection?.grantedScopes ?? [],
22973
+ input.settings
22298
22974
  );
22299
22975
  if (reviewed !== null) return reviewed;
22300
22976
  let headers = { ...target.server.headers };
@@ -22303,7 +22979,9 @@ async function listTools(input, target) {
22303
22979
  input.db,
22304
22980
  input.settings
22305
22981
  )({
22306
- workspaceId: input.workspaceId,
22982
+ // The selected account's credentials remain in its origin workspace;
22983
+ // approval preferences remain scoped to the workspace being configured.
22984
+ workspaceId: target.connection?.workspaceId ?? input.workspaceId,
22307
22985
  ...target.connection?.subjectId ? { subjectId: input.grant.subjectId } : {},
22308
22986
  serverId: target.server.id,
22309
22987
  toolName: "tools/list",
@@ -22560,7 +23238,11 @@ export {
22560
23238
  PR_REVIEW_AUTOMATION_SETUP,
22561
23239
  ProductionEditableArtifactSnapshotVerifier,
22562
23240
  PublicSkillSearchError,
23241
+ REALTIME_VOICE_DEBIT_TYPE,
23242
+ REALTIME_VOICE_INSUFFICIENT_CREDITS_MESSAGE,
23243
+ REALTIME_VOICE_SOURCE_TYPE,
22563
23244
  RIG_DEFAULT_VARIABLE_SET_LOAD_CONCURRENCY,
23245
+ RealtimeVoiceUnavailableError,
22564
23246
  SESSION_AUTHORIZATION_DEFAULT_REAUTHORIZE_MS,
22565
23247
  SESSION_CREATED_BY_KINDS,
22566
23248
  SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS,
@@ -22579,14 +23261,20 @@ export {
22579
23261
  SystemEditableArtifactLiveScheduler,
22580
23262
  TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS,
22581
23263
  TURN_ACTIVITY_CANCELLATION_HEARTBEAT_INTERVAL_MS,
23264
+ TranscriptionBillingRefusedError,
22582
23265
  TranscriptionServiceError,
22583
23266
  USER_PRESENCE_FLUSH_DELAY_MS,
22584
23267
  USER_PRESENCE_THROTTLE_MS,
22585
23268
  VIDEO_GENERATION_ADAPTER_VERSION,
22586
23269
  VIDEO_GENERATION_MODEL_CATALOG,
23270
+ VOICE_INPUT_DEBIT_TYPE,
23271
+ VOICE_INPUT_SOURCE_TYPE,
23272
+ WEB_SEARCH_DEBIT_TYPE,
23273
+ WEB_SEARCH_SOURCE_TYPE,
22587
23274
  WORKSPACE_INSIGHTS_MODEL_FILTER_MAX_UTF8_BYTES,
22588
23275
  WORKSPACE_INSIGHTS_PROVIDER_FILTER_MAX_UTF8_BYTES,
22589
23276
  WebCryptoEditableArtifactLiveTokens,
23277
+ WebSearchBillingRefusedError,
22590
23278
  WorkspaceInsightsFilterValidationError,
22591
23279
  acceptSessionUserMessage,
22592
23280
  acceptSessionUserMessageWithOutcome,
@@ -22604,6 +23292,7 @@ export {
22604
23292
  appendRigSetupCommand,
22605
23293
  applyCapabilityEnablement,
22606
23294
  applyGitHubRepositoryBindings,
23295
+ applyOperatorOAuthClientRequirement,
22607
23296
  applySessionAgentConfigWriteThrough,
22608
23297
  approveSkill,
22609
23298
  assertAllowedEnvironmentVariableName,
@@ -22679,12 +23368,15 @@ export {
22679
23368
  createCompanyProfileDurableLearningAdapter,
22680
23369
  createGitHubSkillSourceClient,
22681
23370
  createPublicSkillSearchClient,
23371
+ createRealtimeVoiceBilling,
22682
23372
  createRigForApi,
22683
23373
  createRigVersionForApi,
22684
23374
  createSessionForRequest,
22685
23375
  createSessionForRequestWithOutcome,
22686
23376
  createUserPresenceRecorder,
22687
23377
  createValidatedScheduledTask,
23378
+ createVoiceInputBilling,
23379
+ createWebSearchBilling,
22688
23380
  creationInitiatorForGrant,
22689
23381
  creditsDefaultSessionModel,
22690
23382
  decodeEditableArtifactBrokerHint,
@@ -22697,6 +23389,7 @@ export {
22697
23389
  deleteHumanQueuePrompt,
22698
23390
  deleteRigForApi,
22699
23391
  deleteScheduledTaskLifecycle,
23392
+ deploymentRealtimeVoice,
22700
23393
  describeConfiguredExportFormats,
22701
23394
  describeEditableArtifactExportFormats,
22702
23395
  directPersonalConnectionSubjectId,
@@ -22920,6 +23613,8 @@ export {
22920
23613
  readSessionAttachmentFiles,
22921
23614
  readSessionLineage,
22922
23615
  readSkill,
23616
+ realtimeVoiceOfferProblem,
23617
+ reconcileUnsettledVoiceInputCharges,
22923
23618
  recordManagedAuthLoggedFailure,
22924
23619
  recordRigAuditEvent,
22925
23620
  recordSessionCreated,
@@ -23056,6 +23751,7 @@ export {
23056
23751
  settingsWithRigProviderImage,
23057
23752
  settingsWithSessionMcpServerMetadata,
23058
23753
  settingsWithWorkspaceSandboxImage,
23754
+ settleVoiceInputUsage,
23059
23755
  signedJsonAutomationAdapter,
23060
23756
  skillBundleHash,
23061
23757
  stableJson,
@@ -23113,6 +23809,10 @@ export {
23113
23809
  videoGenerationProviderIdempotencyKey,
23114
23810
  videoGenerationRequestDigest,
23115
23811
  visibleMcpAccountConnections,
23812
+ voiceCreditStanding,
23813
+ voiceInputBillableUsage,
23814
+ voiceInsufficientCreditsMessage,
23815
+ webSearchCreditBillingActive,
23116
23816
  withAgentConfigHttpErrors,
23117
23817
  withDefaultEnabledCapabilityMcpTools,
23118
23818
  withFrozenPersonalConnectionDelegations,