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