@absolutejs/voice 0.0.22-beta.11 → 0.0.22-beta.13
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 +1 -1
- package/dist/index.js +93 -9
- package/dist/modelAdapters.d.ts +30 -0
- package/package.json +1 -1
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, VoiceProviderRouterHealthOptions, VoiceProviderRouterOptions, VoiceProviderRouterPolicy, VoiceProviderRouterProviderHealth, 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,98 @@ 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 healthOptions = typeof options.providerHealth === "object" ? options.providerHealth : options.providerHealth ? {} : undefined;
|
|
7250
|
+
const healthState = new Map;
|
|
7251
|
+
const now = () => healthOptions?.now?.() ?? Date.now();
|
|
7252
|
+
const failureThreshold = Math.max(1, healthOptions?.failureThreshold ?? 1);
|
|
7253
|
+
const cooldownMs = Math.max(0, healthOptions?.cooldownMs ?? 30000);
|
|
7254
|
+
const rateLimitCooldownMs = Math.max(0, healthOptions?.rateLimitCooldownMs ?? 60000);
|
|
7255
|
+
const getHealth = (provider) => {
|
|
7256
|
+
const existing = healthState.get(provider);
|
|
7257
|
+
if (existing) {
|
|
7258
|
+
return existing;
|
|
7259
|
+
}
|
|
7260
|
+
const next = {
|
|
7261
|
+
consecutiveFailures: 0,
|
|
7262
|
+
provider,
|
|
7263
|
+
status: "healthy"
|
|
7264
|
+
};
|
|
7265
|
+
healthState.set(provider, next);
|
|
7266
|
+
return next;
|
|
7267
|
+
};
|
|
7268
|
+
const isSuppressed = (provider) => {
|
|
7269
|
+
if (!healthOptions) {
|
|
7270
|
+
return false;
|
|
7271
|
+
}
|
|
7272
|
+
const health = getHealth(provider);
|
|
7273
|
+
return typeof health.suppressedUntil === "number" && health.suppressedUntil > now();
|
|
7274
|
+
};
|
|
7275
|
+
const recordProviderSuccess = (provider) => {
|
|
7276
|
+
if (!healthOptions) {
|
|
7277
|
+
return;
|
|
7278
|
+
}
|
|
7279
|
+
const health = getHealth(provider);
|
|
7280
|
+
health.consecutiveFailures = 0;
|
|
7281
|
+
health.status = "healthy";
|
|
7282
|
+
health.suppressedUntil = undefined;
|
|
7283
|
+
};
|
|
7284
|
+
const recordProviderError = (provider, isProviderError, rateLimited) => {
|
|
7285
|
+
if (!healthOptions || !isProviderError) {
|
|
7286
|
+
return;
|
|
7287
|
+
}
|
|
7288
|
+
const currentTime = now();
|
|
7289
|
+
const health = getHealth(provider);
|
|
7290
|
+
health.consecutiveFailures += 1;
|
|
7291
|
+
health.lastFailureAt = currentTime;
|
|
7292
|
+
if (rateLimited) {
|
|
7293
|
+
health.lastRateLimitedAt = currentTime;
|
|
7294
|
+
}
|
|
7295
|
+
if (rateLimited || health.consecutiveFailures >= failureThreshold) {
|
|
7296
|
+
health.status = "suppressed";
|
|
7297
|
+
health.suppressedUntil = currentTime + (rateLimited ? rateLimitCooldownMs : cooldownMs);
|
|
7298
|
+
}
|
|
7299
|
+
};
|
|
7300
|
+
const resolveAllowedProviders = async (input) => {
|
|
7301
|
+
const allowProviders = policy?.allowProviders ?? options.allowProviders;
|
|
7302
|
+
const allowed = typeof allowProviders === "function" ? await allowProviders(input) : allowProviders;
|
|
7303
|
+
return new Set(allowed ?? providerIds);
|
|
7304
|
+
};
|
|
7305
|
+
const sortProviders = (providers) => {
|
|
7306
|
+
if (strategy !== "prefer-cheapest" && strategy !== "prefer-fastest") {
|
|
7307
|
+
return providers;
|
|
7308
|
+
}
|
|
7309
|
+
return [...providers].sort((left, right) => {
|
|
7310
|
+
const leftProfile = options.providerProfiles?.[left];
|
|
7311
|
+
const rightProfile = options.providerProfiles?.[right];
|
|
7312
|
+
const leftValue = strategy === "prefer-cheapest" ? leftProfile?.cost ?? Number.MAX_SAFE_INTEGER : leftProfile?.latencyMs ?? Number.MAX_SAFE_INTEGER;
|
|
7313
|
+
const rightValue = strategy === "prefer-cheapest" ? rightProfile?.cost ?? Number.MAX_SAFE_INTEGER : rightProfile?.latencyMs ?? Number.MAX_SAFE_INTEGER;
|
|
7314
|
+
return leftValue - rightValue || (leftProfile?.priority ?? Number.MAX_SAFE_INTEGER) - (rightProfile?.priority ?? Number.MAX_SAFE_INTEGER);
|
|
7315
|
+
});
|
|
7316
|
+
};
|
|
7244
7317
|
const resolveOrder = async (input) => {
|
|
7245
7318
|
const selectedProvider = await options.selectProvider?.(input);
|
|
7319
|
+
const allowedProviders = await resolveAllowedProviders(input);
|
|
7246
7320
|
const fallbackOrder = typeof options.fallback === "function" ? await options.fallback(input) : options.fallback;
|
|
7247
|
-
const
|
|
7321
|
+
const rankedProviders = sortProviders([
|
|
7322
|
+
...fallbackOrder ?? providerIds
|
|
7323
|
+
]).filter((provider) => allowedProviders.has(provider));
|
|
7324
|
+
const healthyRankedProviders = healthOptions ? rankedProviders.filter((provider) => !isSuppressed(provider)) : rankedProviders;
|
|
7325
|
+
const candidateRankedProviders = healthyRankedProviders.length ? healthyRankedProviders : rankedProviders;
|
|
7326
|
+
const preferred = selectedProvider && allowedProviders.has(selectedProvider) && (!healthOptions || !isSuppressed(selectedProvider)) ? selectedProvider : candidateRankedProviders[0] ?? firstProvider;
|
|
7248
7327
|
const seen = new Set;
|
|
7249
7328
|
const order = [];
|
|
7250
|
-
|
|
7329
|
+
const candidates = strategy === "ordered" ? candidateRankedProviders : [
|
|
7251
7330
|
preferred,
|
|
7252
|
-
...
|
|
7253
|
-
...providerIds
|
|
7254
|
-
]
|
|
7255
|
-
|
|
7331
|
+
...candidateRankedProviders,
|
|
7332
|
+
...providerIds.filter((provider) => !healthOptions || !isSuppressed(provider))
|
|
7333
|
+
];
|
|
7334
|
+
for (const provider of candidates) {
|
|
7335
|
+
if (!provider || seen.has(provider) || !allowedProviders.has(provider) || !options.providers[provider]) {
|
|
7256
7336
|
continue;
|
|
7257
7337
|
}
|
|
7258
7338
|
seen.add(provider);
|
|
@@ -7281,6 +7361,7 @@ var createVoiceProviderRouter = (options) => {
|
|
|
7281
7361
|
const startedAt = Date.now();
|
|
7282
7362
|
try {
|
|
7283
7363
|
const output = await model.generate(input);
|
|
7364
|
+
recordProviderSuccess(provider);
|
|
7284
7365
|
await emit({
|
|
7285
7366
|
at: Date.now(),
|
|
7286
7367
|
elapsedMs: Date.now() - startedAt,
|
|
@@ -7295,18 +7376,21 @@ var createVoiceProviderRouter = (options) => {
|
|
|
7295
7376
|
lastError = error;
|
|
7296
7377
|
const hasNextProvider = index < order.length - 1;
|
|
7297
7378
|
const isProviderError = options.isProviderError?.(error, provider) ?? true;
|
|
7379
|
+
const rateLimited = options.isRateLimitError?.(error, provider) ?? defaultIsRateLimitError(error);
|
|
7380
|
+
const shouldFallback = fallbackMode === "provider-error" ? isProviderError : fallbackMode === "rate-limit" ? isProviderError && rateLimited : false;
|
|
7381
|
+
recordProviderError(provider, isProviderError, rateLimited);
|
|
7298
7382
|
const nextProvider = hasNextProvider ? order[index + 1] : undefined;
|
|
7299
7383
|
await emit({
|
|
7300
7384
|
at: Date.now(),
|
|
7301
7385
|
elapsedMs: Date.now() - startedAt,
|
|
7302
7386
|
error: errorMessage(error),
|
|
7303
|
-
fallbackProvider:
|
|
7387
|
+
fallbackProvider: shouldFallback ? nextProvider : undefined,
|
|
7304
7388
|
provider,
|
|
7305
|
-
rateLimited
|
|
7389
|
+
rateLimited,
|
|
7306
7390
|
selectedProvider,
|
|
7307
7391
|
status: "error"
|
|
7308
7392
|
}, input);
|
|
7309
|
-
if (!hasNextProvider || !
|
|
7393
|
+
if (!hasNextProvider || !shouldFallback) {
|
|
7310
7394
|
throw error;
|
|
7311
7395
|
}
|
|
7312
7396
|
}
|
package/dist/modelAdapters.d.ts
CHANGED
|
@@ -45,11 +45,41 @@ 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
|
+
};
|
|
59
|
+
export type VoiceProviderRouterHealthOptions = {
|
|
60
|
+
cooldownMs?: number;
|
|
61
|
+
failureThreshold?: number;
|
|
62
|
+
now?: () => number;
|
|
63
|
+
rateLimitCooldownMs?: number;
|
|
64
|
+
};
|
|
65
|
+
export type VoiceProviderRouterProviderHealth<TProvider extends string = string> = {
|
|
66
|
+
consecutiveFailures: number;
|
|
67
|
+
lastFailureAt?: number;
|
|
68
|
+
lastRateLimitedAt?: number;
|
|
69
|
+
provider: TProvider;
|
|
70
|
+
status: 'healthy' | 'suppressed';
|
|
71
|
+
suppressedUntil?: number;
|
|
72
|
+
};
|
|
48
73
|
export type VoiceProviderRouterOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown, TProvider extends string = string> = {
|
|
74
|
+
allowProviders?: readonly TProvider[] | ((input: VoiceAgentModelInput<TContext, TSession>) => readonly TProvider[] | Promise<readonly TProvider[]>);
|
|
49
75
|
fallback?: TProvider[] | ((input: VoiceAgentModelInput<TContext, TSession>) => readonly TProvider[] | Promise<readonly TProvider[]>);
|
|
76
|
+
fallbackMode?: VoiceProviderRouterFallbackMode;
|
|
50
77
|
isProviderError?: (error: unknown, provider: TProvider) => boolean;
|
|
51
78
|
isRateLimitError?: (error: unknown, provider: TProvider) => boolean;
|
|
52
79
|
onProviderEvent?: (event: VoiceProviderRouterEvent<TProvider>, input: VoiceAgentModelInput<TContext, TSession>) => Promise<void> | void;
|
|
80
|
+
policy?: VoiceProviderRouterPolicy<TContext, TSession, TProvider>;
|
|
81
|
+
providerHealth?: boolean | VoiceProviderRouterHealthOptions;
|
|
82
|
+
providerProfiles?: Partial<Record<TProvider, VoiceProviderRouterProviderProfile>>;
|
|
53
83
|
providers: Partial<Record<TProvider, VoiceAgentModel<TContext, TSession, TResult>>>;
|
|
54
84
|
selectProvider?: (input: VoiceAgentModelInput<TContext, TSession>) => TProvider | undefined | Promise<TProvider | undefined>;
|
|
55
85
|
};
|