@absolutejs/voice 0.0.22-beta.32 → 0.0.22-beta.33

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
@@ -7,6 +7,7 @@ export { createStoredVoiceCallReviewArtifact, createStoredVoiceExternalObjectMap
7
7
  export { createVoiceAssistantMemoryHandle, createVoiceAssistantMemoryRecord, createVoiceMemoryAssistantMemoryStore, resolveVoiceAssistantMemoryNamespace } from './assistantMemory';
8
8
  export { createAnthropicVoiceAssistantModel, createGeminiVoiceAssistantModel, createJSONVoiceAssistantModel, createOpenAIVoiceAssistantModel, createVoiceProviderRouter } from './modelAdapters';
9
9
  export { createVoiceProviderHealthHTMLHandler, createVoiceProviderHealthJSONHandler, createVoiceProviderHealthRoutes, renderVoiceProviderHealthHTML, summarizeVoiceProviderHealth } from './providerHealth';
10
+ export { createVoiceSTTProviderRouter, createVoiceTTSProviderRouter } from './providerAdapters';
10
11
  export { buildVoiceTraceReplay, createVoiceMemoryTraceSinkDeliveryStore, createVoiceTraceHTTPSink, createVoiceMemoryTraceEventStore, createVoiceTraceSinkDeliveryId, createVoiceTraceSinkDeliveryRecord, createVoiceTraceSinkStore, createVoiceTraceEvent, createVoiceTraceEventId, deliverVoiceTraceEventsToSinks, evaluateVoiceTrace, exportVoiceTrace, filterVoiceTraceEvents, pruneVoiceTraceEvents, redactVoiceTraceEvent, redactVoiceTraceEvents, redactVoiceTraceText, renderVoiceTraceHTML, renderVoiceTraceMarkdown, resolveVoiceTraceRedactionOptions, selectVoiceTraceEventsForPrune, summarizeVoiceTrace } from './trace';
11
12
  export { createVoiceSQLiteExternalObjectMapStore, createVoiceSQLiteIntegrationEventStore, createVoiceSQLiteReviewStore, createVoiceSQLiteRuntimeStorage, createVoiceSQLiteSessionStore, createVoiceSQLiteTaskStore, createVoiceSQLiteTraceSinkDeliveryStore, createVoiceSQLiteTraceEventStore } from './sqliteStore';
12
13
  export { createVoicePostgresExternalObjectMapStore, createVoicePostgresIntegrationEventStore, createVoicePostgresReviewStore, createVoicePostgresRuntimeStorage, createVoicePostgresSessionStore, createVoicePostgresTaskStore, createVoicePostgresTraceSinkDeliveryStore, createVoicePostgresTraceEventStore } from './postgresStore';
@@ -36,6 +37,7 @@ export type { VoiceAssistantMemoryBinding, VoiceAssistantMemoryHandle, VoiceAssi
36
37
  export type { VoiceSessionListHTMLHandlerOptions, VoiceSessionListItem, VoiceSessionListOptions, VoiceSessionListRoutesOptions, VoiceSessionListStatus, VoiceSessionReplay, VoiceSessionReplayHTMLHandlerOptions, VoiceSessionReplayOptions, VoiceSessionReplayRoutesOptions, VoiceSessionReplayTurn } from './sessionReplay';
37
38
  export type { AnthropicVoiceAssistantModelOptions, GeminiVoiceAssistantModelOptions, OpenAIVoiceAssistantModelOptions, VoiceProviderRouterEvent, VoiceProviderRouterFallbackMode, VoiceProviderRouterHealthOptions, VoiceProviderRouterOptions, VoiceProviderRouterPolicy, VoiceProviderRouterProviderHealth, VoiceProviderRouterProviderProfile, VoiceJSONAssistantModelHandler, VoiceJSONAssistantModelOptions } from './modelAdapters';
38
39
  export type { VoiceProviderHealthStatus, VoiceProviderHealthSummary, VoiceProviderHealthSummaryOptions } from './providerHealth';
40
+ export type { VoiceIOProviderRouterEvent, VoiceSTTProviderRouterOptions, VoiceTTSProviderRouterOptions } from './providerAdapters';
39
41
  export type { VoiceAgent, VoiceAgentMessage, VoiceAgentMessageRole, VoiceAgentModel, VoiceAgentModelInput, VoiceAgentModelOutput, VoiceAgentOptions, VoiceAgentRunResult, VoiceAgentSquadOptions, VoiceAgentTool, VoiceAgentToolCall, VoiceAgentToolResult } from './agent';
40
42
  export type { VoiceOpsRuntime, VoiceOpsRuntimeConfig, VoiceOpsRuntimeSummary, VoiceOpsRuntimeSinkWorkerConfig, VoiceOpsRuntimeTaskWorkerConfig, VoiceOpsRuntimeTickResult, VoiceOpsRuntimeWebhookWorkerConfig } from './opsRuntime';
41
43
  export type { VoiceOpsPresetName, VoiceOpsPresetOverrides, VoiceResolvedOpsPreset } from './opsPresets';
package/dist/index.js CHANGED
@@ -8858,6 +8858,288 @@ var createGeminiVoiceAssistantModel = (options) => {
8858
8858
  }
8859
8859
  };
8860
8860
  };
8861
+ // src/providerAdapters.ts
8862
+ class VoiceIOProviderTimeoutError extends Error {
8863
+ provider;
8864
+ timeoutMs;
8865
+ constructor(kind, provider, timeoutMs) {
8866
+ super(`Voice ${kind} provider ${provider} exceeded ${timeoutMs}ms latency budget.`);
8867
+ this.name = "VoiceIOProviderTimeoutError";
8868
+ this.provider = provider;
8869
+ this.timeoutMs = timeoutMs;
8870
+ }
8871
+ }
8872
+ var errorMessage2 = (error) => error instanceof Error ? error.message : String(error);
8873
+ var createEmitter = () => {
8874
+ const listeners = new Map;
8875
+ return {
8876
+ emit: async (event, payload) => {
8877
+ await Promise.all([...listeners.get(event) ?? []].map((handler) => Promise.resolve(handler(payload))));
8878
+ },
8879
+ on: (event, handler) => {
8880
+ const set = listeners.get(event) ?? new Set;
8881
+ set.add(handler);
8882
+ listeners.set(event, set);
8883
+ return () => {
8884
+ set.delete(handler);
8885
+ };
8886
+ }
8887
+ };
8888
+ };
8889
+ var getTimeoutMs = (options, provider) => {
8890
+ const timeoutMs = options.providerProfiles?.[provider]?.timeoutMs ?? options.timeoutMs;
8891
+ return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : undefined;
8892
+ };
8893
+ var withTimeout = async (input) => {
8894
+ if (!input.timeoutMs) {
8895
+ return input.run();
8896
+ }
8897
+ let timeout;
8898
+ try {
8899
+ return await Promise.race([
8900
+ Promise.resolve(input.run()),
8901
+ new Promise((_, reject) => {
8902
+ timeout = setTimeout(() => reject(new VoiceIOProviderTimeoutError(input.kind, input.provider, input.timeoutMs)), input.timeoutMs);
8903
+ })
8904
+ ]);
8905
+ } finally {
8906
+ if (timeout) {
8907
+ clearTimeout(timeout);
8908
+ }
8909
+ }
8910
+ };
8911
+ var createResolver = (options) => {
8912
+ const providerIds = Object.keys(options.adapters);
8913
+ const firstProvider = providerIds[0];
8914
+ const resolveOrder = async (input) => {
8915
+ const selectedProvider = await options.selectProvider?.(input) ?? firstProvider;
8916
+ const fallbackOrder = typeof options.fallback === "function" ? await options.fallback(input) : options.fallback;
8917
+ const candidates = [selectedProvider, ...fallbackOrder ?? providerIds];
8918
+ const seen = new Set;
8919
+ const order = candidates.filter((provider) => {
8920
+ if (!provider || seen.has(provider) || !options.adapters[provider]) {
8921
+ return false;
8922
+ }
8923
+ seen.add(provider);
8924
+ return true;
8925
+ });
8926
+ return {
8927
+ order,
8928
+ selectedProvider
8929
+ };
8930
+ };
8931
+ const emit = async (event, input) => {
8932
+ await options.onProviderEvent?.(event, input);
8933
+ };
8934
+ return {
8935
+ emit,
8936
+ providerIds,
8937
+ resolveOrder
8938
+ };
8939
+ };
8940
+ var createVoiceSTTProviderRouter = (options) => {
8941
+ const resolver = createResolver(options);
8942
+ return {
8943
+ kind: "stt",
8944
+ open: async (input) => {
8945
+ const { order, selectedProvider } = await resolver.resolveOrder(input);
8946
+ if (!selectedProvider || order.length === 0) {
8947
+ throw new Error("Voice STT provider router has no available providers.");
8948
+ }
8949
+ let lastError;
8950
+ for (const [index, provider] of order.entries()) {
8951
+ const adapter = options.adapters[provider];
8952
+ if (!adapter) {
8953
+ continue;
8954
+ }
8955
+ const startedAt = Date.now();
8956
+ try {
8957
+ const session = await withTimeout({
8958
+ kind: "stt",
8959
+ operation: "open",
8960
+ provider,
8961
+ run: () => adapter.open(input),
8962
+ timeoutMs: getTimeoutMs(options, provider)
8963
+ });
8964
+ await resolver.emit({
8965
+ at: Date.now(),
8966
+ attempt: index + 1,
8967
+ elapsedMs: Date.now() - startedAt,
8968
+ fallbackProvider: provider === selectedProvider ? undefined : provider,
8969
+ kind: "stt",
8970
+ latencyBudgetMs: getTimeoutMs(options, provider),
8971
+ operation: "open",
8972
+ provider,
8973
+ selectedProvider,
8974
+ status: provider === selectedProvider ? "success" : "fallback"
8975
+ }, input);
8976
+ return session;
8977
+ } catch (error) {
8978
+ lastError = error;
8979
+ const hasNextProvider = index < order.length - 1;
8980
+ const shouldFallback = options.isProviderError?.(error, provider) ?? true;
8981
+ await resolver.emit({
8982
+ at: Date.now(),
8983
+ attempt: index + 1,
8984
+ elapsedMs: Date.now() - startedAt,
8985
+ error: errorMessage2(error),
8986
+ fallbackProvider: shouldFallback ? order[index + 1] : undefined,
8987
+ kind: "stt",
8988
+ latencyBudgetMs: getTimeoutMs(options, provider),
8989
+ operation: "open",
8990
+ provider,
8991
+ selectedProvider,
8992
+ status: "error",
8993
+ timedOut: error instanceof VoiceIOProviderTimeoutError
8994
+ }, input);
8995
+ if (!hasNextProvider || !shouldFallback) {
8996
+ throw error;
8997
+ }
8998
+ }
8999
+ }
9000
+ throw lastError ?? new Error("Voice STT provider router did not open a provider.");
9001
+ }
9002
+ };
9003
+ };
9004
+ var createVoiceTTSProviderRouter = (options) => {
9005
+ const resolver = createResolver(options);
9006
+ return {
9007
+ kind: "tts",
9008
+ open: async (input) => {
9009
+ const { order, selectedProvider } = await resolver.resolveOrder(input);
9010
+ if (!selectedProvider || order.length === 0) {
9011
+ throw new Error("Voice TTS provider router has no available providers.");
9012
+ }
9013
+ const emitter = createEmitter();
9014
+ let activeSession;
9015
+ let activeProvider;
9016
+ let nextProviderIndex = 0;
9017
+ const attach = (session) => {
9018
+ session.on("audio", (event) => emitter.emit("audio", event));
9019
+ session.on("error", (event) => emitter.emit("error", event));
9020
+ session.on("close", (event) => emitter.emit("close", event));
9021
+ };
9022
+ const openProvider = async (provider, attempt) => {
9023
+ const adapter = options.adapters[provider];
9024
+ if (!adapter) {
9025
+ throw new Error(`Voice TTS provider ${provider} is not configured.`);
9026
+ }
9027
+ const startedAt = Date.now();
9028
+ const session = await withTimeout({
9029
+ kind: "tts",
9030
+ operation: "open",
9031
+ provider,
9032
+ run: () => adapter.open(input),
9033
+ timeoutMs: getTimeoutMs(options, provider)
9034
+ });
9035
+ attach(session);
9036
+ activeSession = session;
9037
+ activeProvider = provider;
9038
+ await resolver.emit({
9039
+ at: Date.now(),
9040
+ attempt,
9041
+ elapsedMs: Date.now() - startedAt,
9042
+ fallbackProvider: provider === selectedProvider ? undefined : provider,
9043
+ kind: "tts",
9044
+ latencyBudgetMs: getTimeoutMs(options, provider),
9045
+ operation: "open",
9046
+ provider,
9047
+ selectedProvider,
9048
+ status: provider === selectedProvider ? "success" : "fallback"
9049
+ }, input);
9050
+ return session;
9051
+ };
9052
+ const failProvider = async (inputEvent) => {
9053
+ const shouldFallback = options.isProviderError?.(inputEvent.error, inputEvent.provider) ?? true;
9054
+ await resolver.emit({
9055
+ at: Date.now(),
9056
+ attempt: inputEvent.attempt,
9057
+ elapsedMs: Date.now() - inputEvent.startedAt,
9058
+ error: errorMessage2(inputEvent.error),
9059
+ fallbackProvider: shouldFallback ? order[nextProviderIndex] : undefined,
9060
+ kind: "tts",
9061
+ latencyBudgetMs: getTimeoutMs(options, inputEvent.provider),
9062
+ operation: inputEvent.operation,
9063
+ provider: inputEvent.provider,
9064
+ selectedProvider,
9065
+ status: "error",
9066
+ timedOut: inputEvent.error instanceof VoiceIOProviderTimeoutError
9067
+ }, input);
9068
+ return shouldFallback;
9069
+ };
9070
+ for (const [index, provider] of order.entries()) {
9071
+ nextProviderIndex = index + 1;
9072
+ const startedAt = Date.now();
9073
+ try {
9074
+ await openProvider(provider, index + 1);
9075
+ break;
9076
+ } catch (error) {
9077
+ const shouldFallback = await failProvider({
9078
+ attempt: index + 1,
9079
+ error,
9080
+ operation: "open",
9081
+ provider,
9082
+ startedAt
9083
+ });
9084
+ if (!shouldFallback || index >= order.length - 1) {
9085
+ throw error;
9086
+ }
9087
+ }
9088
+ }
9089
+ if (!activeSession || !activeProvider) {
9090
+ throw new Error("Voice TTS provider router did not open a provider.");
9091
+ }
9092
+ const sendWithFallback = async (text) => {
9093
+ for (;; ) {
9094
+ const session = activeSession;
9095
+ const provider = activeProvider;
9096
+ if (!session || !provider) {
9097
+ throw new Error("Voice TTS provider router has no active provider.");
9098
+ }
9099
+ const startedAt = Date.now();
9100
+ try {
9101
+ await withTimeout({
9102
+ kind: "tts",
9103
+ operation: "send",
9104
+ provider,
9105
+ run: () => session.send(text),
9106
+ timeoutMs: getTimeoutMs(options, provider)
9107
+ });
9108
+ return;
9109
+ } catch (error) {
9110
+ const shouldFallback = await failProvider({
9111
+ attempt: nextProviderIndex,
9112
+ error,
9113
+ operation: "send",
9114
+ provider,
9115
+ startedAt
9116
+ });
9117
+ const nextProvider = order[nextProviderIndex];
9118
+ if (!shouldFallback || !nextProvider) {
9119
+ throw error;
9120
+ }
9121
+ nextProviderIndex += 1;
9122
+ await session.close("tts-provider-fallback").catch(() => {});
9123
+ await openProvider(nextProvider, nextProviderIndex);
9124
+ }
9125
+ }
9126
+ };
9127
+ return {
9128
+ close: async (reason) => {
9129
+ await activeSession?.close(reason);
9130
+ activeSession = undefined;
9131
+ activeProvider = undefined;
9132
+ await emitter.emit("close", {
9133
+ reason,
9134
+ type: "close"
9135
+ });
9136
+ },
9137
+ on: emitter.on,
9138
+ send: sendWithFallback
9139
+ };
9140
+ }
9141
+ };
9142
+ };
8861
9143
  // src/sqliteStore.ts
8862
9144
  import { Database } from "bun:sqlite";
8863
9145
  var normalizeTableNameSegment = (value) => value.trim().replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "voice";
@@ -10556,10 +10838,10 @@ var createVoiceOpsTaskProcessorWorker = (options) => ({
10556
10838
  result.completed += 1;
10557
10839
  } catch (error) {
10558
10840
  await options.onError?.(error, task);
10559
- const errorMessage2 = error instanceof Error ? error.message : String(error);
10841
+ const errorMessage3 = error instanceof Error ? error.message : String(error);
10560
10842
  const failedTask = failVoiceOpsTask(task, {
10561
10843
  actor: task.claimedBy ?? "ops-worker",
10562
- error: errorMessage2
10844
+ error: errorMessage3
10563
10845
  });
10564
10846
  if (shouldDeadLetterTask(failedTask, options.maxFailures)) {
10565
10847
  const deadLetterTask = deadLetterVoiceOpsTask(failedTask, {
@@ -11886,6 +12168,7 @@ export {
11886
12168
  createVoiceTaskUpdatedEvent,
11887
12169
  createVoiceTaskSLABreachedEvent,
11888
12170
  createVoiceTaskCreatedEvent,
12171
+ createVoiceTTSProviderRouter,
11889
12172
  createVoiceSessionsJSONHandler,
11890
12173
  createVoiceSessionsHTMLHandler,
11891
12174
  createVoiceSessionReplayRoutes,
@@ -11895,6 +12178,7 @@ export {
11895
12178
  createVoiceSessionListRoutes,
11896
12179
  createVoiceSession,
11897
12180
  createVoiceSTTRoutingCorrectionHandler,
12181
+ createVoiceSTTProviderRouter,
11898
12182
  createVoiceSQLiteTraceSinkDeliveryStore,
11899
12183
  createVoiceSQLiteTraceEventStore,
11900
12184
  createVoiceSQLiteTaskStore,
@@ -0,0 +1,33 @@
1
+ import type { STTAdapter, STTAdapterOpenOptions, TTSAdapter, TTSAdapterOpenOptions } from './types';
2
+ import type { VoiceProviderRouterProviderProfile } from './modelAdapters';
3
+ type MaybePromise<T> = T | Promise<T>;
4
+ type VoiceIOProviderKind = 'stt' | 'tts';
5
+ type VoiceIOProviderStatus = 'error' | 'fallback' | 'success';
6
+ export type VoiceIOProviderRouterEvent<TProvider extends string = string> = {
7
+ at: number;
8
+ attempt: number;
9
+ elapsedMs: number;
10
+ error?: string;
11
+ fallbackProvider?: TProvider;
12
+ kind: VoiceIOProviderKind;
13
+ latencyBudgetMs?: number;
14
+ operation: 'open' | 'send';
15
+ provider: TProvider;
16
+ selectedProvider: TProvider;
17
+ status: VoiceIOProviderStatus;
18
+ timedOut?: boolean;
19
+ };
20
+ export type VoiceIOProviderRouterOptions<TProvider extends string, TAdapter, TOpenOptions> = {
21
+ adapters: Partial<Record<TProvider, TAdapter>>;
22
+ fallback?: readonly TProvider[] | ((input: TOpenOptions) => MaybePromise<readonly TProvider[]>);
23
+ isProviderError?: (error: unknown, provider: TProvider) => boolean;
24
+ onProviderEvent?: (event: VoiceIOProviderRouterEvent<TProvider>, input: TOpenOptions) => Promise<void> | void;
25
+ providerProfiles?: Partial<Record<TProvider, VoiceProviderRouterProviderProfile>>;
26
+ selectProvider?: (input: TOpenOptions) => MaybePromise<TProvider | undefined>;
27
+ timeoutMs?: number;
28
+ };
29
+ export type VoiceSTTProviderRouterOptions<TProvider extends string = string, TOptions extends STTAdapterOpenOptions = STTAdapterOpenOptions> = VoiceIOProviderRouterOptions<TProvider, STTAdapter<TOptions>, TOptions>;
30
+ export type VoiceTTSProviderRouterOptions<TProvider extends string = string, TOptions extends TTSAdapterOpenOptions = TTSAdapterOpenOptions> = VoiceIOProviderRouterOptions<TProvider, TTSAdapter<TOptions>, TOptions>;
31
+ export declare const createVoiceSTTProviderRouter: <TProvider extends string = string, TOptions extends STTAdapterOpenOptions = STTAdapterOpenOptions>(options: VoiceSTTProviderRouterOptions<TProvider, TOptions>) => STTAdapter<TOptions>;
32
+ export declare const createVoiceTTSProviderRouter: <TProvider extends string = string, TOptions extends TTSAdapterOpenOptions = TTSAdapterOpenOptions>(options: VoiceTTSProviderRouterOptions<TProvider, TOptions>) => TTSAdapter<TOptions>;
33
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.32",
3
+ "version": "0.0.22-beta.33",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",