@absolutejs/voice 0.0.22-beta.291 → 0.0.22-beta.292
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/README.md +53 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +90 -14
- package/dist/modelAdapters.d.ts +37 -0
- package/dist/testing/index.js +87 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3888,6 +3888,59 @@ Built-in policy presets:
|
|
|
3888
3888
|
- `cost-cap`: rank by cost and reject providers above `maxCost`.
|
|
3889
3889
|
- `balanced`: weighted score using cost, latency, quality, and priority.
|
|
3890
3890
|
|
|
3891
|
+
Use `createVoiceProviderOrchestrationProfile(...)` when one app has multiple provider surfaces with different tradeoffs. This is the Vapi-style "choose providers without glue" layer, but still code-owned: a live call can prefer low latency with a circuit breaker, while a background summary can prefer lower cost and stricter budget caps.
|
|
3892
|
+
|
|
3893
|
+
```ts
|
|
3894
|
+
import {
|
|
3895
|
+
createVoiceProviderOrchestrationProfile,
|
|
3896
|
+
createVoiceProviderRouter
|
|
3897
|
+
} from '@absolutejs/voice';
|
|
3898
|
+
|
|
3899
|
+
const providerProfile = createVoiceProviderOrchestrationProfile({
|
|
3900
|
+
id: 'support-agent-providers',
|
|
3901
|
+
defaultSurface: 'live-call',
|
|
3902
|
+
surfaces: {
|
|
3903
|
+
'live-call': {
|
|
3904
|
+
policy: 'latency-first',
|
|
3905
|
+
fallback: ['openai', 'anthropic', 'gemini'],
|
|
3906
|
+
maxLatencyMs: 900,
|
|
3907
|
+
providerHealth: {
|
|
3908
|
+
failureThreshold: 1,
|
|
3909
|
+
cooldownMs: 30_000,
|
|
3910
|
+
rateLimitCooldownMs: 120_000
|
|
3911
|
+
},
|
|
3912
|
+
providerProfiles: {
|
|
3913
|
+
openai: { cost: 6, latencyMs: 650, quality: 0.92, timeoutMs: 3500 },
|
|
3914
|
+
anthropic: { cost: 7, latencyMs: 850, quality: 0.95, timeoutMs: 4500 },
|
|
3915
|
+
gemini: { cost: 2, latencyMs: 700, quality: 0.86, timeoutMs: 3500 }
|
|
3916
|
+
}
|
|
3917
|
+
},
|
|
3918
|
+
'background-summary': {
|
|
3919
|
+
policy: 'cost-cap',
|
|
3920
|
+
fallback: ['gemini', 'openai'],
|
|
3921
|
+
maxCost: 3,
|
|
3922
|
+
minQuality: 0.82,
|
|
3923
|
+
providerProfiles: {
|
|
3924
|
+
openai: { cost: 6, latencyMs: 650, quality: 0.92 },
|
|
3925
|
+
gemini: { cost: 2, latencyMs: 700, quality: 0.86 }
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
});
|
|
3930
|
+
|
|
3931
|
+
const liveModel = createVoiceProviderRouter({
|
|
3932
|
+
providers,
|
|
3933
|
+
orchestrationProfile: providerProfile,
|
|
3934
|
+
orchestrationSurface: 'live-call'
|
|
3935
|
+
});
|
|
3936
|
+
|
|
3937
|
+
const summaryModel = createVoiceProviderRouter({
|
|
3938
|
+
providers,
|
|
3939
|
+
orchestrationProfile: providerProfile,
|
|
3940
|
+
orchestrationSurface: 'background-summary'
|
|
3941
|
+
});
|
|
3942
|
+
```
|
|
3943
|
+
|
|
3891
3944
|
Budget filters are strict. If you pass `maxCost`, `maxLatencyMs`, or `minQuality`, providers outside those limits are removed before ranking, even if they were selected by the request.
|
|
3892
3945
|
|
|
3893
3946
|
```ts
|
package/dist/index.d.ts
CHANGED
|
@@ -46,7 +46,7 @@ export { applyVoiceTelephonyOutcome, assertVoiceTelephonyWebhookNormalizationEvi
|
|
|
46
46
|
export { assertVoicePhoneCallControlEvidence, assertVoicePhoneAssistantEvidence, createVoicePhoneAgent, evaluateVoicePhoneCallControlEvidence, evaluateVoicePhoneAssistantEvidence } from './phoneAgent';
|
|
47
47
|
export { createStoredVoiceCallReviewArtifact, createStoredVoiceExternalObjectMap, createStoredVoiceIntegrationEvent, createStoredVoiceOpsTask, createVoiceFileIncidentBundleStore, createVoiceFileExternalObjectMapStore, createVoiceFileAssistantMemoryStore, createVoiceFileAuditEventStore, createVoiceFileAuditSinkDeliveryStore, createVoiceFileCampaignStore, createVoiceFileIntegrationEventStore, createVoiceFileReviewStore, createVoiceFileRuntimeStorage, createVoiceFileSessionStore, createVoiceFileTaskStore, createVoiceFileTraceSinkDeliveryStore, createVoiceFileTraceEventStore } from './fileStore';
|
|
48
48
|
export { createVoiceAssistantMemoryHandle, createVoiceAssistantMemoryRecord, createVoiceMemoryAssistantMemoryStore, resolveVoiceAssistantMemoryNamespace } from './assistantMemory';
|
|
49
|
-
export { createAnthropicVoiceAssistantModel, createGeminiVoiceAssistantModel, createJSONVoiceAssistantModel, createOpenAIVoiceAssistantModel, resolveVoiceProviderRoutingPolicyPreset, createVoiceProviderRouter } from './modelAdapters';
|
|
49
|
+
export { createAnthropicVoiceAssistantModel, createGeminiVoiceAssistantModel, createJSONVoiceAssistantModel, createOpenAIVoiceAssistantModel, createVoiceProviderOrchestrationProfile, resolveVoiceProviderRoutingPolicyPreset, createVoiceProviderRouter } from './modelAdapters';
|
|
50
50
|
export { createOpenAIRealtimeAdapter } from './openaiRealtime';
|
|
51
51
|
export { createOpenAIVoiceTTS } from './openaiTTS';
|
|
52
52
|
export { createVoiceProviderHealthHTMLHandler, createVoiceProviderHealthJSONHandler, createVoiceProviderHealthRoutes, renderVoiceProviderHealthHTML, summarizeVoiceProviderHealth } from './providerHealth';
|
|
@@ -106,7 +106,7 @@ export type { VoiceEvalBaselineComparison, VoiceEvalBaselineComparisonOptions, V
|
|
|
106
106
|
export type { VoiceSimulationSuiteAssertionInput, VoiceSimulationSuiteAssertionReport, VoiceSimulationSuiteEvalRoutesOptions, VoiceSimulationSuiteOptions, VoiceSimulationSuiteReport, VoiceSimulationSuiteRoutesOptions, VoiceSimulationSuiteSection, VoiceSimulationSuiteSectionSummary, VoiceSimulationSuiteStatus } from './simulationSuite';
|
|
107
107
|
export type { VoiceWorkflowContract, VoiceWorkflowContractDefinition, VoiceWorkflowContractField, VoiceWorkflowContractFieldMatch, VoiceWorkflowContractPresetName, VoiceWorkflowContractPresetOptions, VoiceWorkflowContractTracePayload, VoiceWorkflowContractValidation, VoiceWorkflowContractValidationIssue, VoiceWorkflowOutcome } from './workflowContract';
|
|
108
108
|
export type { VoiceSessionListHTMLHandlerOptions, VoiceSessionListItem, VoiceSessionListOptions, VoiceSessionListRoutesOptions, VoiceSessionListStatus, VoiceProviderFallbackRecoverySummary, VoiceSessionReplay, VoiceSessionReplayHTMLHandlerOptions, VoiceSessionReplayOptions, VoiceSessionReplayRoutesOptions, VoiceSessionReplayTurn } from './sessionReplay';
|
|
109
|
-
export type { AnthropicVoiceAssistantModelOptions, GeminiVoiceAssistantModelOptions, OpenAIVoiceAssistantModelOptions, VoiceProviderRouterEvent, VoiceProviderRouterFallbackMode, VoiceProviderRouterHealthOptions, VoiceProviderRouterOptions, VoiceProviderRouterPolicy, VoiceProviderRouterPolicyPreset, VoiceProviderRouterPolicyWeights, VoiceProviderRouterProviderHealth, VoiceProviderRouterProviderProfile, VoiceProviderRouterStrategy, VoiceJSONAssistantModelHandler, VoiceJSONAssistantModelOptions } from './modelAdapters';
|
|
109
|
+
export type { AnthropicVoiceAssistantModelOptions, GeminiVoiceAssistantModelOptions, OpenAIVoiceAssistantModelOptions, VoiceProviderRouterEvent, VoiceProviderRouterFallbackMode, VoiceProviderRouterHealthOptions, VoiceProviderRouterOptions, VoiceProviderOrchestrationProfile, VoiceProviderOrchestrationProfileOptions, VoiceProviderOrchestrationResolvedSurface, VoiceProviderOrchestrationSurface, VoiceProviderRouterPolicy, VoiceProviderRouterPolicyPreset, VoiceProviderRouterPolicyWeights, VoiceProviderRouterProviderHealth, VoiceProviderRouterProviderProfile, VoiceProviderRouterStrategy, VoiceJSONAssistantModelHandler, VoiceJSONAssistantModelOptions } from './modelAdapters';
|
|
110
110
|
export type { OpenAIVoiceTTSOptions, OpenAIVoiceTTSVoice } from './openaiTTS';
|
|
111
111
|
export type { OpenAIRealtimeAdapterOptions, OpenAIRealtimeModel, OpenAIRealtimeNoiseReduction, OpenAIRealtimeResponseMode, OpenAIRealtimeTranscriptionModel, OpenAIRealtimeVoice } from './openaiRealtime';
|
|
112
112
|
export type { VoiceProviderHealthStatus, VoiceProviderHealthSummary, VoiceProviderHealthSummaryOptions } from './providerHealth';
|
package/dist/index.js
CHANGED
|
@@ -21650,6 +21650,7 @@ var createStoredVoiceExternalObjectMap = (mapping) => createVoiceExternalObjectM
|
|
|
21650
21650
|
sourceType: mapping.sourceType
|
|
21651
21651
|
});
|
|
21652
21652
|
// src/modelAdapters.ts
|
|
21653
|
+
var isVoiceProviderRoutingPolicyPreset = (value) => value === "balanced" || value === "cost-cap" || value === "cost-first" || value === "latency-first" || value === "quality-first";
|
|
21653
21654
|
var resolveVoiceProviderRoutingPolicyPreset = (preset, options = {}) => {
|
|
21654
21655
|
switch (preset) {
|
|
21655
21656
|
case "balanced":
|
|
@@ -21691,6 +21692,75 @@ var resolveVoiceProviderRoutingPolicyPreset = (preset, options = {}) => {
|
|
|
21691
21692
|
};
|
|
21692
21693
|
}
|
|
21693
21694
|
};
|
|
21695
|
+
var resolveVoiceProviderRoutingPolicy = (policy) => {
|
|
21696
|
+
if (!policy) {
|
|
21697
|
+
return;
|
|
21698
|
+
}
|
|
21699
|
+
if (typeof policy === "string") {
|
|
21700
|
+
return isVoiceProviderRoutingPolicyPreset(policy) ? resolveVoiceProviderRoutingPolicyPreset(policy) : {
|
|
21701
|
+
strategy: policy
|
|
21702
|
+
};
|
|
21703
|
+
}
|
|
21704
|
+
return policy;
|
|
21705
|
+
};
|
|
21706
|
+
var mergeDefinedProviderPolicyFields = (base, surface) => {
|
|
21707
|
+
const next = {
|
|
21708
|
+
...base ?? {}
|
|
21709
|
+
};
|
|
21710
|
+
if (surface.allowProviders !== undefined) {
|
|
21711
|
+
next.allowProviders = surface.allowProviders;
|
|
21712
|
+
}
|
|
21713
|
+
if (surface.fallbackMode !== undefined) {
|
|
21714
|
+
next.fallbackMode = surface.fallbackMode;
|
|
21715
|
+
}
|
|
21716
|
+
if (surface.maxCost !== undefined) {
|
|
21717
|
+
next.maxCost = surface.maxCost;
|
|
21718
|
+
}
|
|
21719
|
+
if (surface.maxLatencyMs !== undefined) {
|
|
21720
|
+
next.maxLatencyMs = surface.maxLatencyMs;
|
|
21721
|
+
}
|
|
21722
|
+
if (surface.minQuality !== undefined) {
|
|
21723
|
+
next.minQuality = surface.minQuality;
|
|
21724
|
+
}
|
|
21725
|
+
if (surface.strategy !== undefined) {
|
|
21726
|
+
next.strategy = surface.strategy;
|
|
21727
|
+
}
|
|
21728
|
+
if (surface.weights !== undefined) {
|
|
21729
|
+
next.weights = {
|
|
21730
|
+
...base?.weights ?? {},
|
|
21731
|
+
...surface.weights
|
|
21732
|
+
};
|
|
21733
|
+
}
|
|
21734
|
+
return next;
|
|
21735
|
+
};
|
|
21736
|
+
var createVoiceProviderOrchestrationProfile = (options) => {
|
|
21737
|
+
const surfaceNames = Object.keys(options.surfaces);
|
|
21738
|
+
const defaultSurface = options.defaultSurface ?? surfaceNames[0];
|
|
21739
|
+
if (!defaultSurface || !options.surfaces[defaultSurface]) {
|
|
21740
|
+
throw new Error("Voice provider orchestration profile has no surfaces.");
|
|
21741
|
+
}
|
|
21742
|
+
return {
|
|
21743
|
+
defaultSurface,
|
|
21744
|
+
id: options.id,
|
|
21745
|
+
resolve: (surface = defaultSurface) => {
|
|
21746
|
+
const config = options.surfaces[surface];
|
|
21747
|
+
if (!config) {
|
|
21748
|
+
throw new Error(`Voice provider orchestration profile ${options.id} has no surface "${surface}".`);
|
|
21749
|
+
}
|
|
21750
|
+
const policy = mergeDefinedProviderPolicyFields(resolveVoiceProviderRoutingPolicy(config.policy), config);
|
|
21751
|
+
return {
|
|
21752
|
+
allowProviders: config.allowProviders,
|
|
21753
|
+
fallback: config.fallback,
|
|
21754
|
+
fallbackMode: config.fallbackMode,
|
|
21755
|
+
policy,
|
|
21756
|
+
providerHealth: config.providerHealth,
|
|
21757
|
+
providerProfiles: config.providerProfiles,
|
|
21758
|
+
timeoutMs: config.timeoutMs
|
|
21759
|
+
};
|
|
21760
|
+
},
|
|
21761
|
+
surfaces: options.surfaces
|
|
21762
|
+
};
|
|
21763
|
+
};
|
|
21694
21764
|
var OUTPUT_SCHEMA = {
|
|
21695
21765
|
additionalProperties: false,
|
|
21696
21766
|
properties: {
|
|
@@ -21858,19 +21928,23 @@ var createJSONVoiceAssistantModel = (options) => ({
|
|
|
21858
21928
|
var createVoiceProviderRouter = (options) => {
|
|
21859
21929
|
const providerIds = Object.keys(options.providers);
|
|
21860
21930
|
const firstProvider = providerIds[0];
|
|
21861
|
-
const
|
|
21862
|
-
|
|
21863
|
-
} : options.policy;
|
|
21931
|
+
const orchestrationSurface = options.orchestrationProfile?.resolve(options.orchestrationSurface);
|
|
21932
|
+
const policy = resolveVoiceProviderRoutingPolicy(options.policy) ?? resolveVoiceProviderRoutingPolicy(orchestrationSurface?.policy);
|
|
21864
21933
|
const strategy = policy?.strategy ?? "prefer-selected";
|
|
21865
|
-
const fallbackMode = policy?.fallbackMode ?? options.fallbackMode ?? "provider-error";
|
|
21866
|
-
const
|
|
21934
|
+
const fallbackMode = policy?.fallbackMode ?? options.fallbackMode ?? orchestrationSurface?.fallbackMode ?? "provider-error";
|
|
21935
|
+
const providerProfiles = {
|
|
21936
|
+
...orchestrationSurface?.providerProfiles ?? {},
|
|
21937
|
+
...options.providerProfiles ?? {}
|
|
21938
|
+
};
|
|
21939
|
+
const providerHealthOption = options.providerHealth ?? orchestrationSurface?.providerHealth;
|
|
21940
|
+
const healthOptions = typeof providerHealthOption === "object" ? providerHealthOption : providerHealthOption ? {} : undefined;
|
|
21867
21941
|
const healthState = new Map;
|
|
21868
21942
|
const now = () => healthOptions?.now?.() ?? Date.now();
|
|
21869
21943
|
const failureThreshold = Math.max(1, healthOptions?.failureThreshold ?? 1);
|
|
21870
21944
|
const cooldownMs = Math.max(0, healthOptions?.cooldownMs ?? 30000);
|
|
21871
21945
|
const rateLimitCooldownMs = Math.max(0, healthOptions?.rateLimitCooldownMs ?? 60000);
|
|
21872
21946
|
const getProviderTimeoutMs = (provider) => {
|
|
21873
|
-
const timeoutMs =
|
|
21947
|
+
const timeoutMs = providerProfiles[provider]?.timeoutMs ?? options.timeoutMs ?? orchestrationSurface?.timeoutMs;
|
|
21874
21948
|
return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : undefined;
|
|
21875
21949
|
};
|
|
21876
21950
|
const getHealth = (provider) => {
|
|
@@ -21936,12 +22010,12 @@ var createVoiceProviderRouter = (options) => {
|
|
|
21936
22010
|
return cloneHealth(provider);
|
|
21937
22011
|
};
|
|
21938
22012
|
const resolveAllowedProviders = async (input) => {
|
|
21939
|
-
const allowProviders = policy?.allowProviders ?? options.allowProviders;
|
|
22013
|
+
const allowProviders = policy?.allowProviders ?? options.allowProviders ?? orchestrationSurface?.allowProviders;
|
|
21940
22014
|
const allowed = typeof allowProviders === "function" ? await allowProviders(input) : allowProviders;
|
|
21941
22015
|
return new Set(allowed ?? providerIds);
|
|
21942
22016
|
};
|
|
21943
22017
|
const passesBudgetFilters = (provider) => {
|
|
21944
|
-
const profile =
|
|
22018
|
+
const profile = providerProfiles[provider];
|
|
21945
22019
|
if (typeof policy?.maxCost === "number" && typeof profile?.cost === "number" && profile.cost > policy.maxCost) {
|
|
21946
22020
|
return false;
|
|
21947
22021
|
}
|
|
@@ -21954,7 +22028,7 @@ var createVoiceProviderRouter = (options) => {
|
|
|
21954
22028
|
return true;
|
|
21955
22029
|
};
|
|
21956
22030
|
const getBalancedScore = (provider) => {
|
|
21957
|
-
const profile =
|
|
22031
|
+
const profile = providerProfiles[provider];
|
|
21958
22032
|
if (policy?.scoreProvider) {
|
|
21959
22033
|
return policy.scoreProvider(provider, profile);
|
|
21960
22034
|
}
|
|
@@ -21966,8 +22040,8 @@ var createVoiceProviderRouter = (options) => {
|
|
|
21966
22040
|
return providers;
|
|
21967
22041
|
}
|
|
21968
22042
|
return [...providers].sort((left, right) => {
|
|
21969
|
-
const leftProfile =
|
|
21970
|
-
const rightProfile =
|
|
22043
|
+
const leftProfile = providerProfiles[left];
|
|
22044
|
+
const rightProfile = providerProfiles[right];
|
|
21971
22045
|
if (strategy === "quality-first") {
|
|
21972
22046
|
return (rightProfile?.quality ?? Number.MIN_SAFE_INTEGER) - (leftProfile?.quality ?? Number.MIN_SAFE_INTEGER) || (leftProfile?.priority ?? Number.MAX_SAFE_INTEGER) - (rightProfile?.priority ?? Number.MAX_SAFE_INTEGER) || (leftProfile?.latencyMs ?? Number.MAX_SAFE_INTEGER) - (rightProfile?.latencyMs ?? Number.MAX_SAFE_INTEGER) || (leftProfile?.cost ?? Number.MAX_SAFE_INTEGER) - (rightProfile?.cost ?? Number.MAX_SAFE_INTEGER);
|
|
21973
22047
|
}
|
|
@@ -21982,7 +22056,8 @@ var createVoiceProviderRouter = (options) => {
|
|
|
21982
22056
|
const resolveOrder = async (input) => {
|
|
21983
22057
|
const selectedProvider = await options.selectProvider?.(input);
|
|
21984
22058
|
const allowedProviders = await resolveAllowedProviders(input);
|
|
21985
|
-
const
|
|
22059
|
+
const fallbackSource = options.fallback ?? orchestrationSurface?.fallback;
|
|
22060
|
+
const fallbackOrder = typeof fallbackSource === "function" ? await fallbackSource(input) : fallbackSource;
|
|
21986
22061
|
const allowedRankedProviders = sortProviders([
|
|
21987
22062
|
...fallbackOrder ?? providerIds
|
|
21988
22063
|
]).filter((provider) => allowedProviders.has(provider));
|
|
@@ -30823,11 +30898,11 @@ var withTimeout2 = async (input) => {
|
|
|
30823
30898
|
}
|
|
30824
30899
|
}
|
|
30825
30900
|
};
|
|
30826
|
-
var
|
|
30901
|
+
var isVoiceProviderRoutingPolicyPreset2 = (policy) => policy === "balanced" || policy === "cost-cap" || policy === "cost-first" || policy === "latency-first" || policy === "quality-first";
|
|
30827
30902
|
var createResolver = (options) => {
|
|
30828
30903
|
const providerIds = Object.keys(options.adapters);
|
|
30829
30904
|
const firstProvider = providerIds[0];
|
|
30830
|
-
const policy = typeof options.policy === "string" ?
|
|
30905
|
+
const policy = typeof options.policy === "string" ? isVoiceProviderRoutingPolicyPreset2(options.policy) ? resolveVoiceProviderRoutingPolicyPreset(options.policy) : {
|
|
30831
30906
|
strategy: options.policy
|
|
30832
30907
|
} : options.policy;
|
|
30833
30908
|
const strategy = policy?.strategy ?? "prefer-selected";
|
|
@@ -33231,6 +33306,7 @@ export {
|
|
|
33231
33306
|
createVoiceQualityRoutes,
|
|
33232
33307
|
createVoiceProviderSloRoutes,
|
|
33233
33308
|
createVoiceProviderRouter,
|
|
33309
|
+
createVoiceProviderOrchestrationProfile,
|
|
33234
33310
|
createVoiceProviderHealthRoutes,
|
|
33235
33311
|
createVoiceProviderHealthJSONHandler,
|
|
33236
33312
|
createVoiceProviderHealthHTMLHandler,
|
package/dist/modelAdapters.d.ts
CHANGED
|
@@ -92,6 +92,41 @@ export type VoiceProviderRouterProviderHealth<TProvider extends string = string>
|
|
|
92
92
|
status: 'healthy' | 'suppressed';
|
|
93
93
|
suppressedUntil?: number;
|
|
94
94
|
};
|
|
95
|
+
export type VoiceProviderOrchestrationSurface<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TProvider extends string = string> = {
|
|
96
|
+
allowProviders?: readonly TProvider[] | ((input: VoiceAgentModelInput<TContext, TSession>) => readonly TProvider[] | Promise<readonly TProvider[]>);
|
|
97
|
+
fallback?: readonly TProvider[] | ((input: VoiceAgentModelInput<TContext, TSession>) => readonly TProvider[] | Promise<readonly TProvider[]>);
|
|
98
|
+
fallbackMode?: VoiceProviderRouterFallbackMode;
|
|
99
|
+
maxCost?: number;
|
|
100
|
+
maxLatencyMs?: number;
|
|
101
|
+
minQuality?: number;
|
|
102
|
+
policy?: VoiceProviderRouterPolicy<TContext, TSession, TProvider>;
|
|
103
|
+
providerHealth?: boolean | VoiceProviderRouterHealthOptions;
|
|
104
|
+
providerProfiles?: Partial<Record<TProvider, VoiceProviderRouterProviderProfile>>;
|
|
105
|
+
strategy?: VoiceProviderRouterStrategy;
|
|
106
|
+
timeoutMs?: number;
|
|
107
|
+
weights?: VoiceProviderRouterPolicyWeights;
|
|
108
|
+
};
|
|
109
|
+
export type VoiceProviderOrchestrationProfile<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TProvider extends string = string, TSurface extends string = string> = {
|
|
110
|
+
defaultSurface?: TSurface;
|
|
111
|
+
id: string;
|
|
112
|
+
resolve: (surface?: TSurface) => VoiceProviderOrchestrationResolvedSurface<TContext, TSession, TProvider>;
|
|
113
|
+
surfaces: Record<TSurface, VoiceProviderOrchestrationSurface<TContext, TSession, TProvider>>;
|
|
114
|
+
};
|
|
115
|
+
export type VoiceProviderOrchestrationProfileOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TProvider extends string = string, TSurface extends string = string> = {
|
|
116
|
+
defaultSurface?: TSurface;
|
|
117
|
+
id: string;
|
|
118
|
+
surfaces: Record<TSurface, VoiceProviderOrchestrationSurface<TContext, TSession, TProvider>>;
|
|
119
|
+
};
|
|
120
|
+
export type VoiceProviderOrchestrationResolvedSurface<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TProvider extends string = string> = {
|
|
121
|
+
allowProviders?: VoiceProviderOrchestrationSurface<TContext, TSession, TProvider>['allowProviders'];
|
|
122
|
+
fallback?: VoiceProviderOrchestrationSurface<TContext, TSession, TProvider>['fallback'];
|
|
123
|
+
fallbackMode?: VoiceProviderRouterFallbackMode;
|
|
124
|
+
policy?: VoiceProviderRouterPolicy<TContext, TSession, TProvider>;
|
|
125
|
+
providerHealth?: boolean | VoiceProviderRouterHealthOptions;
|
|
126
|
+
providerProfiles?: Partial<Record<TProvider, VoiceProviderRouterProviderProfile>>;
|
|
127
|
+
timeoutMs?: number;
|
|
128
|
+
};
|
|
129
|
+
export declare const createVoiceProviderOrchestrationProfile: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TProvider extends string = string, TSurface extends string = string>(options: VoiceProviderOrchestrationProfileOptions<TContext, TSession, TProvider, TSurface>) => VoiceProviderOrchestrationProfile<TContext, TSession, TProvider, TSurface>;
|
|
95
130
|
export type VoiceProviderRouterOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown, TProvider extends string = string> = {
|
|
96
131
|
allowProviders?: readonly TProvider[] | ((input: VoiceAgentModelInput<TContext, TSession>) => readonly TProvider[] | Promise<readonly TProvider[]>);
|
|
97
132
|
fallback?: TProvider[] | ((input: VoiceAgentModelInput<TContext, TSession>) => readonly TProvider[] | Promise<readonly TProvider[]>);
|
|
@@ -100,6 +135,8 @@ export type VoiceProviderRouterOptions<TContext = unknown, TSession extends Voic
|
|
|
100
135
|
isRateLimitError?: (error: unknown, provider: TProvider) => boolean;
|
|
101
136
|
isTimeoutError?: (error: unknown, provider: TProvider) => boolean;
|
|
102
137
|
onProviderEvent?: (event: VoiceProviderRouterEvent<TProvider>, input: VoiceAgentModelInput<TContext, TSession>) => Promise<void> | void;
|
|
138
|
+
orchestrationProfile?: VoiceProviderOrchestrationProfile<TContext, TSession, TProvider>;
|
|
139
|
+
orchestrationSurface?: string;
|
|
103
140
|
policy?: VoiceProviderRouterPolicy<TContext, TSession, TProvider>;
|
|
104
141
|
providerHealth?: boolean | VoiceProviderRouterHealthOptions;
|
|
105
142
|
providerProfiles?: Partial<Record<TProvider, VoiceProviderRouterProviderProfile>>;
|
package/dist/testing/index.js
CHANGED
|
@@ -3791,6 +3791,7 @@ var createVoiceIOProviderFailureSimulator = (options) => {
|
|
|
3791
3791
|
};
|
|
3792
3792
|
};
|
|
3793
3793
|
// src/modelAdapters.ts
|
|
3794
|
+
var isVoiceProviderRoutingPolicyPreset = (value) => value === "balanced" || value === "cost-cap" || value === "cost-first" || value === "latency-first" || value === "quality-first";
|
|
3794
3795
|
var resolveVoiceProviderRoutingPolicyPreset = (preset, options = {}) => {
|
|
3795
3796
|
switch (preset) {
|
|
3796
3797
|
case "balanced":
|
|
@@ -3832,6 +3833,75 @@ var resolveVoiceProviderRoutingPolicyPreset = (preset, options = {}) => {
|
|
|
3832
3833
|
};
|
|
3833
3834
|
}
|
|
3834
3835
|
};
|
|
3836
|
+
var resolveVoiceProviderRoutingPolicy = (policy) => {
|
|
3837
|
+
if (!policy) {
|
|
3838
|
+
return;
|
|
3839
|
+
}
|
|
3840
|
+
if (typeof policy === "string") {
|
|
3841
|
+
return isVoiceProviderRoutingPolicyPreset(policy) ? resolveVoiceProviderRoutingPolicyPreset(policy) : {
|
|
3842
|
+
strategy: policy
|
|
3843
|
+
};
|
|
3844
|
+
}
|
|
3845
|
+
return policy;
|
|
3846
|
+
};
|
|
3847
|
+
var mergeDefinedProviderPolicyFields = (base, surface) => {
|
|
3848
|
+
const next = {
|
|
3849
|
+
...base ?? {}
|
|
3850
|
+
};
|
|
3851
|
+
if (surface.allowProviders !== undefined) {
|
|
3852
|
+
next.allowProviders = surface.allowProviders;
|
|
3853
|
+
}
|
|
3854
|
+
if (surface.fallbackMode !== undefined) {
|
|
3855
|
+
next.fallbackMode = surface.fallbackMode;
|
|
3856
|
+
}
|
|
3857
|
+
if (surface.maxCost !== undefined) {
|
|
3858
|
+
next.maxCost = surface.maxCost;
|
|
3859
|
+
}
|
|
3860
|
+
if (surface.maxLatencyMs !== undefined) {
|
|
3861
|
+
next.maxLatencyMs = surface.maxLatencyMs;
|
|
3862
|
+
}
|
|
3863
|
+
if (surface.minQuality !== undefined) {
|
|
3864
|
+
next.minQuality = surface.minQuality;
|
|
3865
|
+
}
|
|
3866
|
+
if (surface.strategy !== undefined) {
|
|
3867
|
+
next.strategy = surface.strategy;
|
|
3868
|
+
}
|
|
3869
|
+
if (surface.weights !== undefined) {
|
|
3870
|
+
next.weights = {
|
|
3871
|
+
...base?.weights ?? {},
|
|
3872
|
+
...surface.weights
|
|
3873
|
+
};
|
|
3874
|
+
}
|
|
3875
|
+
return next;
|
|
3876
|
+
};
|
|
3877
|
+
var createVoiceProviderOrchestrationProfile = (options) => {
|
|
3878
|
+
const surfaceNames = Object.keys(options.surfaces);
|
|
3879
|
+
const defaultSurface = options.defaultSurface ?? surfaceNames[0];
|
|
3880
|
+
if (!defaultSurface || !options.surfaces[defaultSurface]) {
|
|
3881
|
+
throw new Error("Voice provider orchestration profile has no surfaces.");
|
|
3882
|
+
}
|
|
3883
|
+
return {
|
|
3884
|
+
defaultSurface,
|
|
3885
|
+
id: options.id,
|
|
3886
|
+
resolve: (surface = defaultSurface) => {
|
|
3887
|
+
const config = options.surfaces[surface];
|
|
3888
|
+
if (!config) {
|
|
3889
|
+
throw new Error(`Voice provider orchestration profile ${options.id} has no surface "${surface}".`);
|
|
3890
|
+
}
|
|
3891
|
+
const policy = mergeDefinedProviderPolicyFields(resolveVoiceProviderRoutingPolicy(config.policy), config);
|
|
3892
|
+
return {
|
|
3893
|
+
allowProviders: config.allowProviders,
|
|
3894
|
+
fallback: config.fallback,
|
|
3895
|
+
fallbackMode: config.fallbackMode,
|
|
3896
|
+
policy,
|
|
3897
|
+
providerHealth: config.providerHealth,
|
|
3898
|
+
providerProfiles: config.providerProfiles,
|
|
3899
|
+
timeoutMs: config.timeoutMs
|
|
3900
|
+
};
|
|
3901
|
+
},
|
|
3902
|
+
surfaces: options.surfaces
|
|
3903
|
+
};
|
|
3904
|
+
};
|
|
3835
3905
|
var OUTPUT_SCHEMA = {
|
|
3836
3906
|
additionalProperties: false,
|
|
3837
3907
|
properties: {
|
|
@@ -3999,19 +4069,23 @@ var createJSONVoiceAssistantModel = (options) => ({
|
|
|
3999
4069
|
var createVoiceProviderRouter = (options) => {
|
|
4000
4070
|
const providerIds = Object.keys(options.providers);
|
|
4001
4071
|
const firstProvider = providerIds[0];
|
|
4002
|
-
const
|
|
4003
|
-
|
|
4004
|
-
} : options.policy;
|
|
4072
|
+
const orchestrationSurface = options.orchestrationProfile?.resolve(options.orchestrationSurface);
|
|
4073
|
+
const policy = resolveVoiceProviderRoutingPolicy(options.policy) ?? resolveVoiceProviderRoutingPolicy(orchestrationSurface?.policy);
|
|
4005
4074
|
const strategy = policy?.strategy ?? "prefer-selected";
|
|
4006
|
-
const fallbackMode = policy?.fallbackMode ?? options.fallbackMode ?? "provider-error";
|
|
4007
|
-
const
|
|
4075
|
+
const fallbackMode = policy?.fallbackMode ?? options.fallbackMode ?? orchestrationSurface?.fallbackMode ?? "provider-error";
|
|
4076
|
+
const providerProfiles = {
|
|
4077
|
+
...orchestrationSurface?.providerProfiles ?? {},
|
|
4078
|
+
...options.providerProfiles ?? {}
|
|
4079
|
+
};
|
|
4080
|
+
const providerHealthOption = options.providerHealth ?? orchestrationSurface?.providerHealth;
|
|
4081
|
+
const healthOptions = typeof providerHealthOption === "object" ? providerHealthOption : providerHealthOption ? {} : undefined;
|
|
4008
4082
|
const healthState = new Map;
|
|
4009
4083
|
const now = () => healthOptions?.now?.() ?? Date.now();
|
|
4010
4084
|
const failureThreshold = Math.max(1, healthOptions?.failureThreshold ?? 1);
|
|
4011
4085
|
const cooldownMs = Math.max(0, healthOptions?.cooldownMs ?? 30000);
|
|
4012
4086
|
const rateLimitCooldownMs = Math.max(0, healthOptions?.rateLimitCooldownMs ?? 60000);
|
|
4013
4087
|
const getProviderTimeoutMs = (provider) => {
|
|
4014
|
-
const timeoutMs =
|
|
4088
|
+
const timeoutMs = providerProfiles[provider]?.timeoutMs ?? options.timeoutMs ?? orchestrationSurface?.timeoutMs;
|
|
4015
4089
|
return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : undefined;
|
|
4016
4090
|
};
|
|
4017
4091
|
const getHealth = (provider) => {
|
|
@@ -4077,12 +4151,12 @@ var createVoiceProviderRouter = (options) => {
|
|
|
4077
4151
|
return cloneHealth(provider);
|
|
4078
4152
|
};
|
|
4079
4153
|
const resolveAllowedProviders = async (input) => {
|
|
4080
|
-
const allowProviders = policy?.allowProviders ?? options.allowProviders;
|
|
4154
|
+
const allowProviders = policy?.allowProviders ?? options.allowProviders ?? orchestrationSurface?.allowProviders;
|
|
4081
4155
|
const allowed = typeof allowProviders === "function" ? await allowProviders(input) : allowProviders;
|
|
4082
4156
|
return new Set(allowed ?? providerIds);
|
|
4083
4157
|
};
|
|
4084
4158
|
const passesBudgetFilters = (provider) => {
|
|
4085
|
-
const profile =
|
|
4159
|
+
const profile = providerProfiles[provider];
|
|
4086
4160
|
if (typeof policy?.maxCost === "number" && typeof profile?.cost === "number" && profile.cost > policy.maxCost) {
|
|
4087
4161
|
return false;
|
|
4088
4162
|
}
|
|
@@ -4095,7 +4169,7 @@ var createVoiceProviderRouter = (options) => {
|
|
|
4095
4169
|
return true;
|
|
4096
4170
|
};
|
|
4097
4171
|
const getBalancedScore = (provider) => {
|
|
4098
|
-
const profile =
|
|
4172
|
+
const profile = providerProfiles[provider];
|
|
4099
4173
|
if (policy?.scoreProvider) {
|
|
4100
4174
|
return policy.scoreProvider(provider, profile);
|
|
4101
4175
|
}
|
|
@@ -4107,8 +4181,8 @@ var createVoiceProviderRouter = (options) => {
|
|
|
4107
4181
|
return providers;
|
|
4108
4182
|
}
|
|
4109
4183
|
return [...providers].sort((left, right) => {
|
|
4110
|
-
const leftProfile =
|
|
4111
|
-
const rightProfile =
|
|
4184
|
+
const leftProfile = providerProfiles[left];
|
|
4185
|
+
const rightProfile = providerProfiles[right];
|
|
4112
4186
|
if (strategy === "quality-first") {
|
|
4113
4187
|
return (rightProfile?.quality ?? Number.MIN_SAFE_INTEGER) - (leftProfile?.quality ?? Number.MIN_SAFE_INTEGER) || (leftProfile?.priority ?? Number.MAX_SAFE_INTEGER) - (rightProfile?.priority ?? Number.MAX_SAFE_INTEGER) || (leftProfile?.latencyMs ?? Number.MAX_SAFE_INTEGER) - (rightProfile?.latencyMs ?? Number.MAX_SAFE_INTEGER) || (leftProfile?.cost ?? Number.MAX_SAFE_INTEGER) - (rightProfile?.cost ?? Number.MAX_SAFE_INTEGER);
|
|
4114
4188
|
}
|
|
@@ -4123,7 +4197,8 @@ var createVoiceProviderRouter = (options) => {
|
|
|
4123
4197
|
const resolveOrder = async (input) => {
|
|
4124
4198
|
const selectedProvider = await options.selectProvider?.(input);
|
|
4125
4199
|
const allowedProviders = await resolveAllowedProviders(input);
|
|
4126
|
-
const
|
|
4200
|
+
const fallbackSource = options.fallback ?? orchestrationSurface?.fallback;
|
|
4201
|
+
const fallbackOrder = typeof fallbackSource === "function" ? await fallbackSource(input) : fallbackSource;
|
|
4127
4202
|
const allowedRankedProviders = sortProviders([
|
|
4128
4203
|
...fallbackOrder ?? providerIds
|
|
4129
4204
|
]).filter((provider) => allowedProviders.has(provider));
|