@absolutejs/voice 0.0.22-beta.3 → 0.0.22-beta.31

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.
Files changed (41) hide show
  1. package/dist/angular/index.d.ts +1 -0
  2. package/dist/angular/index.js +172 -2
  3. package/dist/angular/voice-provider-status.service.d.ts +12 -0
  4. package/dist/angular/voice-stream.service.d.ts +2 -0
  5. package/dist/assistant.d.ts +20 -0
  6. package/dist/assistantHealth.d.ts +81 -0
  7. package/dist/assistantMemory.d.ts +63 -0
  8. package/dist/client/actions.d.ts +22 -0
  9. package/dist/client/connection.d.ts +3 -0
  10. package/dist/client/htmxBootstrap.js +44 -2
  11. package/dist/client/index.d.ts +2 -0
  12. package/dist/client/index.js +125 -2
  13. package/dist/client/providerStatus.d.ts +19 -0
  14. package/dist/fileStore.d.ts +5 -2
  15. package/dist/handoff.d.ts +54 -0
  16. package/dist/handoffHealth.d.ts +94 -0
  17. package/dist/index.d.ts +20 -4
  18. package/dist/index.js +2509 -21
  19. package/dist/modelAdapters.d.ts +93 -0
  20. package/dist/opsWebhook.d.ts +126 -0
  21. package/dist/providerHealth.d.ts +78 -0
  22. package/dist/queue.d.ts +52 -0
  23. package/dist/react/index.d.ts +1 -0
  24. package/dist/react/index.js +148 -2
  25. package/dist/react/useVoiceController.d.ts +2 -0
  26. package/dist/react/useVoiceProviderStatus.d.ts +8 -0
  27. package/dist/react/useVoiceStream.d.ts +2 -0
  28. package/dist/sessionReplay.d.ts +175 -0
  29. package/dist/svelte/createVoiceProviderStatus.d.ts +8 -0
  30. package/dist/svelte/index.d.ts +1 -0
  31. package/dist/svelte/index.js +127 -2
  32. package/dist/testing/index.d.ts +1 -0
  33. package/dist/testing/index.js +1310 -7
  34. package/dist/testing/providerSimulator.d.ts +44 -0
  35. package/dist/trace.d.ts +1 -1
  36. package/dist/types.d.ts +84 -2
  37. package/dist/vue/index.d.ts +1 -0
  38. package/dist/vue/index.js +161 -2
  39. package/dist/vue/useVoiceProviderStatus.d.ts +9 -0
  40. package/dist/vue/useVoiceStream.d.ts +2 -0
  41. package/package.json +1 -1
@@ -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,126 @@
1
+ import { Elysia } from 'elysia';
2
+ import type { StoredVoiceIntegrationEvent, VoiceIntegrationEventType } from './ops';
3
+ import type { VoiceIntegrationHTTPSinkOptions, VoiceIntegrationSink } from './opsSinks';
4
+ type MaybePromise<T> = T | Promise<T>;
5
+ export type VoiceOpsWebhookLinkResolver = string | ((input: {
6
+ event: StoredVoiceIntegrationEvent;
7
+ }) => MaybePromise<string | undefined>);
8
+ export type VoiceOpsWebhookEntity = {
9
+ disposition?: string;
10
+ outcome?: string;
11
+ priority?: string;
12
+ queue?: string;
13
+ reviewId?: string;
14
+ scenarioId?: string;
15
+ sessionId?: string;
16
+ status?: string;
17
+ target?: string;
18
+ taskId?: string;
19
+ };
20
+ export type VoiceOpsWebhookEnvelope = {
21
+ entity: VoiceOpsWebhookEntity;
22
+ event: {
23
+ createdAt: number;
24
+ id: string;
25
+ payload: Record<string, unknown>;
26
+ type: VoiceIntegrationEventType;
27
+ };
28
+ links?: {
29
+ event?: string;
30
+ replay?: string;
31
+ review?: string;
32
+ task?: string;
33
+ };
34
+ schemaVersion: 1;
35
+ source: 'absolutejs-voice';
36
+ };
37
+ export type VoiceOpsWebhookSinkOptions = Omit<VoiceIntegrationHTTPSinkOptions<VoiceOpsWebhookEnvelope>, 'body'> & {
38
+ baseUrl?: string;
39
+ eventHref?: VoiceOpsWebhookLinkResolver;
40
+ replayHref?: VoiceOpsWebhookLinkResolver;
41
+ reviewHref?: VoiceOpsWebhookLinkResolver;
42
+ taskHref?: VoiceOpsWebhookLinkResolver;
43
+ };
44
+ export type VoiceOpsWebhookVerificationResult = {
45
+ ok: true;
46
+ } | {
47
+ ok: false;
48
+ reason: 'invalid-signature' | 'missing-secret' | 'missing-signature' | 'missing-timestamp' | 'stale-timestamp' | 'unsupported-algorithm';
49
+ };
50
+ export type VoiceOpsWebhookReceiverRoutesOptions = {
51
+ onEnvelope?: (input: {
52
+ envelope: VoiceOpsWebhookEnvelope;
53
+ request: Request;
54
+ }) => MaybePromise<void>;
55
+ path?: string;
56
+ signingSecret?: string;
57
+ toleranceMs?: number;
58
+ };
59
+ export declare const createVoiceOpsWebhookEnvelope: (input: {
60
+ baseUrl?: string;
61
+ event: StoredVoiceIntegrationEvent;
62
+ eventHref?: VoiceOpsWebhookLinkResolver;
63
+ replayHref?: VoiceOpsWebhookLinkResolver;
64
+ reviewHref?: VoiceOpsWebhookLinkResolver;
65
+ taskHref?: VoiceOpsWebhookLinkResolver;
66
+ }) => Promise<VoiceOpsWebhookEnvelope>;
67
+ export declare const createVoiceOpsWebhookSink: (options: VoiceOpsWebhookSinkOptions) => VoiceIntegrationSink;
68
+ export declare const verifyVoiceOpsWebhookSignature: (input: {
69
+ body: string;
70
+ now?: number;
71
+ secret?: string;
72
+ signature?: string | null;
73
+ timestamp?: string | null;
74
+ toleranceMs?: number;
75
+ }) => Promise<VoiceOpsWebhookVerificationResult>;
76
+ export declare const createVoiceOpsWebhookReceiverRoutes: (options?: VoiceOpsWebhookReceiverRoutesOptions) => Elysia<"", {
77
+ decorator: {};
78
+ store: {};
79
+ derive: {};
80
+ resolve: {};
81
+ }, {
82
+ typebox: {};
83
+ error: {};
84
+ }, {
85
+ schema: {};
86
+ standaloneSchema: {};
87
+ macro: {};
88
+ macroFn: {};
89
+ parser: {};
90
+ response: {};
91
+ }, {
92
+ [x: string]: {
93
+ post: {
94
+ body: unknown;
95
+ params: {};
96
+ query: unknown;
97
+ headers: unknown;
98
+ response: {
99
+ 200: {
100
+ ok: boolean;
101
+ reason: "invalid-signature" | "missing-secret" | "missing-signature" | "missing-timestamp" | "stale-timestamp" | "unsupported-algorithm";
102
+ eventId?: undefined;
103
+ type?: undefined;
104
+ } | {
105
+ eventId: string;
106
+ ok: boolean;
107
+ type: VoiceIntegrationEventType;
108
+ reason?: undefined;
109
+ };
110
+ };
111
+ };
112
+ };
113
+ }, {
114
+ derive: {};
115
+ resolve: {};
116
+ schema: {};
117
+ standaloneSchema: {};
118
+ response: {};
119
+ }, {
120
+ derive: {};
121
+ resolve: {};
122
+ schema: {};
123
+ standaloneSchema: {};
124
+ response: {};
125
+ }>;
126
+ export {};
@@ -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
+ }>;
package/dist/queue.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { RedisClient } from 'bun';
2
2
  import type { VoiceIntegrationSink } from './opsSinks';
3
3
  import type { VoiceTraceRedactionConfig, VoiceTraceSink, VoiceTraceSinkDeliveryRecord, VoiceTraceSinkDeliveryStore, VoiceTraceSinkDeliveryQueueStatus } from './trace';
4
+ import type { StoredVoiceHandoffDelivery, VoiceHandoffAdapter, VoiceHandoffDeliveryQueueStatus, VoiceHandoffDeliveryStore, VoiceSessionHandle, VoiceSessionRecord } from './types';
4
5
  import type { VoiceOpsTaskPriority, StoredVoiceOpsTask, StoredVoiceIntegrationEvent, VoiceIntegrationDeliveryStatus, VoiceIntegrationEventStore, VoiceIntegrationWebhookConfig, VoiceOpsTaskKind, VoiceOpsTaskStatus, VoiceOpsTaskStore } from './ops';
5
6
  export type VoiceOpsTaskLease = {
6
7
  expiresAt: number;
@@ -159,6 +160,50 @@ export type VoiceTraceSinkDeliveryQueueSummary = {
159
160
  skipped: number;
160
161
  total: number;
161
162
  };
163
+ export type VoiceHandoffDeliveryWorkerOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown, TDelivery extends StoredVoiceHandoffDelivery<TContext, TSession, TResult> = StoredVoiceHandoffDelivery<TContext, TSession, TResult>> = {
164
+ adapters: VoiceHandoffAdapter<TContext, TSession, TResult>[];
165
+ api: VoiceSessionHandle<TContext, TSession, TResult>;
166
+ deadLetters?: VoiceHandoffDeliveryStore<TDelivery>;
167
+ deliveries: VoiceHandoffDeliveryStore<TDelivery>;
168
+ failMode?: 'record' | 'throw';
169
+ idempotency?: VoiceIdempotencyStore;
170
+ idempotencyTtlSeconds?: number;
171
+ leaseMs?: number;
172
+ leases: VoiceRedisTaskLeaseCoordinator;
173
+ maxFailures?: number;
174
+ onDeadLetter?: (delivery: TDelivery) => Promise<void> | void;
175
+ statuses?: VoiceHandoffDeliveryQueueStatus[];
176
+ workerId: string;
177
+ };
178
+ export type VoiceHandoffDeliveryWorkerResult = {
179
+ alreadyProcessed: number;
180
+ attempted: number;
181
+ deadLettered: number;
182
+ delivered: number;
183
+ failed: number;
184
+ skipped: number;
185
+ };
186
+ export type VoiceHandoffDeliveryWorkerLoopOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown, TDelivery extends StoredVoiceHandoffDelivery<TContext, TSession, TResult> = StoredVoiceHandoffDelivery<TContext, TSession, TResult>> = {
187
+ onError?: (error: unknown) => Promise<void> | void;
188
+ pollIntervalMs?: number;
189
+ worker: ReturnType<typeof createVoiceHandoffDeliveryWorker<TContext, TSession, TResult, TDelivery>>;
190
+ };
191
+ export type VoiceHandoffDeliveryWorkerLoop = {
192
+ isRunning: () => boolean;
193
+ start: () => void;
194
+ stop: () => void;
195
+ tick: () => Promise<VoiceHandoffDeliveryWorkerResult>;
196
+ };
197
+ export type VoiceHandoffDeliveryQueueSummary = {
198
+ byAction: Array<[StoredVoiceHandoffDelivery['action'], number]>;
199
+ deadLettered: number;
200
+ delivered: number;
201
+ failed: number;
202
+ pending: number;
203
+ retryEligible: number;
204
+ skipped: number;
205
+ total: number;
206
+ };
162
207
  export type VoiceOpsTaskWorkerOptions<TTask extends StoredVoiceOpsTask = StoredVoiceOpsTask> = {
163
208
  leaseMs?: number;
164
209
  leases: VoiceRedisTaskLeaseCoordinator;
@@ -252,6 +297,9 @@ export declare const summarizeVoiceIntegrationEvents: <TEvent extends StoredVoic
252
297
  export declare const summarizeVoiceTraceSinkDeliveries: <TDelivery extends VoiceTraceSinkDeliveryRecord = VoiceTraceSinkDeliveryRecord>(deliveries: TDelivery[], input?: {
253
298
  deadLetters?: VoiceTraceSinkDeliveryStore<TDelivery>;
254
299
  }) => Promise<VoiceTraceSinkDeliveryQueueSummary> | VoiceTraceSinkDeliveryQueueSummary;
300
+ export declare const summarizeVoiceHandoffDeliveries: <TDelivery extends StoredVoiceHandoffDelivery = StoredVoiceHandoffDelivery>(deliveries: TDelivery[], input?: {
301
+ deadLetters?: VoiceHandoffDeliveryStore<TDelivery>;
302
+ }) => Promise<VoiceHandoffDeliveryQueueSummary> | VoiceHandoffDeliveryQueueSummary;
255
303
  export declare const summarizeVoiceOpsTaskQueue: <TTask extends StoredVoiceOpsTask = StoredVoiceOpsTask>(tasks: TTask[], input?: {
256
304
  deadLetters?: VoiceOpsTaskStore<TTask>;
257
305
  }) => Promise<VoiceOpsTaskQueueSummary> | VoiceOpsTaskQueueSummary;
@@ -269,6 +317,10 @@ export declare const createVoiceTraceSinkDeliveryWorker: <TDelivery extends Voic
269
317
  drain: () => Promise<VoiceTraceSinkDeliveryWorkerResult>;
270
318
  };
271
319
  export declare const createVoiceTraceSinkDeliveryWorkerLoop: <TDelivery extends VoiceTraceSinkDeliveryRecord = VoiceTraceSinkDeliveryRecord>(options: VoiceTraceSinkDeliveryWorkerLoopOptions<TDelivery>) => VoiceTraceSinkDeliveryWorkerLoop;
320
+ export declare const createVoiceHandoffDeliveryWorker: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown, TDelivery extends StoredVoiceHandoffDelivery<TContext, TSession, TResult> = StoredVoiceHandoffDelivery<TContext, TSession, TResult>>(options: VoiceHandoffDeliveryWorkerOptions<TContext, TSession, TResult, TDelivery>) => {
321
+ drain: () => Promise<VoiceHandoffDeliveryWorkerResult>;
322
+ };
323
+ export declare const createVoiceHandoffDeliveryWorkerLoop: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown, TDelivery extends StoredVoiceHandoffDelivery<TContext, TSession, TResult> = StoredVoiceHandoffDelivery<TContext, TSession, TResult>>(options: VoiceHandoffDeliveryWorkerLoopOptions<TContext, TSession, TResult, TDelivery>) => VoiceHandoffDeliveryWorkerLoop;
272
324
  export declare const createVoiceOpsTaskWorker: <TTask extends StoredVoiceOpsTask = StoredVoiceOpsTask>(options: VoiceOpsTaskWorkerOptions<TTask>) => VoiceOpsTaskWorker<TTask>;
273
325
  export declare const createVoiceOpsTaskProcessorWorker: <TTask extends StoredVoiceOpsTask = StoredVoiceOpsTask>(options: VoiceOpsTaskProcessorWorkerOptions<TTask>) => {
274
326
  drain: () => Promise<VoiceOpsTaskProcessorWorkerResult>;
@@ -1,2 +1,3 @@
1
1
  export { useVoiceStream } from './useVoiceStream';
2
2
  export { useVoiceController } from './useVoiceController';
3
+ export { useVoiceProviderStatus } from './useVoiceProviderStatus';
@@ -120,6 +120,12 @@ var serverMessageToAction = (message) => {
120
120
  sessionId: message.sessionId,
121
121
  type: "complete"
122
122
  };
123
+ case "call_lifecycle":
124
+ return {
125
+ event: message.event,
126
+ sessionId: message.sessionId,
127
+ type: "call_lifecycle"
128
+ };
123
129
  case "error":
124
130
  return {
125
131
  message: normalizeErrorMessage(message.message),
@@ -163,7 +169,7 @@ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
163
169
  var noop = () => {};
164
170
  var noopUnsubscribe = () => noop;
165
171
  var NOOP_CONNECTION = {
166
- start: () => {},
172
+ callControl: noop,
167
173
  close: noop,
168
174
  endTurn: noop,
169
175
  getReadyState: () => WS_CLOSED,
@@ -171,6 +177,7 @@ var NOOP_CONNECTION = {
171
177
  getSessionId: () => "",
172
178
  send: noop,
173
179
  sendAudio: noop,
180
+ start: () => {},
174
181
  subscribe: noopUnsubscribe
175
182
  };
176
183
  var createSessionId = () => crypto.randomUUID();
@@ -192,6 +199,7 @@ var isVoiceServerMessage = (value) => {
192
199
  switch (value.type) {
193
200
  case "audio":
194
201
  case "assistant":
202
+ case "call_lifecycle":
195
203
  case "complete":
196
204
  case "error":
197
205
  case "final":
@@ -332,6 +340,12 @@ var createVoiceConnection = (path, options = {}) => {
332
340
  const endTurn = () => {
333
341
  send({ type: "end_turn" });
334
342
  };
343
+ const callControl = (message) => {
344
+ send({
345
+ ...message,
346
+ type: "call_control"
347
+ });
348
+ };
335
349
  const close = () => {
336
350
  clearTimers();
337
351
  if (state.ws) {
@@ -349,7 +363,7 @@ var createVoiceConnection = (path, options = {}) => {
349
363
  };
350
364
  connect();
351
365
  return {
352
- start,
366
+ callControl,
353
367
  close,
354
368
  endTurn,
355
369
  getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
@@ -357,6 +371,7 @@ var createVoiceConnection = (path, options = {}) => {
357
371
  getSessionId: () => state.sessionId,
358
372
  send,
359
373
  sendAudio,
374
+ start,
360
375
  subscribe
361
376
  };
362
377
  };
@@ -365,6 +380,7 @@ var createVoiceConnection = (path, options = {}) => {
365
380
  var createInitialState = () => ({
366
381
  assistantAudio: [],
367
382
  assistantTexts: [],
383
+ call: null,
368
384
  error: null,
369
385
  isConnected: false,
370
386
  scenarioId: null,
@@ -408,6 +424,20 @@ var createVoiceStreamStore = () => {
408
424
  status: "completed"
409
425
  };
410
426
  break;
427
+ case "call_lifecycle":
428
+ state = {
429
+ ...state,
430
+ call: {
431
+ ...state.call,
432
+ disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
433
+ endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
434
+ events: [...state.call?.events ?? [], action.event],
435
+ lastEventAt: action.event.at,
436
+ startedAt: state.call?.startedAt ?? action.event.at
437
+ },
438
+ sessionId: action.sessionId
439
+ };
440
+ break;
411
441
  case "connected":
412
442
  state = {
413
443
  ...state,
@@ -494,6 +524,9 @@ var createVoiceStream = (path, options = {}) => {
494
524
  }
495
525
  });
496
526
  return {
527
+ callControl(message) {
528
+ connection.callControl(message);
529
+ },
497
530
  close() {
498
531
  unsubscribeConnection();
499
532
  connection.close();
@@ -537,6 +570,9 @@ var createVoiceStream = (path, options = {}) => {
537
570
  get assistantAudio() {
538
571
  return store.getSnapshot().assistantAudio;
539
572
  },
573
+ get call() {
574
+ return store.getSnapshot().call;
575
+ },
540
576
  sendAudio(audio) {
541
577
  connection.sendAudio(audio);
542
578
  },
@@ -553,6 +589,7 @@ var createVoiceStream = (path, options = {}) => {
553
589
  var EMPTY_SNAPSHOT = {
554
590
  assistantAudio: [],
555
591
  assistantTexts: [],
592
+ call: null,
556
593
  error: null,
557
594
  isConnected: false,
558
595
  partial: "",
@@ -570,6 +607,7 @@ var useVoiceStream = (path, options = {}) => {
570
607
  const snapshot = useSyncExternalStore(stream.subscribe, stream.getSnapshot, stream.getServerSnapshot) ?? EMPTY_SNAPSHOT;
571
608
  return {
572
609
  ...snapshot,
610
+ callControl: (message) => stream.callControl(message),
573
611
  close: () => stream.close(),
574
612
  endTurn: () => stream.endTurn(),
575
613
  sendAudio: (audio) => stream.sendAudio(audio)
@@ -1040,6 +1078,7 @@ var resolveVoiceRuntimePreset = (name = "default") => {
1040
1078
  var createInitialState2 = (stream) => ({
1041
1079
  assistantAudio: [...stream.assistantAudio],
1042
1080
  assistantTexts: [...stream.assistantTexts],
1081
+ call: stream.call,
1043
1082
  error: stream.error,
1044
1083
  isConnected: stream.isConnected,
1045
1084
  isRecording: false,
@@ -1069,6 +1108,7 @@ var createVoiceController = (path, options = {}) => {
1069
1108
  ...state,
1070
1109
  assistantAudio: [...stream.assistantAudio],
1071
1110
  assistantTexts: [...stream.assistantTexts],
1111
+ call: stream.call,
1072
1112
  error: stream.error,
1073
1113
  isConnected: stream.isConnected,
1074
1114
  partial: stream.partial,
@@ -1146,6 +1186,7 @@ var createVoiceController = (path, options = {}) => {
1146
1186
  bindHTMX(bindingOptions) {
1147
1187
  return bindVoiceHTMX(stream, bindingOptions);
1148
1188
  },
1189
+ callControl: (message) => stream.callControl(message),
1149
1190
  close,
1150
1191
  endTurn: () => stream.endTurn(),
1151
1192
  get error() {
@@ -1198,6 +1239,9 @@ var createVoiceController = (path, options = {}) => {
1198
1239
  },
1199
1240
  get assistantAudio() {
1200
1241
  return state.assistantAudio;
1242
+ },
1243
+ get call() {
1244
+ return state.call;
1201
1245
  }
1202
1246
  };
1203
1247
  };
@@ -1206,6 +1250,7 @@ var createVoiceController = (path, options = {}) => {
1206
1250
  var EMPTY_SNAPSHOT2 = {
1207
1251
  assistantAudio: [],
1208
1252
  assistantTexts: [],
1253
+ call: null,
1209
1254
  error: null,
1210
1255
  isConnected: false,
1211
1256
  isRecording: false,
@@ -1226,6 +1271,7 @@ var useVoiceController = (path, options = {}) => {
1226
1271
  return {
1227
1272
  ...snapshot,
1228
1273
  bindHTMX: controller.bindHTMX,
1274
+ callControl: (message) => controller.callControl(message),
1229
1275
  close: () => controller.close(),
1230
1276
  endTurn: () => controller.endTurn(),
1231
1277
  sendAudio: (audio) => controller.sendAudio(audio),
@@ -1234,7 +1280,107 @@ var useVoiceController = (path, options = {}) => {
1234
1280
  toggleRecording: () => controller.toggleRecording()
1235
1281
  };
1236
1282
  };
1283
+ // src/react/useVoiceProviderStatus.tsx
1284
+ import { useEffect as useEffect3, useRef as useRef3, useSyncExternalStore as useSyncExternalStore3 } from "react";
1285
+
1286
+ // src/client/providerStatus.ts
1287
+ var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
1288
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1289
+ const response = await fetchImpl(path);
1290
+ if (!response.ok) {
1291
+ throw new Error(`Voice provider status failed: HTTP ${response.status}`);
1292
+ }
1293
+ return await response.json();
1294
+ };
1295
+ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
1296
+ const listeners = new Set;
1297
+ let closed = false;
1298
+ let timer;
1299
+ let snapshot = {
1300
+ error: null,
1301
+ isLoading: false,
1302
+ providers: []
1303
+ };
1304
+ const emit = () => {
1305
+ for (const listener of listeners) {
1306
+ listener();
1307
+ }
1308
+ };
1309
+ const refresh = async () => {
1310
+ if (closed) {
1311
+ return snapshot.providers;
1312
+ }
1313
+ snapshot = {
1314
+ ...snapshot,
1315
+ error: null,
1316
+ isLoading: true
1317
+ };
1318
+ emit();
1319
+ try {
1320
+ const providers = await fetchVoiceProviderStatus(path, options);
1321
+ snapshot = {
1322
+ error: null,
1323
+ isLoading: false,
1324
+ providers,
1325
+ updatedAt: Date.now()
1326
+ };
1327
+ emit();
1328
+ return providers;
1329
+ } catch (error) {
1330
+ snapshot = {
1331
+ ...snapshot,
1332
+ error: error instanceof Error ? error.message : String(error),
1333
+ isLoading: false
1334
+ };
1335
+ emit();
1336
+ throw error;
1337
+ }
1338
+ };
1339
+ const close = () => {
1340
+ closed = true;
1341
+ if (timer) {
1342
+ clearInterval(timer);
1343
+ timer = undefined;
1344
+ }
1345
+ listeners.clear();
1346
+ };
1347
+ if (options.intervalMs && options.intervalMs > 0) {
1348
+ timer = setInterval(() => {
1349
+ refresh().catch(() => {});
1350
+ }, options.intervalMs);
1351
+ }
1352
+ return {
1353
+ close,
1354
+ getServerSnapshot: () => snapshot,
1355
+ getSnapshot: () => snapshot,
1356
+ refresh,
1357
+ subscribe: (listener) => {
1358
+ listeners.add(listener);
1359
+ return () => {
1360
+ listeners.delete(listener);
1361
+ };
1362
+ }
1363
+ };
1364
+ };
1365
+
1366
+ // src/react/useVoiceProviderStatus.tsx
1367
+ var useVoiceProviderStatus = (path = "/api/provider-status", options = {}) => {
1368
+ const storeRef = useRef3(null);
1369
+ if (!storeRef.current) {
1370
+ storeRef.current = createVoiceProviderStatusStore(path, options);
1371
+ }
1372
+ const store = storeRef.current;
1373
+ useEffect3(() => {
1374
+ store.refresh().catch(() => {});
1375
+ return () => store.close();
1376
+ }, [store]);
1377
+ return {
1378
+ ...useSyncExternalStore3(store.subscribe, store.getSnapshot, store.getServerSnapshot),
1379
+ refresh: store.refresh
1380
+ };
1381
+ };
1237
1382
  export {
1238
1383
  useVoiceStream,
1384
+ useVoiceProviderStatus,
1239
1385
  useVoiceController
1240
1386
  };
@@ -1,12 +1,14 @@
1
1
  import type { VoiceControllerOptions } from '../types';
2
2
  export declare const useVoiceController: <TResult = unknown>(path: string, options?: VoiceControllerOptions) => {
3
3
  bindHTMX: (options: import("..").VoiceHTMXBindingOptions) => () => void;
4
+ callControl: (message: Parameters<(message: Omit<import("..").VoiceClientCallControlMessage, "type">) => void>[0]) => void;
4
5
  close: () => void;
5
6
  endTurn: () => void;
6
7
  sendAudio: (audio: Uint8Array | ArrayBuffer) => void;
7
8
  startRecording: () => Promise<void>;
8
9
  stopRecording: () => void;
9
10
  toggleRecording: () => Promise<void>;
11
+ call: import("..").VoiceCallLifecycleState | null;
10
12
  sessionId: string | null;
11
13
  scenarioId: string | null;
12
14
  status: import("..").VoiceSessionStatus | "idle";