@absolutejs/voice 0.0.22-beta.2 → 0.0.22-beta.21

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.
@@ -0,0 +1,93 @@
1
+ import type { VoiceAgentModel, VoiceAgentModelInput, VoiceAgentModelOutput } from './agent';
2
+ import type { VoiceSessionRecord } from './types';
3
+ export type VoiceJSONAssistantModelHandler<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = (input: VoiceAgentModelInput<TContext, TSession>) => Promise<Record<string, unknown> | VoiceAgentModelOutput<TResult>> | Record<string, unknown> | VoiceAgentModelOutput<TResult>;
4
+ export type VoiceJSONAssistantModelOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
5
+ generate: VoiceJSONAssistantModelHandler<TContext, TSession, TResult>;
6
+ mapOutput?: (output: Record<string, unknown>) => VoiceAgentModelOutput<TResult>;
7
+ };
8
+ export type OpenAIVoiceAssistantModelOptions = {
9
+ apiKey: string;
10
+ baseUrl?: string;
11
+ fetch?: typeof fetch;
12
+ maxOutputTokens?: number;
13
+ model?: string;
14
+ onUsage?: (usage: Record<string, unknown>) => Promise<void> | void;
15
+ temperature?: number;
16
+ };
17
+ export type AnthropicVoiceAssistantModelOptions = {
18
+ apiKey: string;
19
+ baseUrl?: string;
20
+ fetch?: typeof fetch;
21
+ maxOutputTokens?: number;
22
+ model?: string;
23
+ onUsage?: (usage: Record<string, unknown>) => Promise<void> | void;
24
+ temperature?: number;
25
+ version?: string;
26
+ };
27
+ export type GeminiVoiceAssistantModelOptions = {
28
+ apiKey: string;
29
+ baseUrl?: string;
30
+ fetch?: typeof fetch;
31
+ maxOutputTokens?: number;
32
+ maxRetries?: number;
33
+ model?: string;
34
+ onUsage?: (usage: Record<string, unknown>) => Promise<void> | void;
35
+ temperature?: number;
36
+ };
37
+ export type VoiceProviderRouterEvent<TProvider extends string = string> = {
38
+ at: number;
39
+ elapsedMs: number;
40
+ error?: string;
41
+ fallbackProvider?: TProvider;
42
+ provider: TProvider;
43
+ providerHealth?: VoiceProviderRouterProviderHealth<TProvider>;
44
+ rateLimited?: boolean;
45
+ recovered?: boolean;
46
+ selectedProvider: TProvider;
47
+ suppressionRemainingMs?: number;
48
+ suppressedUntil?: number;
49
+ status: 'error' | 'fallback' | 'success';
50
+ };
51
+ export type VoiceProviderRouterFallbackMode = 'never' | 'provider-error' | 'rate-limit';
52
+ export type VoiceProviderRouterPolicy<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TProvider extends string = string> = 'ordered' | 'prefer-cheapest' | 'prefer-fastest' | 'prefer-selected' | {
53
+ allowProviders?: readonly TProvider[] | ((input: VoiceAgentModelInput<TContext, TSession>) => readonly TProvider[] | Promise<readonly TProvider[]>);
54
+ fallbackMode?: VoiceProviderRouterFallbackMode;
55
+ strategy?: 'ordered' | 'prefer-cheapest' | 'prefer-fastest' | 'prefer-selected';
56
+ };
57
+ export type VoiceProviderRouterProviderProfile = {
58
+ cost?: number;
59
+ latencyMs?: number;
60
+ priority?: number;
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
+ };
76
+ export type VoiceProviderRouterOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown, TProvider extends string = string> = {
77
+ allowProviders?: readonly TProvider[] | ((input: VoiceAgentModelInput<TContext, TSession>) => readonly TProvider[] | Promise<readonly TProvider[]>);
78
+ fallback?: TProvider[] | ((input: VoiceAgentModelInput<TContext, TSession>) => readonly TProvider[] | Promise<readonly TProvider[]>);
79
+ fallbackMode?: VoiceProviderRouterFallbackMode;
80
+ isProviderError?: (error: unknown, provider: TProvider) => boolean;
81
+ isRateLimitError?: (error: unknown, provider: TProvider) => boolean;
82
+ onProviderEvent?: (event: VoiceProviderRouterEvent<TProvider>, input: VoiceAgentModelInput<TContext, TSession>) => Promise<void> | void;
83
+ policy?: VoiceProviderRouterPolicy<TContext, TSession, TProvider>;
84
+ providerHealth?: boolean | VoiceProviderRouterHealthOptions;
85
+ providerProfiles?: Partial<Record<TProvider, VoiceProviderRouterProviderProfile>>;
86
+ providers: Partial<Record<TProvider, VoiceAgentModel<TContext, TSession, TResult>>>;
87
+ selectProvider?: (input: VoiceAgentModelInput<TContext, TSession>) => TProvider | undefined | Promise<TProvider | undefined>;
88
+ };
89
+ export declare const createJSONVoiceAssistantModel: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options: VoiceJSONAssistantModelOptions<TContext, TSession, TResult>) => VoiceAgentModel<TContext, TSession, TResult>;
90
+ export declare const createVoiceProviderRouter: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown, TProvider extends string = string>(options: VoiceProviderRouterOptions<TContext, TSession, TResult, TProvider>) => VoiceAgentModel<TContext, TSession, TResult>;
91
+ export declare const createOpenAIVoiceAssistantModel: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options: OpenAIVoiceAssistantModelOptions) => VoiceAgentModel<TContext, TSession, TResult>;
92
+ export declare const createAnthropicVoiceAssistantModel: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options: AnthropicVoiceAssistantModelOptions) => VoiceAgentModel<TContext, TSession, TResult>;
93
+ export declare const createGeminiVoiceAssistantModel: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options: GeminiVoiceAssistantModelOptions) => VoiceAgentModel<TContext, TSession, TResult>;
@@ -0,0 +1,78 @@
1
+ import { Elysia } from 'elysia';
2
+ import type { StoredVoiceTraceEvent, VoiceTraceEventStore } from './trace';
3
+ export type VoiceProviderHealthStatus = 'healthy' | 'idle' | 'rate-limited' | 'degraded' | 'recoverable' | 'suppressed';
4
+ export type VoiceProviderHealthSummary<TProvider extends string = string> = {
5
+ averageElapsedMs?: number;
6
+ errorCount: number;
7
+ fallbackCount: number;
8
+ lastError?: string;
9
+ lastErrorAt?: number;
10
+ lastSuccessAt?: number;
11
+ provider: TProvider;
12
+ rateLimited: boolean;
13
+ recommended: boolean;
14
+ runCount: number;
15
+ status: VoiceProviderHealthStatus;
16
+ suppressionRemainingMs?: number;
17
+ suppressedUntil?: number;
18
+ };
19
+ export type VoiceProviderHealthSummaryOptions<TProvider extends string = string> = {
20
+ events?: StoredVoiceTraceEvent[];
21
+ now?: number;
22
+ providers?: readonly TProvider[];
23
+ store?: VoiceTraceEventStore;
24
+ };
25
+ export type VoiceProviderHealthHandlerOptions<TProvider extends string = string> = VoiceProviderHealthSummaryOptions<TProvider>;
26
+ export type VoiceProviderHealthHTMLHandlerOptions<TProvider extends string = string> = VoiceProviderHealthHandlerOptions<TProvider> & {
27
+ headers?: HeadersInit;
28
+ render?: (providers: VoiceProviderHealthSummary<TProvider>[]) => string | Promise<string>;
29
+ };
30
+ export type VoiceProviderHealthRoutesOptions<TProvider extends string = string> = VoiceProviderHealthHTMLHandlerOptions<TProvider> & {
31
+ htmlPath?: false | string;
32
+ name?: string;
33
+ path?: string;
34
+ };
35
+ export declare const summarizeVoiceProviderHealth: <TProvider extends string = string>(input: StoredVoiceTraceEvent[] | VoiceProviderHealthSummaryOptions<TProvider>) => Promise<VoiceProviderHealthSummary<TProvider>[]>;
36
+ export declare const renderVoiceProviderHealthHTML: (providers: VoiceProviderHealthSummary[]) => string;
37
+ export declare const createVoiceProviderHealthJSONHandler: <TProvider extends string = string>(options: VoiceProviderHealthHandlerOptions<TProvider>) => () => Promise<VoiceProviderHealthSummary<TProvider>[]>;
38
+ export declare const createVoiceProviderHealthHTMLHandler: <TProvider extends string = string>(options: VoiceProviderHealthHTMLHandlerOptions<TProvider>) => () => Promise<Response>;
39
+ export declare const createVoiceProviderHealthRoutes: <TProvider extends string = string>(options: VoiceProviderHealthRoutesOptions<TProvider>) => Elysia<"", {
40
+ decorator: {};
41
+ store: {};
42
+ derive: {};
43
+ resolve: {};
44
+ }, {
45
+ typebox: {};
46
+ error: {};
47
+ }, {
48
+ schema: {};
49
+ standaloneSchema: {};
50
+ macro: {};
51
+ macroFn: {};
52
+ parser: {};
53
+ response: {};
54
+ }, {
55
+ [x: string]: {
56
+ get: {
57
+ body: unknown;
58
+ params: {};
59
+ query: unknown;
60
+ headers: unknown;
61
+ response: {
62
+ 200: VoiceProviderHealthSummary<TProvider>[];
63
+ };
64
+ };
65
+ };
66
+ }, {
67
+ derive: {};
68
+ resolve: {};
69
+ schema: {};
70
+ standaloneSchema: {};
71
+ response: {};
72
+ }, {
73
+ derive: {};
74
+ resolve: {};
75
+ schema: {};
76
+ standaloneSchema: {};
77
+ response: {};
78
+ }>;
@@ -1,2 +1,3 @@
1
1
  export { useVoiceStream } from './useVoiceStream';
2
2
  export { useVoiceController } from './useVoiceController';
3
+ export { useVoiceProviderStatus } from './useVoiceProviderStatus';
@@ -1234,7 +1234,107 @@ var useVoiceController = (path, options = {}) => {
1234
1234
  toggleRecording: () => controller.toggleRecording()
1235
1235
  };
1236
1236
  };
1237
+ // src/react/useVoiceProviderStatus.tsx
1238
+ import { useEffect as useEffect3, useRef as useRef3, useSyncExternalStore as useSyncExternalStore3 } from "react";
1239
+
1240
+ // src/client/providerStatus.ts
1241
+ var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
1242
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1243
+ const response = await fetchImpl(path);
1244
+ if (!response.ok) {
1245
+ throw new Error(`Voice provider status failed: HTTP ${response.status}`);
1246
+ }
1247
+ return await response.json();
1248
+ };
1249
+ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
1250
+ const listeners = new Set;
1251
+ let closed = false;
1252
+ let timer;
1253
+ let snapshot = {
1254
+ error: null,
1255
+ isLoading: false,
1256
+ providers: []
1257
+ };
1258
+ const emit = () => {
1259
+ for (const listener of listeners) {
1260
+ listener();
1261
+ }
1262
+ };
1263
+ const refresh = async () => {
1264
+ if (closed) {
1265
+ return snapshot.providers;
1266
+ }
1267
+ snapshot = {
1268
+ ...snapshot,
1269
+ error: null,
1270
+ isLoading: true
1271
+ };
1272
+ emit();
1273
+ try {
1274
+ const providers = await fetchVoiceProviderStatus(path, options);
1275
+ snapshot = {
1276
+ error: null,
1277
+ isLoading: false,
1278
+ providers,
1279
+ updatedAt: Date.now()
1280
+ };
1281
+ emit();
1282
+ return providers;
1283
+ } catch (error) {
1284
+ snapshot = {
1285
+ ...snapshot,
1286
+ error: error instanceof Error ? error.message : String(error),
1287
+ isLoading: false
1288
+ };
1289
+ emit();
1290
+ throw error;
1291
+ }
1292
+ };
1293
+ const close = () => {
1294
+ closed = true;
1295
+ if (timer) {
1296
+ clearInterval(timer);
1297
+ timer = undefined;
1298
+ }
1299
+ listeners.clear();
1300
+ };
1301
+ if (options.intervalMs && options.intervalMs > 0) {
1302
+ timer = setInterval(() => {
1303
+ refresh().catch(() => {});
1304
+ }, options.intervalMs);
1305
+ }
1306
+ return {
1307
+ close,
1308
+ getServerSnapshot: () => snapshot,
1309
+ getSnapshot: () => snapshot,
1310
+ refresh,
1311
+ subscribe: (listener) => {
1312
+ listeners.add(listener);
1313
+ return () => {
1314
+ listeners.delete(listener);
1315
+ };
1316
+ }
1317
+ };
1318
+ };
1319
+
1320
+ // src/react/useVoiceProviderStatus.tsx
1321
+ var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
1322
+ const storeRef = useRef3(null);
1323
+ if (!storeRef.current) {
1324
+ storeRef.current = createVoiceProviderStatusStore(path, options);
1325
+ }
1326
+ const store = storeRef.current;
1327
+ useEffect3(() => {
1328
+ store.refresh().catch(() => {});
1329
+ return () => store.close();
1330
+ }, [store]);
1331
+ return {
1332
+ ...useSyncExternalStore3(store.subscribe, store.getSnapshot, store.getServerSnapshot),
1333
+ refresh: store.refresh
1334
+ };
1335
+ };
1237
1336
  export {
1238
1337
  useVoiceStream,
1338
+ useVoiceProviderStatus,
1239
1339
  useVoiceController
1240
1340
  };
@@ -0,0 +1,8 @@
1
+ import { type VoiceProviderStatusClientOptions } from '../client/providerStatus';
2
+ export declare const useVoiceProviderStatus: <TProvider extends string = string>(path?: string, options?: VoiceProviderStatusClientOptions) => {
3
+ refresh: () => Promise<import("..").VoiceProviderHealthSummary<TProvider>[]>;
4
+ error: string | null;
5
+ isLoading: boolean;
6
+ providers: import("..").VoiceProviderHealthSummary<TProvider>[];
7
+ updatedAt?: number;
8
+ };
@@ -0,0 +1,94 @@
1
+ import { Elysia } from 'elysia';
2
+ import { type StoredVoiceTraceEvent, type VoiceTraceEvaluationOptions, type VoiceTraceEventStore, type VoiceTraceRedactionConfig, type VoiceTraceSummary, type VoiceTraceEvaluation } from './trace';
3
+ export type VoiceSessionReplayTurn = {
4
+ assistantReplies: string[];
5
+ committedText?: string;
6
+ errors: Array<Record<string, unknown>>;
7
+ id: string;
8
+ modelCalls: Array<Record<string, unknown>>;
9
+ tools: Array<Record<string, unknown>>;
10
+ transcripts: Array<{
11
+ isFinal: boolean;
12
+ text?: string;
13
+ }>;
14
+ };
15
+ export type VoiceSessionReplay = {
16
+ evaluation: VoiceTraceEvaluation;
17
+ events: StoredVoiceTraceEvent[];
18
+ html: string;
19
+ markdown: string;
20
+ sessionId: string;
21
+ summary: VoiceTraceSummary;
22
+ timeline: Array<{
23
+ at: number;
24
+ offsetMs?: number;
25
+ payload: Record<string, unknown>;
26
+ turnId?: string;
27
+ type: StoredVoiceTraceEvent['type'];
28
+ }>;
29
+ turns: VoiceSessionReplayTurn[];
30
+ };
31
+ export type VoiceSessionReplayOptions = {
32
+ evaluation?: VoiceTraceEvaluationOptions;
33
+ events?: StoredVoiceTraceEvent[];
34
+ redact?: VoiceTraceRedactionConfig;
35
+ sessionId: string;
36
+ store?: VoiceTraceEventStore;
37
+ title?: string;
38
+ };
39
+ export type VoiceSessionReplayHTMLHandlerOptions = Omit<VoiceSessionReplayOptions, 'sessionId'> & {
40
+ headers?: HeadersInit;
41
+ render?: (replay: VoiceSessionReplay) => string | Promise<string>;
42
+ };
43
+ export type VoiceSessionReplayRoutesOptions = VoiceSessionReplayHTMLHandlerOptions & {
44
+ htmlPath?: false | string;
45
+ name?: string;
46
+ path?: string;
47
+ };
48
+ export declare const summarizeVoiceSessionReplay: (options: VoiceSessionReplayOptions) => Promise<VoiceSessionReplay>;
49
+ export declare const createVoiceSessionReplayJSONHandler: (options: Omit<VoiceSessionReplayOptions, "sessionId">) => ({ params }: {
50
+ params: Record<string, string | undefined>;
51
+ }) => Promise<VoiceSessionReplay>;
52
+ export declare const createVoiceSessionReplayHTMLHandler: (options: VoiceSessionReplayHTMLHandlerOptions) => ({ params }: {
53
+ params: Record<string, string | undefined>;
54
+ }) => Promise<Response>;
55
+ export declare const createVoiceSessionReplayRoutes: (options: VoiceSessionReplayRoutesOptions) => Elysia<"", {
56
+ decorator: {};
57
+ store: {};
58
+ derive: {};
59
+ resolve: {};
60
+ }, {
61
+ typebox: {};
62
+ error: {};
63
+ }, {
64
+ schema: {};
65
+ standaloneSchema: {};
66
+ macro: {};
67
+ macroFn: {};
68
+ parser: {};
69
+ response: {};
70
+ }, {
71
+ [x: string]: {
72
+ get: {
73
+ body: unknown;
74
+ params: {};
75
+ query: unknown;
76
+ headers: unknown;
77
+ response: {
78
+ 200: VoiceSessionReplay;
79
+ };
80
+ };
81
+ };
82
+ }, {
83
+ derive: {};
84
+ resolve: {};
85
+ schema: {};
86
+ standaloneSchema: {};
87
+ response: {};
88
+ }, {
89
+ derive: {};
90
+ resolve: {};
91
+ schema: {};
92
+ standaloneSchema: {};
93
+ response: {};
94
+ }>;
@@ -0,0 +1,8 @@
1
+ import type { VoiceProviderStatusClientOptions } from '../client/providerStatus';
2
+ export declare const createVoiceProviderStatus: <TProvider extends string = string>(path?: string, options?: VoiceProviderStatusClientOptions) => {
3
+ close: () => void;
4
+ getServerSnapshot: () => import("../client").VoiceProviderStatusSnapshot<TProvider>;
5
+ getSnapshot: () => import("../client").VoiceProviderStatusSnapshot<TProvider>;
6
+ refresh: () => Promise<import("..").VoiceProviderHealthSummary<TProvider>[]>;
7
+ subscribe: (listener: () => void) => () => void;
8
+ };
@@ -1,2 +1,3 @@
1
1
  export { createVoiceStream } from './createVoiceStream';
2
+ export { createVoiceProviderStatus } from './createVoiceProviderStatus';
2
3
  export { createVoiceController } from '../client/controller';
@@ -548,6 +548,88 @@ var createVoiceStream = (path, options = {}) => {
548
548
 
549
549
  // src/svelte/createVoiceStream.ts
550
550
  var createVoiceStream2 = (path, options = {}) => createVoiceStream(path, options);
551
+ // src/client/providerStatus.ts
552
+ var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
553
+ const fetchImpl = options.fetch ?? globalThis.fetch;
554
+ const response = await fetchImpl(path);
555
+ if (!response.ok) {
556
+ throw new Error(`Voice provider status failed: HTTP ${response.status}`);
557
+ }
558
+ return await response.json();
559
+ };
560
+ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
561
+ const listeners = new Set;
562
+ let closed = false;
563
+ let timer;
564
+ let snapshot = {
565
+ error: null,
566
+ isLoading: false,
567
+ providers: []
568
+ };
569
+ const emit = () => {
570
+ for (const listener of listeners) {
571
+ listener();
572
+ }
573
+ };
574
+ const refresh = async () => {
575
+ if (closed) {
576
+ return snapshot.providers;
577
+ }
578
+ snapshot = {
579
+ ...snapshot,
580
+ error: null,
581
+ isLoading: true
582
+ };
583
+ emit();
584
+ try {
585
+ const providers = await fetchVoiceProviderStatus(path, options);
586
+ snapshot = {
587
+ error: null,
588
+ isLoading: false,
589
+ providers,
590
+ updatedAt: Date.now()
591
+ };
592
+ emit();
593
+ return providers;
594
+ } catch (error) {
595
+ snapshot = {
596
+ ...snapshot,
597
+ error: error instanceof Error ? error.message : String(error),
598
+ isLoading: false
599
+ };
600
+ emit();
601
+ throw error;
602
+ }
603
+ };
604
+ const close = () => {
605
+ closed = true;
606
+ if (timer) {
607
+ clearInterval(timer);
608
+ timer = undefined;
609
+ }
610
+ listeners.clear();
611
+ };
612
+ if (options.intervalMs && options.intervalMs > 0) {
613
+ timer = setInterval(() => {
614
+ refresh().catch(() => {});
615
+ }, options.intervalMs);
616
+ }
617
+ return {
618
+ close,
619
+ getServerSnapshot: () => snapshot,
620
+ getSnapshot: () => snapshot,
621
+ refresh,
622
+ subscribe: (listener) => {
623
+ listeners.add(listener);
624
+ return () => {
625
+ listeners.delete(listener);
626
+ };
627
+ }
628
+ };
629
+ };
630
+
631
+ // src/svelte/createVoiceProviderStatus.ts
632
+ var createVoiceProviderStatus = (path = "/api/provider-status", options = {}) => createVoiceProviderStatusStore(path, options);
551
633
  // src/client/htmx.ts
552
634
  var DEFAULT_EVENT_NAME = "voice-refresh";
553
635
  var DEFAULT_QUERY_PARAM = "sessionId";
@@ -1173,5 +1255,6 @@ var createVoiceController = (path, options = {}) => {
1173
1255
  };
1174
1256
  export {
1175
1257
  createVoiceStream2 as createVoiceStream,
1258
+ createVoiceProviderStatus,
1176
1259
  createVoiceController
1177
1260
  };
@@ -3,6 +3,7 @@ export * from './benchmark';
3
3
  export * from './corrected';
4
4
  export * from './duplex';
5
5
  export * from './fixtures';
6
+ export * from './providerSimulator';
6
7
  export * from './resilience';
7
8
  export * from './review';
8
9
  export * from './sessionBenchmark';