@absolutejs/voice 0.0.22-beta.11 → 0.0.22-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -26,7 +26,7 @@ export { resolveTurnDetectionConfig, TURN_PROFILE_DEFAULTS } from './turnProfile
26
26
  export { createVoiceCallReviewFromLiveTelephonyReport, createVoiceCallReviewRecorder, renderVoiceCallReviewHTML, renderVoiceCallReviewMarkdown } from './testing/review';
27
27
  export type { VoiceAssistant, VoiceAssistantArtifactPlan, VoiceAssistantExperiment, VoiceAssistantExperimentOptions, VoiceAssistantGuardrailInput, VoiceAssistantGuardrails, VoiceAssistantMemoryLifecycle, VoiceAssistantMemoryLifecycleInput, VoiceAssistantOptions, VoiceAssistantOutputGuardrailInput, VoiceAssistantPreset, VoiceAssistantRunsSummary, VoiceAssistantRunSummary, VoiceAssistantVariant } from './assistant';
28
28
  export type { VoiceAssistantMemoryBinding, VoiceAssistantMemoryHandle, VoiceAssistantMemoryOptions, VoiceAssistantMemoryRecord, VoiceAssistantMemoryStore } from './assistantMemory';
29
- export type { AnthropicVoiceAssistantModelOptions, GeminiVoiceAssistantModelOptions, OpenAIVoiceAssistantModelOptions, VoiceProviderRouterEvent, VoiceProviderRouterOptions, VoiceJSONAssistantModelHandler, VoiceJSONAssistantModelOptions } from './modelAdapters';
29
+ export type { AnthropicVoiceAssistantModelOptions, GeminiVoiceAssistantModelOptions, OpenAIVoiceAssistantModelOptions, VoiceProviderRouterEvent, VoiceProviderRouterFallbackMode, VoiceProviderRouterOptions, VoiceProviderRouterPolicy, VoiceProviderRouterProviderProfile, VoiceJSONAssistantModelHandler, VoiceJSONAssistantModelOptions } from './modelAdapters';
30
30
  export type { VoiceAgent, VoiceAgentMessage, VoiceAgentMessageRole, VoiceAgentModel, VoiceAgentModelInput, VoiceAgentModelOutput, VoiceAgentOptions, VoiceAgentRunResult, VoiceAgentSquadOptions, VoiceAgentTool, VoiceAgentToolCall, VoiceAgentToolResult } from './agent';
31
31
  export type { VoiceOpsRuntime, VoiceOpsRuntimeConfig, VoiceOpsRuntimeSummary, VoiceOpsRuntimeSinkWorkerConfig, VoiceOpsRuntimeTaskWorkerConfig, VoiceOpsRuntimeTickResult, VoiceOpsRuntimeWebhookWorkerConfig } from './opsRuntime';
32
32
  export type { VoiceOpsPresetName, VoiceOpsPresetOverrides, VoiceResolvedOpsPreset } from './opsPresets';
package/dist/index.js CHANGED
@@ -7241,18 +7241,41 @@ var createJSONVoiceAssistantModel = (options) => ({
7241
7241
  var createVoiceProviderRouter = (options) => {
7242
7242
  const providerIds = Object.keys(options.providers);
7243
7243
  const firstProvider = providerIds[0];
7244
+ const policy = typeof options.policy === "string" ? {
7245
+ strategy: options.policy
7246
+ } : options.policy;
7247
+ const strategy = policy?.strategy ?? "prefer-selected";
7248
+ const fallbackMode = policy?.fallbackMode ?? options.fallbackMode ?? "provider-error";
7249
+ const resolveAllowedProviders = async (input) => {
7250
+ const allowProviders = policy?.allowProviders ?? options.allowProviders;
7251
+ const allowed = typeof allowProviders === "function" ? await allowProviders(input) : allowProviders;
7252
+ return new Set(allowed ?? providerIds);
7253
+ };
7254
+ const sortProviders = (providers) => {
7255
+ if (strategy !== "prefer-cheapest" && strategy !== "prefer-fastest") {
7256
+ return providers;
7257
+ }
7258
+ return [...providers].sort((left, right) => {
7259
+ const leftProfile = options.providerProfiles?.[left];
7260
+ const rightProfile = options.providerProfiles?.[right];
7261
+ const leftValue = strategy === "prefer-cheapest" ? leftProfile?.cost ?? Number.MAX_SAFE_INTEGER : leftProfile?.latencyMs ?? Number.MAX_SAFE_INTEGER;
7262
+ const rightValue = strategy === "prefer-cheapest" ? rightProfile?.cost ?? Number.MAX_SAFE_INTEGER : rightProfile?.latencyMs ?? Number.MAX_SAFE_INTEGER;
7263
+ return leftValue - rightValue || (leftProfile?.priority ?? Number.MAX_SAFE_INTEGER) - (rightProfile?.priority ?? Number.MAX_SAFE_INTEGER);
7264
+ });
7265
+ };
7244
7266
  const resolveOrder = async (input) => {
7245
7267
  const selectedProvider = await options.selectProvider?.(input);
7268
+ const allowedProviders = await resolveAllowedProviders(input);
7246
7269
  const fallbackOrder = typeof options.fallback === "function" ? await options.fallback(input) : options.fallback;
7247
- const preferred = selectedProvider ?? fallbackOrder?.[0] ?? firstProvider;
7270
+ const rankedProviders = sortProviders([
7271
+ ...fallbackOrder ?? providerIds
7272
+ ]).filter((provider) => allowedProviders.has(provider));
7273
+ const preferred = selectedProvider && allowedProviders.has(selectedProvider) ? selectedProvider : rankedProviders[0] ?? firstProvider;
7248
7274
  const seen = new Set;
7249
7275
  const order = [];
7250
- for (const provider of [
7251
- preferred,
7252
- ...fallbackOrder ?? [],
7253
- ...providerIds
7254
- ]) {
7255
- if (!provider || seen.has(provider) || !options.providers[provider]) {
7276
+ const candidates = strategy === "ordered" ? rankedProviders : [preferred, ...rankedProviders, ...providerIds];
7277
+ for (const provider of candidates) {
7278
+ if (!provider || seen.has(provider) || !allowedProviders.has(provider) || !options.providers[provider]) {
7256
7279
  continue;
7257
7280
  }
7258
7281
  seen.add(provider);
@@ -7295,18 +7318,20 @@ var createVoiceProviderRouter = (options) => {
7295
7318
  lastError = error;
7296
7319
  const hasNextProvider = index < order.length - 1;
7297
7320
  const isProviderError = options.isProviderError?.(error, provider) ?? true;
7321
+ const rateLimited = options.isRateLimitError?.(error, provider) ?? defaultIsRateLimitError(error);
7322
+ const shouldFallback = fallbackMode === "provider-error" ? isProviderError : fallbackMode === "rate-limit" ? isProviderError && rateLimited : false;
7298
7323
  const nextProvider = hasNextProvider ? order[index + 1] : undefined;
7299
7324
  await emit({
7300
7325
  at: Date.now(),
7301
7326
  elapsedMs: Date.now() - startedAt,
7302
7327
  error: errorMessage(error),
7303
- fallbackProvider: isProviderError ? nextProvider : undefined,
7328
+ fallbackProvider: shouldFallback ? nextProvider : undefined,
7304
7329
  provider,
7305
- rateLimited: options.isRateLimitError?.(error, provider) ?? defaultIsRateLimitError(error),
7330
+ rateLimited,
7306
7331
  selectedProvider,
7307
7332
  status: "error"
7308
7333
  }, input);
7309
- if (!hasNextProvider || !isProviderError) {
7334
+ if (!hasNextProvider || !shouldFallback) {
7310
7335
  throw error;
7311
7336
  }
7312
7337
  }
@@ -45,11 +45,26 @@ export type VoiceProviderRouterEvent<TProvider extends string = string> = {
45
45
  selectedProvider: TProvider;
46
46
  status: 'error' | 'fallback' | 'success';
47
47
  };
48
+ export type VoiceProviderRouterFallbackMode = 'never' | 'provider-error' | 'rate-limit';
49
+ export type VoiceProviderRouterPolicy<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TProvider extends string = string> = 'ordered' | 'prefer-cheapest' | 'prefer-fastest' | 'prefer-selected' | {
50
+ allowProviders?: readonly TProvider[] | ((input: VoiceAgentModelInput<TContext, TSession>) => readonly TProvider[] | Promise<readonly TProvider[]>);
51
+ fallbackMode?: VoiceProviderRouterFallbackMode;
52
+ strategy?: 'ordered' | 'prefer-cheapest' | 'prefer-fastest' | 'prefer-selected';
53
+ };
54
+ export type VoiceProviderRouterProviderProfile = {
55
+ cost?: number;
56
+ latencyMs?: number;
57
+ priority?: number;
58
+ };
48
59
  export type VoiceProviderRouterOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown, TProvider extends string = string> = {
60
+ allowProviders?: readonly TProvider[] | ((input: VoiceAgentModelInput<TContext, TSession>) => readonly TProvider[] | Promise<readonly TProvider[]>);
49
61
  fallback?: TProvider[] | ((input: VoiceAgentModelInput<TContext, TSession>) => readonly TProvider[] | Promise<readonly TProvider[]>);
62
+ fallbackMode?: VoiceProviderRouterFallbackMode;
50
63
  isProviderError?: (error: unknown, provider: TProvider) => boolean;
51
64
  isRateLimitError?: (error: unknown, provider: TProvider) => boolean;
52
65
  onProviderEvent?: (event: VoiceProviderRouterEvent<TProvider>, input: VoiceAgentModelInput<TContext, TSession>) => Promise<void> | void;
66
+ policy?: VoiceProviderRouterPolicy<TContext, TSession, TProvider>;
67
+ providerProfiles?: Partial<Record<TProvider, VoiceProviderRouterProviderProfile>>;
53
68
  providers: Partial<Record<TProvider, VoiceAgentModel<TContext, TSession, TResult>>>;
54
69
  selectProvider?: (input: VoiceAgentModelInput<TContext, TSession>) => TProvider | undefined | Promise<TProvider | undefined>;
55
70
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.11",
3
+ "version": "0.0.22-beta.12",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",