@opengeni/core 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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
@@ -4834,6 +4835,7 @@ import {
4834
4835
  ORGANIZATION_GATEWAY_MODEL_ID_PREFIX as ORGANIZATION_GATEWAY_MODEL_ID_PREFIX2,
4835
4836
  ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX as ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX2,
4836
4837
  allowedFirstPartyMcpToolsForSession as allowedFirstPartyMcpToolsForSession3,
4838
+ deploymentUnavailableFirstPartyMcpTools,
4837
4839
  resolveFirstPartyMcpToolPolicy,
4838
4840
  policyProviderIdForModel,
4839
4841
  resolveTurnExecutionPolicyV1,
@@ -5370,6 +5372,7 @@ import {
5370
5372
  readActiveSandbox,
5371
5373
  retainWorkspaceMutationProcess,
5372
5374
  SandboxRetainedProcessPromotionFencedError,
5375
+ SandboxWorkspaceMutationOutputRejectedError,
5373
5376
  retainedProcessSettlementIdentity,
5374
5377
  settleRetainedProcess,
5375
5378
  verifyDirectWorkspaceMutationSettlement,
@@ -5382,6 +5385,7 @@ import {
5382
5385
  NatsControlRpc,
5383
5386
  NatsOpStreamTransport,
5384
5387
  RoutingSandboxSession,
5388
+ RoutingMutationOutputRejectedError,
5385
5389
  resolveModalCheckpointProviderBindingForSession
5386
5390
  } from "@opengeni/runtime/sandbox";
5387
5391
  var retainWorkspaceProviderCommand = createProviderCommandRetainer(
@@ -5540,22 +5544,35 @@ function wrapChannelABoxWithRouting(services, ids, established) {
5540
5544
  throw new Error(
5541
5545
  "Outcome-unknown command settlement requires its exact retained invocation"
5542
5546
  );
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
- });
5547
+ try {
5548
+ await verifyDirectWorkspaceMutationSettlement(db, {
5549
+ accountId: ids.accountId,
5550
+ workspaceId: ids.workspaceId,
5551
+ sessionId: ids.sessionId,
5552
+ requestId: ids.directRequest.requestId,
5553
+ holderId: ids.directRequest.holderId,
5554
+ initiatorSubjectId: ids.resourceSubjectId,
5555
+ sandboxGroupId: homeLease.sandboxGroupId,
5556
+ expectedEpoch: backend.leaseEpoch,
5557
+ expectedInstanceId: backend.providerInstanceId,
5558
+ routeTargetId: exactAdmission.routeTargetId,
5559
+ routeEpoch: exactAdmission.routeEpoch,
5560
+ admission: exactAdmission,
5561
+ operation: op,
5562
+ outcome
5563
+ });
5564
+ } catch (error) {
5565
+ if (error instanceof SandboxWorkspaceMutationOutputRejectedError && error.matchesPhysicalSettlement({
5566
+ accountId: ids.accountId,
5567
+ workspaceId: ids.workspaceId,
5568
+ admission: exactAdmission,
5569
+ operation: op,
5570
+ outcome
5571
+ })) {
5572
+ throw new RoutingMutationOutputRejectedError(op, error.code, { cause: error });
5573
+ }
5574
+ throw error;
5575
+ }
5559
5576
  } : void 0;
5560
5577
  const beforeProcessMutation = homeLease ? async ({
5561
5578
  op,
@@ -5578,15 +5595,28 @@ function wrapChannelABoxWithRouting(services, ids, established) {
5578
5595
  if (!admission || typeof admission !== "object" || typeof admission.id !== "string" || typeof admission.workspaceGeneration !== "number") {
5579
5596
  throw new Error("API retained-process mutation settlement lacked its exact admission");
5580
5597
  }
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
- });
5598
+ try {
5599
+ await verifyRetainedProcessMutationSettlement(db, {
5600
+ accountId: ids.accountId,
5601
+ workspaceId: ids.workspaceId,
5602
+ sessionId: ids.sessionId,
5603
+ processId: process.id,
5604
+ admission,
5605
+ operation: op,
5606
+ outcome
5607
+ });
5608
+ } catch (error) {
5609
+ if (error instanceof SandboxWorkspaceMutationOutputRejectedError && error.matchesPhysicalSettlement({
5610
+ accountId: ids.accountId,
5611
+ workspaceId: ids.workspaceId,
5612
+ admission,
5613
+ operation: op,
5614
+ outcome
5615
+ })) {
5616
+ throw new RoutingMutationOutputRejectedError(op, error.code, { cause: error });
5617
+ }
5618
+ throw error;
5619
+ }
5590
5620
  } : void 0;
5591
5621
  const settleProcess = homeLease ? async ({
5592
5622
  backend,
@@ -7802,7 +7832,8 @@ import {
7802
7832
  allowedFirstPartyMcpToolsForSession as allowedFirstPartyMcpToolsForSession2,
7803
7833
  codeSearchDeploymentPolicy,
7804
7834
  resolveModelProviderForTurn as resolveModelProviderForTurn2,
7805
- resolveFirstPartyDelegationSecret as resolveFirstPartyDelegationSecret2
7835
+ resolveFirstPartyDelegationSecret as resolveFirstPartyDelegationSecret2,
7836
+ webSearchToolPlan
7806
7837
  } from "@opengeni/config";
7807
7838
  import {
7808
7839
  AGENT_SKILL_MANAGE_TOOL_NAMES,
@@ -8076,9 +8107,14 @@ function sessionEffectiveToolProjectionInput(session, toolRefs, context) {
8076
8107
  const sandboxAvailable = context !== void 0 && sandboxBackend !== "none";
8077
8108
  const supportsImages = model?.model.api === "responses" && model.model.capabilities.inputModalities.includes("image");
8078
8109
  const hostedToolNames = [];
8110
+ const webSearchPlan = context ? webSearchToolPlan(context.settings, {
8111
+ hostedWebSearch: model?.model.hostedWebSearch ?? context.settings.webSearchEnabled,
8112
+ transportHostedSearch: model?.provider.kind === "xai-subscription"
8113
+ }) : { hostedWebSearch: false, providerTools: [] };
8079
8114
  if (model?.provider.kind === "xai-subscription") {
8080
8115
  if (context?.settings.webSearchEnabled) hostedToolNames.push("web_search", "x_search");
8081
- } else if (model?.model.hostedWebSearch) hostedToolNames.push("web_search");
8116
+ } else if (model?.model.hostedWebSearch && webSearchPlan.hostedWebSearch)
8117
+ hostedToolNames.push("web_search");
8082
8118
  const mediaAttachment = context?.mediaAttachments?.get(session.id) ?? (context?.objectStorageAvailable === false ? { image: null, video: false } : void 0);
8083
8119
  const media = resolveAgentMediaToolSurface(session.agent, mediaAttachment);
8084
8120
  hostedToolNames.push(...media.hosted);
@@ -8087,7 +8123,8 @@ function sessionEffectiveToolProjectionInput(session, toolRefs, context) {
8087
8123
  "list_models",
8088
8124
  "skill_read",
8089
8125
  ...AGENT_SKILL_MANAGE_TOOL_NAMES,
8090
- ...media.runtime
8126
+ ...media.runtime,
8127
+ ...webSearchPlan.providerTools
8091
8128
  ] : [];
8092
8129
  const sandboxToolNames = sandboxAvailable ? [
8093
8130
  "exec_command",
@@ -8146,6 +8183,7 @@ function sessionEffectiveToolProjectionInput(session, toolRefs, context) {
8146
8183
  "skill_read",
8147
8184
  "request_human_input",
8148
8185
  "list_models",
8186
+ ...webSearchPlan.providerTools,
8149
8187
  ...toolRefs.some((ref) => ref.id === "opengeni" && ref.eager === true) || !progressiveDisclosure ? firstPartyMcpTools.filter((name) => !interactionNames.has(name) || !progressiveDisclosure) : [],
8150
8188
  ...!progressiveDisclosure ? runtimeToolNames : []
8151
8189
  ]);
@@ -8155,7 +8193,7 @@ function sessionEffectiveToolProjectionInput(session, toolRefs, context) {
8155
8193
  ...session.toolPolicy.mode !== "workspace_default" ? session.tools.map((tool) => tool.id) : []
8156
8194
  ]),
8157
8195
  hasSkills: context ? sessionHasSkills(session, context) : false,
8158
- webSearch: hostedToolNames.includes("web_search"),
8196
+ webSearch: hostedToolNames.includes("web_search") || webSearchPlan.providerTools.length > 0,
8159
8197
  humanInput: context?.humanInputEnabled ?? false,
8160
8198
  media: media.toolsKnown ? media.hosted.length + media.runtime.length > 0 : void 0,
8161
8199
  routerInHistory: context?.routerInHistory === true || context?.routerHistorySessionIds?.has(session.id) === true,
@@ -10167,6 +10205,12 @@ async function withSessionCreateUsageRecording(input) {
10167
10205
  }
10168
10206
  async function createSessionForRequestInFileScope(unresolvedDeps, grant, workspaceId, rawPayload, authorization, agentChildPresentation, requestOptions = {}) {
10169
10207
  const payload = CreateSessionRequest.parse(rawPayload);
10208
+ if (payload.firstPartyMcpTools) {
10209
+ const unavailable = deploymentUnavailableFirstPartyMcpTools(unresolvedDeps.settings);
10210
+ payload.firstPartyMcpTools = payload.firstPartyMcpTools.filter(
10211
+ (tool) => !unavailable.has(tool)
10212
+ );
10213
+ }
10170
10214
  const surface = resolveTurnSurface({
10171
10215
  grant,
10172
10216
  authorization,
@@ -11464,6 +11508,10 @@ async function acceptSessionUserMessageInFileScope(deps, grant, workspaceId, ses
11464
11508
  (await requireWorkspace5(db, workspaceId)).settings
11465
11509
  )
11466
11510
  });
11511
+ const existingEffectiveFirstPartyTools = allowedFirstPartyMcpToolsForSession3(
11512
+ settings,
11513
+ existingSession.firstPartyMcpTools
11514
+ );
11467
11515
  const { personalConnectionDelegations, mcpAccountBindings } = await freezeConnectionAccounts({
11468
11516
  db,
11469
11517
  accountId: grant.accountId,
@@ -11473,7 +11521,7 @@ async function acceptSessionUserMessageInFileScope(deps, grant, workspaceId, ses
11473
11521
  resources: [...existingSession.resources, ...requestedResources],
11474
11522
  source: connectionDelegationSource,
11475
11523
  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")),
11524
+ 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
11525
  atlassianEnabled: existingSession.firstPartyMcpTools.some((tool) => tool.startsWith("atlassian_")) && (!existingSession.firstPartyMcpPermissions?.length || existingSession.firstPartyMcpPermissions.includes("connections:read")),
11478
11526
  ...input.connectionAccounts ? { authoritySelections: input.connectionAccounts } : {}
11479
11527
  });
@@ -12329,6 +12377,10 @@ async function freezeSessionRealtimeConnectionAccounts(input) {
12329
12377
  (await requireWorkspace6(db, workspaceId)).settings
12330
12378
  )
12331
12379
  });
12380
+ const effectiveFirstPartyTools = allowedFirstPartyMcpToolsForSession4(
12381
+ settings,
12382
+ session.firstPartyMcpTools
12383
+ );
12332
12384
  return await freezeConnectionAccounts({
12333
12385
  db,
12334
12386
  settings: runtimeSettings,
@@ -12338,7 +12390,7 @@ async function freezeSessionRealtimeConnectionAccounts(input) {
12338
12390
  resources: session.resources,
12339
12391
  source: personalConnectionDelegationSourceForGrant(grant),
12340
12392
  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")),
12393
+ 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
12394
  atlassianEnabled: session.firstPartyMcpTools.some((tool) => tool.startsWith("atlassian_")) && (!session.firstPartyMcpPermissions?.length || session.firstPartyMcpPermissions.includes("connections:read"))
12343
12395
  });
12344
12396
  }
@@ -12603,6 +12655,652 @@ async function searchKnowledgeEntries(db, context, input, embedder, settings) {
12603
12655
  );
12604
12656
  }
12605
12657
 
12658
+ // src/domain/voice-input-billing.ts
12659
+ import {
12660
+ calculateVoiceInputCost,
12661
+ configuredStaticUsageLimits as configuredStaticUsageLimits2,
12662
+ voiceInputCreditBillingActive,
12663
+ voiceInputPricingHasTokenRates
12664
+ } from "@opengeni/config";
12665
+ import {
12666
+ applyCreditDebitAfterUse as applyCreditDebitAfterUse2,
12667
+ checkWorkspaceAllowance as checkWorkspaceAllowance3,
12668
+ creditDebitAttributionMetadata,
12669
+ getSpendableCreditBalance as getSpendableCreditBalance3,
12670
+ listUnsettledVoiceTranscriptionCharges,
12671
+ recordUsageEvent as recordUsageEvent3,
12672
+ sumUsageQuantity as sumUsageQuantity3,
12673
+ VOICE_TRANSCRIPTION_DEBIT_TYPE,
12674
+ VOICE_TRANSCRIPTION_SOURCE_TYPE,
12675
+ voiceTranscriptionSettlementKeys
12676
+ } from "@opengeni/db";
12677
+
12678
+ // src/transcription.ts
12679
+ var TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS = 10 * 60 * 1e3;
12680
+ var TranscriptionBillingRefusedError = class extends Error {
12681
+ status;
12682
+ code;
12683
+ details;
12684
+ constructor(input) {
12685
+ super(input.message);
12686
+ this.name = "TranscriptionBillingRefusedError";
12687
+ this.code = input.code;
12688
+ this.status = input.code === "monthly_model_cost_limit" ? 429 : 402;
12689
+ this.details = input.details ?? {};
12690
+ }
12691
+ };
12692
+ var TranscriptionServiceError = class extends Error {
12693
+ code;
12694
+ status;
12695
+ retryable;
12696
+ /** Explicit rejection before any transcription result; safe to try another provider. */
12697
+ fallbackSafe;
12698
+ constructor(input) {
12699
+ super(input.message);
12700
+ this.name = "TranscriptionServiceError";
12701
+ this.code = input.code;
12702
+ this.status = input.status ?? statusForVoiceInputError(input.code);
12703
+ this.retryable = input.retryable ?? false;
12704
+ this.fallbackSafe = input.fallbackSafe ?? false;
12705
+ }
12706
+ };
12707
+ function statusForVoiceInputError(code) {
12708
+ switch (code) {
12709
+ case "permission_denied":
12710
+ return 403;
12711
+ case "policy_blocked":
12712
+ return 403;
12713
+ case "not_supported":
12714
+ return 415;
12715
+ case "unavailable":
12716
+ return 503;
12717
+ case "too_large":
12718
+ return 413;
12719
+ case "invalid_audio":
12720
+ return 400;
12721
+ case "timeout":
12722
+ return 504;
12723
+ case "cancelled":
12724
+ return 499;
12725
+ case "network":
12726
+ case "provider":
12727
+ return 502;
12728
+ case "unknown":
12729
+ default:
12730
+ return 500;
12731
+ }
12732
+ }
12733
+ function normalizeMimeType(mimeType) {
12734
+ return mimeType.trim().toLowerCase();
12735
+ }
12736
+ function isAcceptedMimeType(mimeType, accepted) {
12737
+ const normalized = normalizeMimeType(mimeType);
12738
+ if (accepted.some((candidate) => normalizeMimeType(candidate) === normalized)) {
12739
+ return true;
12740
+ }
12741
+ const bare = normalized.split(";")[0]?.trim() ?? normalized;
12742
+ return accepted.some((candidate) => {
12743
+ const allowed = normalizeMimeType(candidate);
12744
+ return allowed === bare || allowed.split(";")[0]?.trim() === bare;
12745
+ });
12746
+ }
12747
+ function filenameForMimeType(mimeType) {
12748
+ const bare = normalizeMimeType(mimeType).split(";")[0] ?? "audio/webm";
12749
+ switch (bare) {
12750
+ case "audio/mp4":
12751
+ case "audio/m4a":
12752
+ return "audio.mp4";
12753
+ case "audio/ogg":
12754
+ return "audio.ogg";
12755
+ case "audio/mpeg":
12756
+ case "audio/mp3":
12757
+ return "audio.mp3";
12758
+ case "audio/wav":
12759
+ case "audio/x-wav":
12760
+ return "audio.wav";
12761
+ case "audio/webm":
12762
+ default:
12763
+ return "audio.webm";
12764
+ }
12765
+ }
12766
+
12767
+ // src/domain/voice-input-billing.ts
12768
+ var VOICE_INPUT_DEBIT_TYPE = VOICE_TRANSCRIPTION_DEBIT_TYPE;
12769
+ var VOICE_INPUT_SOURCE_TYPE = VOICE_TRANSCRIPTION_SOURCE_TYPE;
12770
+ function voiceInputBillableUsage(input) {
12771
+ if (input.usage?.kind === "tokens" && voiceInputPricingHasTokenRates(input.pricing)) {
12772
+ return { usage: input.usage, basis: "provider_tokens" };
12773
+ }
12774
+ if (input.usage?.kind === "duration") {
12775
+ return { usage: input.usage, basis: "provider_duration" };
12776
+ }
12777
+ if (input.trustedDurationSeconds !== void 0 && Number.isFinite(input.trustedDurationSeconds) && input.trustedDurationSeconds >= 0) {
12778
+ return {
12779
+ usage: { kind: "duration", seconds: input.trustedDurationSeconds },
12780
+ basis: "server_duration"
12781
+ };
12782
+ }
12783
+ throw new TranscriptionServiceError({
12784
+ code: "provider",
12785
+ message: "Transcription usage was not reported."
12786
+ });
12787
+ }
12788
+ function initiatingHuman(attribution) {
12789
+ return attribution.kind === "human" || attribution.kind === "turn" ? attribution.initiatingHumanSubjectId : null;
12790
+ }
12791
+ function startOfUtcMonth2() {
12792
+ const now = /* @__PURE__ */ new Date();
12793
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
12794
+ }
12795
+ function createVoiceInputBilling(deps) {
12796
+ const reconcileInterval = deps.reconcileIntervalMilliseconds ?? 5 * 6e4;
12797
+ const lastReconciled = /* @__PURE__ */ new Map();
12798
+ const reconcileDue = (key) => {
12799
+ const now = Date.now();
12800
+ const last = lastReconciled.get(key);
12801
+ if (last !== void 0 && now - last < reconcileInterval) return false;
12802
+ if (lastReconciled.size >= 1e4) lastReconciled.clear();
12803
+ lastReconciled.set(key, now);
12804
+ return true;
12805
+ };
12806
+ return {
12807
+ async admit({ accountId, workspaceId, attribution }) {
12808
+ if (!voiceInputCreditBillingActive(deps.settings)) return;
12809
+ if (attribution.kind === "unknown") {
12810
+ throw new TranscriptionServiceError({
12811
+ code: "policy_blocked",
12812
+ status: 403,
12813
+ message: "Voice input payer could not be verified."
12814
+ });
12815
+ }
12816
+ if (reconcileDue(`${accountId}:${workspaceId}`)) {
12817
+ await reconcileUnsettledVoiceInputCharges(deps.db, { accountId, workspaceId }).catch(
12818
+ (error) => {
12819
+ deps.onReconcileError?.(error, { accountId, workspaceId });
12820
+ }
12821
+ );
12822
+ }
12823
+ const balance = await getSpendableCreditBalance3(deps.db, accountId);
12824
+ if (balance.balanceMicros <= 0) {
12825
+ throw new TranscriptionBillingRefusedError({
12826
+ code: "insufficient_credits",
12827
+ message: "Voice input needs Opengeni credits. Add credits to continue."
12828
+ });
12829
+ }
12830
+ const refusal = await checkWorkspaceAllowance3(deps.db, {
12831
+ accountId,
12832
+ workspaceId,
12833
+ subjectId: initiatingHuman(attribution)
12834
+ });
12835
+ if (refusal) {
12836
+ throw new TranscriptionBillingRefusedError({
12837
+ code: refusal.code,
12838
+ message: refusal.message,
12839
+ details: {
12840
+ scope: refusal.scope,
12841
+ resetsAt: refusal.resetsAt,
12842
+ ...refusal.subjectId ? { subjectId: refusal.subjectId } : {}
12843
+ }
12844
+ });
12845
+ }
12846
+ if (deps.settings.usageLimitsMode === "static" || deps.settings.usageLimitsMode === "managed") {
12847
+ const cap = configuredStaticUsageLimits2(deps.settings).maxMonthlyCostMicrosPerAccount;
12848
+ if (cap) {
12849
+ const used = await sumUsageQuantity3(deps.db, {
12850
+ accountId,
12851
+ eventType: "model.cost",
12852
+ since: startOfUtcMonth2()
12853
+ });
12854
+ if (used >= cap) {
12855
+ throw new TranscriptionBillingRefusedError({
12856
+ code: "monthly_model_cost_limit",
12857
+ message: "The monthly model cost limit has been reached."
12858
+ });
12859
+ }
12860
+ }
12861
+ }
12862
+ },
12863
+ async settle(input) {
12864
+ if (!voiceInputCreditBillingActive(deps.settings)) return { creditCostMicros: 0 };
12865
+ const { usage, basis } = voiceInputBillableUsage({
12866
+ pricing: input.pricing,
12867
+ usage: input.usage,
12868
+ trustedDurationSeconds: input.billing.trustedDurationSeconds
12869
+ });
12870
+ const cost = calculateVoiceInputCost(input.pricing, usage);
12871
+ return await settleVoiceInputUsage(deps.db, {
12872
+ accountId: input.accountId,
12873
+ workspaceId: input.workspaceId,
12874
+ providerId: input.providerId,
12875
+ model: input.model,
12876
+ billing: input.billing,
12877
+ usage,
12878
+ basis,
12879
+ providerCostMicros: cost.providerCostMicros,
12880
+ creditCostMicros: cost.creditCostMicros
12881
+ });
12882
+ }
12883
+ };
12884
+ }
12885
+ async function settleVoiceInputUsage(db, input) {
12886
+ const attribution = input.billing.attribution;
12887
+ const keys = voiceTranscriptionSettlementKeys({
12888
+ workspaceId: input.workspaceId,
12889
+ sourceId: input.billing.sourceId
12890
+ });
12891
+ const receipt2 = await recordUsageEvent3(db, {
12892
+ accountId: input.accountId,
12893
+ workspaceId: input.workspaceId,
12894
+ eventType: "model.cost",
12895
+ quantity: input.creditCostMicros,
12896
+ unit: "usd_micros",
12897
+ sourceResourceType: VOICE_TRANSCRIPTION_SOURCE_TYPE,
12898
+ sourceResourceId: input.billing.sourceId,
12899
+ idempotencyKey: keys.usageIdempotencyKey,
12900
+ // The API is the writer; the payer is the trusted attribution snapshot.
12901
+ initiator: { kind: "service", subjectId: "api:voice-input" },
12902
+ initiatorContext: { creditDebitAttribution: attribution }
12903
+ });
12904
+ const amount = Number(receipt2.quantity);
12905
+ if (amount > 0) {
12906
+ await applyCreditDebitAfterUse2(db, {
12907
+ accountId: input.accountId,
12908
+ workspaceId: input.workspaceId,
12909
+ type: VOICE_TRANSCRIPTION_DEBIT_TYPE,
12910
+ amountMicros: amount,
12911
+ sourceType: VOICE_TRANSCRIPTION_SOURCE_TYPE,
12912
+ sourceId: input.billing.sourceId,
12913
+ idempotencyKey: keys.debitIdempotencyKey,
12914
+ metadata: {
12915
+ providerId: input.providerId,
12916
+ model: input.model,
12917
+ basis: input.basis,
12918
+ providerCostMicros: input.providerCostMicros,
12919
+ ...input.usage.kind === "tokens" ? {
12920
+ inputTokens: input.usage.inputTokens,
12921
+ audioInputTokens: input.usage.audioInputTokens,
12922
+ textInputTokens: input.usage.textInputTokens,
12923
+ outputTokens: input.usage.outputTokens
12924
+ } : { audioMilliseconds: Math.ceil(input.usage.seconds * 1e3) },
12925
+ ...attribution.kind === "unknown" ? {} : creditDebitAttributionMetadata(attribution)
12926
+ }
12927
+ });
12928
+ }
12929
+ return { creditCostMicros: amount };
12930
+ }
12931
+ async function reconcileUnsettledVoiceInputCharges(db, input) {
12932
+ const pending = await listUnsettledVoiceTranscriptionCharges(db, {
12933
+ accountId: input.accountId,
12934
+ workspaceId: input.workspaceId,
12935
+ ...input.minAgeMilliseconds !== void 0 ? { minAgeMilliseconds: input.minAgeMilliseconds } : {}
12936
+ });
12937
+ for (const charge of pending) {
12938
+ await applyCreditDebitAfterUse2(db, {
12939
+ accountId: input.accountId,
12940
+ workspaceId: input.workspaceId,
12941
+ type: VOICE_TRANSCRIPTION_DEBIT_TYPE,
12942
+ amountMicros: charge.amountMicros,
12943
+ sourceType: VOICE_TRANSCRIPTION_SOURCE_TYPE,
12944
+ sourceId: charge.sourceId,
12945
+ idempotencyKey: voiceTranscriptionSettlementKeys({
12946
+ workspaceId: input.workspaceId,
12947
+ sourceId: charge.sourceId
12948
+ }).debitIdempotencyKey,
12949
+ metadata: {
12950
+ basis: "reconciled_receipt",
12951
+ ...charge.attribution.kind === "unknown" ? {} : creditDebitAttributionMetadata(charge.attribution)
12952
+ }
12953
+ });
12954
+ }
12955
+ return { reconciled: pending.length };
12956
+ }
12957
+
12958
+ // src/domain/web-search-billing.ts
12959
+ import {
12960
+ applyCreditDebitAfterUse as applyCreditDebitAfterUse3,
12961
+ checkWorkspaceAllowance as checkWorkspaceAllowance4,
12962
+ creditDebitAttributionForTurn as creditDebitAttributionForTurn2,
12963
+ creditDebitAttributionMetadata as creditDebitAttributionMetadata2,
12964
+ getSpendableCreditBalance as getSpendableCreditBalance4,
12965
+ recordUsageEvent as recordUsageEvent4,
12966
+ withRlsContext
12967
+ } from "@opengeni/db";
12968
+ var WEB_SEARCH_DEBIT_TYPE = "web_search_debit";
12969
+ var WEB_SEARCH_SOURCE_TYPE = "web_search";
12970
+ var WEB_SEARCH_BILLING_INITIATOR = "worker:web-search";
12971
+ function webSearchCreditBillingActive(settings) {
12972
+ return settings.billingMode === "stripe" || settings.usageLimitsMode === "managed";
12973
+ }
12974
+ var WebSearchBillingRefusedError = class extends Error {
12975
+ constructor(code, message) {
12976
+ super(message);
12977
+ this.code = code;
12978
+ this.name = "WebSearchBillingRefusedError";
12979
+ }
12980
+ };
12981
+ function createWebSearchBilling(deps) {
12982
+ const active = webSearchCreditBillingActive(deps.settings);
12983
+ const attributionByTurn = /* @__PURE__ */ new Map();
12984
+ const attributionFor = (scope) => {
12985
+ let attribution = attributionByTurn.get(scope.turnId);
12986
+ if (!attribution) {
12987
+ attribution = creditDebitAttributionForTurn2(deps.db, scope);
12988
+ attributionByTurn.set(scope.turnId, attribution);
12989
+ attribution.catch(() => attributionByTurn.delete(scope.turnId));
12990
+ }
12991
+ return attribution;
12992
+ };
12993
+ return {
12994
+ active,
12995
+ async admit(scope, expectedProviderMicros) {
12996
+ if (!active || expectedProviderMicros <= 0) return;
12997
+ const attribution = await attributionFor(scope);
12998
+ const balance = await getSpendableCreditBalance4(deps.db, scope.accountId);
12999
+ if (balance.balanceMicros <= 0) {
13000
+ throw new WebSearchBillingRefusedError(
13001
+ "insufficient_credits",
13002
+ "Web search needs Opengeni credits, and this account has none left."
13003
+ );
13004
+ }
13005
+ const refusal = await checkWorkspaceAllowance4(deps.db, {
13006
+ accountId: scope.accountId,
13007
+ workspaceId: scope.workspaceId,
13008
+ subjectId: attribution.kind === "turn" ? attribution.initiatingHumanSubjectId : null
13009
+ });
13010
+ if (refusal) throw new WebSearchBillingRefusedError("allowance_exhausted", refusal.message);
13011
+ },
13012
+ async settle(scope, cost) {
13013
+ const shared = {
13014
+ accountId: scope.accountId,
13015
+ workspaceId: scope.workspaceId,
13016
+ sessionId: scope.sessionId,
13017
+ turnId: scope.turnId,
13018
+ turnAttemptId: scope.attemptId,
13019
+ sourceResourceType: WEB_SEARCH_SOURCE_TYPE,
13020
+ sourceResourceId: `${scope.attemptId}:${cost.operationId}`
13021
+ };
13022
+ await recordUsageEvent4(deps.db, {
13023
+ ...shared,
13024
+ eventType: `web_search.${cost.operation}_requests`,
13025
+ quantity: 1,
13026
+ unit: "request",
13027
+ idempotencyKey: `usage:web_search.${cost.operation}_requests:${shared.sourceResourceId}`
13028
+ });
13029
+ if (!active || cost.creditMicros <= 0) return;
13030
+ const attribution = await attributionFor(scope);
13031
+ await withRlsContext(
13032
+ deps.db,
13033
+ { accountId: scope.accountId, workspaceId: scope.workspaceId },
13034
+ async (tx) => {
13035
+ await recordUsageEvent4(tx, {
13036
+ ...shared,
13037
+ eventType: "web_search.cost",
13038
+ quantity: cost.creditMicros,
13039
+ unit: "usd_micros",
13040
+ idempotencyKey: `usage:web_search.cost:${shared.sourceResourceId}`,
13041
+ initiator: { kind: "service", subjectId: WEB_SEARCH_BILLING_INITIATOR },
13042
+ initiatorContext: { creditDebitAttribution: attribution }
13043
+ });
13044
+ await applyCreditDebitAfterUse3(tx, {
13045
+ accountId: scope.accountId,
13046
+ workspaceId: scope.workspaceId,
13047
+ type: WEB_SEARCH_DEBIT_TYPE,
13048
+ amountMicros: cost.creditMicros,
13049
+ sourceType: WEB_SEARCH_SOURCE_TYPE,
13050
+ sourceId: shared.sourceResourceId,
13051
+ idempotencyKey: `credit:${WEB_SEARCH_DEBIT_TYPE}:${shared.sourceResourceId}`,
13052
+ metadata: {
13053
+ // turnId lets the allowance trigger resolve the causal human
13054
+ // from the immutable turn receipt.
13055
+ ...creditDebitAttributionMetadata2(attribution),
13056
+ provider: cost.provider,
13057
+ operation: cost.operation,
13058
+ providerCostMicros: cost.providerMicros,
13059
+ marginBps: cost.marginBps,
13060
+ basis: cost.basis
13061
+ }
13062
+ });
13063
+ }
13064
+ );
13065
+ }
13066
+ };
13067
+ }
13068
+
13069
+ // src/domain/realtime-voice-billing.ts
13070
+ import {
13071
+ AI_GATEWAY_REALTIME_MODELS,
13072
+ AZURE_LIVE_REALTIME_MODEL_ID,
13073
+ configuredStaticUsageLimits as configuredStaticUsageLimits3,
13074
+ parseRealtimeVoicePricingJson,
13075
+ parseRealtimeVoicePricingTableJson,
13076
+ realtimeVoiceMinuteCreditMicros,
13077
+ realtimeVoiceStartedMinutes,
13078
+ voiceInputCreditBillingActive as voiceInputCreditBillingActive2
13079
+ } from "@opengeni/config";
13080
+ import {
13081
+ applyCreditDebitAfterUse as applyCreditDebitAfterUse4,
13082
+ checkWorkspaceAllowance as checkWorkspaceAllowance5,
13083
+ creditDebitAttributionMetadata as creditDebitAttributionMetadata3,
13084
+ existingUsageEventIdempotencyKeys,
13085
+ getSpendableCreditBalance as getSpendableCreditBalance5,
13086
+ loadSessionRealtimeBillingFacts,
13087
+ recordUsageEvent as recordUsageEvent5,
13088
+ sumUsageQuantity as sumUsageQuantity4,
13089
+ withRlsContext as withRlsContext2
13090
+ } from "@opengeni/db";
13091
+ var REALTIME_VOICE_DEBIT_TYPE = "voice_realtime_debit";
13092
+ var REALTIME_VOICE_SOURCE_TYPE = "voice_realtime";
13093
+ function deploymentRealtimeVoice(settings, model) {
13094
+ const azureConfigured = Boolean(settings.azureLiveEndpoint && settings.azureLiveApiKey?.trim());
13095
+ if (model === AZURE_LIVE_REALTIME_MODEL_ID) {
13096
+ return {
13097
+ provider: "azure-live",
13098
+ configured: azureConfigured,
13099
+ pricing: parseRealtimeVoicePricingJson(settings.azureLivePricingJson)
13100
+ };
13101
+ }
13102
+ for (const gateway of Object.values(AI_GATEWAY_REALTIME_MODELS)) {
13103
+ if (gateway.managedModelId !== model) continue;
13104
+ return {
13105
+ provider: "ai-gateway",
13106
+ configured: Boolean(settings.vercelAiGatewayApiKey),
13107
+ pricing: parseRealtimeVoicePricingTableJson(settings.aiGatewayRealtimePricingJson)[gateway.upstreamModelId] ?? null
13108
+ };
13109
+ }
13110
+ return null;
13111
+ }
13112
+ var RealtimeVoiceUnavailableError = class extends Error {
13113
+ constructor(code, message) {
13114
+ super(message);
13115
+ this.code = code;
13116
+ }
13117
+ name = "RealtimeVoiceUnavailableError";
13118
+ };
13119
+ function realtimeVoiceOfferProblem(settings, voice) {
13120
+ if (!voice.configured) {
13121
+ return new RealtimeVoiceUnavailableError(
13122
+ "not_configured",
13123
+ "Opengeni live voice is not configured on this deployment."
13124
+ );
13125
+ }
13126
+ if (voiceInputCreditBillingActive2(settings) && !voice.pricing) {
13127
+ return new RealtimeVoiceUnavailableError(
13128
+ "pricing_unconfigured",
13129
+ "Opengeni live voice is not available on this deployment yet."
13130
+ );
13131
+ }
13132
+ return null;
13133
+ }
13134
+ var REALTIME_VOICE_INSUFFICIENT_CREDITS_MESSAGE = "Live voice needs Opengeni credits. Add credits to continue.";
13135
+ function startOfUtcMonth3(now) {
13136
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
13137
+ }
13138
+ function initiatingHuman2(attribution) {
13139
+ return attribution.kind === "human" || attribution.kind === "turn" ? attribution.initiatingHumanSubjectId : null;
13140
+ }
13141
+ function createRealtimeVoiceBilling(deps) {
13142
+ const billingActive = () => voiceInputCreditBillingActive2(deps.settings);
13143
+ async function refusal(input) {
13144
+ const balance = await getSpendableCreditBalance5(deps.db, input.accountId);
13145
+ if (balance.balanceMicros <= 0) {
13146
+ return new TranscriptionBillingRefusedError({
13147
+ code: "insufficient_credits",
13148
+ message: REALTIME_VOICE_INSUFFICIENT_CREDITS_MESSAGE
13149
+ });
13150
+ }
13151
+ const allowance = await checkWorkspaceAllowance5(deps.db, {
13152
+ accountId: input.accountId,
13153
+ workspaceId: input.workspaceId,
13154
+ subjectId: initiatingHuman2(input.attribution)
13155
+ });
13156
+ if (allowance) {
13157
+ return new TranscriptionBillingRefusedError({
13158
+ code: allowance.code,
13159
+ message: allowance.message,
13160
+ details: {
13161
+ scope: allowance.scope,
13162
+ resetsAt: allowance.resetsAt,
13163
+ ...allowance.subjectId ? { subjectId: allowance.subjectId } : {}
13164
+ }
13165
+ });
13166
+ }
13167
+ if (deps.settings.usageLimitsMode === "static" || deps.settings.usageLimitsMode === "managed") {
13168
+ const cap = configuredStaticUsageLimits3(deps.settings).maxMonthlyCostMicrosPerAccount;
13169
+ if (cap) {
13170
+ const used = await sumUsageQuantity4(deps.db, {
13171
+ accountId: input.accountId,
13172
+ eventType: "model.cost",
13173
+ since: startOfUtcMonth3(input.now)
13174
+ });
13175
+ if (used >= cap) {
13176
+ return new TranscriptionBillingRefusedError({
13177
+ code: "monthly_model_cost_limit",
13178
+ message: "The monthly model cost limit has been reached."
13179
+ });
13180
+ }
13181
+ }
13182
+ }
13183
+ return null;
13184
+ }
13185
+ return {
13186
+ /**
13187
+ * Throws RealtimeVoiceUnavailableError when the deployment cannot offer
13188
+ * the model and TranscriptionBillingRefusedError for a credit refusal.
13189
+ * Workspace-funded models are always admitted here.
13190
+ */
13191
+ async admit(input) {
13192
+ const voice = deploymentRealtimeVoice(deps.settings, input.model);
13193
+ if (!voice) return;
13194
+ const problem = realtimeVoiceOfferProblem(deps.settings, voice);
13195
+ if (problem) throw problem;
13196
+ if (!billingActive()) return;
13197
+ const refused = await refusal({ ...input, now: input.now ?? /* @__PURE__ */ new Date() });
13198
+ if (refused) throw refused;
13199
+ },
13200
+ /** Spendable credits are positive (always true when credits are not enforced). */
13201
+ async hasSpendableCredits(accountId) {
13202
+ if (!billingActive()) return true;
13203
+ const balance = await getSpendableCreditBalance5(deps.db, accountId);
13204
+ return balance.balanceMicros > 0;
13205
+ },
13206
+ /**
13207
+ * Settle every started minute observed so far for one live-voice mode and
13208
+ * report whether the call must stop. Safe to call repeatedly and
13209
+ * concurrently: receipts and debits are idempotent per (connection, minute).
13210
+ */
13211
+ async settle(input) {
13212
+ const now = input.now ?? /* @__PURE__ */ new Date();
13213
+ if (!billingActive()) return { debitedMicros: 0, stop: null };
13214
+ const facts = await loadSessionRealtimeBillingFacts(deps.db, { ...input, now });
13215
+ if (!facts) return { debitedMicros: 0, stop: null };
13216
+ const voice = deploymentRealtimeVoice(deps.settings, facts.model);
13217
+ if (!voice) return { debitedMicros: 0, stop: null };
13218
+ if (!voice.pricing) {
13219
+ return {
13220
+ debitedMicros: 0,
13221
+ stop: new RealtimeVoiceUnavailableError(
13222
+ "pricing_unconfigured",
13223
+ "Opengeni live voice is no longer available on this deployment."
13224
+ )
13225
+ };
13226
+ }
13227
+ const attribution = input.callerSubjectId === facts.ownerSubjectId ? input.attribution : { kind: "unknown" };
13228
+ const cost = realtimeVoiceMinuteCreditMicros(voice.pricing);
13229
+ const minutes = facts.connections.flatMap(
13230
+ (connection) => Array.from(
13231
+ { length: realtimeVoiceStartedMinutes(connection.startedAt, connection.observedUntil) },
13232
+ (_, index) => ({
13233
+ connectionId: connection.id,
13234
+ minute: index + 1,
13235
+ startsAt: new Date(connection.startedAt.getTime() + index * 6e4),
13236
+ key: `voice.realtime_cost:${connection.id}:${index + 1}`
13237
+ })
13238
+ )
13239
+ );
13240
+ const recorded = await existingUsageEventIdempotencyKeys(deps.db, {
13241
+ accountId: facts.accountId,
13242
+ workspaceId: facts.workspaceId,
13243
+ keys: minutes.map((minute) => minute.key)
13244
+ });
13245
+ let debitedMicros = 0;
13246
+ for (const minute of minutes) {
13247
+ if (recorded.has(minute.key)) continue;
13248
+ debitedMicros += await withRlsContext2(
13249
+ deps.db,
13250
+ { accountId: facts.accountId, workspaceId: facts.workspaceId },
13251
+ async (tx) => {
13252
+ const sourceId = `${minute.connectionId}:${minute.minute}`;
13253
+ const receipt2 = await recordUsageEvent5(tx, {
13254
+ accountId: facts.accountId,
13255
+ workspaceId: facts.workspaceId,
13256
+ sessionId: facts.sessionId,
13257
+ eventType: "model.cost",
13258
+ quantity: cost.creditCostMicros,
13259
+ unit: "usd_micros",
13260
+ sourceResourceType: REALTIME_VOICE_SOURCE_TYPE,
13261
+ sourceResourceId: sourceId,
13262
+ idempotencyKey: minute.key,
13263
+ occurredAt: minute.startsAt,
13264
+ initiator: { kind: "service", subjectId: "api:voice-realtime" },
13265
+ initiatorContext: { creditDebitAttribution: attribution }
13266
+ });
13267
+ const amount = Number(receipt2.quantity);
13268
+ if (amount <= 0) return 0;
13269
+ const debit = await applyCreditDebitAfterUse4(tx, {
13270
+ accountId: facts.accountId,
13271
+ workspaceId: facts.workspaceId,
13272
+ type: REALTIME_VOICE_DEBIT_TYPE,
13273
+ amountMicros: amount,
13274
+ sourceType: REALTIME_VOICE_SOURCE_TYPE,
13275
+ sourceId,
13276
+ idempotencyKey: `credit:${REALTIME_VOICE_DEBIT_TYPE}:${sourceId}`,
13277
+ occurredAt: minute.startsAt,
13278
+ metadata: {
13279
+ provider: voice.provider,
13280
+ model: facts.model,
13281
+ realtimeId: facts.realtimeId,
13282
+ connectionId: minute.connectionId,
13283
+ minute: minute.minute,
13284
+ basis: "server_observed_started_minute",
13285
+ providerCostMicros: cost.providerCostMicros,
13286
+ ...attribution.kind === "unknown" ? {} : creditDebitAttributionMetadata3(attribution)
13287
+ }
13288
+ });
13289
+ return debit.debitedMicros;
13290
+ }
13291
+ );
13292
+ }
13293
+ const stop = facts.state === "active" ? await refusal({
13294
+ accountId: facts.accountId,
13295
+ workspaceId: facts.workspaceId,
13296
+ attribution,
13297
+ now
13298
+ }) : null;
13299
+ return { debitedMicros, stop };
13300
+ }
13301
+ };
13302
+ }
13303
+
12606
13304
  // src/workflow-wake-contract.ts
12607
13305
  var SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID = "opengeni-session-workflow-wake-dispatcher";
12608
13306
  var SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE = "sessionWorkflowWakeDispatcherWorkflow";
@@ -12704,83 +13402,6 @@ function createUserPresenceRecorder(options) {
12704
13402
  };
12705
13403
  }
12706
13404
 
12707
- // src/transcription.ts
12708
- var TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS = 10 * 60 * 1e3;
12709
- var TranscriptionServiceError = class extends Error {
12710
- code;
12711
- status;
12712
- retryable;
12713
- /** Explicit rejection before any transcription result; safe to try another provider. */
12714
- fallbackSafe;
12715
- constructor(input) {
12716
- super(input.message);
12717
- this.name = "TranscriptionServiceError";
12718
- this.code = input.code;
12719
- this.status = input.status ?? statusForVoiceInputError(input.code);
12720
- this.retryable = input.retryable ?? false;
12721
- this.fallbackSafe = input.fallbackSafe ?? false;
12722
- }
12723
- };
12724
- function statusForVoiceInputError(code) {
12725
- switch (code) {
12726
- case "permission_denied":
12727
- return 403;
12728
- case "policy_blocked":
12729
- return 403;
12730
- case "not_supported":
12731
- return 415;
12732
- case "unavailable":
12733
- return 503;
12734
- case "too_large":
12735
- return 413;
12736
- case "invalid_audio":
12737
- return 400;
12738
- case "timeout":
12739
- return 504;
12740
- case "cancelled":
12741
- return 499;
12742
- case "network":
12743
- case "provider":
12744
- return 502;
12745
- case "unknown":
12746
- default:
12747
- return 500;
12748
- }
12749
- }
12750
- function normalizeMimeType(mimeType) {
12751
- return mimeType.trim().toLowerCase();
12752
- }
12753
- function isAcceptedMimeType(mimeType, accepted) {
12754
- const normalized = normalizeMimeType(mimeType);
12755
- if (accepted.some((candidate) => normalizeMimeType(candidate) === normalized)) {
12756
- return true;
12757
- }
12758
- const bare = normalized.split(";")[0]?.trim() ?? normalized;
12759
- return accepted.some((candidate) => {
12760
- const allowed = normalizeMimeType(candidate);
12761
- return allowed === bare || allowed.split(";")[0]?.trim() === bare;
12762
- });
12763
- }
12764
- function filenameForMimeType(mimeType) {
12765
- const bare = normalizeMimeType(mimeType).split(";")[0] ?? "audio/webm";
12766
- switch (bare) {
12767
- case "audio/mp4":
12768
- case "audio/m4a":
12769
- return "audio.mp4";
12770
- case "audio/ogg":
12771
- return "audio.ogg";
12772
- case "audio/mpeg":
12773
- case "audio/mp3":
12774
- return "audio.mp3";
12775
- case "audio/wav":
12776
- case "audio/x-wav":
12777
- return "audio.wav";
12778
- case "audio/webm":
12779
- default:
12780
- return "audio.webm";
12781
- }
12782
- }
12783
-
12784
13405
  // src/goal-admission.ts
12785
13406
  import {
12786
13407
  isModelAvailableForNewSelection as isModelAvailableForNewSelection2,
@@ -12799,13 +13420,13 @@ import { getWorkspaceModelPolicy as getWorkspaceModelPolicy3 } from "@opengeni/d
12799
13420
  // src/billing/agent-run-admission.ts
12800
13421
  import {
12801
13422
  canonicalizeConfiguredModelId as canonicalizeConfiguredModelId4,
12802
- configuredStaticUsageLimits as configuredStaticUsageLimits2
13423
+ configuredStaticUsageLimits as configuredStaticUsageLimits4
12803
13424
  } from "@opengeni/config";
12804
13425
  import {
12805
- checkWorkspaceAllowance as checkWorkspaceAllowance3,
12806
- getSpendableCreditBalance as getSpendableCreditBalance3,
13426
+ checkWorkspaceAllowance as checkWorkspaceAllowance6,
13427
+ getSpendableCreditBalance as getSpendableCreditBalance6,
12807
13428
  isCodexBilledTurn as isCodexBilledTurn2,
12808
- sumUsageQuantity as sumUsageQuantity3
13429
+ sumUsageQuantity as sumUsageQuantity5
12809
13430
  } from "@opengeni/db";
12810
13431
  async function agentRunAdmissionDenial(services, input) {
12811
13432
  const codexBilled = await isCodexBilledTurn2({
@@ -12829,7 +13450,7 @@ async function agentRunAdmissionDenial(services, input) {
12829
13450
  });
12830
13451
  if (!decision.allowed) return "insufficient_credits";
12831
13452
  } else {
12832
- const balance = await getSpendableCreditBalance3(
13453
+ const balance = await getSpendableCreditBalance6(
12833
13454
  services.db,
12834
13455
  input.accountId,
12835
13456
  canonicalizeConfiguredModelId4(services.settings, input.model)
@@ -12838,7 +13459,7 @@ async function agentRunAdmissionDenial(services, input) {
12838
13459
  }
12839
13460
  }
12840
13461
  if (!externallyBilled) {
12841
- const refusal = await checkWorkspaceAllowance3(services.db, {
13462
+ const refusal = await checkWorkspaceAllowance6(services.db, {
12842
13463
  accountId: input.accountId,
12843
13464
  workspaceId: input.workspaceId,
12844
13465
  subjectId: input.initiatingHumanSubjectId ?? null
@@ -12848,22 +13469,22 @@ async function agentRunAdmissionDenial(services, input) {
12848
13469
  if (services.settings.usageLimitsMode !== "static" && services.settings.usageLimitsMode !== "managed") {
12849
13470
  return null;
12850
13471
  }
12851
- const limits = configuredStaticUsageLimits2(services.settings);
13472
+ const limits = configuredStaticUsageLimits4(services.settings);
12852
13473
  if (!externallyBilled && limits.maxMonthlyCostMicrosPerAccount) {
12853
- const used = await sumUsageQuantity3(services.db, {
13474
+ const used = await sumUsageQuantity5(services.db, {
12854
13475
  accountId: input.accountId,
12855
13476
  eventType: "model.cost",
12856
- since: startOfUtcMonth2()
13477
+ since: startOfUtcMonth4()
12857
13478
  });
12858
13479
  if (used >= limits.maxMonthlyCostMicrosPerAccount) {
12859
13480
  return "monthly_model_cost_limit";
12860
13481
  }
12861
13482
  }
12862
13483
  if (limits.maxMonthlyAgentRunsPerWorkspace) {
12863
- const used = await sumUsageQuantity3(services.db, {
13484
+ const used = await sumUsageQuantity5(services.db, {
12864
13485
  workspaceId: input.workspaceId,
12865
13486
  eventType: "agent_run.created",
12866
- since: startOfUtcMonth2()
13487
+ since: startOfUtcMonth4()
12867
13488
  });
12868
13489
  if (used + input.requestedAgentRuns > limits.maxMonthlyAgentRunsPerWorkspace) {
12869
13490
  return "monthly_agent_run_limit";
@@ -12871,7 +13492,7 @@ async function agentRunAdmissionDenial(services, input) {
12871
13492
  }
12872
13493
  return null;
12873
13494
  }
12874
- function startOfUtcMonth2() {
13495
+ function startOfUtcMonth4() {
12875
13496
  const now = /* @__PURE__ */ new Date();
12876
13497
  return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
12877
13498
  }
@@ -15581,7 +16202,7 @@ import {
15581
16202
  SCHEDULED_TASK_OCCURRENCE_PAYLOAD_INGRESS_HEADROOM_BYTES
15582
16203
  } from "@opengeni/contracts";
15583
16204
  import {
15584
- allowedFirstPartyMcpToolsForSession as allowedFirstPartyMcpToolsForSession4,
16205
+ allowedFirstPartyMcpToolsForSession as allowedFirstPartyMcpToolsForSession5,
15585
16206
  resolveFirstPartyMcpToolPolicy as resolveFirstPartyMcpToolPolicy2
15586
16207
  } from "@opengeni/config";
15587
16208
  import {
@@ -15689,7 +16310,7 @@ async function scheduledConnectionTools(db, workspaceId, settings, target, taskT
15689
16310
  }).toolRefs;
15690
16311
  }
15691
16312
  function scheduledConnectionSurfaceEligibility(settings, target) {
15692
- const tools = target?.firstPartyMcpTools ?? resolveFirstPartyMcpToolPolicy2(settings).default;
16313
+ const tools = allowedFirstPartyMcpToolsForSession5(settings, target?.firstPartyMcpTools);
15693
16314
  const permissions = target?.firstPartyMcpPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS3;
15694
16315
  return {
15695
16316
  googleDrivePublicationEnabled: tools.includes("editable_artifact_export") && tools.includes("editable_artifact_export_status") && permissions.includes("artifacts:read") && permissions.includes("artifacts:publish"),
@@ -16014,7 +16635,7 @@ async function frozenScheduledTaskCreatorPolicy(input) {
16014
16635
  restricted ||= Boolean(
16015
16636
  turnPolicy.kind === "valid" && turnPolicy.policy.credentialRestriction === "developer_setup" || sessionPolicy.kind === "valid" && sessionPolicy.policy.credentialRestriction === "developer_setup"
16016
16637
  );
16017
- const firstPartyMcpTools = allowedFirstPartyMcpToolsForSession4(
16638
+ const firstPartyMcpTools = allowedFirstPartyMcpToolsForSession5(
16018
16639
  input.settings,
16019
16640
  session.firstPartyMcpTools
16020
16641
  );
@@ -17315,7 +17936,7 @@ function trimmedScheduledTaskName(name) {
17315
17936
 
17316
17937
  // src/domain/scheduled-task-access.ts
17317
17938
  import {
17318
- allowedFirstPartyMcpToolsForSession as allowedFirstPartyMcpToolsForSession5,
17939
+ allowedFirstPartyMcpToolsForSession as allowedFirstPartyMcpToolsForSession6,
17319
17940
  resolveFirstPartyMcpToolPolicy as resolveFirstPartyMcpToolPolicy3
17320
17941
  } from "@opengeni/config";
17321
17942
  import {
@@ -17420,7 +18041,7 @@ function sameMembers(left, right) {
17420
18041
  function planScheduledTaskOpenGeniTools(input) {
17421
18042
  const stored = input.creatorPolicy?.firstPartyMcpTools;
17422
18043
  if (!stored) return { missing: [], policy: null };
17423
- const effective = new Set(allowedFirstPartyMcpToolsForSession5(input.settings, stored));
18044
+ const effective = new Set(allowedFirstPartyMcpToolsForSession6(input.settings, stored));
17424
18045
  const missing = resolveFirstPartyMcpToolPolicy3(input.settings).default.filter(
17425
18046
  (tool) => !effective.has(tool) && !input.leaveOut?.has(tool)
17426
18047
  );
@@ -17939,7 +18560,7 @@ function orderScheduledTaskAccessAttention(items) {
17939
18560
  }
17940
18561
 
17941
18562
  // src/domain/insights.ts
17942
- import { configuredStaticUsageLimits as configuredStaticUsageLimits3 } from "@opengeni/config";
18563
+ import { configuredStaticUsageLimits as configuredStaticUsageLimits5 } from "@opengeni/config";
17943
18564
  import {
17944
18565
  WorkspaceInsightsSnapshot
17945
18566
  } from "@opengeni/contracts";
@@ -18070,7 +18691,7 @@ function cacheHitPct(cached2, input) {
18070
18691
  function startOfUtcDay(date) {
18071
18692
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
18072
18693
  }
18073
- function startOfUtcMonth3(date) {
18694
+ function startOfUtcMonth5(date) {
18074
18695
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));
18075
18696
  }
18076
18697
  function startOfUtcYear(date) {
@@ -18099,7 +18720,7 @@ function resolveRangeWindow(range, now) {
18099
18720
  cacheSeriesLabel = "Cache hit % / UTC day";
18100
18721
  break;
18101
18722
  case "month":
18102
- since = startOfUtcMonth3(now);
18723
+ since = startOfUtcMonth5(now);
18103
18724
  rangeLabel = "This month (UTC)";
18104
18725
  priorLabel = "Prior equal window";
18105
18726
  seriesLabel = "Credit $ / UTC day";
@@ -18230,7 +18851,7 @@ async function getWorkspaceInsights(db, settings, input, observePhase) {
18230
18851
  until: window.until,
18231
18852
  priorSince: window.priorSince,
18232
18853
  priorUntil: window.priorUntil,
18233
- monthSince: startOfUtcMonth3(now),
18854
+ monthSince: startOfUtcMonth5(now),
18234
18855
  granularity: input.range === "today" ? "hour" : "day",
18235
18856
  warmGroupLimit: 24
18236
18857
  })
@@ -18473,7 +19094,7 @@ async function getWorkspaceInsights(db, settings, input, observePhase) {
18473
19094
  billing: fact ? billingPathOf(fact.billingPath) : null
18474
19095
  };
18475
19096
  });
18476
- const limits = configuredStaticUsageLimits3(settings);
19097
+ const limits = configuredStaticUsageLimits5(settings);
18477
19098
  const floor = floorRows.filter((row) => {
18478
19099
  if (!model) return true;
18479
19100
  return row.model === model;
@@ -20721,7 +21342,7 @@ async function submitComposerDraftForRequest(deps, grant, workspaceId, sessionId
20721
21342
  import { DraftTimelineAnnotations as DraftTimelineAnnotations3 } from "@opengeni/contracts";
20722
21343
  import { HTTPException as HTTPException30 } from "hono/http-exception";
20723
21344
  import {
20724
- checkWorkspaceAllowance as checkWorkspaceAllowance4,
21345
+ checkWorkspaceAllowance as checkWorkspaceAllowance7,
20725
21346
  deleteSessionQueueItemInTransaction,
20726
21347
  editQueuedTurnInTransaction,
20727
21348
  getComposerDraftInTransaction as getComposerDraftInTransaction2,
@@ -20819,7 +21440,7 @@ async function assertFreshAgentCommandAllowance(tx, deps, context, targetSession
20819
21440
  }
20820
21441
  }
20821
21442
  const frozen = await frozenInitiatorForCommandActor2(tx, context.workspaceId, agentActor(context));
20822
- const refusal = await checkWorkspaceAllowance4(tx, {
21443
+ const refusal = await checkWorkspaceAllowance7(tx, {
20823
21444
  accountId: target.accountId,
20824
21445
  workspaceId: context.workspaceId,
20825
21446
  subjectId: initiatingHumanForAllowance(frozen)
@@ -22142,7 +22763,6 @@ import { createPinnedIntegrationTransport as createPinnedIntegrationTransport2 }
22142
22763
  import {
22143
22764
  buildConnectionTokenResolver,
22144
22765
  getConnectionMetadata as getConnectionMetadata5,
22145
- listConnectionsMetadata as listConnectionsMetadata4,
22146
22766
  listEnabledMcpCapabilityServers as listEnabledMcpCapabilityServers3,
22147
22767
  listConnectorToolPermissionPolicies,
22148
22768
  listChangedConnectorToolPermissions,
@@ -22155,6 +22775,7 @@ import {
22155
22775
  import { HTTPException as HTTPException32 } from "hono/http-exception";
22156
22776
  import {
22157
22777
  GMAIL_REST_MCP_TOOLS,
22778
+ gmailToolAvailableOnDeployment,
22158
22779
  gmailToolSupportsScopes,
22159
22780
  isOfficialGmailMcpConfig,
22160
22781
  isOfficialSlackMcpConfig
@@ -22240,11 +22861,12 @@ async function resolveTarget2(input) {
22240
22861
  throw new HTTPException32(403, {
22241
22862
  message: "Only the authenticated connection owner may manage personal tool permissions"
22242
22863
  });
22243
- const visible = ref ? await listConnectionsMetadata4(
22244
- input.db,
22245
- input.workspaceId,
22246
- input.personalOwnerVerified ? input.grant.subjectId : null
22247
- ) : [];
22864
+ const source = personalConnectionDelegationSourceForGrant(input.grant);
22865
+ const visible = ref ? await visibleMcpAccountConnections(input.db, {
22866
+ accountId: input.grant.accountId,
22867
+ workspaceId: input.workspaceId,
22868
+ source: input.personalOwnerVerified && source.kind === "subject" ? source : { kind: "none" }
22869
+ }) : [];
22248
22870
  const bindings = mcpAccountBindingsFromVisibleConnections({
22249
22871
  accountId: input.grant.accountId,
22250
22872
  workspaceId: input.workspaceId,
@@ -22286,15 +22908,20 @@ async function resolveTarget2(input) {
22286
22908
  listedTools: null
22287
22909
  };
22288
22910
  }
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;
22911
+ function reviewedConnectorToolCatalog(server, grantedScopes, deployment) {
22912
+ const tools = isOfficialGmailMcpConfig(server.url, server.connectionRef) ? GMAIL_REST_MCP_TOOLS.filter(
22913
+ (tool) => gmailToolSupportsScopes(tool.name, grantedScopes) && gmailToolAvailableOnDeployment(tool.name, {
22914
+ watchTopicName: deployment.gmailWatchTopicName
22915
+ })
22916
+ ) : isOfficialSlackMcpConfig(server.url, server.connectionRef) ? slackRestMcpToolsForScopes(grantedScopes) : null;
22291
22917
  return tools?.filter((tool) => !server.allowedTools || server.allowedTools.includes(tool.name)) ?? null;
22292
22918
  }
22293
22919
  async function listTools(input, target) {
22294
22920
  if (target.listedTools !== null) return target.listedTools;
22295
22921
  const reviewed = reviewedConnectorToolCatalog(
22296
22922
  target.server,
22297
- target.connection?.grantedScopes ?? []
22923
+ target.connection?.grantedScopes ?? [],
22924
+ input.settings
22298
22925
  );
22299
22926
  if (reviewed !== null) return reviewed;
22300
22927
  let headers = { ...target.server.headers };
@@ -22303,7 +22930,9 @@ async function listTools(input, target) {
22303
22930
  input.db,
22304
22931
  input.settings
22305
22932
  )({
22306
- workspaceId: input.workspaceId,
22933
+ // The selected account's credentials remain in its origin workspace;
22934
+ // approval preferences remain scoped to the workspace being configured.
22935
+ workspaceId: target.connection?.workspaceId ?? input.workspaceId,
22307
22936
  ...target.connection?.subjectId ? { subjectId: input.grant.subjectId } : {},
22308
22937
  serverId: target.server.id,
22309
22938
  toolName: "tools/list",
@@ -22560,7 +23189,11 @@ export {
22560
23189
  PR_REVIEW_AUTOMATION_SETUP,
22561
23190
  ProductionEditableArtifactSnapshotVerifier,
22562
23191
  PublicSkillSearchError,
23192
+ REALTIME_VOICE_DEBIT_TYPE,
23193
+ REALTIME_VOICE_INSUFFICIENT_CREDITS_MESSAGE,
23194
+ REALTIME_VOICE_SOURCE_TYPE,
22563
23195
  RIG_DEFAULT_VARIABLE_SET_LOAD_CONCURRENCY,
23196
+ RealtimeVoiceUnavailableError,
22564
23197
  SESSION_AUTHORIZATION_DEFAULT_REAUTHORIZE_MS,
22565
23198
  SESSION_CREATED_BY_KINDS,
22566
23199
  SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS,
@@ -22579,14 +23212,20 @@ export {
22579
23212
  SystemEditableArtifactLiveScheduler,
22580
23213
  TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS,
22581
23214
  TURN_ACTIVITY_CANCELLATION_HEARTBEAT_INTERVAL_MS,
23215
+ TranscriptionBillingRefusedError,
22582
23216
  TranscriptionServiceError,
22583
23217
  USER_PRESENCE_FLUSH_DELAY_MS,
22584
23218
  USER_PRESENCE_THROTTLE_MS,
22585
23219
  VIDEO_GENERATION_ADAPTER_VERSION,
22586
23220
  VIDEO_GENERATION_MODEL_CATALOG,
23221
+ VOICE_INPUT_DEBIT_TYPE,
23222
+ VOICE_INPUT_SOURCE_TYPE,
23223
+ WEB_SEARCH_DEBIT_TYPE,
23224
+ WEB_SEARCH_SOURCE_TYPE,
22587
23225
  WORKSPACE_INSIGHTS_MODEL_FILTER_MAX_UTF8_BYTES,
22588
23226
  WORKSPACE_INSIGHTS_PROVIDER_FILTER_MAX_UTF8_BYTES,
22589
23227
  WebCryptoEditableArtifactLiveTokens,
23228
+ WebSearchBillingRefusedError,
22590
23229
  WorkspaceInsightsFilterValidationError,
22591
23230
  acceptSessionUserMessage,
22592
23231
  acceptSessionUserMessageWithOutcome,
@@ -22679,12 +23318,15 @@ export {
22679
23318
  createCompanyProfileDurableLearningAdapter,
22680
23319
  createGitHubSkillSourceClient,
22681
23320
  createPublicSkillSearchClient,
23321
+ createRealtimeVoiceBilling,
22682
23322
  createRigForApi,
22683
23323
  createRigVersionForApi,
22684
23324
  createSessionForRequest,
22685
23325
  createSessionForRequestWithOutcome,
22686
23326
  createUserPresenceRecorder,
22687
23327
  createValidatedScheduledTask,
23328
+ createVoiceInputBilling,
23329
+ createWebSearchBilling,
22688
23330
  creationInitiatorForGrant,
22689
23331
  creditsDefaultSessionModel,
22690
23332
  decodeEditableArtifactBrokerHint,
@@ -22697,6 +23339,7 @@ export {
22697
23339
  deleteHumanQueuePrompt,
22698
23340
  deleteRigForApi,
22699
23341
  deleteScheduledTaskLifecycle,
23342
+ deploymentRealtimeVoice,
22700
23343
  describeConfiguredExportFormats,
22701
23344
  describeEditableArtifactExportFormats,
22702
23345
  directPersonalConnectionSubjectId,
@@ -22920,6 +23563,8 @@ export {
22920
23563
  readSessionAttachmentFiles,
22921
23564
  readSessionLineage,
22922
23565
  readSkill,
23566
+ realtimeVoiceOfferProblem,
23567
+ reconcileUnsettledVoiceInputCharges,
22923
23568
  recordManagedAuthLoggedFailure,
22924
23569
  recordRigAuditEvent,
22925
23570
  recordSessionCreated,
@@ -23056,6 +23701,7 @@ export {
23056
23701
  settingsWithRigProviderImage,
23057
23702
  settingsWithSessionMcpServerMetadata,
23058
23703
  settingsWithWorkspaceSandboxImage,
23704
+ settleVoiceInputUsage,
23059
23705
  signedJsonAutomationAdapter,
23060
23706
  skillBundleHash,
23061
23707
  stableJson,
@@ -23113,6 +23759,8 @@ export {
23113
23759
  videoGenerationProviderIdempotencyKey,
23114
23760
  videoGenerationRequestDigest,
23115
23761
  visibleMcpAccountConnections,
23762
+ voiceInputBillableUsage,
23763
+ webSearchCreditBillingActive,
23116
23764
  withAgentConfigHttpErrors,
23117
23765
  withDefaultEnabledCapabilityMcpTools,
23118
23766
  withFrozenPersonalConnectionDelegations,