@absolutejs/voice 0.0.22-beta.56 → 0.0.22-beta.57
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 +32 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +55 -3
- package/dist/providerAdapters.d.ts +12 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1242,6 +1242,38 @@ const model = createVoiceProviderRouter({
|
|
|
1242
1242
|
});
|
|
1243
1243
|
```
|
|
1244
1244
|
|
|
1245
|
+
The same profile and policy shape also works for STT and TTS provider routers, so a self-hosted app can choose the fastest provider for live calls, cap cost for background work, or require a minimum quality score without hard-coding provider branches.
|
|
1246
|
+
|
|
1247
|
+
```ts
|
|
1248
|
+
const stt = createVoiceSTTProviderRouter({
|
|
1249
|
+
adapters: {
|
|
1250
|
+
deepgram,
|
|
1251
|
+
assemblyai
|
|
1252
|
+
},
|
|
1253
|
+
providerHealth: { cooldownMs: 30_000 },
|
|
1254
|
+
providerProfiles: {
|
|
1255
|
+
deepgram: { cost: 4, latencyMs: 180, quality: 0.93, timeoutMs: 1500 },
|
|
1256
|
+
assemblyai: { cost: 2, latencyMs: 650, quality: 0.88, timeoutMs: 3000 }
|
|
1257
|
+
},
|
|
1258
|
+
policy: resolveVoiceProviderRoutingPolicyPreset('latency-first')
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
const tts = createVoiceTTSProviderRouter({
|
|
1262
|
+
adapters: {
|
|
1263
|
+
elevenlabs,
|
|
1264
|
+
openai
|
|
1265
|
+
},
|
|
1266
|
+
providerProfiles: {
|
|
1267
|
+
elevenlabs: { cost: 5, latencyMs: 220, quality: 0.94 },
|
|
1268
|
+
openai: { cost: 2, latencyMs: 320, quality: 0.87 }
|
|
1269
|
+
},
|
|
1270
|
+
policy: resolveVoiceProviderRoutingPolicyPreset('cost-cap', {
|
|
1271
|
+
maxCost: 3,
|
|
1272
|
+
minQuality: 0.85
|
|
1273
|
+
})
|
|
1274
|
+
});
|
|
1275
|
+
```
|
|
1276
|
+
|
|
1245
1277
|
## Presets
|
|
1246
1278
|
|
|
1247
1279
|
Voice now ships named runtime presets so apps can start from a useful baseline instead of hand-tuning silence and capture settings every time.
|
package/dist/index.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export type { VoiceProviderHealthStatus, VoiceProviderHealthSummary, VoiceProvid
|
|
|
51
51
|
export type { VoiceOpsConsoleLink, VoiceOpsConsoleReport, VoiceOpsConsoleRoutesOptions } from './opsConsoleRoutes';
|
|
52
52
|
export type { VoiceQualityLink, VoiceQualityMetric, VoiceQualityReport, VoiceQualityRoutesOptions, VoiceQualityStatus, VoiceQualityThresholds } from './qualityRoutes';
|
|
53
53
|
export type { VoiceResilienceIOSimulator, VoiceResilienceLink, VoiceResiliencePageData, VoiceResilienceRoutesOptions, VoiceResilienceSimulationProvider, VoiceRoutingEvent, VoiceRoutingEventKind } from './resilienceRoutes';
|
|
54
|
-
export type { VoiceIOProviderRouterEvent, VoiceSTTProviderRouterOptions, VoiceTTSProviderRouterOptions } from './providerAdapters';
|
|
54
|
+
export type { VoiceIOProviderRouterEvent, VoiceIOProviderRouterOptions, VoiceIOProviderRouterPolicy, VoiceIOProviderRouterPolicyConfig, VoiceSTTProviderRouterOptions, VoiceTTSProviderRouterOptions } from './providerAdapters';
|
|
55
55
|
export type { VoiceAgent, VoiceAgentMessage, VoiceAgentMessageRole, VoiceAgentModel, VoiceAgentModelInput, VoiceAgentModelOutput, VoiceAgentOptions, VoiceAgentRunResult, VoiceAgentSquadOptions, VoiceAgentTool, VoiceAgentToolCall, VoiceAgentToolResult } from './agent';
|
|
56
56
|
export type { VoiceOpsRuntime, VoiceOpsRuntimeConfig, VoiceOpsRuntimeSummary, VoiceOpsRuntimeSinkWorkerConfig, VoiceOpsRuntimeTaskWorkerConfig, VoiceOpsRuntimeTickResult, VoiceOpsRuntimeWebhookWorkerConfig } from './opsRuntime';
|
|
57
57
|
export type { VoiceOpsPresetName, VoiceOpsPresetOverrides, VoiceResolvedOpsPreset } from './opsPresets';
|
package/dist/index.js
CHANGED
|
@@ -10953,9 +10953,14 @@ var withTimeout = async (input) => {
|
|
|
10953
10953
|
}
|
|
10954
10954
|
}
|
|
10955
10955
|
};
|
|
10956
|
+
var isVoiceProviderRoutingPolicyPreset = (policy) => policy === "balanced" || policy === "cost-cap" || policy === "cost-first" || policy === "latency-first" || policy === "quality-first";
|
|
10956
10957
|
var createResolver = (options) => {
|
|
10957
10958
|
const providerIds = Object.keys(options.adapters);
|
|
10958
10959
|
const firstProvider = providerIds[0];
|
|
10960
|
+
const policy = typeof options.policy === "string" ? isVoiceProviderRoutingPolicyPreset(options.policy) ? resolveVoiceProviderRoutingPolicyPreset(options.policy) : {
|
|
10961
|
+
strategy: options.policy
|
|
10962
|
+
} : options.policy;
|
|
10963
|
+
const strategy = policy?.strategy ?? "prefer-selected";
|
|
10959
10964
|
const healthOptions = typeof options.providerHealth === "object" ? options.providerHealth : options.providerHealth ? {} : undefined;
|
|
10960
10965
|
const healthState = new Map;
|
|
10961
10966
|
const now = () => healthOptions?.now?.() ?? Date.now();
|
|
@@ -11019,23 +11024,70 @@ var createResolver = (options) => {
|
|
|
11019
11024
|
}
|
|
11020
11025
|
return cloneHealth(provider);
|
|
11021
11026
|
};
|
|
11027
|
+
const resolveAllowedProviders = async (input) => {
|
|
11028
|
+
const allowed = typeof policy?.allowProviders === "function" ? await policy.allowProviders(input) : policy?.allowProviders;
|
|
11029
|
+
return new Set(allowed ?? providerIds);
|
|
11030
|
+
};
|
|
11031
|
+
const passesBudgetFilters = (provider) => {
|
|
11032
|
+
const profile = options.providerProfiles?.[provider];
|
|
11033
|
+
if (typeof policy?.maxCost === "number" && typeof profile?.cost === "number" && profile.cost > policy.maxCost) {
|
|
11034
|
+
return false;
|
|
11035
|
+
}
|
|
11036
|
+
if (typeof policy?.maxLatencyMs === "number" && typeof profile?.latencyMs === "number" && profile.latencyMs > policy.maxLatencyMs) {
|
|
11037
|
+
return false;
|
|
11038
|
+
}
|
|
11039
|
+
if (typeof policy?.minQuality === "number" && typeof profile?.quality === "number" && profile.quality < policy.minQuality) {
|
|
11040
|
+
return false;
|
|
11041
|
+
}
|
|
11042
|
+
return true;
|
|
11043
|
+
};
|
|
11044
|
+
const getBalancedScore = (provider) => {
|
|
11045
|
+
const profile = options.providerProfiles?.[provider];
|
|
11046
|
+
if (policy?.scoreProvider) {
|
|
11047
|
+
return policy.scoreProvider(provider, profile);
|
|
11048
|
+
}
|
|
11049
|
+
const weights = policy?.weights ?? {};
|
|
11050
|
+
return (profile?.cost ?? Number.MAX_SAFE_INTEGER) * (weights.cost ?? 1) + (profile?.latencyMs ?? Number.MAX_SAFE_INTEGER) * (weights.latencyMs ?? 0.005) + (profile?.priority ?? 0) * (weights.priority ?? 1) - (profile?.quality ?? 0) * (weights.quality ?? 10);
|
|
11051
|
+
};
|
|
11052
|
+
const sortProviders = (providers) => {
|
|
11053
|
+
if (strategy !== "prefer-cheapest" && strategy !== "prefer-fastest" && strategy !== "quality-first" && strategy !== "balanced") {
|
|
11054
|
+
return providers;
|
|
11055
|
+
}
|
|
11056
|
+
return [...providers].sort((left, right) => {
|
|
11057
|
+
const leftProfile = options.providerProfiles?.[left];
|
|
11058
|
+
const rightProfile = options.providerProfiles?.[right];
|
|
11059
|
+
if (strategy === "quality-first") {
|
|
11060
|
+
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);
|
|
11061
|
+
}
|
|
11062
|
+
if (strategy === "balanced") {
|
|
11063
|
+
return getBalancedScore(left) - getBalancedScore(right);
|
|
11064
|
+
}
|
|
11065
|
+
const leftValue = strategy === "prefer-cheapest" ? leftProfile?.cost ?? Number.MAX_SAFE_INTEGER : leftProfile?.latencyMs ?? Number.MAX_SAFE_INTEGER;
|
|
11066
|
+
const rightValue = strategy === "prefer-cheapest" ? rightProfile?.cost ?? Number.MAX_SAFE_INTEGER : rightProfile?.latencyMs ?? Number.MAX_SAFE_INTEGER;
|
|
11067
|
+
return leftValue - rightValue || (leftProfile?.priority ?? Number.MAX_SAFE_INTEGER) - (rightProfile?.priority ?? Number.MAX_SAFE_INTEGER);
|
|
11068
|
+
});
|
|
11069
|
+
};
|
|
11022
11070
|
const resolveOrder = async (input) => {
|
|
11023
|
-
const
|
|
11071
|
+
const requestedProvider = await options.selectProvider?.(input);
|
|
11072
|
+
const selectedProvider = requestedProvider ?? firstProvider;
|
|
11073
|
+
const allowedProviders = await resolveAllowedProviders(input);
|
|
11024
11074
|
const fallbackOrder = typeof options.fallback === "function" ? await options.fallback(input) : options.fallback;
|
|
11025
11075
|
const candidates = [selectedProvider, ...fallbackOrder ?? providerIds];
|
|
11026
11076
|
const seen = new Set;
|
|
11027
|
-
const
|
|
11077
|
+
const orderedCandidates = candidates.filter((provider) => {
|
|
11028
11078
|
if (!provider || seen.has(provider) || !options.adapters[provider]) {
|
|
11029
11079
|
return false;
|
|
11030
11080
|
}
|
|
11031
11081
|
seen.add(provider);
|
|
11032
11082
|
return true;
|
|
11033
11083
|
});
|
|
11084
|
+
const rankedOrder = sortProviders(orderedCandidates).filter((provider) => allowedProviders.has(provider)).filter(passesBudgetFilters);
|
|
11034
11085
|
const healthyOrder = healthOptions ? rankedOrder.filter((provider) => !isSuppressed(provider)) : rankedOrder;
|
|
11035
11086
|
const order = healthyOrder.length ? healthyOrder : rankedOrder;
|
|
11087
|
+
const preferred = strategy === "prefer-selected" && selectedProvider && allowedProviders.has(selectedProvider) && passesBudgetFilters(selectedProvider) && (!healthOptions || !isSuppressed(selectedProvider)) ? selectedProvider : order[0];
|
|
11036
11088
|
return {
|
|
11037
11089
|
order,
|
|
11038
|
-
selectedProvider:
|
|
11090
|
+
selectedProvider: preferred
|
|
11039
11091
|
};
|
|
11040
11092
|
};
|
|
11041
11093
|
const emit = async (event, input) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { STTAdapter, STTAdapterOpenOptions, TTSAdapter, TTSAdapterOpenOptions } from './types';
|
|
2
|
-
import type { VoiceProviderRouterHealthOptions, VoiceProviderRouterProviderHealth, VoiceProviderRouterProviderProfile } from './modelAdapters';
|
|
2
|
+
import type { VoiceProviderRouterHealthOptions, VoiceProviderRouterProviderHealth, VoiceProviderRouterProviderProfile, VoiceProviderRouterPolicyPreset, VoiceProviderRouterPolicyWeights, VoiceProviderRouterStrategy } from './modelAdapters';
|
|
3
3
|
type MaybePromise<T> = T | Promise<T>;
|
|
4
4
|
type VoiceIOProviderKind = 'stt' | 'tts';
|
|
5
5
|
type VoiceIOProviderStatus = 'error' | 'fallback' | 'success';
|
|
@@ -20,11 +20,22 @@ export type VoiceIOProviderRouterEvent<TProvider extends string = string> = {
|
|
|
20
20
|
suppressedUntil?: number;
|
|
21
21
|
timedOut?: boolean;
|
|
22
22
|
};
|
|
23
|
+
export type VoiceIOProviderRouterPolicyConfig<TOpenOptions = unknown, TProvider extends string = string> = {
|
|
24
|
+
allowProviders?: readonly TProvider[] | ((input: TOpenOptions) => MaybePromise<readonly TProvider[]>);
|
|
25
|
+
maxCost?: number;
|
|
26
|
+
maxLatencyMs?: number;
|
|
27
|
+
minQuality?: number;
|
|
28
|
+
scoreProvider?: (provider: TProvider, profile: VoiceProviderRouterProviderProfile | undefined) => number;
|
|
29
|
+
strategy?: VoiceProviderRouterStrategy;
|
|
30
|
+
weights?: VoiceProviderRouterPolicyWeights;
|
|
31
|
+
};
|
|
32
|
+
export type VoiceIOProviderRouterPolicy<TOpenOptions = unknown, TProvider extends string = string> = VoiceProviderRouterStrategy | VoiceProviderRouterPolicyPreset | VoiceIOProviderRouterPolicyConfig<TOpenOptions, TProvider>;
|
|
23
33
|
export type VoiceIOProviderRouterOptions<TProvider extends string, TAdapter, TOpenOptions> = {
|
|
24
34
|
adapters: Partial<Record<TProvider, TAdapter>>;
|
|
25
35
|
fallback?: readonly TProvider[] | ((input: TOpenOptions) => MaybePromise<readonly TProvider[]>);
|
|
26
36
|
isProviderError?: (error: unknown, provider: TProvider) => boolean;
|
|
27
37
|
onProviderEvent?: (event: VoiceIOProviderRouterEvent<TProvider>, input: TOpenOptions) => Promise<void> | void;
|
|
38
|
+
policy?: VoiceIOProviderRouterPolicy<TOpenOptions, TProvider>;
|
|
28
39
|
providerHealth?: boolean | VoiceProviderRouterHealthOptions;
|
|
29
40
|
providerProfiles?: Partial<Record<TProvider, VoiceProviderRouterProviderProfile>>;
|
|
30
41
|
selectProvider?: (input: TOpenOptions) => MaybePromise<TProvider | undefined>;
|