@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 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 policy = typeof options.policy === "string" ? options.policy === "balanced" || options.policy === "cost-cap" || options.policy === "cost-first" || options.policy === "latency-first" || options.policy === "quality-first" ? resolveVoiceProviderRoutingPolicyPreset(options.policy) : {
21862
- strategy: options.policy
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 healthOptions = typeof options.providerHealth === "object" ? options.providerHealth : options.providerHealth ? {} : undefined;
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 = options.providerProfiles?.[provider]?.timeoutMs ?? options.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 = options.providerProfiles?.[provider];
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 = options.providerProfiles?.[provider];
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 = options.providerProfiles?.[left];
21970
- const rightProfile = options.providerProfiles?.[right];
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 fallbackOrder = typeof options.fallback === "function" ? await options.fallback(input) : options.fallback;
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 isVoiceProviderRoutingPolicyPreset = (policy) => policy === "balanced" || policy === "cost-cap" || policy === "cost-first" || policy === "latency-first" || policy === "quality-first";
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" ? isVoiceProviderRoutingPolicyPreset(options.policy) ? resolveVoiceProviderRoutingPolicyPreset(options.policy) : {
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,
@@ -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>>;
@@ -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 policy = typeof options.policy === "string" ? options.policy === "balanced" || options.policy === "cost-cap" || options.policy === "cost-first" || options.policy === "latency-first" || options.policy === "quality-first" ? resolveVoiceProviderRoutingPolicyPreset(options.policy) : {
4003
- strategy: options.policy
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 healthOptions = typeof options.providerHealth === "object" ? options.providerHealth : options.providerHealth ? {} : undefined;
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 = options.providerProfiles?.[provider]?.timeoutMs ?? options.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 = options.providerProfiles?.[provider];
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 = options.providerProfiles?.[provider];
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 = options.providerProfiles?.[left];
4111
- const rightProfile = options.providerProfiles?.[right];
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 fallbackOrder = typeof options.fallback === "function" ? await options.fallback(input) : options.fallback;
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.291",
3
+ "version": "0.0.22-beta.292",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",