@absolutejs/voice 0.0.22-beta.12 → 0.0.22-beta.14
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 +82 -2
- package/dist/modelAdapters.d.ts +18 -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, VoiceProviderRouterFallbackMode, VoiceProviderRouterOptions, VoiceProviderRouterPolicy, VoiceProviderRouterProviderProfile, 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
|
@@ -7246,6 +7246,74 @@ var createVoiceProviderRouter = (options) => {
|
|
|
7246
7246
|
} : options.policy;
|
|
7247
7247
|
const strategy = policy?.strategy ?? "prefer-selected";
|
|
7248
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 cloneHealth = (provider) => {
|
|
7269
|
+
if (!healthOptions) {
|
|
7270
|
+
return;
|
|
7271
|
+
}
|
|
7272
|
+
return {
|
|
7273
|
+
...getHealth(provider)
|
|
7274
|
+
};
|
|
7275
|
+
};
|
|
7276
|
+
const getSuppressionRemainingMs = (provider) => {
|
|
7277
|
+
if (!healthOptions) {
|
|
7278
|
+
return;
|
|
7279
|
+
}
|
|
7280
|
+
const suppressedUntil = getHealth(provider).suppressedUntil;
|
|
7281
|
+
return typeof suppressedUntil === "number" ? Math.max(0, suppressedUntil - now()) : undefined;
|
|
7282
|
+
};
|
|
7283
|
+
const isSuppressed = (provider) => {
|
|
7284
|
+
if (!healthOptions) {
|
|
7285
|
+
return false;
|
|
7286
|
+
}
|
|
7287
|
+
const health = getHealth(provider);
|
|
7288
|
+
return typeof health.suppressedUntil === "number" && health.suppressedUntil > now();
|
|
7289
|
+
};
|
|
7290
|
+
const recordProviderSuccess = (provider) => {
|
|
7291
|
+
if (!healthOptions) {
|
|
7292
|
+
return;
|
|
7293
|
+
}
|
|
7294
|
+
const health = getHealth(provider);
|
|
7295
|
+
health.consecutiveFailures = 0;
|
|
7296
|
+
health.status = "healthy";
|
|
7297
|
+
health.suppressedUntil = undefined;
|
|
7298
|
+
return cloneHealth(provider);
|
|
7299
|
+
};
|
|
7300
|
+
const recordProviderError = (provider, isProviderError, rateLimited) => {
|
|
7301
|
+
if (!healthOptions || !isProviderError) {
|
|
7302
|
+
return cloneHealth(provider);
|
|
7303
|
+
}
|
|
7304
|
+
const currentTime = now();
|
|
7305
|
+
const health = getHealth(provider);
|
|
7306
|
+
health.consecutiveFailures += 1;
|
|
7307
|
+
health.lastFailureAt = currentTime;
|
|
7308
|
+
if (rateLimited) {
|
|
7309
|
+
health.lastRateLimitedAt = currentTime;
|
|
7310
|
+
}
|
|
7311
|
+
if (rateLimited || health.consecutiveFailures >= failureThreshold) {
|
|
7312
|
+
health.status = "suppressed";
|
|
7313
|
+
health.suppressedUntil = currentTime + (rateLimited ? rateLimitCooldownMs : cooldownMs);
|
|
7314
|
+
}
|
|
7315
|
+
return cloneHealth(provider);
|
|
7316
|
+
};
|
|
7249
7317
|
const resolveAllowedProviders = async (input) => {
|
|
7250
7318
|
const allowProviders = policy?.allowProviders ?? options.allowProviders;
|
|
7251
7319
|
const allowed = typeof allowProviders === "function" ? await allowProviders(input) : allowProviders;
|
|
@@ -7270,10 +7338,16 @@ var createVoiceProviderRouter = (options) => {
|
|
|
7270
7338
|
const rankedProviders = sortProviders([
|
|
7271
7339
|
...fallbackOrder ?? providerIds
|
|
7272
7340
|
]).filter((provider) => allowedProviders.has(provider));
|
|
7273
|
-
const
|
|
7341
|
+
const healthyRankedProviders = healthOptions ? rankedProviders.filter((provider) => !isSuppressed(provider)) : rankedProviders;
|
|
7342
|
+
const candidateRankedProviders = healthyRankedProviders.length ? healthyRankedProviders : rankedProviders;
|
|
7343
|
+
const preferred = selectedProvider && allowedProviders.has(selectedProvider) && (!healthOptions || !isSuppressed(selectedProvider)) ? selectedProvider : candidateRankedProviders[0] ?? firstProvider;
|
|
7274
7344
|
const seen = new Set;
|
|
7275
7345
|
const order = [];
|
|
7276
|
-
const candidates = strategy === "ordered" ?
|
|
7346
|
+
const candidates = strategy === "ordered" ? candidateRankedProviders : [
|
|
7347
|
+
preferred,
|
|
7348
|
+
...candidateRankedProviders,
|
|
7349
|
+
...providerIds.filter((provider) => !healthOptions || !isSuppressed(provider))
|
|
7350
|
+
];
|
|
7277
7351
|
for (const provider of candidates) {
|
|
7278
7352
|
if (!provider || seen.has(provider) || !allowedProviders.has(provider) || !options.providers[provider]) {
|
|
7279
7353
|
continue;
|
|
@@ -7304,11 +7378,13 @@ var createVoiceProviderRouter = (options) => {
|
|
|
7304
7378
|
const startedAt = Date.now();
|
|
7305
7379
|
try {
|
|
7306
7380
|
const output = await model.generate(input);
|
|
7381
|
+
const providerHealth = recordProviderSuccess(provider);
|
|
7307
7382
|
await emit({
|
|
7308
7383
|
at: Date.now(),
|
|
7309
7384
|
elapsedMs: Date.now() - startedAt,
|
|
7310
7385
|
fallbackProvider: provider === selectedProvider ? undefined : provider,
|
|
7311
7386
|
provider,
|
|
7387
|
+
providerHealth,
|
|
7312
7388
|
recovered: provider !== selectedProvider,
|
|
7313
7389
|
selectedProvider,
|
|
7314
7390
|
status: provider === selectedProvider ? "success" : "fallback"
|
|
@@ -7320,6 +7396,7 @@ var createVoiceProviderRouter = (options) => {
|
|
|
7320
7396
|
const isProviderError = options.isProviderError?.(error, provider) ?? true;
|
|
7321
7397
|
const rateLimited = options.isRateLimitError?.(error, provider) ?? defaultIsRateLimitError(error);
|
|
7322
7398
|
const shouldFallback = fallbackMode === "provider-error" ? isProviderError : fallbackMode === "rate-limit" ? isProviderError && rateLimited : false;
|
|
7399
|
+
const providerHealth = recordProviderError(provider, isProviderError, rateLimited);
|
|
7323
7400
|
const nextProvider = hasNextProvider ? order[index + 1] : undefined;
|
|
7324
7401
|
await emit({
|
|
7325
7402
|
at: Date.now(),
|
|
@@ -7327,8 +7404,11 @@ var createVoiceProviderRouter = (options) => {
|
|
|
7327
7404
|
error: errorMessage(error),
|
|
7328
7405
|
fallbackProvider: shouldFallback ? nextProvider : undefined,
|
|
7329
7406
|
provider,
|
|
7407
|
+
providerHealth,
|
|
7330
7408
|
rateLimited,
|
|
7331
7409
|
selectedProvider,
|
|
7410
|
+
suppressionRemainingMs: getSuppressionRemainingMs(provider),
|
|
7411
|
+
suppressedUntil: providerHealth?.suppressedUntil,
|
|
7332
7412
|
status: "error"
|
|
7333
7413
|
}, input);
|
|
7334
7414
|
if (!hasNextProvider || !shouldFallback) {
|
package/dist/modelAdapters.d.ts
CHANGED
|
@@ -40,9 +40,12 @@ export type VoiceProviderRouterEvent<TProvider extends string = string> = {
|
|
|
40
40
|
error?: string;
|
|
41
41
|
fallbackProvider?: TProvider;
|
|
42
42
|
provider: TProvider;
|
|
43
|
+
providerHealth?: VoiceProviderRouterProviderHealth<TProvider>;
|
|
43
44
|
rateLimited?: boolean;
|
|
44
45
|
recovered?: boolean;
|
|
45
46
|
selectedProvider: TProvider;
|
|
47
|
+
suppressionRemainingMs?: number;
|
|
48
|
+
suppressedUntil?: number;
|
|
46
49
|
status: 'error' | 'fallback' | 'success';
|
|
47
50
|
};
|
|
48
51
|
export type VoiceProviderRouterFallbackMode = 'never' | 'provider-error' | 'rate-limit';
|
|
@@ -56,6 +59,20 @@ export type VoiceProviderRouterProviderProfile = {
|
|
|
56
59
|
latencyMs?: number;
|
|
57
60
|
priority?: number;
|
|
58
61
|
};
|
|
62
|
+
export type VoiceProviderRouterHealthOptions = {
|
|
63
|
+
cooldownMs?: number;
|
|
64
|
+
failureThreshold?: number;
|
|
65
|
+
now?: () => number;
|
|
66
|
+
rateLimitCooldownMs?: number;
|
|
67
|
+
};
|
|
68
|
+
export type VoiceProviderRouterProviderHealth<TProvider extends string = string> = {
|
|
69
|
+
consecutiveFailures: number;
|
|
70
|
+
lastFailureAt?: number;
|
|
71
|
+
lastRateLimitedAt?: number;
|
|
72
|
+
provider: TProvider;
|
|
73
|
+
status: 'healthy' | 'suppressed';
|
|
74
|
+
suppressedUntil?: number;
|
|
75
|
+
};
|
|
59
76
|
export type VoiceProviderRouterOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown, TProvider extends string = string> = {
|
|
60
77
|
allowProviders?: readonly TProvider[] | ((input: VoiceAgentModelInput<TContext, TSession>) => readonly TProvider[] | Promise<readonly TProvider[]>);
|
|
61
78
|
fallback?: TProvider[] | ((input: VoiceAgentModelInput<TContext, TSession>) => readonly TProvider[] | Promise<readonly TProvider[]>);
|
|
@@ -64,6 +81,7 @@ export type VoiceProviderRouterOptions<TContext = unknown, TSession extends Voic
|
|
|
64
81
|
isRateLimitError?: (error: unknown, provider: TProvider) => boolean;
|
|
65
82
|
onProviderEvent?: (event: VoiceProviderRouterEvent<TProvider>, input: VoiceAgentModelInput<TContext, TSession>) => Promise<void> | void;
|
|
66
83
|
policy?: VoiceProviderRouterPolicy<TContext, TSession, TProvider>;
|
|
84
|
+
providerHealth?: boolean | VoiceProviderRouterHealthOptions;
|
|
67
85
|
providerProfiles?: Partial<Record<TProvider, VoiceProviderRouterProviderProfile>>;
|
|
68
86
|
providers: Partial<Record<TProvider, VoiceAgentModel<TContext, TSession, TResult>>>;
|
|
69
87
|
selectProvider?: (input: VoiceAgentModelInput<TContext, TSession>) => TProvider | undefined | Promise<TProvider | undefined>;
|