voice-runtime 0.0.1 → 1.0.0

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,3976 @@
1
+ import WebSocket from 'ws';
2
+ import { IncomingMessage, ServerResponse } from 'node:http';
3
+ import { AddressInfo } from 'node:net';
4
+
5
+ declare const brand: unique symbol;
6
+ type Brand<T, B extends string> = T & {
7
+ readonly [brand]: B;
8
+ };
9
+
10
+ type AgentId = Brand<string, "AgentId">;
11
+ type SessionId = Brand<string, "SessionId">;
12
+ type CallId = Brand<string, "CallId">;
13
+ type TurnId = Brand<string, "TurnId">;
14
+ type ToolId = Brand<string, "ToolId">;
15
+ type ToolName = Brand<string, "ToolName">;
16
+ type ToolCallId = Brand<string, "ToolCallId">;
17
+ type MediaEventId = Brand<string, "MediaEventId">;
18
+ type ProviderEventId = Brand<string, "ProviderEventId">;
19
+ type MemoryEntryId = Brand<string, "MemoryEntryId">;
20
+ type UserId = Brand<string, "UserId">;
21
+ type OrganizationId = Brand<string, "OrganizationId">;
22
+ type WorkflowId = Brand<string, "WorkflowId">;
23
+
24
+ type Timestamp = Brand<string, "Timestamp">;
25
+ declare function toTimestamp(date: Date): Timestamp;
26
+ declare function nowTimestamp(): Timestamp;
27
+
28
+ interface Clock {
29
+ now(): Timestamp;
30
+ monotonicMs(): number;
31
+ }
32
+ declare function monotonicOffsetMs(clock: Clock, startedAtMs: number): number;
33
+ declare function createSystemClock(): Clock;
34
+
35
+ interface CounterIdGenerator<TId extends string = string> {
36
+ next(): TId;
37
+ }
38
+ interface IdGenerator {
39
+ agent(): AgentId;
40
+ session(): SessionId;
41
+ call(): CallId;
42
+ turn(): TurnId;
43
+ tool(): ToolId;
44
+ toolCall(): ToolCallId;
45
+ mediaEvent(): MediaEventId;
46
+ memoryEntry(): MemoryEntryId;
47
+ }
48
+ declare function counterIdGenerator<TId extends string = string>(prefix: string): CounterIdGenerator<TId>;
49
+ declare function createDefaultIdGenerator(): IdGenerator;
50
+
51
+ type ErrorCategory = "validation" | "auth" | "provider" | "network" | "timeout" | "rate_limit" | "cancelled" | "interrupted" | "tool" | "media" | "internal";
52
+ /**
53
+ * Canonical error names used on `NormalizedError.name` and by `tvicErrorFromJSON`
54
+ * to identify errors after JSON round-trips (the `Symbol.for('tvic.error')`
55
+ * marker on `TvicThrowableError` is lost in JSON).
56
+ *
57
+ * Frozen at module load so third parties cannot mutate the registry at
58
+ * runtime.
59
+ */
60
+ declare const TVIC_ERROR_NAMES: Readonly<{
61
+ readonly AuthError: "AuthError";
62
+ readonly RateLimitError: "RateLimitError";
63
+ readonly InvalidRequestError: "InvalidRequestError";
64
+ readonly ConnectionError: "ConnectionError";
65
+ readonly ProviderError: "ProviderError";
66
+ readonly SignatureError: "SignatureError";
67
+ readonly ValidationError: "ValidationError";
68
+ readonly TimeoutError: "TimeoutError";
69
+ readonly InternalError: "InternalError";
70
+ readonly MediaError: "MediaError";
71
+ readonly ToolError: "ToolError";
72
+ readonly CancelledError: "CancelledError";
73
+ readonly InterruptedError: "InterruptedError";
74
+ readonly UnknownError: "UnknownError";
75
+ }>;
76
+ type TvicErrorName = (typeof TVIC_ERROR_NAMES)[keyof typeof TVIC_ERROR_NAMES];
77
+ declare function isTvicErrorName(value: unknown): value is TvicErrorName;
78
+ /**
79
+ * The cross-realm marker for TVIC errors. Lives on the `TvicThrowableError`
80
+ * class instance, not on `NormalizedError`, because `JSON.stringify` strips
81
+ * symbol-keyed properties. Use `tvicErrorFromJSON()` to re-hydrate.
82
+ */
83
+ declare const TVIC_ERROR_MARKER: unique symbol;
84
+ interface NormalizedError {
85
+ readonly name: TvicErrorName;
86
+ readonly code: string;
87
+ readonly category: ErrorCategory;
88
+ readonly message: string;
89
+ readonly retriable: boolean;
90
+ readonly provider?: string;
91
+ readonly cause?: unknown;
92
+ readonly metadata?: Readonly<Record<string, unknown>>;
93
+ }
94
+ interface ErrorFactoryOptions {
95
+ readonly category?: ErrorCategory;
96
+ readonly retriable?: boolean;
97
+ readonly provider?: string;
98
+ readonly cause?: unknown;
99
+ readonly metadata?: Readonly<Record<string, unknown>>;
100
+ }
101
+ declare function isNormalizedError(value: unknown): value is NormalizedError;
102
+ declare function unknownErrorMessage(error: unknown): string;
103
+ /**
104
+ * Recognizes the pre-v2 normalized shape that did not carry `name`, then
105
+ * upgrades it to the canonical name for its category. This is deliberately
106
+ * separate from `isNormalizedError`: new callers must still be able to tell
107
+ * whether a value is already current, while compatibility boundaries can
108
+ * preserve old provider/runtime errors instead of reclassifying them.
109
+ */
110
+ declare function normalizeLegacyError(value: unknown): NormalizedError | null;
111
+ declare function normalizeUnknownError(error: unknown, options: {
112
+ readonly code: string;
113
+ readonly category?: ErrorCategory;
114
+ readonly provider?: string;
115
+ readonly retriable?: boolean;
116
+ }): NormalizedError;
117
+ declare function normalizedError(code: string, message: string, options?: ErrorFactoryOptions): NormalizedError;
118
+ declare function providerError(code: string, message: string, options?: Omit<ErrorFactoryOptions, "category">): NormalizedError;
119
+ declare function mediaError(code: string, message?: string, options?: Omit<ErrorFactoryOptions, "category">): NormalizedError;
120
+ declare function validationError(code: string, message: string, options?: Omit<ErrorFactoryOptions, "category" | "retriable">): NormalizedError;
121
+ declare function timeoutError(code: string, message: string, options?: Omit<ErrorFactoryOptions, "category">): NormalizedError;
122
+ declare function internalError(code: string, message: string, options?: Omit<ErrorFactoryOptions, "category" | "retriable">): NormalizedError;
123
+ declare function authError(code: string, message: string, options?: Omit<ErrorFactoryOptions, "category">): NormalizedError;
124
+ declare function rateLimitError(code: string, message: string, options?: Omit<ErrorFactoryOptions, "category">): NormalizedError;
125
+ declare function connectionError(code: string, message: string, options?: Omit<ErrorFactoryOptions, "category">): NormalizedError;
126
+ declare function signatureError(code: string, message: string, options?: Omit<ErrorFactoryOptions, "category" | "retriable">): NormalizedError;
127
+ declare function cancelledError(code: string, message: string, options?: Omit<ErrorFactoryOptions, "category" | "retriable">): NormalizedError;
128
+ declare function interruptedError(code: string, message: string, options?: Omit<ErrorFactoryOptions, "category" | "retriable">): NormalizedError;
129
+ declare function toolError(code: string, message: string, options?: Omit<ErrorFactoryOptions, "category">): NormalizedError;
130
+ /**
131
+ * A throwable Error that carries a `NormalizedError` and the cross-realm
132
+ * `Symbol.for('tvic.error')` marker. Use `TvicThrowableError.from(value)`
133
+ * to construct — it normalizes unknown values, preserves the original cause,
134
+ * and reuses already-thrown `TvicThrowableError` instances.
135
+ *
136
+ * Note: the marker is lost in `JSON.stringify` because symbol-keyed
137
+ * properties are dropped. Use `tvicErrorFromJSON()` to re-hydrate.
138
+ * Error-like values from another realm in the same JavaScript agent are
139
+ * recognized by their intrinsic Error tag, not only by `instanceof`.
140
+ */
141
+ declare class TvicThrowableError extends Error {
142
+ readonly [TVIC_ERROR_MARKER]: true;
143
+ readonly error: NormalizedError;
144
+ readonly name: TvicErrorName;
145
+ readonly code: string;
146
+ readonly category: ErrorCategory;
147
+ readonly retriable: boolean;
148
+ readonly provider?: string;
149
+ readonly metadata?: Readonly<Record<string, unknown>>;
150
+ readonly cause?: unknown;
151
+ constructor(error: NormalizedError);
152
+ static from(value: unknown): TvicThrowableError;
153
+ toJSON(): NormalizedError;
154
+ }
155
+ /**
156
+ * Type guard that detects a `TvicThrowableError` instance *or* any object
157
+ * that carries the cross-realm `Symbol.for('tvic.error')` marker. Works
158
+ * across Web Workers, iframes, and `vm` contexts because `Symbol.for` is
159
+ * the only symbol-sharing primitive within the same JavaScript agent. Separate
160
+ * workers require JSON serialization or another explicit transport.
161
+ *
162
+ * The marker is an *identity tag*, not a security boundary: a hostile party
163
+ * who can write into your realm can also fake the marker. The structural
164
+ * companion to this guard is `isNormalizedError`, which checks the actual
165
+ * `NormalizedError` field shape.
166
+ *
167
+ * Does NOT work across `JSON.parse` boundaries — use `tvicErrorFromJSON()`.
168
+ */
169
+ declare function isTvicError(value: unknown): value is NormalizedError | TvicThrowableError;
170
+ /**
171
+ * Re-hydrate a `NormalizedError` after a `JSON.parse` round-trip. The
172
+ * `Symbol.for('tvic.error')` marker is lost in JSON, so `isTvicError()`
173
+ * returns `false` for parsed values. This factory validates the parsed
174
+ * structural payload, including its canonical name/category pairing and
175
+ * required field types.
176
+ *
177
+ * Returns `null` if the value cannot be re-hydrated to a valid
178
+ * `NormalizedError` (unknown name, empty message, wrong field types).
179
+ * Callers should always null-check the return value.
180
+ */
181
+ declare function tvicErrorFromJSON(value: unknown): NormalizedError | null;
182
+
183
+ type DurableErrorCode = "RECORD_NOT_FOUND" | "RECORD_CONFLICT" | "LEASE_UNAVAILABLE" | "LEASE_LOST" | "BACKEND_UNAVAILABLE" | "CORRUPT_RECORD" | "INVALID_ARGUMENT" | "MEMORY_BACKEND_UNAVAILABLE" | "MEMORY_ENTRY_TOO_LARGE" | "MEMORY_SESSION_QUOTA_EXCEEDED";
184
+ declare class DurableError extends Error {
185
+ readonly code: DurableErrorCode;
186
+ readonly retriable: boolean;
187
+ constructor(code: DurableErrorCode, message: string, retriable: boolean);
188
+ }
189
+ declare class RecordNotFoundError extends DurableError {
190
+ constructor(key: string);
191
+ }
192
+ declare class RecordConflictError extends DurableError {
193
+ constructor(key: string);
194
+ }
195
+ declare class LeaseUnavailableError extends DurableError {
196
+ constructor(sessionId: string);
197
+ }
198
+ declare class LeaseLostError extends DurableError {
199
+ constructor(sessionId: string);
200
+ }
201
+ declare class BackendUnavailableError extends DurableError {
202
+ constructor(message: string);
203
+ }
204
+ declare class CorruptRecordError extends DurableError {
205
+ readonly key: string;
206
+ readonly schemaVersion: number | undefined;
207
+ constructor(key: string, message: string, schemaVersion?: number);
208
+ }
209
+ declare class InvalidArgumentError extends DurableError {
210
+ constructor(message: string);
211
+ }
212
+ declare class MemoryBackendUnavailableError extends DurableError {
213
+ constructor(message: string);
214
+ }
215
+ declare class MemoryEntryTooLargeError extends DurableError {
216
+ readonly key: string;
217
+ readonly sizeBytes: number;
218
+ readonly maxBytes: number;
219
+ constructor(key: string, sizeBytes: number, maxBytes: number);
220
+ }
221
+ declare class MemorySessionQuotaExceededError extends DurableError {
222
+ readonly sessionId: string;
223
+ readonly usedBytes: number;
224
+ readonly requestedBytes: number;
225
+ readonly maxBytes: number;
226
+ constructor(sessionId: string, usedBytes: number, requestedBytes: number, maxBytes: number);
227
+ }
228
+
229
+ declare const TELEPHONY_SAMPLE_RATE_HZ = 8000;
230
+ declare const RUNTIME_SAMPLE_RATE_HZ = 16000;
231
+ declare const PCM16_16K_MONO: {
232
+ readonly encoding: "pcm_s16le";
233
+ readonly sampleRateHz: 16000;
234
+ readonly channels: 1;
235
+ };
236
+ declare const PCM16_8K_MONO: {
237
+ readonly encoding: "pcm_s16le";
238
+ readonly sampleRateHz: 8000;
239
+ readonly channels: 1;
240
+ };
241
+ declare const PROVIDER_NAMES: {
242
+ readonly twilio: "twilio-media-streams";
243
+ readonly deepgram: "deepgram";
244
+ readonly cartesia: "cartesia";
245
+ readonly elevenlabs: "elevenlabs";
246
+ readonly elevenlabsStt: "elevenlabs-stt-realtime";
247
+ readonly sarvam: "sarvam";
248
+ readonly assemblyaiStt: "assemblyai-stt";
249
+ readonly sonioxStt: "soniox-stt";
250
+ readonly openaiResponses: "openai-responses";
251
+ readonly webClientAudio: "web-client-audio";
252
+ };
253
+ declare const PROVIDER_ERROR_CODES: {
254
+ readonly twilioMedia: "twilio.media_stream.error";
255
+ readonly deepgramStt: "deepgram.stt.error";
256
+ readonly cartesiaTts: "cartesia.tts.error";
257
+ readonly elevenlabsTts: "elevenlabs.tts.error";
258
+ readonly elevenlabsStt: "elevenlabs.stt.error";
259
+ readonly sarvamStt: "sarvam.stt.error";
260
+ readonly assemblyaiStt: "assemblyai.stt.error";
261
+ readonly sonioxStt: "soniox.stt.error";
262
+ readonly openaiResponses: "openai.responses.error";
263
+ readonly openaiHttp: "openai.http_error";
264
+ readonly openaiResponseFailed: "openai.response.failed";
265
+ readonly webClientAudio: "web_client_audio.error";
266
+ };
267
+ /** Stable runtime taxonomy consumed by reconnect and call-failure policy. */
268
+ declare const STT_ERROR_CODES: {
269
+ readonly transportWriteFailed: "stt.transport.write_failed";
270
+ readonly unexpectedEof: "stt.transport.unexpected_eof";
271
+ readonly connectFailed: "stt.transport.connect_failed";
272
+ readonly connectTimeout: "stt.transport.connect_timeout";
273
+ readonly authFailed: "stt.provider.auth_failed";
274
+ readonly invalidRequest: "stt.provider.invalid_request";
275
+ readonly inputRejected: "stt.provider.input_rejected";
276
+ readonly rateLimited: "stt.provider.rate_limited";
277
+ readonly quotaExceeded: "stt.provider.quota_exceeded";
278
+ readonly serviceUnavailable: "stt.provider.service_unavailable";
279
+ readonly providerInternal: "stt.provider.internal";
280
+ readonly protocolError: "stt.provider.protocol_error";
281
+ readonly sessionExpired: "stt.provider.session_expired";
282
+ readonly streamEnded: "stt.stream_ended";
283
+ readonly bufferOverflow: "stt.reconnect.buffer_overflow";
284
+ readonly recoveryExhausted: "stt.reconnect.recovery_exhausted";
285
+ readonly closed: "stt.reconnect.closed";
286
+ };
287
+
288
+ type MediaDirection = "input" | "output" | "internal";
289
+ type CallDirection = "inbound" | "outbound";
290
+ /**
291
+ * Channels TVIC 1.0 can actually execute. `sip` and `whatsapp` are intentionally
292
+ * absent until a working transport exists — the union never claims a capability
293
+ * the runtime cannot run.
294
+ */
295
+ type ChannelKind = "phone" | "web_audio" | "simulated";
296
+
297
+ /**
298
+ * Encodings carried on the wire by upstream providers. Adapters MUST decode or
299
+ * convert to a NormalizedAudioEncoding before emitting MediaEvent at the runtime
300
+ * boundary. These show up only inside adapter-internal transport types.
301
+ */
302
+ type TransportAudioEncoding = "pcm_s16le" | "pcm_s16be" | "pcm_f32le" | "mulaw" | "alaw" | "opus" | "g722" | "mp3" | "ogg_opus";
303
+ /**
304
+ * The only encodings allowed in MediaEvent payloads. The runtime contract is
305
+ * PCM-only at the boundary; provider-specific encodings must be decoded at the
306
+ * adapter edge.
307
+ */
308
+ type NormalizedAudioEncoding = "pcm_s16le" | "pcm_s16be" | "pcm_f32le";
309
+ type SampleRateHz = 8000 | 16000 | 22050 | 24000 | 44100 | 48000;
310
+ type ChannelLayout = 1 | 2;
311
+ interface TransportAudioFormat {
312
+ readonly encoding: TransportAudioEncoding;
313
+ readonly sampleRateHz: SampleRateHz;
314
+ readonly channels: ChannelLayout;
315
+ readonly frameDurationMs?: number;
316
+ }
317
+ interface AudioFormat {
318
+ readonly encoding: NormalizedAudioEncoding;
319
+ readonly sampleRateHz: SampleRateHz;
320
+ readonly channels: ChannelLayout;
321
+ readonly frameDurationMs?: number;
322
+ }
323
+ interface AudioPayload {
324
+ readonly format: AudioFormat;
325
+ readonly durationMs: number;
326
+ readonly frameCount: number;
327
+ readonly bytes: Uint8Array;
328
+ readonly checksum?: string;
329
+ }
330
+ declare function isSampleRateHz(value: unknown): value is SampleRateHz;
331
+ declare function isReconstructableAudioFormat(format: unknown): format is AudioFormat;
332
+
333
+ type BackoffStrategy = "fixed" | "linear" | "exponential";
334
+ interface RetryPolicy {
335
+ readonly maxAttempts: number;
336
+ readonly initialDelayMs: number;
337
+ readonly maxDelayMs: number;
338
+ readonly backoff: BackoffStrategy;
339
+ readonly jitter: boolean;
340
+ readonly retryableErrorCodes?: readonly string[];
341
+ }
342
+ type TimeoutAction = "fail" | "interrupt";
343
+ interface TimeoutPolicy {
344
+ readonly timeoutMs: number;
345
+ readonly onTimeout: TimeoutAction;
346
+ }
347
+ type InterruptionMode = "allow" | "ignore" | "graceful";
348
+ interface InterruptionPolicy {
349
+ readonly mode: InterruptionMode;
350
+ readonly minSpeechMs: number;
351
+ readonly trimOutputOnInterrupt: boolean;
352
+ }
353
+ interface IdempotencyPolicy {
354
+ readonly enabled: boolean;
355
+ readonly keyTemplate?: string;
356
+ readonly ttlMs?: number;
357
+ }
358
+
359
+ type MediaEventType = "media.stream.started" | "media.stream.ended" | "media.audio.chunk" | "media.audio.committed" | "media.turn.commit_requested" | "media.interrupt.requested" | "dtmf.received" | "media.error";
360
+ /**
361
+ * Coarse-grained category of a media event. Use `kind` (a string field on
362
+ * every `MediaEvent`) to filter or dispatch without depending on the
363
+ * finer-grained `type` literal.
364
+ *
365
+ * - `"media"` — audio data frames, audio delivery confirmations
366
+ * - `"signal"` — control signals (commit, interrupt, DTMF)
367
+ * - `"lifecycle"` — stream start/end, errors
368
+ */
369
+ type MediaEventKind = "media" | "signal" | "lifecycle";
370
+ /**
371
+ * Compile-time map of every `MediaEventType` to its `MediaEventKind`. Using
372
+ * a `Record<MediaEventType, MediaEventKind>` (not a `Map`) means adding a
373
+ * new event type to the union forces a compile error here until the
374
+ * entry is added. The map is the single source of truth for "what kind
375
+ * is this event?"
376
+ */
377
+ declare const EVENT_KIND_MAP: {
378
+ readonly "media.stream.started": "lifecycle";
379
+ readonly "media.stream.ended": "lifecycle";
380
+ readonly "media.audio.chunk": "media";
381
+ readonly "media.audio.committed": "media";
382
+ readonly "media.turn.commit_requested": "signal";
383
+ readonly "media.interrupt.requested": "signal";
384
+ readonly "dtmf.received": "signal";
385
+ readonly "media.error": "lifecycle";
386
+ };
387
+ declare function kindForMediaEvent(type: MediaEventType): MediaEventKind;
388
+ declare const DTMF_DIGITS: readonly ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "#", "A", "B", "C", "D"];
389
+ type DtmfDigit = (typeof DTMF_DIGITS)[number];
390
+ declare function isDtmfDigit(value: string | undefined): value is DtmfDigit;
391
+ interface MediaEventBase<T extends MediaEventType, D extends MediaDirection> {
392
+ readonly id: MediaEventId;
393
+ readonly type: T;
394
+ /**
395
+ * Coarse-grained category derived from `type`. Every media event must carry
396
+ * the category that corresponds to its event type; use `createMediaEvent`
397
+ * when constructing an event so the value cannot drift from the map below.
398
+ */
399
+ readonly kind: MediaEventKind;
400
+ readonly sessionId: SessionId;
401
+ readonly callId?: CallId;
402
+ readonly turnId?: TurnId;
403
+ readonly sequence: number;
404
+ readonly direction: D;
405
+ readonly timestamp: Timestamp;
406
+ /**
407
+ * Source-clock position of the audio. Input events carry the transport's own
408
+ * timeline (e.g. Twilio's media timestamp, the browser client's capture
409
+ * clock). Output chunks are constructed with `0` by synthesis adapters and
410
+ * restamped by `PipelineVoiceLoop` with its monotonic send clock before
411
+ * delivery; delivery proof travels via commit marks, not this field.
412
+ */
413
+ readonly monotonicOffsetMs: number;
414
+ readonly provider?: string;
415
+ readonly metadata?: Readonly<Record<string, unknown>>;
416
+ }
417
+ type StreamEndReason = "completed" | "cancelled" | "remote_hangup" | "timeout" | "error";
418
+ interface MediaStreamStartedEvent<D extends MediaDirection = MediaDirection> extends MediaEventBase<"media.stream.started", D> {
419
+ readonly format: AudioFormat;
420
+ }
421
+ interface MediaStreamEndedEvent<D extends MediaDirection = MediaDirection> extends MediaEventBase<"media.stream.ended", D> {
422
+ readonly reason: StreamEndReason;
423
+ readonly durationMs: number;
424
+ }
425
+ interface MediaAudioChunkEvent<D extends MediaDirection = MediaDirection> extends MediaEventBase<"media.audio.chunk", D> {
426
+ readonly audio: AudioPayload;
427
+ }
428
+ interface MediaAudioCommittedEvent extends MediaEventBase<"media.audio.committed", "output"> {
429
+ readonly durationMs: number;
430
+ readonly frameCount: number;
431
+ readonly sequenceRange: readonly [number, number];
432
+ readonly chunkIds: readonly MediaEventId[];
433
+ }
434
+ interface DtmfReceivedEvent extends MediaEventBase<"dtmf.received", "input"> {
435
+ readonly digit: DtmfDigit;
436
+ readonly durationMs?: number;
437
+ }
438
+ interface TurnCommitRequestedEvent extends MediaEventBase<"media.turn.commit_requested", "input"> {
439
+ }
440
+ interface InterruptRequestedEvent extends MediaEventBase<"media.interrupt.requested", "input"> {
441
+ }
442
+ interface MediaErrorEvent<D extends MediaDirection = MediaDirection> extends MediaEventBase<"media.error", D> {
443
+ readonly error: NormalizedError;
444
+ }
445
+ type InputMediaEvent = MediaStreamStartedEvent<"input"> | MediaStreamEndedEvent<"input"> | MediaAudioChunkEvent<"input"> | TurnCommitRequestedEvent | InterruptRequestedEvent | DtmfReceivedEvent | MediaErrorEvent<"input">;
446
+ type OutputMediaEvent = MediaStreamStartedEvent<"output"> | MediaStreamEndedEvent<"output"> | MediaAudioChunkEvent<"output"> | MediaAudioCommittedEvent | MediaErrorEvent<"output">;
447
+ type InternalMediaEvent = MediaStreamStartedEvent<"internal"> | MediaStreamEndedEvent<"internal"> | MediaErrorEvent<"internal">;
448
+ type MediaEvent = InputMediaEvent | OutputMediaEvent | InternalMediaEvent;
449
+ /**
450
+ * Initialization shape for any media event. The union preserves each event's
451
+ * type-specific fields while leaving out the derived `kind` field.
452
+ */
453
+ type WithoutMediaEventKind<T> = T extends MediaEvent ? Omit<T, "kind"> : never;
454
+ type AnyMediaEventInit = WithoutMediaEventKind<MediaEvent>;
455
+ /**
456
+ * Adds the required coarse-grained category to a media event. The overload
457
+ * keeps the correlated event type available to callers; the implementation
458
+ * itself returns the complete `MediaEvent` union and contains no assertion.
459
+ */
460
+ declare function createMediaEvent<T extends AnyMediaEventInit>(init: T): Extract<MediaEvent, {
461
+ readonly type: T["type"];
462
+ readonly direction: T["direction"];
463
+ }>;
464
+ type InputAudioChunk = MediaAudioChunkEvent<"input">;
465
+ type OutputAudioChunk = MediaAudioChunkEvent<"output">;
466
+
467
+ type JsonSchemaDocument = Readonly<Record<string, unknown>>;
468
+ /**
469
+ * Optional tenant identity propagated into every tool execution. The runtime
470
+ * populates this from the session attachment's `memoryUserId` /
471
+ * `organizationId` / `workflowId`. The tool author reads `tenant.userId`
472
+ * inside `execute` to enforce their own auth — TVIC ships no RBAC layer
473
+ * (that is a tenant concern: the customer has an IdP).
474
+ */
475
+ interface ToolTenant {
476
+ readonly userId?: UserId;
477
+ readonly organizationId?: OrganizationId;
478
+ readonly workflowId?: WorkflowId;
479
+ /** Tenant-supplied scopes (e.g., "crm.read", "billing.write"). */
480
+ readonly scopes?: readonly string[];
481
+ }
482
+ interface ToolExecutionContext {
483
+ readonly sessionId: SessionId;
484
+ readonly turnId: TurnId;
485
+ readonly toolCallId: ToolCallId;
486
+ readonly attempt: number;
487
+ readonly signal: AbortSignal;
488
+ readonly logger: ToolLogger;
489
+ readonly tenant?: ToolTenant;
490
+ }
491
+ interface ToolLogger {
492
+ debug(message: string, fields?: Readonly<Record<string, unknown>>): void;
493
+ info(message: string, fields?: Readonly<Record<string, unknown>>): void;
494
+ warn(message: string, fields?: Readonly<Record<string, unknown>>): void;
495
+ error(message: string, fields?: Readonly<Record<string, unknown>>): void;
496
+ }
497
+ type ToolExecutor<TInput, TOutput> = (input: TInput, ctx: ToolExecutionContext) => Promise<TOutput>;
498
+ interface ToolDefinition<TInput = unknown, TOutput = unknown> {
499
+ readonly id: ToolId;
500
+ readonly name: ToolName;
501
+ readonly description: string;
502
+ readonly version: string;
503
+ readonly inputSchema: JsonSchemaDocument;
504
+ readonly outputSchema: JsonSchemaDocument;
505
+ readonly timeout: TimeoutPolicy;
506
+ readonly retry: RetryPolicy;
507
+ readonly idempotency: IdempotencyPolicy;
508
+ /**
509
+ * @deprecated Use `ctx.tenant` (the `ToolTenant` field on
510
+ * `ToolExecutionContext`). The `authScope` field is kept as a no-op
511
+ * typedef for one release; the runtime never enforced it. Migration:
512
+ * read `ctx.tenant?.scopes` inside `execute` and enforce your own
513
+ * auth.
514
+ */
515
+ readonly authScope?: readonly string[];
516
+ readonly tags?: readonly string[];
517
+ readonly metadata?: Readonly<Record<string, unknown>>;
518
+ readonly execute: ToolExecutor<TInput, TOutput>;
519
+ }
520
+ type ToolCallStatus = "queued" | "running" | "succeeded" | "failed" | "timed_out" | "cancelled";
521
+ type ToolIdempotencyStatus = "claimed" | "succeeded" | "failed" | "timed_out" | "cancelled";
522
+ interface ToolIdempotencyRecord {
523
+ readonly key: string;
524
+ readonly sessionId?: SessionId;
525
+ readonly toolId?: ToolId;
526
+ readonly toolVersion?: string;
527
+ readonly requestHash: string;
528
+ readonly status: ToolIdempotencyStatus;
529
+ readonly owner?: string;
530
+ readonly claimedFence?: number;
531
+ readonly expiresAtMs: number;
532
+ readonly output?: unknown;
533
+ readonly error?: NormalizedError;
534
+ }
535
+ /** Ownership context used to fence durable idempotency claims to a session. */
536
+ interface ToolIdempotencyLease {
537
+ readonly sessionId: SessionId;
538
+ readonly holder: string;
539
+ readonly fence: number;
540
+ }
541
+ interface ToolIdempotencyClaim {
542
+ readonly key: string;
543
+ readonly lease?: ToolIdempotencyLease;
544
+ readonly toolId?: ToolId;
545
+ readonly toolVersion?: string;
546
+ readonly requestHash: string;
547
+ readonly owner: string;
548
+ readonly ttlMs: number;
549
+ }
550
+ type ToolIdempotencyClaimResult = {
551
+ readonly status: "claimed";
552
+ readonly record: ToolIdempotencyRecord;
553
+ } | {
554
+ readonly status: "succeeded";
555
+ readonly record: ToolIdempotencyRecord;
556
+ } | {
557
+ readonly status: "in_progress";
558
+ readonly record: ToolIdempotencyRecord;
559
+ } | {
560
+ readonly status: "conflict";
561
+ readonly record: ToolIdempotencyRecord;
562
+ };
563
+ interface ToolIdempotencyOutcome {
564
+ readonly status: Exclude<ToolIdempotencyStatus, "claimed">;
565
+ readonly ttlMs: number;
566
+ readonly owner: string;
567
+ readonly lease?: ToolIdempotencyLease;
568
+ readonly output?: unknown;
569
+ readonly error?: NormalizedError;
570
+ }
571
+ interface ToolIdempotencyStore {
572
+ lookup(key: string, requestHash: string): Promise<ToolIdempotencyRecord | null>;
573
+ claim(input: ToolIdempotencyClaim): Promise<ToolIdempotencyClaimResult>;
574
+ complete(key: string, requestHash: string, outcome: ToolIdempotencyOutcome): Promise<void>;
575
+ }
576
+ interface ToolCallBase {
577
+ readonly toolCallId: ToolCallId;
578
+ readonly toolId: ToolId;
579
+ readonly toolName: ToolName;
580
+ readonly sessionId: SessionId;
581
+ readonly turnId: TurnId;
582
+ readonly input: unknown;
583
+ readonly attempts: number;
584
+ readonly idempotencyKey?: string;
585
+ readonly metadata?: Readonly<Record<string, unknown>>;
586
+ }
587
+ interface QueuedToolCall extends ToolCallBase {
588
+ readonly status: "queued";
589
+ readonly queuedAt: Timestamp;
590
+ }
591
+ interface RunningToolCall extends ToolCallBase {
592
+ readonly status: "running";
593
+ readonly queuedAt: Timestamp;
594
+ readonly startedAt: Timestamp;
595
+ }
596
+ interface SucceededToolCall extends ToolCallBase {
597
+ readonly status: "succeeded";
598
+ readonly queuedAt: Timestamp;
599
+ readonly startedAt: Timestamp;
600
+ readonly endedAt: Timestamp;
601
+ readonly output: unknown;
602
+ }
603
+ type FailedToolCallStatus = "failed" | "timed_out" | "cancelled";
604
+ interface FailedToolCall extends ToolCallBase {
605
+ readonly status: FailedToolCallStatus;
606
+ readonly queuedAt: Timestamp;
607
+ readonly startedAt: Timestamp;
608
+ readonly endedAt: Timestamp;
609
+ readonly error: NormalizedError;
610
+ }
611
+ type ToolCall = QueuedToolCall | RunningToolCall | SucceededToolCall | FailedToolCall;
612
+ type TerminalToolCall = SucceededToolCall | FailedToolCall;
613
+
614
+ type MemoryScope = "session" | "user" | "organization" | "workflow";
615
+ type MemoryRef = {
616
+ readonly scope: "session";
617
+ readonly sessionId: SessionId;
618
+ } | {
619
+ readonly scope: "user";
620
+ readonly userId: UserId;
621
+ } | {
622
+ readonly scope: "organization";
623
+ readonly organizationId: OrganizationId;
624
+ } | {
625
+ readonly scope: "workflow";
626
+ readonly workflowId: WorkflowId;
627
+ };
628
+ type MemoryKind = "fact" | "summary" | "open_item" | "entity_ref" | "raw" | "working_memory";
629
+ interface MemoryEntry<T = unknown> {
630
+ readonly id: MemoryEntryId;
631
+ readonly ref: MemoryRef;
632
+ readonly key: string;
633
+ readonly kind: MemoryKind;
634
+ readonly value: T;
635
+ readonly version: number;
636
+ readonly createdAt: Timestamp;
637
+ readonly updatedAt: Timestamp;
638
+ readonly expiresAtMs?: number;
639
+ readonly tags?: readonly string[];
640
+ readonly metadata?: Readonly<Record<string, unknown>>;
641
+ }
642
+ /**
643
+ * Values in the provider-neutral memory contract must be finite, acyclic JSON
644
+ * data. Adapters validate this at runtime because TypeScript cannot protect
645
+ * JavaScript callers and JSONB-backed adapters cannot preserve richer objects.
646
+ */
647
+ type JsonValue = null | string | boolean | number | readonly JsonValue[] | {
648
+ readonly [key: string]: JsonValue;
649
+ };
650
+ interface MemoryPutOptions {
651
+ readonly tags?: readonly string[];
652
+ readonly metadata?: Readonly<Record<string, unknown>>;
653
+ readonly ttlMs?: number;
654
+ /**
655
+ * Trusted runtime attribution for a session-scoped entry. Adapters persist
656
+ * this separately from caller metadata so `deleteForUser` can erase a
657
+ * user's session data without treating arbitrary metadata as ownership.
658
+ * This is not an authorization mechanism; callers must already be trusted.
659
+ */
660
+ readonly sessionUserId?: UserId;
661
+ /**
662
+ * Runtime-only aggregate quota for a session-scoped write. Adapters that
663
+ * advertise `write.sessionQuota` must enforce this atomically, including
664
+ * entries written before the current process started. The byte count is the
665
+ * adapter's canonical UTF-8 JSON representation; callers should not assume
666
+ * identical byte counts across adapters with different storage encodings.
667
+ */
668
+ readonly maxSessionBytes?: number;
669
+ /**
670
+ * If true, the put is a no-op when an entry with the same (ref, key, kind)
671
+ * already exists; the existing entry is returned unchanged. If false and the
672
+ * entry exists with a different value, the call throws `RecordConflictError`.
673
+ * Default: false.
674
+ */
675
+ readonly ifNotExists?: boolean;
676
+ }
677
+ interface MemoryQuery {
678
+ readonly key?: string;
679
+ readonly prefix?: string;
680
+ readonly kind?: MemoryKind;
681
+ readonly tags?: readonly string[];
682
+ readonly limit?: number;
683
+ /**
684
+ * v0.1 continuation token. Adapters must interpret this as a non-negative
685
+ * decimal offset; opaque provider-specific cursors are not portable yet.
686
+ */
687
+ readonly cursor?: string;
688
+ }
689
+ interface MemoryCapabilities {
690
+ readonly search: {
691
+ readonly exact: boolean;
692
+ readonly vector: boolean;
693
+ readonly hybrid: boolean;
694
+ };
695
+ readonly write: {
696
+ readonly explicit: boolean;
697
+ readonly implicit: boolean;
698
+ /** Whether `put(..., { maxSessionBytes })` is atomically enforced. */
699
+ readonly sessionQuota?: boolean;
700
+ };
701
+ readonly retention: {
702
+ readonly ttl: boolean;
703
+ readonly policy: boolean;
704
+ };
705
+ readonly purge: {
706
+ readonly perEntry: boolean;
707
+ readonly perScope: boolean;
708
+ readonly tenant: boolean;
709
+ };
710
+ readonly maxEntryBytes?: number;
711
+ }
712
+ interface Memory {
713
+ /** Implementation name (e.g., "in-memory", "postgres", "mem0"). */
714
+ readonly name: string;
715
+ /** Adapter version (semver-style string). */
716
+ readonly version: string;
717
+ /** Capability declaration used by the runtime to fail fast on mismatch. */
718
+ readonly capabilities: MemoryCapabilities;
719
+ get<T = unknown>(ref: MemoryRef, key: string, kind?: MemoryKind): Promise<MemoryEntry<T> | null>;
720
+ put<T = unknown>(ref: MemoryRef, key: string, kind: MemoryKind, value: T, options?: MemoryPutOptions): Promise<MemoryEntry<T>>;
721
+ list<T = unknown>(ref: MemoryRef, query?: MemoryQuery): Promise<readonly MemoryEntry<T>[]>;
722
+ delete(ref: MemoryRef, key: string, kind?: MemoryKind): Promise<boolean>;
723
+ /** Delete every entry under the given `MemoryRef`. Returns the count deleted. */
724
+ deleteAll(ref: MemoryRef): Promise<number>;
725
+ /**
726
+ * Delete user-owned data only: the user's `user` scope and session entries
727
+ * explicitly attributed to that user by a trusted runtime write. Shared
728
+ * organization/workflow scopes are never part of this cascade. This is a
729
+ * destructive, privileged erasure primitive; the adapter does not authorize
730
+ * the caller or prove that the supplied user id belongs to the requester.
731
+ */
732
+ deleteForUser(userId: UserId): Promise<number>;
733
+ }
734
+
735
+ type CallStatus = "created" | "ringing" | "connected" | "active" | "held" | "ended" | "failed";
736
+ /** Transports TVIC 1.0 can execute. SIP/WebRTC return alongside native topologies. */
737
+ type MediaTransportKind = "websocket";
738
+ interface MediaTransport {
739
+ readonly kind: MediaTransportKind;
740
+ readonly format: AudioFormat;
741
+ readonly remoteEndpoint?: string;
742
+ readonly metadata?: Readonly<Record<string, unknown>>;
743
+ }
744
+ interface CallBase {
745
+ readonly id: CallId;
746
+ readonly provider: string;
747
+ readonly direction: CallDirection;
748
+ readonly from: string;
749
+ readonly to: string;
750
+ readonly sessionId?: SessionId;
751
+ readonly mediaTransport: MediaTransport;
752
+ readonly metadata?: Readonly<Record<string, unknown>>;
753
+ readonly createdAt: Timestamp;
754
+ }
755
+ interface CreatedCall extends CallBase {
756
+ readonly status: "created";
757
+ }
758
+ interface RingingCall extends CallBase {
759
+ readonly status: "ringing";
760
+ }
761
+ interface ConnectedCall extends CallBase {
762
+ readonly status: "connected" | "active" | "held";
763
+ readonly startedAt: Timestamp;
764
+ }
765
+ interface EndedCall extends CallBase {
766
+ readonly status: "ended";
767
+ readonly startedAt: Timestamp;
768
+ readonly endedAt: Timestamp;
769
+ }
770
+ interface FailedCall extends CallBase {
771
+ readonly status: "failed";
772
+ readonly endedAt: Timestamp;
773
+ readonly error: NormalizedError;
774
+ readonly startedAt?: Timestamp;
775
+ }
776
+ type Call = CreatedCall | RingingCall | ConnectedCall | EndedCall | FailedCall;
777
+
778
+ type SessionCancellationReason = "caller_hangup" | "transport_lost" | "recovery_expired" | "operator_requested" | "shutdown";
779
+ type SessionStatus = "created" | "starting" | "active" | "interrupted" | "waiting_for_tool" | "ending" | "completed" | "failed" | "cancelled";
780
+ interface SessionState {
781
+ readonly variables: Readonly<Record<string, unknown>>;
782
+ readonly currentTurnId?: TurnId;
783
+ readonly pendingToolCallIds: readonly ToolCallId[];
784
+ readonly turnSequence: number;
785
+ }
786
+ interface SessionBase {
787
+ readonly id: SessionId;
788
+ readonly agentId: AgentId;
789
+ readonly channel: ChannelKind;
790
+ readonly callId?: CallId;
791
+ /**
792
+ * Reserved for a future durable reference index. The runtime currently
793
+ * leaves this empty; memory adapters and session-end snapshots are the
794
+ * authoritative memory surfaces.
795
+ */
796
+ readonly memoryRefs: readonly MemoryEntryId[];
797
+ readonly metadata?: Readonly<Record<string, unknown>>;
798
+ readonly createdAt: Timestamp;
799
+ readonly state: SessionState;
800
+ }
801
+ interface CreatedSession extends SessionBase {
802
+ readonly status: "created";
803
+ }
804
+ interface StartingSession extends SessionBase {
805
+ readonly status: "starting";
806
+ }
807
+ interface ActiveSession extends SessionBase {
808
+ readonly status: "active" | "interrupted" | "waiting_for_tool" | "ending";
809
+ readonly startedAt: Timestamp;
810
+ }
811
+ type TerminalSessionStatus = "completed" | "failed" | "cancelled";
812
+ interface TerminalSessionBase extends SessionBase {
813
+ readonly startedAt: Timestamp;
814
+ readonly endedAt: Timestamp;
815
+ }
816
+ interface CompletedSession extends TerminalSessionBase {
817
+ readonly status: "completed";
818
+ }
819
+ interface FailedSession extends TerminalSessionBase {
820
+ readonly status: "failed";
821
+ readonly error: NormalizedError;
822
+ }
823
+ interface CancelledSession extends TerminalSessionBase {
824
+ readonly status: "cancelled";
825
+ readonly cancelReason: SessionCancellationReason;
826
+ }
827
+ type TerminalSession = CompletedSession | FailedSession | CancelledSession;
828
+ type Session = CreatedSession | StartingSession | ActiveSession | TerminalSession;
829
+
830
+ type TurnCancellationReason = "barge_in" | "dtmf" | "explicit" | "timeout" | "not_heard" | "transport_lost" | "lease_lost" | "runtime_restarted";
831
+ type TurnStatus = "started" | "listening" | "thinking" | "calling_tool" | "speaking" | "interrupted" | "completed" | "cancelled" | "failed";
832
+ interface TurnInput {
833
+ readonly transcript?: string;
834
+ readonly mediaEventIds: readonly MediaEventId[];
835
+ readonly metadata?: Readonly<Record<string, unknown>>;
836
+ }
837
+ interface TurnOutput {
838
+ readonly text?: string;
839
+ readonly mediaEventIds: readonly MediaEventId[];
840
+ readonly metadata?: Readonly<Record<string, unknown>>;
841
+ }
842
+ /**
843
+ * Turn timing on the session's monotonic clock.
844
+ *
845
+ * Every stage measurement is anchored at the endpoint commit, the moment the runtime
846
+ * decided the participant had finished speaking, rather than at internal turn setup.
847
+ * That anchor is what a caller actually experiences as the start of waiting.
848
+ *
849
+ * `endpointMs` is measured before that anchor and is therefore additive: perceived
850
+ * caller-to-audio delay is `endpointMs + firstAudioMs`. It is usually the largest
851
+ * single contributor, so a report that omits it understates the wait.
852
+ */
853
+ interface TurnLatency {
854
+ /** Participant speech start to endpoint commit. Requires a speech-start signal. */
855
+ readonly listenedMs?: number;
856
+ /** Last immutable final segment to endpoint commit, the endpoint wait itself. */
857
+ readonly endpointMs?: number;
858
+ /** Endpoint commit to the first model token. */
859
+ readonly firstTokenMs?: number;
860
+ /** Endpoint commit to the first audio frame accepted by the transport. */
861
+ readonly firstAudioMs?: number;
862
+ /** Total time spent in tool execution during the turn. */
863
+ readonly toolMs?: number;
864
+ /** Interruption decision to queued output being stopped. */
865
+ readonly interruptionTailMs?: number;
866
+ /** Endpoint commit to terminal turn state. */
867
+ readonly totalMs?: number;
868
+ /** Time without an owner during runtime reattachment; excluded from stage timings. */
869
+ readonly recoveryGapMs?: number;
870
+ }
871
+ interface TurnBase {
872
+ readonly id: TurnId;
873
+ readonly sessionId: SessionId;
874
+ readonly sequence: number;
875
+ readonly input: TurnInput;
876
+ readonly output: TurnOutput;
877
+ readonly toolCallIds: readonly ToolCallId[];
878
+ readonly startedAt: Timestamp;
879
+ readonly latency: TurnLatency;
880
+ readonly metadata?: Readonly<Record<string, unknown>>;
881
+ }
882
+ interface ActiveTurn extends TurnBase {
883
+ readonly status: "started" | "listening" | "thinking" | "calling_tool" | "speaking" | "interrupted";
884
+ }
885
+ interface CompletedTurn extends TurnBase {
886
+ readonly status: "completed";
887
+ readonly endedAt: Timestamp;
888
+ }
889
+ interface CancelledTurn extends TurnBase {
890
+ readonly status: "cancelled";
891
+ readonly endedAt: Timestamp;
892
+ readonly reason: TurnCancellationReason;
893
+ }
894
+ interface FailedTurn extends TurnBase {
895
+ readonly status: "failed";
896
+ readonly endedAt: Timestamp;
897
+ readonly error: NormalizedError;
898
+ }
899
+ type TerminalTurn = CompletedTurn | CancelledTurn | FailedTurn;
900
+ type Turn = ActiveTurn | TerminalTurn;
901
+
902
+ type ProviderKind = "telephony" | "stt" | "tts" | "llm" | "storage";
903
+ /** Transports TVIC 1.0 executes. gRPC/http2 arrive with the SDK-based STT wave. */
904
+ type ProviderTransport = "http" | "sse" | "websocket";
905
+ type TurnDetectionMode = "provider" | "stt_endpointing" | "vad" | "semantic" | "manual";
906
+ type ProviderDataPolicy = "standard" | "no_training" | "zero_retention";
907
+ type CallControlCapability = "inbound" | "outbound" | "reject" | "progress" | "dtmf_receive" | "dtmf_send" | "transfer_blind" | "transfer_consult" | "early_media" | "conference";
908
+ interface ProviderStreamingCapabilities {
909
+ /** Accepts incremental input instead of requiring a complete request payload. */
910
+ readonly input: boolean;
911
+ /** Produces incremental output instead of one complete response payload. */
912
+ readonly output: boolean;
913
+ /** Streaming is supported by the provider protocol, not emulated by chunked requests. */
914
+ readonly native: boolean;
915
+ }
916
+ interface ProviderCancellationCapabilities {
917
+ /** Active provider work can be stopped rather than merely ignored by TVIC. */
918
+ readonly request: boolean;
919
+ /** In-progress generated output can be stopped. */
920
+ readonly output: boolean;
921
+ /** Already queued transport audio can be removed before playout. */
922
+ readonly buffer: boolean;
923
+ /** Provider conversation state can be truncated to the audio actually played. */
924
+ readonly truncation: boolean;
925
+ }
926
+ interface ProviderAudioCapabilities {
927
+ /** Formats accepted at the TVIC adapter boundary after any wire decoding. */
928
+ readonly input?: readonly AudioFormat[];
929
+ /** Formats emitted at the TVIC adapter boundary before any wire encoding. */
930
+ readonly output?: readonly AudioFormat[];
931
+ }
932
+ interface ProviderToolCapabilities {
933
+ readonly functionCalling: boolean;
934
+ readonly parallelCalls: boolean;
935
+ }
936
+ interface ProviderPlayoutCapabilities {
937
+ /** The transport can remove queued output that has not played. */
938
+ readonly clearBuffer: boolean;
939
+ /** The transport acknowledges a marker after preceding audio is consumed. */
940
+ readonly acknowledgement: boolean;
941
+ /** The transport reports an exact played position, not only a marker boundary. */
942
+ readonly position: boolean;
943
+ }
944
+ /**
945
+ * Capabilities describe one configured adapter/deployment, not every feature sold by
946
+ * the provider. Mutable catalog facts may be omitted until they are verified.
947
+ */
948
+ interface ProviderCapabilities {
949
+ readonly streaming: ProviderStreamingCapabilities;
950
+ readonly cancellation: ProviderCancellationCapabilities;
951
+ readonly transports: readonly ProviderTransport[];
952
+ readonly audio?: ProviderAudioCapabilities;
953
+ readonly languages?: readonly string[];
954
+ readonly models?: readonly string[];
955
+ readonly voices?: readonly string[];
956
+ readonly turnDetection?: readonly TurnDetectionMode[];
957
+ readonly tools?: ProviderToolCapabilities;
958
+ readonly playout?: ProviderPlayoutCapabilities;
959
+ readonly callControl?: readonly CallControlCapability[];
960
+ readonly regions?: readonly string[];
961
+ readonly dataPolicies?: readonly ProviderDataPolicy[];
962
+ readonly metadata?: Readonly<Record<string, unknown>>;
963
+ }
964
+ interface Provider {
965
+ readonly name: string;
966
+ readonly kind: ProviderKind;
967
+ readonly version: string;
968
+ readonly capabilities: ProviderCapabilities;
969
+ readonly region?: string;
970
+ readonly metadata?: Readonly<Record<string, unknown>>;
971
+ }
972
+ /**
973
+ * A requirement is opt-in: a present flag can only mean "must be supported".
974
+ * Callers omit capabilities they do not require instead of passing a misleading
975
+ * `false` that could be read as "must not support".
976
+ */
977
+ type RequiredProviderCapabilities<T extends object> = {
978
+ readonly [K in keyof T]?: true;
979
+ };
980
+ interface ProviderRequirements {
981
+ readonly kind: ProviderKind;
982
+ readonly streaming?: RequiredProviderCapabilities<ProviderStreamingCapabilities>;
983
+ readonly cancellation?: RequiredProviderCapabilities<ProviderCancellationCapabilities>;
984
+ readonly transport?: ProviderTransport;
985
+ readonly inputFormat?: AudioFormat;
986
+ readonly outputFormat?: AudioFormat;
987
+ readonly language?: string;
988
+ readonly model?: string;
989
+ readonly voice?: string;
990
+ readonly turnDetection?: TurnDetectionMode;
991
+ readonly functionCalling?: boolean;
992
+ readonly parallelToolCalls?: boolean;
993
+ readonly playout?: RequiredProviderCapabilities<ProviderPlayoutCapabilities>;
994
+ readonly callControl?: readonly CallControlCapability[];
995
+ readonly region?: string;
996
+ readonly dataPolicy?: ProviderDataPolicy;
997
+ }
998
+ type ProviderCompatibilityIssueCode = "kind.unsupported" | "streaming.unsupported" | "cancellation.unsupported" | "transport.unsupported" | "audio.input_unsupported" | "audio.output_unsupported" | "language.unsupported" | "model.unsupported" | "voice.unsupported" | "turn_detection.unsupported" | "tools.unsupported" | "playout.unsupported" | "call_control.unsupported" | "region.unsupported" | "data_policy.unsupported";
999
+ interface ProviderCompatibilityIssue {
1000
+ readonly code: ProviderCompatibilityIssueCode;
1001
+ readonly requirement: string;
1002
+ readonly message: string;
1003
+ }
1004
+ type ProviderCompatibility = {
1005
+ readonly compatible: true;
1006
+ readonly issues: readonly [];
1007
+ } | {
1008
+ readonly compatible: false;
1009
+ readonly issues: readonly ProviderCompatibilityIssue[];
1010
+ };
1011
+ declare function evaluateProviderCompatibility(provider: Provider, requirements: ProviderRequirements): ProviderCompatibility;
1012
+ declare function sameAudioFormat(left: AudioFormat, right: AudioFormat): boolean;
1013
+
1014
+ interface OutboundCallRequest {
1015
+ readonly callId: CallId;
1016
+ readonly sessionId: SessionId;
1017
+ readonly from: string;
1018
+ readonly to: string;
1019
+ readonly metadata?: Readonly<Record<string, unknown>>;
1020
+ }
1021
+ interface InboundCallContext {
1022
+ readonly call: Call;
1023
+ }
1024
+ type InboundMediaEvent = InputMediaEvent | InternalMediaEvent;
1025
+ interface CallHandle {
1026
+ readonly callId: CallId;
1027
+ readonly events: AsyncIterable<InboundMediaEvent>;
1028
+ /**
1029
+ * Sends an outbound frame. Returns `true` if it reached the transport, `false`
1030
+ * if the socket was closed/closing and the frame was dropped, so the caller can
1031
+ * tell "delivered" from "silently swallowed" instead of assuming success.
1032
+ */
1033
+ send(event: OutputMediaEvent): Promise<boolean>;
1034
+ /** Delivers whole-turn assistant text to transports with a text channel. */
1035
+ deliverText?(turnId: TurnId, sequence: number, text: string): Promise<boolean>;
1036
+ clear(): Promise<void>;
1037
+ close(reason: StreamEndReason): Promise<void>;
1038
+ /**
1039
+ * Resolves once the transport confirms the marked output was actually played out
1040
+ * to the caller (e.g. a Twilio mark ack): `true` if heard, `false` if the call
1041
+ * dropped before playout. Optional: providers that can't observe playout omit it,
1042
+ * and callers then fall back to "reached transport" as their delivery signal.
1043
+ */
1044
+ confirmPlayout?(markId: string, timeoutMs: number): Promise<boolean>;
1045
+ }
1046
+ interface TelephonyProvider extends Provider {
1047
+ readonly kind: "telephony";
1048
+ dial(request: OutboundCallRequest): Promise<CallHandle>;
1049
+ accept(ctx: InboundCallContext): Promise<CallHandle>;
1050
+ hangup(callId: CallId): Promise<void>;
1051
+ }
1052
+
1053
+ interface SttEventBase {
1054
+ readonly id: ProviderEventId;
1055
+ readonly direction: "input";
1056
+ readonly sessionId: SessionId;
1057
+ readonly turnId?: TurnId;
1058
+ readonly sequence: number;
1059
+ readonly provider: string;
1060
+ readonly metadata?: Readonly<Record<string, unknown>>;
1061
+ }
1062
+ type TranscriptSegmentEventType = "stt.partial" | "stt.final";
1063
+ /** A provider-defined transcript segment; final means immutable, not end-of-turn. */
1064
+ interface TranscriptSegmentEvent extends SttEventBase {
1065
+ readonly type: TranscriptSegmentEventType;
1066
+ readonly text: string;
1067
+ readonly segmentId?: string;
1068
+ readonly revision?: number;
1069
+ readonly confidence?: number;
1070
+ readonly language?: string;
1071
+ readonly startTimestamp: Timestamp;
1072
+ readonly endTimestamp: Timestamp;
1073
+ /** Offset into the provider audio stream, independent from wall-clock receipt time. */
1074
+ readonly audioStartMs?: number;
1075
+ readonly audioEndMs?: number;
1076
+ }
1077
+ type TranscriptEndpointReason = "provider" | "silence" | "manual" | "max_duration";
1078
+ /** The STT/turn detector decided buffered final segments form a complete user turn. */
1079
+ interface TranscriptEndpointEvent extends SttEventBase {
1080
+ readonly type: "stt.endpoint";
1081
+ readonly reason: TranscriptEndpointReason;
1082
+ readonly timestamp: Timestamp;
1083
+ readonly audioOffsetMs?: number;
1084
+ readonly confidence?: number;
1085
+ }
1086
+ interface TranscriptSpeechStartedEvent extends SttEventBase {
1087
+ readonly type: "stt.speech.started";
1088
+ readonly timestamp: Timestamp;
1089
+ readonly audioOffsetMs?: number;
1090
+ }
1091
+ type TranscriptEvent = TranscriptSegmentEvent | TranscriptEndpointEvent | TranscriptSpeechStartedEvent;
1092
+ type TranscriptEventType = TranscriptEvent["type"];
1093
+ declare function isTranscriptSegmentEvent(event: TranscriptEvent): event is TranscriptSegmentEvent;
1094
+
1095
+ interface SttOpenRequest {
1096
+ readonly sessionId: SessionId;
1097
+ readonly format: AudioFormat;
1098
+ readonly language?: string;
1099
+ readonly model?: string;
1100
+ readonly allowUnknownModel?: boolean;
1101
+ readonly interimResults: boolean;
1102
+ readonly vocabulary?: readonly string[];
1103
+ readonly metadata?: Readonly<Record<string, unknown>>;
1104
+ readonly signal?: AbortSignal;
1105
+ }
1106
+ type SttCommitMode = "provider" | "none";
1107
+ /**
1108
+ * Origin of audio offsets emitted by an STT stream. Reconnectable streams must
1109
+ * declare this so the runtime can keep one session-relative timeline across fresh
1110
+ * provider generations.
1111
+ */
1112
+ type SttTimestampOrigin = "session" | "generation";
1113
+ declare const STT_STREAM_ENDED_REASON = "stream_ended";
1114
+ interface SttStream {
1115
+ readonly events: AsyncIterable<TranscriptEvent>;
1116
+ readonly commitMode?: SttCommitMode;
1117
+ readonly timestampOrigin?: SttTimestampOrigin;
1118
+ sendAudio(chunk: InputAudioChunk): Promise<void>;
1119
+ commit(): Promise<void>;
1120
+ close(): Promise<void>;
1121
+ }
1122
+ interface SpeechToTextProvider extends Provider {
1123
+ readonly kind: "stt";
1124
+ open(request: SttOpenRequest): Promise<SttStream>;
1125
+ }
1126
+
1127
+ interface TtsSessionOpenRequest {
1128
+ readonly sessionId: SessionId;
1129
+ readonly turnId: TurnId;
1130
+ readonly voice?: string;
1131
+ readonly model?: string;
1132
+ readonly format: AudioFormat;
1133
+ readonly speed?: number;
1134
+ readonly timestamps?: boolean;
1135
+ readonly metadata?: Readonly<Record<string, unknown>>;
1136
+ /** Aborts startup (e.g. a connect timeout) so a stalled connection does not leak. */
1137
+ readonly signal?: AbortSignal;
1138
+ }
1139
+ interface TtsSynthesisRequest extends TtsSessionOpenRequest {
1140
+ readonly text: string;
1141
+ readonly stream: boolean;
1142
+ }
1143
+ type TtsAlignmentUnit = "character" | "word" | "phoneme";
1144
+ type TtsFlushId = string | number;
1145
+ type TtsFlushAcknowledgement = "provider" | "transport";
1146
+ interface TtsFlushResult {
1147
+ readonly id: TtsFlushId;
1148
+ readonly acknowledgedBy: TtsFlushAcknowledgement;
1149
+ }
1150
+ interface TtsAlignmentEvent {
1151
+ readonly type: "tts.alignment";
1152
+ readonly sessionId: SessionId;
1153
+ readonly turnId: TurnId;
1154
+ readonly sequence: number;
1155
+ readonly provider: string;
1156
+ readonly timestamp: Timestamp;
1157
+ readonly unit: TtsAlignmentUnit;
1158
+ readonly tokens: readonly string[];
1159
+ readonly startMs: readonly number[];
1160
+ readonly endMs: readonly number[];
1161
+ readonly flushId?: TtsFlushId;
1162
+ }
1163
+ interface TtsFlushCompletedEvent {
1164
+ readonly type: "tts.flush.completed";
1165
+ readonly sessionId: SessionId;
1166
+ readonly turnId: TurnId;
1167
+ readonly sequence: number;
1168
+ readonly provider: string;
1169
+ readonly timestamp: Timestamp;
1170
+ readonly flushId: TtsFlushId;
1171
+ readonly acknowledgedBy: TtsFlushAcknowledgement;
1172
+ }
1173
+ type TtsEvent = OutputAudioChunk | MediaAudioCommittedEvent | TtsAlignmentEvent | TtsFlushCompletedEvent;
1174
+ interface TtsStream {
1175
+ readonly events: AsyncIterable<TtsEvent>;
1176
+ cancel(): Promise<void>;
1177
+ }
1178
+ /** A single prosody-preserving synthesis context receiving incremental text. */
1179
+ interface TtsSession extends TtsStream {
1180
+ sendText(text: string): Promise<void>;
1181
+ /** Resolves once the provider or transport accepts a synthesis boundary. */
1182
+ flush(): Promise<TtsFlushResult>;
1183
+ /** Ends input; events continue until the provider emits its completion event. */
1184
+ finish(): Promise<void>;
1185
+ }
1186
+ interface TextToSpeechProvider extends Provider {
1187
+ readonly kind: "tts";
1188
+ synthesize(request: TtsSynthesisRequest): Promise<TtsStream>;
1189
+ }
1190
+ interface IncrementalTextToSpeechProvider extends TextToSpeechProvider {
1191
+ openSession(request: TtsSessionOpenRequest): Promise<TtsSession>;
1192
+ }
1193
+ declare function isIncrementalTextToSpeechProvider(provider: TextToSpeechProvider): provider is IncrementalTextToSpeechProvider;
1194
+
1195
+ type LlmMessageRole = "system" | "user" | "assistant" | "tool";
1196
+ interface LlmMessage {
1197
+ readonly role: LlmMessageRole;
1198
+ readonly content: string;
1199
+ readonly toolCallRef?: string;
1200
+ readonly toolName?: ToolName;
1201
+ readonly metadata?: Readonly<Record<string, unknown>>;
1202
+ }
1203
+ interface LlmInlineToolCall {
1204
+ readonly callRef: string;
1205
+ readonly toolName: ToolName;
1206
+ readonly input: unknown;
1207
+ }
1208
+ interface LlmCompletionRequest {
1209
+ readonly sessionId: SessionId;
1210
+ readonly turnId: TurnId;
1211
+ readonly model: string;
1212
+ readonly messages: readonly LlmMessage[];
1213
+ readonly tools?: readonly ToolDefinition[];
1214
+ readonly stream: boolean;
1215
+ readonly temperature?: number;
1216
+ readonly maxTokens?: number;
1217
+ /** Stable, non-PII identifier forwarded to providers with a dedicated safety field. */
1218
+ readonly safetyIdentifier?: string;
1219
+ readonly metadata?: Readonly<Record<string, unknown>>;
1220
+ /** Aborts startup (e.g. a connect timeout) so a stalled request does not leak. */
1221
+ readonly signal?: AbortSignal;
1222
+ }
1223
+ interface LlmEventBase {
1224
+ readonly id: ProviderEventId;
1225
+ readonly sessionId: SessionId;
1226
+ readonly turnId: TurnId;
1227
+ readonly sequence: number;
1228
+ readonly provider: string;
1229
+ readonly timestamp: Timestamp;
1230
+ }
1231
+ interface LlmUsage {
1232
+ readonly inputTokens: number;
1233
+ readonly outputTokens: number;
1234
+ readonly cachedTokens?: number;
1235
+ }
1236
+ type LlmStreamEvent = (LlmEventBase & {
1237
+ readonly type: "llm.started";
1238
+ readonly model: string;
1239
+ }) | (LlmEventBase & {
1240
+ readonly type: "llm.token";
1241
+ readonly text: string;
1242
+ }) | (LlmEventBase & {
1243
+ readonly type: "llm.tool_call";
1244
+ readonly call: LlmInlineToolCall;
1245
+ }) | (LlmEventBase & {
1246
+ readonly type: "llm.completed";
1247
+ readonly text: string;
1248
+ readonly toolCalls: readonly LlmInlineToolCall[];
1249
+ readonly usage?: LlmUsage;
1250
+ }) | (LlmEventBase & {
1251
+ readonly type: "llm.failed";
1252
+ readonly error: NormalizedError;
1253
+ });
1254
+ interface LlmCompletion {
1255
+ readonly events: AsyncIterable<LlmStreamEvent>;
1256
+ cancel(): Promise<void>;
1257
+ }
1258
+ interface LLMProvider extends Provider {
1259
+ readonly kind: "llm";
1260
+ complete(request: LlmCompletionRequest): Promise<LlmCompletion>;
1261
+ }
1262
+
1263
+ interface AgentMemoryPolicy {
1264
+ readonly enabled: boolean;
1265
+ readonly scopes: readonly MemoryScope[];
1266
+ /** Maximum cumulative UTF-8 bytes admitted to this session's memory scope. */
1267
+ readonly maxBytesPerSession?: number;
1268
+ readonly readOnly?: boolean;
1269
+ /**
1270
+ * If true (default), session-scoped memory is purged on session end. This
1271
+ * is Vapi's Zero Data Retention posture: the call's ephemeral transcripts
1272
+ * do not outlive the call. User-scoped, organization-scoped, and
1273
+ * workflow-scoped memory are *not* affected — they persist across sessions.
1274
+ *
1275
+ * Pre-1.0, this field was named `deleteOnEnd`. That name was misleading:
1276
+ * it sounded like it deleted *all* memory, but the implementation only
1277
+ * purges the `session` scope. The rename is the contract-clarity fix.
1278
+ */
1279
+ readonly deleteSessionScopeOnEnd?: boolean;
1280
+ /**
1281
+ * If true, the LLM is given a `remember_fact` tool to write to memory.
1282
+ * Default false — explicit opt-in per agent.
1283
+ */
1284
+ readonly canLlmWrite?: boolean;
1285
+ /**
1286
+ * What to load into the agent's context before a call starts.
1287
+ * - "none": do not load any memory.
1288
+ * - "all": load all enabled scopes (default).
1289
+ * - "kind:fact" / "kind:summary" / "kind:open_item" / "kind:entity_ref" / "kind:raw" / "kind:working_memory":
1290
+ * load only entries of the given kind.
1291
+ */
1292
+ readonly preCallLoad?: "none" | "all" | `kind:${MemoryKind}`;
1293
+ }
1294
+ /**
1295
+ * Bounds the live prompt assembled for each LLM request. These limits apply
1296
+ * to live execution context, not recordings or durable operational evidence.
1297
+ */
1298
+ interface AgentContextPolicy {
1299
+ /** Maximum serialized UTF-8 bytes in one LLM request's messages. */
1300
+ readonly maxHistoryBytes?: number;
1301
+ /** Maximum number of messages in one LLM request, including system/current/active messages. */
1302
+ readonly maxHistoryMessages?: number;
1303
+ /** Maximum serialized UTF-8 bytes rendered from pre-call memory/static context. */
1304
+ readonly maxPreCallBytes?: number;
1305
+ /** Maximum pre-call memory entries loaded from one adapter scope. */
1306
+ readonly maxPreCallEntries?: number;
1307
+ }
1308
+ interface AgentAudioPolicy {
1309
+ readonly input: AudioFormat;
1310
+ readonly output: AudioFormat;
1311
+ }
1312
+ interface AgentProviders {
1313
+ readonly telephony: TelephonyProvider;
1314
+ readonly stt: SpeechToTextProvider;
1315
+ readonly llm: LLMProvider;
1316
+ readonly tts?: TextToSpeechProvider;
1317
+ }
1318
+ interface PersonaConfig {
1319
+ /**
1320
+ * Per-tenant context resolution at session start. The runtime calls
1321
+ * this once when a session is attached; the result is
1322
+ * merged into the system prompt alongside the pre-call memory block.
1323
+ */
1324
+ readonly resolveTenantContext?: (input: {
1325
+ readonly sessionId: SessionId;
1326
+ readonly userId?: UserId;
1327
+ readonly organizationId?: OrganizationId;
1328
+ readonly workflowId?: WorkflowId;
1329
+ }) => Promise<{
1330
+ /** Optional override for the agent's instructions. */
1331
+ readonly instructionsOverride?: string;
1332
+ /** Optional pre-call variables, e.g. `{{customer_name}} = "Ada"`. */
1333
+ readonly variables?: ReadonlyMap<string, string>;
1334
+ }>;
1335
+ /**
1336
+ * Per-turn context resolution. The runtime calls this before each
1337
+ * turn, allowing the prompt to adapt as the call progresses (e.g.,
1338
+ * "after turn 3, soften the language"). The default behavior is to
1339
+ * call this on every turn; the user can return the same
1340
+ * `instructionsOverride` to keep the prompt stable.
1341
+ */
1342
+ readonly systemPromptForTurn?: (input: {
1343
+ readonly sessionId: SessionId;
1344
+ readonly turnNumber: number;
1345
+ }) => Promise<{
1346
+ readonly instructionsOverride?: string;
1347
+ }>;
1348
+ }
1349
+ interface Agent {
1350
+ readonly id: AgentId;
1351
+ readonly name: string;
1352
+ readonly version: string;
1353
+ readonly instructions: string;
1354
+ readonly tools: readonly ToolDefinition[];
1355
+ readonly providers: AgentProviders;
1356
+ readonly audioPolicy: AgentAudioPolicy;
1357
+ readonly memoryPolicy: AgentMemoryPolicy;
1358
+ readonly interruptionPolicy: InterruptionPolicy;
1359
+ readonly contextPolicy?: AgentContextPolicy;
1360
+ /** Per-tenant context resolution. Optional. */
1361
+ readonly persona?: PersonaConfig;
1362
+ /** Per-operation timeout for provider streams (wired into the loop's stall guard). */
1363
+ readonly timeoutPolicy: TimeoutPolicy;
1364
+ readonly metadata?: Readonly<Record<string, unknown>>;
1365
+ }
1366
+
1367
+ interface SessionLease {
1368
+ readonly sessionId: SessionId;
1369
+ readonly holder: string;
1370
+ readonly fence: number;
1371
+ readonly acquiredAtMs: number;
1372
+ readonly renewedAtMs: number;
1373
+ readonly expiresAtMs: number;
1374
+ }
1375
+ interface SessionLeaseStore {
1376
+ acquire(sessionId: SessionId, holder: string, ttlMs: number): Promise<SessionLease | null>;
1377
+ renew(sessionId: SessionId, holder: string, fence: number, ttlMs: number): Promise<SessionLease | null>;
1378
+ release(sessionId: SessionId, holder: string, fence: number): Promise<void>;
1379
+ get(sessionId: SessionId): Promise<SessionLease | null>;
1380
+ listRecoveryCandidates(options: {
1381
+ readonly nowMs: number;
1382
+ readonly limit: number;
1383
+ readonly cursor?: string;
1384
+ }): Promise<{
1385
+ readonly sessionIds: readonly SessionId[];
1386
+ readonly nextCursor?: string;
1387
+ }>;
1388
+ close(): Promise<void>;
1389
+ }
1390
+ interface DurableOutboxEvent {
1391
+ readonly id: string;
1392
+ readonly aggregateType: "session" | "turn" | "tool_call";
1393
+ readonly aggregateId: string;
1394
+ readonly sessionId: SessionId;
1395
+ readonly version: number;
1396
+ readonly fence: number;
1397
+ readonly envelope: Readonly<Record<string, unknown>>;
1398
+ }
1399
+ interface SessionRuntimeMetadata {
1400
+ readonly monotonicStartedAtMs: number;
1401
+ readonly lastActivityWallAtMs?: number;
1402
+ readonly clockEpoch?: number;
1403
+ readonly clockDiscontinuityMs?: number;
1404
+ }
1405
+ interface TurnRuntimeMetadata {
1406
+ readonly monotonicStartedAtMs: number;
1407
+ readonly recoveryGapMs?: number;
1408
+ }
1409
+ interface ToolCallRuntimeMetadata {
1410
+ readonly monotonicQueuedAtMs: number;
1411
+ }
1412
+ interface StoredSessionRecord {
1413
+ readonly session: Session;
1414
+ readonly runtime: SessionRuntimeMetadata;
1415
+ /** Persistence-local aggregate version used for outbox ordering/fencing. */
1416
+ readonly version?: number;
1417
+ }
1418
+ interface StoredTurnRecord {
1419
+ readonly turn: Turn;
1420
+ readonly runtime: TurnRuntimeMetadata;
1421
+ readonly version?: number;
1422
+ }
1423
+ interface StoredToolCallRecord {
1424
+ readonly toolCall: ToolCall;
1425
+ readonly runtime: ToolCallRuntimeMetadata;
1426
+ readonly version?: number;
1427
+ }
1428
+ interface SessionStore {
1429
+ get(id: SessionId): Promise<StoredSessionRecord | null>;
1430
+ list(): Promise<readonly StoredSessionRecord[]>;
1431
+ put(record: StoredSessionRecord): Promise<void>;
1432
+ update(id: SessionId, updater: (record: StoredSessionRecord) => StoredSessionRecord): Promise<StoredSessionRecord>;
1433
+ close(): Promise<void>;
1434
+ }
1435
+ interface TurnStore {
1436
+ get(sessionId: SessionId, id: TurnId): Promise<StoredTurnRecord | null>;
1437
+ listBySession(sessionId: SessionId): Promise<readonly StoredTurnRecord[]>;
1438
+ put(record: StoredTurnRecord): Promise<void>;
1439
+ update(sessionId: SessionId, id: TurnId, updater: (record: StoredTurnRecord) => StoredTurnRecord): Promise<StoredTurnRecord>;
1440
+ close(): Promise<void>;
1441
+ }
1442
+ interface ToolCallStore {
1443
+ get(sessionId: SessionId, id: ToolCallId): Promise<StoredToolCallRecord | null>;
1444
+ listBySession(sessionId: SessionId): Promise<readonly StoredToolCallRecord[]>;
1445
+ put(record: StoredToolCallRecord): Promise<void>;
1446
+ update(sessionId: SessionId, id: ToolCallId, updater: (record: StoredToolCallRecord) => StoredToolCallRecord): Promise<StoredToolCallRecord>;
1447
+ close(): Promise<void>;
1448
+ }
1449
+ interface DurableSessionTransaction {
1450
+ getSession(id: SessionId): Promise<StoredSessionRecord | null>;
1451
+ putSession(record: StoredSessionRecord): Promise<void>;
1452
+ updateSession(id: SessionId, updater: (record: StoredSessionRecord) => StoredSessionRecord): Promise<StoredSessionRecord>;
1453
+ getTurn(sessionId: SessionId, id: TurnId): Promise<StoredTurnRecord | null>;
1454
+ listTurns(sessionId: SessionId): Promise<readonly StoredTurnRecord[]>;
1455
+ putTurn(record: StoredTurnRecord): Promise<void>;
1456
+ updateTurn(sessionId: SessionId, id: TurnId, updater: (record: StoredTurnRecord) => StoredTurnRecord): Promise<StoredTurnRecord>;
1457
+ getToolCall(sessionId: SessionId, id: ToolCallId): Promise<StoredToolCallRecord | null>;
1458
+ listToolCalls(sessionId: SessionId): Promise<readonly StoredToolCallRecord[]>;
1459
+ putToolCall(record: StoredToolCallRecord): Promise<void>;
1460
+ updateToolCall(sessionId: SessionId, id: ToolCallId, updater: (record: StoredToolCallRecord) => StoredToolCallRecord): Promise<StoredToolCallRecord>;
1461
+ appendOutbox(event: DurableOutboxEvent): Promise<void>;
1462
+ }
1463
+ interface DurableRuntimeStore {
1464
+ readonly sessions: SessionStore;
1465
+ readonly turns: TurnStore;
1466
+ readonly toolCalls: ToolCallStore;
1467
+ readonly leases: SessionLeaseStore;
1468
+ readonly toolIdempotencyStore?: ToolIdempotencyStore;
1469
+ /**
1470
+ * Closes adapter-owned resources and background workers. When present,
1471
+ * Runtime.stop() delegates the complete durable-store shutdown to this
1472
+ * method instead of closing the aggregate stores independently. Adapters
1473
+ * that do not own background resources may omit it and retain the legacy
1474
+ * component-close behavior.
1475
+ */
1476
+ close?(): Promise<void>;
1477
+ /**
1478
+ * Atomically creates a session and acquires its first owner lease when the
1479
+ * backend supports that transaction boundary. Runtimes use the fallback
1480
+ * start-then-attach path only for adapters that do not implement it.
1481
+ */
1482
+ createSessionWithLease?(record: StoredSessionRecord, holder: string, ttlMs: number, initialEvent?: (lease: SessionLease) => DurableOutboxEvent): Promise<SessionLease | null>;
1483
+ runSessionTransaction<T>(sessionId: SessionId, lease: Pick<SessionLease, "holder" | "fence">, operation: (tx: DurableSessionTransaction) => Promise<T>): Promise<T>;
1484
+ /**
1485
+ * Serialized compatibility boundary for callers that intentionally operate
1486
+ * without a live attachment. A store may provide rollback internally, but
1487
+ * the legacy CRUD interfaces cannot guarantee atomic rollback for arbitrary
1488
+ * custom stores. There is no ownership or fencing guarantee; realtime
1489
+ * attached paths must use runSessionTransaction.
1490
+ */
1491
+ runUnfencedSessionTransaction?<T>(sessionId: SessionId, operation: (tx: DurableSessionTransaction) => Promise<T>): Promise<T>;
1492
+ }
1493
+
1494
+ interface RuntimeOptions {
1495
+ readonly sessionStore?: SessionStore;
1496
+ readonly turnStore?: TurnStore;
1497
+ readonly toolCallStore?: ToolCallStore;
1498
+ readonly clock?: Clock;
1499
+ readonly idGenerator?: IdGenerator;
1500
+ readonly durableStore?: DurableRuntimeStore;
1501
+ /** Overrides for the explicit defaults; omitted values use the safe defaults. */
1502
+ readonly durablePolicy?: Partial<DurableRuntimePolicy>;
1503
+ readonly toolIdempotencyStore?: ToolIdempotencyStore;
1504
+ readonly holderId?: string;
1505
+ readonly onDurableMetric?: (metric: DurableRuntimeMetric) => void;
1506
+ /**
1507
+ * Memory adapter used for cross-call memory. Defaults to `InMemoryMemory`
1508
+ * (process-local) when omitted. For cross-call recall, wire a durable
1509
+ * adapter (e.g., `@tvic/dal-postgres-memory`) or a third-party adapter
1510
+ * (Mem0, Supermemory, etc.).
1511
+ */
1512
+ readonly memory?: Memory;
1513
+ /**
1514
+ * Maximum time the runtime waits for already-admitted memory writes and the
1515
+ * session-scope purge during terminalization. Timing out releases the
1516
+ * caller while preserving write-before-purge ordering. The default is 1
1517
+ * second; the adapter operation is not cancelled.
1518
+ */
1519
+ readonly sessionMemoryFinalizeTimeoutMs?: number;
1520
+ /**
1521
+ * Maximum time the runtime waits for the best-effort `onSessionEnd` observer
1522
+ * after the terminal event has been assembled. The default is 5 seconds;
1523
+ * the observer is not cancelled when this budget expires.
1524
+ */
1525
+ readonly sessionEndHookTimeoutMs?: number;
1526
+ /**
1527
+ * Pre-call context resolver. When a session is created or attached, the
1528
+ * runtime calls this hook when a session is attached to populate the agent's
1529
+ * context with memory entries for the relevant scopes AND with non-memory
1530
+ * context (CRM, feature flags, tenant config). If omitted, the runtime uses a default
1531
+ * resolver that reads from `options.memory` for the active `user` /
1532
+ * `organization` / `workflow` scopes and returns an empty `static` map.
1533
+ *
1534
+ * Pre-1.0, this was named `preCallMemoryResolver` and returned a
1535
+ * `PreCallMemoryContext`. The rename to `PreCallContextResolver` and
1536
+ * the addition of the `static: ReadonlyMap<string, string>` sub-map
1537
+ * fixes a real type lie: the customer previously had to stuff CRM
1538
+ * data and feature flags into the `Memory` map as `kind: "fact"`
1539
+ * entries. The `static` map is the typed input for non-memory context.
1540
+ */
1541
+ readonly preCallContextResolver?: PreCallContextResolver;
1542
+ /** Optional org/workflow ids carried on every session created by this runtime. */
1543
+ readonly defaultOrganizationId?: OrganizationId;
1544
+ readonly defaultWorkflowId?: WorkflowId;
1545
+ /**
1546
+ * Optional callback fired after a session is terminalized and after the
1547
+ * runtime has made its best-effort memory-finalization attempt. The event
1548
+ * reports whether session-scope deletion was skipped, completed, failed, or
1549
+ * timed out. Best-effort: a throw or timeout from the hook does not affect
1550
+ * the terminal session. The runtime does not retry; if you need durability,
1551
+ * call an external system from inside the hook and have that system retry.
1552
+ *
1553
+ * The hook is the canonical place for: post-call LLM summarization,
1554
+ * CRM sync, callback scheduling, escalation, transcript delivery to a
1555
+ * CRM. The runtime does not own any of these — the user picks the LLM,
1556
+ * the prompt, the schema.
1557
+ */
1558
+ readonly onSessionEnd?: (event: SessionEndEvent) => void | Promise<void>;
1559
+ /**
1560
+ * Optional metrics recorder. The runtime emits one `record` call per
1561
+ * observable event (session start, turn end, session end) and one
1562
+ * `onTurn` call per terminal turn. The user provides the implementation
1563
+ * (Earshot-shaped, console.log-shaped, OTel-shaped).
1564
+ */
1565
+ readonly sessionMetricsRecorder?: SessionMetricsRecorder;
1566
+ /**
1567
+ * Optional health check consumed by `NodeMediaPlane.healthPath`. The
1568
+ * user implements the readiness check (DB ping, recovery-coordinator
1569
+ * liveness); the runtime reflects it on the HTTP endpoint. Returns
1570
+ * `ok: true` when all checks pass; the runtime responds with HTTP 200.
1571
+ */
1572
+ readonly healthCheck?: () => Promise<HealthSnapshot>;
1573
+ /**
1574
+ * Optional callback fired at the start of `runtime.stop()` so the
1575
+ * user's deployment wiring (SIGTERM handler, kubernetes preStop hook)
1576
+ * can drain the load balancer before the runtime detaches sessions
1577
+ * and closes stores. Best-effort; the runtime proceeds after the
1578
+ * promise settles or 5s, whichever comes first.
1579
+ */
1580
+ readonly onShutdownStart?: (state: {
1581
+ readonly activeSessions: readonly SessionId[];
1582
+ }) => void | Promise<void>;
1583
+ /**
1584
+ * Optional async provider of the pre-call static context (CRM records,
1585
+ * feature flags, tenant config). Called by the runtime when a session is
1586
+ * attached; the result is merged into the pre-call context's `static`
1587
+ * map. Best-effort: a throw or timeout produces a degraded `static`
1588
+ * context (degraded flag set). This is a separate seam from
1589
+ * `preCallContextResolver` so a custom resolver can choose to either
1590
+ * (a) inline the static fetch into its own resolver, or (b) leave the
1591
+ * fetch to the runtime and just augment the result.
1592
+ */
1593
+ readonly preCallStaticProvider?: () => Promise<ReadonlyMap<string, string>>;
1594
+ }
1595
+ /**
1596
+ * Resolves the pre-call context for a session. The runtime calls this on
1597
+ * `startAttachedSession` and `attachSession` after lease acquisition.
1598
+ *
1599
+ * Pre-1.0, this was named `PreCallMemoryResolver` and returned a
1600
+ * `PreCallMemoryContext`. The rename to `PreCallContextResolver` and
1601
+ * the addition of the `static` sub-map fixes the type lie.
1602
+ */
1603
+ type PreCallContextResolver = (input: {
1604
+ readonly userId?: UserId;
1605
+ readonly organizationId?: OrganizationId;
1606
+ readonly workflowId?: WorkflowId;
1607
+ readonly sessionId: SessionId;
1608
+ readonly memory: Memory;
1609
+ /** Wall-clock source supplied by the runtime for deterministic resolution. */
1610
+ readonly clock?: () => number;
1611
+ /** Scopes permitted by the agent's memory policy for this call. */
1612
+ readonly scopes?: readonly MemoryScope[];
1613
+ /** Optional kind filter from `memoryPolicy.preCallLoad`. */
1614
+ readonly kind?: MemoryKind;
1615
+ /** Maximum memory entries the runtime should load from any one scope. */
1616
+ readonly maxEntries?: number;
1617
+ /** Maximum rendered pre-call context bytes. */
1618
+ readonly maxBytes?: number;
1619
+ }) => Promise<PreCallContext>;
1620
+ interface PreCallContext {
1621
+ /** Map of `${scope}:${scopeId}:${kind}:${key}` to entry. Empty on resolver error. */
1622
+ readonly memory: ReadonlyMap<string, MemoryEntry>;
1623
+ /**
1624
+ * Non-memory context: CRM records, feature flags, tenant config, anything
1625
+ * the LLM should see in the system prompt that is *not* a memory entry.
1626
+ * Rendered into a separate `<context>...</context>` block above the
1627
+ * `<memory>...</memory>` block.
1628
+ */
1629
+ readonly static: ReadonlyMap<string, string>;
1630
+ /** When the resolver ran, wall-clock ms. */
1631
+ readonly resolvedAtMs: number;
1632
+ /** Per-bucket degradation flag. */
1633
+ readonly degraded: {
1634
+ readonly memory: boolean;
1635
+ readonly static: boolean;
1636
+ };
1637
+ }
1638
+ /**
1639
+ * @deprecated Use `PreCallContext` and `PreCallContextResolver`. The
1640
+ * memory-only variant remains as a separate type for one compatibility release.
1641
+ */
1642
+ type PreCallMemoryResolver = (input: {
1643
+ readonly userId?: UserId;
1644
+ readonly organizationId?: OrganizationId;
1645
+ readonly workflowId?: WorkflowId;
1646
+ readonly sessionId: SessionId;
1647
+ readonly memory: Memory;
1648
+ }) => Promise<PreCallMemoryContext>;
1649
+ interface PreCallMemoryContext {
1650
+ readonly entries: ReadonlyMap<string, MemoryEntry>;
1651
+ readonly resolvedAtMs: number;
1652
+ readonly degraded: boolean;
1653
+ }
1654
+ /**
1655
+ * Carries the terminal session, the persisted snapshot, and a snapshot
1656
+ * of the memory adapter's state at the moment of endSession. The
1657
+ * memory snapshot is the source of truth for "what did we learn about
1658
+ * this caller" — the runtime does not try to track per-write
1659
+ * `MemoryChangeRecord`s, because most writes are within the session
1660
+ * scope and are about to be deleted by `deleteSessionScopeOnEnd`. The
1661
+ * snapshot the user actually wants is what survives into `user` /
1662
+ * `organization` / `workflow` scopes.
1663
+ */
1664
+ interface SessionEndEvent {
1665
+ readonly session: TerminalSession;
1666
+ readonly snapshot: SessionSnapshot;
1667
+ /**
1668
+ * Memory state at end-of-session, scoped to the user/org/workflow ids
1669
+ * the runtime resolved for this session. Empty for sessions that wrote
1670
+ * only to `session` scope (which is about to be deleted).
1671
+ */
1672
+ readonly finalMemorySnapshot: SessionEndMemorySnapshot;
1673
+ /** Result of the ordered session-memory drain and optional purge. */
1674
+ readonly memoryFinalization: SessionMemoryFinalization;
1675
+ readonly wallClockMs: number;
1676
+ }
1677
+ type SessionMemoryFinalization = {
1678
+ readonly status: "skipped";
1679
+ } | {
1680
+ readonly status: "completed";
1681
+ readonly deletedEntries: number;
1682
+ } | {
1683
+ readonly status: "failed";
1684
+ readonly error: NormalizedError;
1685
+ } | {
1686
+ readonly status: "timed_out";
1687
+ readonly phase: "drain" | "purge";
1688
+ readonly timeoutMs: number;
1689
+ };
1690
+ interface SessionEndMemorySnapshot {
1691
+ readonly user?: ReadonlyMap<string, MemoryEntry>;
1692
+ readonly organization?: ReadonlyMap<string, MemoryEntry>;
1693
+ readonly workflow?: ReadonlyMap<string, MemoryEntry>;
1694
+ }
1695
+ /**
1696
+ * Contract for an external observability consumer. The runtime emits
1697
+ * one `record` per observable event and one `onTurn` per terminal
1698
+ * turn. Implementations: Earshot-shaped (project into an `earshot.*`
1699
+ * event), OTel-shaped (project into a span event), console.log-shaped.
1700
+ */
1701
+ interface SessionMetricsRecorder {
1702
+ record(name: string, attributes?: Readonly<Record<string, string | number | boolean>>): void;
1703
+ onTurn(turn: TerminalTurn, sessionId: SessionId): void;
1704
+ onSessionEnd?(event: SessionEndEvent): void;
1705
+ }
1706
+ interface HealthSnapshot {
1707
+ readonly ok: boolean;
1708
+ readonly checks?: Readonly<Record<string, HealthCheckResult>>;
1709
+ }
1710
+ interface HealthCheckResult {
1711
+ readonly ok: boolean;
1712
+ readonly latencyMs?: number;
1713
+ readonly message?: string;
1714
+ }
1715
+ interface DurableRuntimeMetric {
1716
+ readonly name: string;
1717
+ readonly value: number;
1718
+ readonly atMs: number;
1719
+ readonly attributes?: Readonly<Record<string, string | number | boolean>>;
1720
+ }
1721
+ interface DurableRuntimePolicy {
1722
+ readonly criticalWriteTimeoutMs: number;
1723
+ readonly criticalSessionP50BudgetMs: number;
1724
+ readonly criticalSessionP95BudgetMs: number;
1725
+ readonly criticalSessionP99BudgetMs: number;
1726
+ readonly turnStartP95BudgetMs: number;
1727
+ readonly turnStartP99BudgetMs: number;
1728
+ readonly toolPathP95BudgetMs: number;
1729
+ readonly toolPathP99BudgetMs: number;
1730
+ readonly failoverP95BudgetMs: number;
1731
+ readonly failoverP99BudgetMs: number;
1732
+ readonly failoverAlertBudgetMs: number;
1733
+ readonly bargeCheckpointRetryBudgetMs: number;
1734
+ readonly bargeCheckpointMaxAttempts: number;
1735
+ readonly persistenceRecoveryGraceMs: number;
1736
+ readonly leaseTtlMs: number;
1737
+ readonly leaseHeartbeatMs: number;
1738
+ readonly recoveryPollMs: number;
1739
+ readonly recoveryGraceMs: number;
1740
+ readonly terminalRetentionMs: number;
1741
+ }
1742
+ declare const DEFAULT_DURABLE_RUNTIME_POLICY: DurableRuntimePolicy;
1743
+ type SessionAttachmentHealth = "healthy" | "persistence_degraded" | "lease_lost" | "detached";
1744
+ interface StartSessionOptions {
1745
+ readonly channel: ChannelKind;
1746
+ readonly call?: Call;
1747
+ readonly variables?: Readonly<Record<string, unknown>>;
1748
+ readonly metadata?: Readonly<Record<string, unknown>>;
1749
+ /** User scope identity for cross-call memory recall. */
1750
+ readonly memoryUserId?: UserId;
1751
+ /** Organization scope for cross-call memory recall. */
1752
+ readonly organizationId?: OrganizationId;
1753
+ /** Workflow scope for cross-call memory recall. */
1754
+ readonly workflowId?: WorkflowId;
1755
+ }
1756
+ type EndSessionReason = "completed" | "cancelled" | "failed" | "timeout";
1757
+ type EndSessionRequest = {
1758
+ readonly reason: "completed";
1759
+ } | {
1760
+ readonly reason: "cancelled";
1761
+ readonly cancelReason: SessionCancellationReason;
1762
+ } | {
1763
+ readonly reason: "failed";
1764
+ readonly error: NormalizedError;
1765
+ } | {
1766
+ readonly reason: "timeout";
1767
+ readonly error: NormalizedError;
1768
+ };
1769
+ interface StartTurnRequest {
1770
+ readonly sessionId: SessionId;
1771
+ readonly input?: TurnInput;
1772
+ readonly metadata?: Readonly<Record<string, unknown>>;
1773
+ }
1774
+ type EndTurnRequest = {
1775
+ readonly reason: "completed";
1776
+ readonly output?: TurnOutput;
1777
+ readonly latency?: TurnLatency;
1778
+ readonly toolCallIds?: readonly ToolCallId[];
1779
+ } | {
1780
+ readonly reason: "cancelled";
1781
+ readonly cancelReason: TurnCancellationReason;
1782
+ readonly output?: TurnOutput;
1783
+ readonly latency?: TurnLatency;
1784
+ readonly toolCallIds?: readonly ToolCallId[];
1785
+ } | {
1786
+ readonly reason: "failed";
1787
+ readonly error: NormalizedError;
1788
+ readonly output?: TurnOutput;
1789
+ readonly latency?: TurnLatency;
1790
+ readonly toolCallIds?: readonly ToolCallId[];
1791
+ };
1792
+ interface SessionSnapshot {
1793
+ readonly session: Session;
1794
+ readonly turns: readonly Turn[];
1795
+ readonly toolCalls: readonly ToolCall[];
1796
+ }
1797
+ interface StartAttachedSessionOptions extends StartSessionOptions {
1798
+ readonly holderId?: string;
1799
+ }
1800
+ interface SessionAttachment {
1801
+ readonly session: ActiveSession;
1802
+ readonly snapshot: SessionSnapshot;
1803
+ readonly lease: SessionLease | null;
1804
+ readonly signal: AbortSignal;
1805
+ readonly health: SessionAttachmentHealth;
1806
+ readonly detach: () => Promise<void>;
1807
+ /**
1808
+ * Pre-call context loaded from the runtime's pre-call context resolver
1809
+ * before the call starts. Contains both memory entries (rendered as
1810
+ * `<memory>...</memory>`) and non-memory static context (rendered as
1811
+ * `<context>...</context>`). Pre-1.0, this was `preCallMemory` and
1812
+ * carried only the memory entries. The rename is the contract-clarity
1813
+ * fix; legacy callers reading `preCallMemory.entries` should switch
1814
+ * to `preCallContext.memory`.
1815
+ */
1816
+ readonly preCallContext?: PreCallContext;
1817
+ /**
1818
+ * @deprecated Use `preCallContext`. The legacy `preCallMemory` field
1819
+ * is kept as a typedef for one release and resolved to a shim that
1820
+ * projects `preCallContext.memory`.
1821
+ */
1822
+ readonly preCallMemory?: PreCallMemoryContext;
1823
+ }
1824
+ interface RuntimeServiceLifecycle {
1825
+ /** Starts the runtime. A runtime can be started once; create a new runtime after stop. */
1826
+ start(): Promise<void>;
1827
+ /**
1828
+ * Stops the runtime and closes its owned resources. The operation is
1829
+ * idempotent and concurrent callers await the same teardown. A stopped
1830
+ * runtime cannot be restarted because durable adapters are closed.
1831
+ */
1832
+ stop(): Promise<void>;
1833
+ readonly isRunning: boolean;
1834
+ }
1835
+ interface Runtime extends RuntimeServiceLifecycle {
1836
+ startSession(agent: Agent, options: StartSessionOptions): Promise<ActiveSession>;
1837
+ startAttachedSession(agent: Agent, options: StartAttachedSessionOptions): Promise<SessionAttachment>;
1838
+ attachSession(agent: Agent, sessionId: SessionId, options?: {
1839
+ readonly holderId?: string;
1840
+ readonly memoryUserId?: UserId;
1841
+ readonly organizationId?: OrganizationId;
1842
+ readonly workflowId?: WorkflowId;
1843
+ }): Promise<SessionAttachment>;
1844
+ getSession(id: SessionId): Promise<Session | null>;
1845
+ endSession(id: SessionId, request: EndSessionRequest): Promise<TerminalSession>;
1846
+ startTurn(request: StartTurnRequest): Promise<Turn>;
1847
+ endTurn(sessionId: SessionId, turnId: TurnId, request: EndTurnRequest): Promise<TerminalTurn>;
1848
+ updateTurnStatus(sessionId: SessionId, turnId: TurnId, status: TurnStatus): Promise<Turn>;
1849
+ /** Marks durable persistence health for the live attachment without changing lease ownership. */
1850
+ setPersistenceHealth(sessionId: SessionId, degraded: boolean): void;
1851
+ /**
1852
+ * Serializes memory writes and session-end purge for one session. Built-in
1853
+ * runtimes provide this; custom runtimes may omit it for compatibility.
1854
+ */
1855
+ runSessionMemoryOperation?<T>(sessionId: SessionId, operation: () => Promise<T>): Promise<T>;
1856
+ startToolCall(toolCall: QueuedToolCall): Promise<RunningToolCall>;
1857
+ finishToolCall(toolCall: TerminalToolCall): Promise<TerminalToolCall>;
1858
+ recoverToolCalls(sessionId: SessionId): Promise<readonly ToolCall[]>;
1859
+ checkpointTurnInterruption(sessionId: SessionId, turnId: TurnId, reason: TurnCancellationReason): Promise<Turn>;
1860
+ /** Session-relative monotonic offset used for turn and media timing. */
1861
+ sessionClockMs(id: SessionId): number;
1862
+ /** Persists a completed tool call as part of runtime session state. */
1863
+ recordToolCall(toolCall: ToolCall): Promise<void>;
1864
+ /**
1865
+ * Optional health check. The runtime exposes this so the
1866
+ * `NodeMediaPlane` (or any other host) can surface readiness via
1867
+ * `/healthz`. Returns `ok: true` when the underlying store + leases
1868
+ * are healthy; the host responds with HTTP 200 vs 503. Best-effort:
1869
+ * a throw produces `ok: false`.
1870
+ */
1871
+ healthCheck(): Promise<HealthSnapshot>;
1872
+ inspectSession(id: SessionId): Promise<SessionSnapshot>;
1873
+ readonly durablePolicy?: DurableRuntimePolicy;
1874
+ readonly toolIdempotencyStore?: ToolIdempotencyStore;
1875
+ readonly memory?: Memory;
1876
+ }
1877
+
1878
+ interface TerminalSessionDraft {
1879
+ readonly id: SessionId;
1880
+ readonly agentId: AgentId;
1881
+ readonly channel: ChannelKind;
1882
+ readonly callId?: CallId;
1883
+ /** Reserved; the runtime currently leaves this reference index empty. */
1884
+ readonly memoryRefs: readonly MemoryEntryId[];
1885
+ readonly metadata?: Readonly<Record<string, unknown>>;
1886
+ readonly createdAt: Timestamp;
1887
+ readonly state: SessionState;
1888
+ readonly startedAt: Timestamp;
1889
+ readonly endedAt: Timestamp;
1890
+ }
1891
+ declare function terminalSessionFromRequest(draft: TerminalSessionDraft, request: EndSessionRequest): TerminalSession;
1892
+ declare function terminalTurnFromRequest(turn: ActiveTurn, request: EndTurnRequest, endedAt: Timestamp, durationMs: number): TerminalTurn;
1893
+ declare function isTerminalSession(session: Session): session is TerminalSession;
1894
+ declare function isTerminalTurn(turn: Turn): turn is TerminalTurn;
1895
+
1896
+ interface ProviderClock {
1897
+ now(): Timestamp;
1898
+ }
1899
+
1900
+ interface TwilioMediaStreamSocket {
1901
+ readonly readyState: number;
1902
+ send(data: string): void;
1903
+ close(code?: number, reason?: string): void;
1904
+ on(event: "message", handler: (data: WebSocket.RawData) => void): this;
1905
+ on(event: "close", handler: () => void): this;
1906
+ on(event: "error", handler: (error: Error) => void): this;
1907
+ }
1908
+ interface TwilioMediaStreamCallHandleOptions {
1909
+ readonly socket: TwilioMediaStreamSocket;
1910
+ readonly callId: CallId;
1911
+ readonly sessionId: SessionId;
1912
+ readonly inputFormat?: AudioFormat;
1913
+ readonly outputFormat?: AudioFormat;
1914
+ readonly clock?: ProviderClock;
1915
+ }
1916
+ declare class TwilioMediaStreamCallHandle implements CallHandle {
1917
+ #private;
1918
+ readonly options: TwilioMediaStreamCallHandleOptions;
1919
+ readonly events: AsyncIterable<InboundMediaEvent>;
1920
+ constructor(options: TwilioMediaStreamCallHandleOptions);
1921
+ readonly callId: CallId;
1922
+ send(event: OutputMediaEvent): Promise<boolean>;
1923
+ clear(): Promise<void>;
1924
+ close(reason: StreamEndReason): Promise<void>;
1925
+ confirmPlayout(markId: string, timeoutMs: number): Promise<boolean>;
1926
+ }
1927
+ declare class TwilioMediaStreamsProvider implements TelephonyProvider {
1928
+ #private;
1929
+ readonly name: "twilio-media-streams";
1930
+ readonly kind = "telephony";
1931
+ readonly version = "0.1.0";
1932
+ readonly capabilities: {
1933
+ streaming: {
1934
+ input: true;
1935
+ output: true;
1936
+ native: true;
1937
+ };
1938
+ cancellation: {
1939
+ request: true;
1940
+ output: true;
1941
+ buffer: true;
1942
+ truncation: false;
1943
+ };
1944
+ transports: "websocket"[];
1945
+ audio: {
1946
+ input: {
1947
+ readonly encoding: "pcm_s16le";
1948
+ readonly sampleRateHz: 16000;
1949
+ readonly channels: 1;
1950
+ }[];
1951
+ output: {
1952
+ readonly encoding: "pcm_s16le";
1953
+ readonly sampleRateHz: 16000;
1954
+ readonly channels: 1;
1955
+ }[];
1956
+ };
1957
+ playout: {
1958
+ clearBuffer: true;
1959
+ acknowledgement: true;
1960
+ position: false;
1961
+ };
1962
+ };
1963
+ dial(): Promise<CallHandle>;
1964
+ accept(ctx: Parameters<TelephonyProvider["accept"]>[0]): Promise<CallHandle>;
1965
+ attachWebSocket(socket: TwilioMediaStreamSocket, callId: CallId, sessionId: SessionId): void;
1966
+ acceptWebSocket(socket: TwilioMediaStreamSocket, callId: CallId, sessionId: SessionId): Promise<TwilioMediaStreamCallHandle>;
1967
+ hangup(_callId: CallId): Promise<void>;
1968
+ }
1969
+ declare function createTwilioMediaStreamsProvider(): TwilioMediaStreamsProvider;
1970
+
1971
+ declare const WEB_CLIENT_AUDIO_CLOSE_CODES: {
1972
+ readonly protocol: 4400;
1973
+ readonly heartbeatTimeout: 4408;
1974
+ readonly superseded: 4409;
1975
+ readonly maxDuration: 4410;
1976
+ readonly resourceLimit: 4413;
1977
+ readonly operatorTerminated: 4500;
1978
+ };
1979
+ declare const WEB_CLIENT_AUDIO_DEFAULTS: {
1980
+ readonly heartbeatIntervalMs: 5000;
1981
+ readonly heartbeatTimeoutMs: 10000;
1982
+ readonly maxSessionDurationMs: number;
1983
+ readonly maxPendingEvents: 512;
1984
+ readonly maxInputFramesPerSecond: 200;
1985
+ readonly maxPendingAcks: 128;
1986
+ };
1987
+ interface WebClientAudioSocket {
1988
+ readonly readyState: number;
1989
+ send(data: string | Buffer): void;
1990
+ close(code?: number, reason?: string): void;
1991
+ on(event: "message", handler: (data: WebSocket.RawData, isBinary: boolean) => void): this;
1992
+ on(event: "close", handler: (code: number, reason: Buffer) => void): this;
1993
+ on(event: "error", handler: (error: Error) => void): this;
1994
+ }
1995
+ type ConnectionObservabilityEvent = {
1996
+ readonly type: "session_started";
1997
+ readonly callId: CallId;
1998
+ readonly sessionId: SessionId;
1999
+ } | {
2000
+ readonly type: "session_ended";
2001
+ readonly callId: CallId;
2002
+ readonly sessionId: SessionId;
2003
+ readonly closeCode: number;
2004
+ readonly reason: string;
2005
+ } | {
2006
+ readonly type: "auth_rejected";
2007
+ readonly reason: string;
2008
+ } | {
2009
+ readonly type: "reconnect_detected";
2010
+ readonly sessionRef: string;
2011
+ readonly supersedes: string;
2012
+ };
2013
+ type WebClientAudioConnectionEvent = ConnectionObservabilityEvent;
2014
+ interface WebClientAudioCallHandleOptions {
2015
+ readonly socket: WebClientAudioSocket;
2016
+ readonly callId: CallId;
2017
+ readonly sessionId: SessionId;
2018
+ readonly heartbeatIntervalMs?: number;
2019
+ readonly heartbeatTimeoutMs?: number;
2020
+ readonly maxSessionDurationMs?: number;
2021
+ readonly maxBinaryFrameBytes?: number;
2022
+ readonly maxInputBytesPerSecond?: number;
2023
+ readonly maxPendingEvents?: number;
2024
+ readonly maxInputFramesPerSecond?: number;
2025
+ readonly maxPendingAcks?: number;
2026
+ readonly expectedMode?: "push_to_talk" | "continuous";
2027
+ readonly clock?: ProviderClock;
2028
+ readonly nowMs?: () => number;
2029
+ readonly onConnectionEvent?: (event: ConnectionObservabilityEvent) => void;
2030
+ readonly onClosed?: () => void;
2031
+ }
2032
+ declare class WebClientAudioCallHandle implements CallHandle {
2033
+ #private;
2034
+ readonly callId: CallId;
2035
+ readonly events: AsyncIterable<InboundMediaEvent>;
2036
+ constructor(options: WebClientAudioCallHandleOptions);
2037
+ send(event: OutputMediaEvent): Promise<boolean>;
2038
+ deliverText(turnId: TurnId, sequence: number, text: string): Promise<boolean>;
2039
+ clear(): Promise<void>;
2040
+ close(reason: StreamEndReason): Promise<void>;
2041
+ terminate(code: number, reason: string): void;
2042
+ confirmPlayout(markId: string, timeoutMs: number): Promise<boolean>;
2043
+ }
2044
+ interface WebClientAudioProviderOptions extends Omit<WebClientAudioCallHandleOptions, "socket" | "callId" | "sessionId" | "onClosed" | "expectedMode"> {
2045
+ }
2046
+ declare class WebClientAudioProvider implements TelephonyProvider {
2047
+ #private;
2048
+ readonly name: "web-client-audio";
2049
+ readonly kind = "telephony";
2050
+ readonly version = "0.1.0";
2051
+ readonly capabilities: {
2052
+ streaming: {
2053
+ input: true;
2054
+ output: true;
2055
+ native: true;
2056
+ };
2057
+ cancellation: {
2058
+ request: true;
2059
+ output: true;
2060
+ buffer: true;
2061
+ truncation: false;
2062
+ };
2063
+ transports: "websocket"[];
2064
+ audio: {
2065
+ input: {
2066
+ readonly encoding: "pcm_s16le";
2067
+ readonly sampleRateHz: 16000;
2068
+ readonly channels: 1;
2069
+ }[];
2070
+ output: {
2071
+ readonly encoding: "pcm_s16le";
2072
+ readonly sampleRateHz: 16000;
2073
+ readonly channels: 1;
2074
+ }[];
2075
+ };
2076
+ playout: {
2077
+ clearBuffer: true;
2078
+ acknowledgement: true;
2079
+ position: false;
2080
+ };
2081
+ };
2082
+ constructor(options?: WebClientAudioProviderOptions);
2083
+ dial(): Promise<CallHandle>;
2084
+ accept(ctx: Parameters<TelephonyProvider["accept"]>[0]): Promise<CallHandle>;
2085
+ attachWebSocket(socket: WebClientAudioSocket, callId: CallId, sessionId: SessionId): void;
2086
+ acceptWebSocket(socket: WebClientAudioSocket, callId: CallId, sessionId: SessionId, connectionOptions?: {
2087
+ readonly expectedMode?: "push_to_talk" | "continuous";
2088
+ }): Promise<WebClientAudioCallHandle>;
2089
+ hangup(callId: CallId): Promise<void>;
2090
+ supersede(callId: CallId): Promise<void>;
2091
+ }
2092
+ declare function createWebClientAudioProvider(options?: WebClientAudioProviderOptions): WebClientAudioProvider;
2093
+
2094
+ interface DeepgramSttProviderOptions {
2095
+ readonly apiKey: string;
2096
+ readonly url?: string;
2097
+ readonly allowUnknownModel?: boolean;
2098
+ readonly clock?: ProviderClock;
2099
+ readonly endpointingMs?: number;
2100
+ readonly vadEvents?: boolean;
2101
+ readonly punctuate?: boolean;
2102
+ readonly webSocketFactory?: (url: string, headers: Readonly<Record<string, string>>) => WebSocket;
2103
+ }
2104
+ declare class DeepgramSttProvider implements SpeechToTextProvider {
2105
+ #private;
2106
+ readonly name: "deepgram";
2107
+ readonly kind = "stt";
2108
+ readonly version = "0.1.0";
2109
+ readonly capabilities: {
2110
+ streaming: {
2111
+ input: true;
2112
+ output: true;
2113
+ native: true;
2114
+ };
2115
+ cancellation: {
2116
+ request: true;
2117
+ output: false;
2118
+ buffer: false;
2119
+ truncation: false;
2120
+ };
2121
+ transports: "websocket"[];
2122
+ languages: string[];
2123
+ audio: {
2124
+ input: {
2125
+ readonly encoding: "pcm_s16le";
2126
+ readonly sampleRateHz: 16000;
2127
+ readonly channels: 1;
2128
+ }[];
2129
+ };
2130
+ models: readonly ["nova-3", "nova-2"];
2131
+ turnDetection: ("stt_endpointing" | "vad")[];
2132
+ };
2133
+ constructor(options: DeepgramSttProviderOptions);
2134
+ open(request: SttOpenRequest): Promise<SttStream>;
2135
+ }
2136
+ declare function createDeepgramSttProvider(options: DeepgramSttProviderOptions): DeepgramSttProvider;
2137
+
2138
+ type SarvamOutputMode = "transcribe" | "translate" | "verbatim" | "translit" | "codemix";
2139
+ interface SarvamSttProviderOptions {
2140
+ readonly apiKey: string;
2141
+ readonly url?: string;
2142
+ readonly allowUnknownModel?: boolean;
2143
+ readonly mode?: SarvamOutputMode;
2144
+ readonly highVadSensitivity?: boolean;
2145
+ readonly vadSignals?: boolean;
2146
+ readonly flushSignal?: boolean;
2147
+ readonly inputAudioCodec?: "pcm_s16le" | "pcm_l16" | "pcm_raw";
2148
+ readonly clock?: ProviderClock;
2149
+ readonly webSocketFactory?: (url: string, headers: Readonly<Record<string, string>>) => WebSocket;
2150
+ }
2151
+ declare class SarvamSttProvider implements SpeechToTextProvider {
2152
+ #private;
2153
+ readonly name: "sarvam";
2154
+ readonly kind = "stt";
2155
+ readonly version = "0.1.0";
2156
+ readonly capabilities: {
2157
+ streaming: {
2158
+ input: true;
2159
+ output: true;
2160
+ native: true;
2161
+ };
2162
+ cancellation: {
2163
+ request: true;
2164
+ output: false;
2165
+ buffer: false;
2166
+ truncation: false;
2167
+ };
2168
+ transports: "websocket"[];
2169
+ languages: readonly ["hi-IN", "bn-IN", "gu-IN", "kn-IN", "ml-IN", "mr-IN", "od-IN", "pa-IN", "ta-IN", "te-IN", "as-IN", "ur-IN", "ne-IN", "kok-IN", "ks-IN", "sd-IN", "sa-IN", "sat-IN", "mni-IN", "brx-IN", "mai-IN", "doi-IN", "en-IN"];
2170
+ audio: {
2171
+ input: ({
2172
+ readonly encoding: "pcm_s16le";
2173
+ readonly sampleRateHz: 16000;
2174
+ readonly channels: 1;
2175
+ } | {
2176
+ readonly encoding: "pcm_s16le";
2177
+ readonly sampleRateHz: 8000;
2178
+ readonly channels: 1;
2179
+ })[];
2180
+ };
2181
+ models: readonly ["saaras:v3", "saaras:v4"];
2182
+ turnDetection: ("vad" | "manual")[];
2183
+ metadata: {
2184
+ interimResults: boolean;
2185
+ outputModes: string[];
2186
+ };
2187
+ };
2188
+ constructor(options: SarvamSttProviderOptions);
2189
+ open(request: SttOpenRequest): Promise<SttStream>;
2190
+ }
2191
+ declare function createSarvamSttProvider(options: SarvamSttProviderOptions): SarvamSttProvider;
2192
+
2193
+ type ElevenLabsSttCommitStrategy = "manual" | "vad";
2194
+ interface ElevenLabsSttProviderOptions {
2195
+ readonly apiKey: string;
2196
+ readonly url?: string;
2197
+ readonly allowUnknownModel?: boolean;
2198
+ readonly modelId?: string;
2199
+ readonly commitStrategy?: ElevenLabsSttCommitStrategy;
2200
+ readonly includeTimestamps?: boolean;
2201
+ readonly includeLanguageDetection?: boolean;
2202
+ readonly vadThreshold?: number;
2203
+ readonly vadSilenceThresholdSecs?: number;
2204
+ readonly clock?: ProviderClock;
2205
+ readonly webSocketFactory?: (url: string, headers: Readonly<Record<string, string>>) => WebSocket;
2206
+ }
2207
+ declare class ElevenLabsSttProvider implements SpeechToTextProvider {
2208
+ #private;
2209
+ readonly name: "elevenlabs-stt-realtime";
2210
+ readonly kind = "stt";
2211
+ readonly version = "0.1.0";
2212
+ readonly capabilities: {
2213
+ streaming: {
2214
+ input: true;
2215
+ output: true;
2216
+ native: true;
2217
+ };
2218
+ cancellation: {
2219
+ request: true;
2220
+ output: false;
2221
+ buffer: false;
2222
+ truncation: false;
2223
+ };
2224
+ transports: "websocket"[];
2225
+ audio: {
2226
+ input: readonly [{
2227
+ readonly encoding: "pcm_s16le";
2228
+ readonly sampleRateHz: 8000;
2229
+ readonly channels: 1;
2230
+ }, {
2231
+ readonly encoding: "pcm_s16le";
2232
+ readonly sampleRateHz: 16000;
2233
+ readonly channels: 1;
2234
+ }, {
2235
+ readonly encoding: "pcm_s16le";
2236
+ readonly sampleRateHz: 22050;
2237
+ readonly channels: 1;
2238
+ }, {
2239
+ readonly encoding: "pcm_s16le";
2240
+ readonly sampleRateHz: 24000;
2241
+ readonly channels: 1;
2242
+ }, {
2243
+ readonly encoding: "pcm_s16le";
2244
+ readonly sampleRateHz: 44100;
2245
+ readonly channels: 1;
2246
+ }, {
2247
+ readonly encoding: "pcm_s16le";
2248
+ readonly sampleRateHz: 48000;
2249
+ readonly channels: 1;
2250
+ }];
2251
+ };
2252
+ models: readonly ["scribe_v2_realtime"];
2253
+ turnDetection: ("vad" | "manual")[];
2254
+ metadata: {
2255
+ realtimeModel: string;
2256
+ partialTranscripts: boolean;
2257
+ };
2258
+ };
2259
+ constructor(options: ElevenLabsSttProviderOptions);
2260
+ open(request: SttOpenRequest): Promise<SttStream>;
2261
+ }
2262
+ declare function createElevenLabsSttProvider(options: ElevenLabsSttProviderOptions): ElevenLabsSttProvider;
2263
+
2264
+ interface AssemblyAiSttProviderOptions {
2265
+ readonly apiKey: string;
2266
+ readonly url?: string;
2267
+ readonly modelId?: string;
2268
+ readonly allowUnknownModel?: boolean;
2269
+ readonly formatTurns?: boolean;
2270
+ readonly prompt?: string;
2271
+ readonly languageDetection?: boolean;
2272
+ readonly minTurnSilenceMs?: number;
2273
+ readonly maxTurnSilenceMs?: number;
2274
+ readonly clock?: ProviderClock;
2275
+ readonly webSocketFactory?: (url: string, headers: Readonly<Record<string, string>>) => WebSocket;
2276
+ }
2277
+ declare class AssemblyAiSttProvider implements SpeechToTextProvider {
2278
+ #private;
2279
+ readonly name: "assemblyai-stt";
2280
+ readonly kind = "stt";
2281
+ readonly version = "0.1.0";
2282
+ readonly capabilities: {
2283
+ streaming: {
2284
+ input: true;
2285
+ output: true;
2286
+ native: true;
2287
+ };
2288
+ cancellation: {
2289
+ request: true;
2290
+ output: false;
2291
+ buffer: false;
2292
+ truncation: false;
2293
+ };
2294
+ transports: "websocket"[];
2295
+ audio: {
2296
+ input: {
2297
+ readonly encoding: "pcm_s16le";
2298
+ readonly sampleRateHz: 16000;
2299
+ readonly channels: 1;
2300
+ }[];
2301
+ };
2302
+ models: readonly ["u3-rt-pro"];
2303
+ turnDetection: "stt_endpointing"[];
2304
+ metadata: {
2305
+ realtimeModel: "u3-rt-pro";
2306
+ partialTranscripts: boolean;
2307
+ formattedTurns: boolean;
2308
+ };
2309
+ };
2310
+ constructor(options: AssemblyAiSttProviderOptions);
2311
+ open(request: SttOpenRequest): Promise<SttStream>;
2312
+ }
2313
+ declare function createAssemblyAiSttProvider(options: AssemblyAiSttProviderOptions): AssemblyAiSttProvider;
2314
+
2315
+ interface SonioxStructuredContext {
2316
+ readonly general?: readonly Readonly<{
2317
+ key: string;
2318
+ value: string;
2319
+ }>[];
2320
+ readonly text?: string;
2321
+ readonly terms?: readonly string[];
2322
+ }
2323
+ interface SonioxSttProviderOptions {
2324
+ readonly apiKey: string;
2325
+ readonly url?: string;
2326
+ readonly modelId?: string;
2327
+ readonly allowUnknownModel?: boolean;
2328
+ readonly context?: SonioxStructuredContext;
2329
+ readonly enableEndpointDetection?: boolean;
2330
+ readonly maxEndpointDelayMs?: number;
2331
+ readonly endpointSensitivity?: number;
2332
+ readonly endpointLatencyAdjustmentLevel?: number;
2333
+ readonly enableLanguageIdentification?: boolean;
2334
+ readonly clock?: ProviderClock;
2335
+ readonly webSocketFactory?: (url: string, headers: Readonly<Record<string, string>>) => WebSocket;
2336
+ }
2337
+ declare class SonioxSttProvider implements SpeechToTextProvider {
2338
+ #private;
2339
+ readonly name: "soniox-stt";
2340
+ readonly kind = "stt";
2341
+ readonly version = "0.1.0";
2342
+ readonly capabilities: {
2343
+ streaming: {
2344
+ input: true;
2345
+ output: true;
2346
+ native: true;
2347
+ };
2348
+ cancellation: {
2349
+ request: true;
2350
+ output: false;
2351
+ buffer: false;
2352
+ truncation: false;
2353
+ };
2354
+ transports: "websocket"[];
2355
+ audio: {
2356
+ input: readonly [{
2357
+ readonly encoding: "pcm_s16le";
2358
+ readonly sampleRateHz: 8000;
2359
+ readonly channels: 1;
2360
+ }, {
2361
+ readonly encoding: "pcm_s16le";
2362
+ readonly sampleRateHz: 16000;
2363
+ readonly channels: 1;
2364
+ }];
2365
+ };
2366
+ models: readonly ["stt-rt-v5"];
2367
+ turnDetection: ("stt_endpointing" | "manual")[];
2368
+ metadata: {
2369
+ realtimeModel: "stt-rt-v5";
2370
+ partialTranscripts: boolean;
2371
+ tokenFinality: boolean;
2372
+ };
2373
+ };
2374
+ constructor(options: SonioxSttProviderOptions);
2375
+ open(request: SttOpenRequest): Promise<SttStream>;
2376
+ }
2377
+ declare function createSonioxSttProvider(options: SonioxSttProviderOptions): SonioxSttProvider;
2378
+
2379
+ interface OpenAiResponsesLlmProviderOptions {
2380
+ readonly apiKey: string;
2381
+ readonly url?: string;
2382
+ readonly clock?: ProviderClock;
2383
+ }
2384
+ declare class OpenAiResponsesLlmProvider implements LLMProvider {
2385
+ #private;
2386
+ readonly name: "openai-responses";
2387
+ readonly kind = "llm";
2388
+ readonly version = "0.1.0";
2389
+ readonly capabilities: {
2390
+ streaming: {
2391
+ input: false;
2392
+ output: true;
2393
+ native: true;
2394
+ };
2395
+ cancellation: {
2396
+ request: true;
2397
+ output: true;
2398
+ buffer: false;
2399
+ truncation: false;
2400
+ };
2401
+ transports: ("http" | "sse")[];
2402
+ models: readonly ["gpt-5", "gpt-5-mini", "gpt-4.1", "gpt-4.1-mini"];
2403
+ tools: {
2404
+ functionCalling: true;
2405
+ parallelCalls: true;
2406
+ };
2407
+ };
2408
+ constructor(options: OpenAiResponsesLlmProviderOptions);
2409
+ complete(request: LlmCompletionRequest): Promise<LlmCompletion>;
2410
+ }
2411
+ declare function createOpenAiResponsesLlmProvider(options: OpenAiResponsesLlmProviderOptions): OpenAiResponsesLlmProvider;
2412
+
2413
+ interface CartesiaTtsProviderOptions {
2414
+ readonly apiKey: string;
2415
+ readonly url?: string;
2416
+ readonly voiceId: string;
2417
+ readonly modelId?: string;
2418
+ readonly language?: string;
2419
+ readonly clock?: ProviderClock;
2420
+ readonly webSocketFactory?: (url: string, headers: Readonly<Record<string, string>>) => WebSocket;
2421
+ }
2422
+ declare class CartesiaTtsProvider implements IncrementalTextToSpeechProvider {
2423
+ #private;
2424
+ readonly name: "cartesia";
2425
+ readonly kind = "tts";
2426
+ readonly version = "0.1.0";
2427
+ readonly capabilities: {
2428
+ streaming: {
2429
+ input: true;
2430
+ output: true;
2431
+ native: true;
2432
+ };
2433
+ cancellation: {
2434
+ request: true;
2435
+ output: false;
2436
+ buffer: false;
2437
+ truncation: false;
2438
+ };
2439
+ transports: "websocket"[];
2440
+ audio: {
2441
+ output: {
2442
+ readonly encoding: "pcm_s16le";
2443
+ readonly sampleRateHz: 16000;
2444
+ readonly channels: 1;
2445
+ }[];
2446
+ };
2447
+ models: readonly ["sonic-3", "sonic-2"];
2448
+ };
2449
+ constructor(options: CartesiaTtsProviderOptions);
2450
+ synthesize(request: TtsSynthesisRequest): Promise<TtsStream>;
2451
+ openSession(request: TtsSessionOpenRequest): Promise<TtsSession>;
2452
+ }
2453
+ declare function createCartesiaTtsProvider(options: CartesiaTtsProviderOptions): CartesiaTtsProvider;
2454
+
2455
+ /**
2456
+ * Vendor facts that change on the provider's release schedule rather than ours:
2457
+ * model catalogs and wire protocol versions.
2458
+ *
2459
+ * These deliberately live at the adapter edge instead of in `@tvic/core`. A model
2460
+ * list is not a runtime contract, it is dated evidence. Compiling it into core made
2461
+ * two things invisible: how old the claim was, and the fact that a vendor deprecating
2462
+ * a model silently turns `capabilities.models` into a false statement.
2463
+ *
2464
+ * Every entry carries the date it was verified and the document it came from.
2465
+ * Reverify before relying on an entry for a production or purchasing decision, and
2466
+ * treat a stale `verifiedAt` as a reason to omit a capability rather than assert it.
2467
+ */
2468
+ interface ProviderCatalogEntry {
2469
+ /** ISO date the model list below was last checked against `source`. */
2470
+ readonly verifiedAt: string;
2471
+ /** Official provider documentation the entry was read from. */
2472
+ readonly source: string;
2473
+ /** Model used when the caller does not choose one. */
2474
+ readonly defaultModel: string;
2475
+ /** Models this adapter is known to work with, not the vendor's full catalog. */
2476
+ readonly models: readonly string[];
2477
+ }
2478
+ /**
2479
+ * TVIC maturity labels are our release claim, not a vendor guarantee.
2480
+ * `experimental` means the adapter has deterministic contract coverage but
2481
+ * still needs a credential-gated live-service check before it can be called
2482
+ * stable in release notes.
2483
+ */
2484
+ type ProviderStability = "stable" | "experimental" | "deferred";
2485
+ declare const PROVIDER_STABILITY: Readonly<{
2486
+ readonly deepgram: "experimental";
2487
+ readonly sarvam: "experimental";
2488
+ readonly elevenlabsStt: "experimental";
2489
+ readonly assemblyai: "experimental";
2490
+ readonly soniox: "experimental";
2491
+ readonly openaiResponses: "experimental";
2492
+ readonly cartesia: "experimental";
2493
+ readonly elevenlabs: "experimental";
2494
+ readonly webClientAudio: "stable";
2495
+ readonly twilio: "stable";
2496
+ }>;
2497
+ declare const PROVIDER_CATALOG: {
2498
+ readonly deepgram: {
2499
+ readonly verifiedAt: "2026-07-24";
2500
+ readonly source: "https://developers.deepgram.com/docs/models-languages-overview";
2501
+ readonly defaultModel: "nova-3";
2502
+ readonly models: readonly ["nova-3", "nova-2"];
2503
+ };
2504
+ readonly sarvam: {
2505
+ readonly verifiedAt: "2026-08-20";
2506
+ readonly source: "https://docs.sarvam.ai/api-reference/legacy/speech-to-text/transcribe/ws";
2507
+ readonly defaultModel: "saaras:v3";
2508
+ readonly models: readonly ["saaras:v3", "saaras:v4"];
2509
+ };
2510
+ readonly cartesia: {
2511
+ readonly verifiedAt: "2026-07-24";
2512
+ readonly source: "https://docs.cartesia.ai/api-reference/tts/tts";
2513
+ readonly defaultModel: "sonic-3";
2514
+ readonly models: readonly ["sonic-3", "sonic-2"];
2515
+ };
2516
+ readonly elevenlabs: {
2517
+ readonly verifiedAt: "2026-07-24";
2518
+ readonly source: "https://elevenlabs.io/docs/models";
2519
+ readonly defaultModel: "eleven_flash_v2_5";
2520
+ readonly models: readonly ["eleven_flash_v2_5", "eleven_turbo_v2_5", "eleven_multilingual_v2"];
2521
+ };
2522
+ readonly elevenlabsStt: {
2523
+ readonly verifiedAt: "2026-08-20";
2524
+ readonly source: "https://elevenlabs.io/docs/api-reference/speech-to-text/v-1-speech-to-text-realtime";
2525
+ readonly defaultModel: "scribe_v2_realtime";
2526
+ readonly models: readonly ["scribe_v2_realtime"];
2527
+ };
2528
+ readonly assemblyai: {
2529
+ readonly verifiedAt: "2026-08-20";
2530
+ readonly source: "https://www.assemblyai.com/docs/streaming/message-sequence";
2531
+ readonly defaultModel: "u3-rt-pro";
2532
+ readonly models: readonly ["u3-rt-pro"];
2533
+ };
2534
+ readonly soniox: {
2535
+ readonly verifiedAt: "2026-08-20";
2536
+ readonly source: "https://soniox.com/docs/api-reference/stt/websocket-api";
2537
+ readonly defaultModel: "stt-rt-v5";
2538
+ readonly models: readonly ["stt-rt-v5"];
2539
+ };
2540
+ readonly openaiResponses: {
2541
+ readonly verifiedAt: "2026-07-24";
2542
+ readonly source: "https://platform.openai.com/docs/models";
2543
+ readonly defaultModel: "gpt-4.1-mini";
2544
+ readonly models: readonly ["gpt-5", "gpt-5-mini", "gpt-4.1", "gpt-4.1-mini"];
2545
+ };
2546
+ };
2547
+ /**
2548
+ * Provider wire protocol versions. Separate from the model catalog because a
2549
+ * protocol version changes adapter parsing, not just which model is selected.
2550
+ */
2551
+ declare const PROVIDER_API_VERSIONS: {
2552
+ readonly cartesia: "2026-03-01";
2553
+ };
2554
+ /**
2555
+ * TVIC's own adapter tuning. These are our decisions about how to drive a provider,
2556
+ * not facts about the provider, so they are not dated catalog entries.
2557
+ */
2558
+ declare const ADAPTER_DEFAULTS: {
2559
+ readonly deepgram: {
2560
+ readonly endpointingMs: 300;
2561
+ readonly vadEvents: true;
2562
+ readonly punctuate: true;
2563
+ };
2564
+ readonly sarvam: {
2565
+ readonly mode: "transcribe";
2566
+ readonly highVadSensitivity: true;
2567
+ readonly vadSignals: true;
2568
+ readonly flushSignal: true;
2569
+ readonly inputAudioCodec: "pcm_s16le";
2570
+ };
2571
+ readonly cartesia: {
2572
+ readonly language: "en";
2573
+ };
2574
+ };
2575
+
2576
+ interface ElevenLabsTtsProviderOptions {
2577
+ readonly apiKey: string;
2578
+ readonly voiceId: string;
2579
+ readonly modelId?: string;
2580
+ readonly language?: string;
2581
+ readonly url?: string;
2582
+ readonly stability?: number;
2583
+ readonly similarityBoost?: number;
2584
+ readonly clock?: ProviderClock;
2585
+ readonly webSocketFactory?: (url: string, headers: Readonly<Record<string, string>>) => WebSocket;
2586
+ }
2587
+ declare class ElevenLabsTtsProvider implements IncrementalTextToSpeechProvider {
2588
+ #private;
2589
+ readonly name: "elevenlabs";
2590
+ readonly kind = "tts";
2591
+ readonly version = "0.1.0";
2592
+ readonly capabilities: {
2593
+ streaming: {
2594
+ input: true;
2595
+ output: true;
2596
+ native: true;
2597
+ };
2598
+ cancellation: {
2599
+ request: true;
2600
+ output: false;
2601
+ buffer: false;
2602
+ truncation: false;
2603
+ };
2604
+ transports: "websocket"[];
2605
+ audio: {
2606
+ output: {
2607
+ readonly encoding: "pcm_s16le";
2608
+ readonly sampleRateHz: 16000;
2609
+ readonly channels: 1;
2610
+ }[];
2611
+ };
2612
+ models: readonly ["eleven_flash_v2_5", "eleven_turbo_v2_5", "eleven_multilingual_v2"];
2613
+ };
2614
+ constructor(options: ElevenLabsTtsProviderOptions);
2615
+ synthesize(request: TtsSynthesisRequest): Promise<TtsStream>;
2616
+ openSession(request: TtsSessionOpenRequest): Promise<TtsSession>;
2617
+ }
2618
+ declare function createElevenLabsTtsProvider(options: ElevenLabsTtsProviderOptions): ElevenLabsTtsProvider;
2619
+
2620
+ /**
2621
+ * A single param value or an array of repeated param values. Twilio sends
2622
+ * each value as a separate URL-encoded occurrence in the POST body, so a
2623
+ * key that appears N times in the request will appear N times here.
2624
+ */
2625
+ type TwilioParamValue = string | readonly string[];
2626
+ /**
2627
+ * The set of POST parameters signed by Twilio. Twilio sorts by key (NOT by
2628
+ * the order they appear in the body), and concatenates `key + value` for
2629
+ * each entry, with no separator.
2630
+ */
2631
+ type TwilioParams = Readonly<Record<string, TwilioParamValue>>;
2632
+ /**
2633
+ * The simplest form: a single string value per key. Most webhooks fit this
2634
+ * shape; use `TwilioParams` directly only when you need to handle repeated
2635
+ * keys.
2636
+ */
2637
+ type TwilioFlatParams = Readonly<Record<string, string>>;
2638
+ interface VerifyTwilioOptions {
2639
+ /** The X-Twilio-Signature header value. */
2640
+ readonly signature: string;
2641
+ /** The full request URL (with query string). */
2642
+ readonly url: string;
2643
+ /** The parsed POST params, as a key-value map. */
2644
+ readonly params: TwilioFlatParams | TwilioParams;
2645
+ /**
2646
+ * The Twilio auth token. Used as the HMAC key. Required.
2647
+ *
2648
+ * For token rotation, wrap the verifier yourself:
2649
+ * const ok = verifyTwilioSignature({...authTokenA}) ||
2650
+ * verifyTwilioSignature({...authTokenB});
2651
+ */
2652
+ readonly authToken: string;
2653
+ /**
2654
+ * When true, return true unconditionally. Use this ONLY in tests, never
2655
+ * in production. Defaults to false.
2656
+ */
2657
+ readonly skipVerification?: boolean;
2658
+ }
2659
+ declare function canonicalizeTwilioData(url: string, params: TwilioFlatParams | TwilioParams): Buffer;
2660
+ /**
2661
+ * Compute the expected signature for a given auth token, URL, and params.
2662
+ * Exposed for tests and golden fixtures. Production code should call
2663
+ * `verifyTwilioSignature` instead.
2664
+ */
2665
+ declare function computeTwilioSignature(authToken: string, url: string, params: TwilioFlatParams | TwilioParams): string;
2666
+ /**
2667
+ * Verify a Twilio X-Twilio-Signature header. Returns true if the signature
2668
+ * matches the configured auth token.
2669
+ *
2670
+ * Returns false (does not throw) for malformed inputs:
2671
+ * - missing or empty signature
2672
+ * - signature is not valid base64 of length 28
2673
+ * - signature decodes to a byte array that does not match the expected HMAC
2674
+ *
2675
+ * Throws (programmer error, not attacker error):
2676
+ * - when `authToken` is empty string
2677
+ *
2678
+ * @remarks
2679
+ * Twilio's signature does NOT include a timestamp or nonce. To prevent replay,
2680
+ * the caller MUST enforce an idempotency check (e.g. dedupe by `CallSid` +
2681
+ * `MessageSid` with a TTL).
2682
+ *
2683
+ * @example
2684
+ * ```ts
2685
+ * const sig = Array.isArray(req.headers["x-twilio-signature"])
2686
+ * ? req.headers["x-twilio-signature"][0]
2687
+ * : req.headers["x-twilio-signature"];
2688
+ * const ok = verifyTwilioSignature({
2689
+ * signature: sig,
2690
+ * url: `https://example.com${req.url}`,
2691
+ * params: parsedFormBody,
2692
+ * authToken: process.env.TWILIO_AUTH_TOKEN!,
2693
+ * });
2694
+ * if (!ok) return new Response("invalid signature", { status: 403 });
2695
+ * ```
2696
+ */
2697
+ declare function verifyTwilioSignature(options: VerifyTwilioOptions): boolean;
2698
+
2699
+ interface VerifyVoiceSessionTokenOptions {
2700
+ /** The token from the WebSocket upgrade query string. */
2701
+ readonly token: string | null | undefined;
2702
+ /** The expected session reference (e.g. "voice_<uuid>"). */
2703
+ readonly sessionRef: string;
2704
+ /** The expiry as a string of non-negative integer milliseconds. */
2705
+ readonly exp: string | null | undefined;
2706
+ /** The shared secret used to sign the token. */
2707
+ readonly secret: string;
2708
+ /** Optional: current time in ms. Defaults to Date.now. Injected for tests. */
2709
+ readonly now?: () => number;
2710
+ /**
2711
+ * Optional: tolerance window in ms for the expiry check. Default 0.
2712
+ * Use this if server and client clocks are skewed.
2713
+ */
2714
+ readonly toleranceMs?: number;
2715
+ /**
2716
+ * When true, return true unconditionally. Use ONLY in tests.
2717
+ */
2718
+ readonly skipVerification?: boolean;
2719
+ }
2720
+ /**
2721
+ * Compute the expected voice-session token for a given session ref
2722
+ * and expiry. Exposed for tests and the example app's token generator.
2723
+ */
2724
+ declare function signVoiceSessionToken(secret: string, sessionRef: string, expMs: number): string;
2725
+ /**
2726
+ * Verify a voice-session token. Returns true on success, false
2727
+ * otherwise. Does NOT throw for invalid input — callers should branch
2728
+ * on the boolean.
2729
+ *
2730
+ * @example
2731
+ * ```ts
2732
+ * const ok = verifyVoiceSessionToken({
2733
+ * token: url.searchParams.get("token"),
2734
+ * sessionRef: matchPathParams.url.sessionRef,
2735
+ * exp: url.searchParams.get("exp"),
2736
+ * secret: process.env.VOICE_TOKEN_SECRET!,
2737
+ * });
2738
+ * if (!ok) return new Response("invalid token", { status: 401 });
2739
+ * ```
2740
+ */
2741
+ declare function verifyVoiceSessionToken(options: VerifyVoiceSessionTokenOptions): boolean;
2742
+
2743
+ type RuntimeProvider = TelephonyProvider | SpeechToTextProvider | TextToSpeechProvider | LLMProvider;
2744
+ type ProviderForKind<K extends ProviderKind> = Extract<RuntimeProvider, {
2745
+ readonly kind: K;
2746
+ }>;
2747
+ declare function isProviderKind<K extends ProviderKind>(provider: RuntimeProvider, kind: K): provider is ProviderForKind<K>;
2748
+ type AudioFormatDirection = "input" | "output";
2749
+ declare function supportsAudioFormat(provider: Provider, format: AudioFormat, direction: AudioFormatDirection): boolean;
2750
+ declare function supportsLanguage(provider: Provider, language: string): boolean;
2751
+ declare function supportsModel(provider: Provider, model: string): boolean;
2752
+ declare function requireProviderKind<K extends ProviderKind>(provider: RuntimeProvider, kind: K): ProviderForKind<K>;
2753
+
2754
+ interface DefineToolInput<TInput, TOutput> {
2755
+ readonly id: string;
2756
+ readonly name: string;
2757
+ readonly description: string;
2758
+ readonly version?: string;
2759
+ readonly inputSchema: JsonSchemaDocument;
2760
+ readonly outputSchema?: JsonSchemaDocument;
2761
+ readonly timeout?: TimeoutPolicy;
2762
+ readonly retry?: RetryPolicy;
2763
+ readonly idempotency?: IdempotencyPolicy;
2764
+ readonly authScope?: readonly string[];
2765
+ readonly tags?: readonly string[];
2766
+ readonly metadata?: Readonly<Record<string, unknown>>;
2767
+ readonly execute: ToolExecutor<TInput, TOutput>;
2768
+ }
2769
+ declare function defineTool<TInput = unknown, TOutput = unknown>(input: DefineToolInput<TInput, TOutput>): ToolDefinition<TInput, TOutput>;
2770
+
2771
+ interface DefineAgentInput {
2772
+ readonly id: string;
2773
+ readonly name: string;
2774
+ readonly version?: string;
2775
+ readonly instructions: string;
2776
+ readonly tools: readonly ToolDefinition[];
2777
+ readonly providers: AgentProviders;
2778
+ readonly audioPolicy: AgentAudioPolicy;
2779
+ readonly memoryPolicy?: AgentMemoryPolicy;
2780
+ readonly contextPolicy?: AgentContextPolicy;
2781
+ readonly interruptionPolicy?: InterruptionPolicy;
2782
+ readonly timeoutPolicy?: TimeoutPolicy;
2783
+ /** Per-tenant context resolution. Optional. */
2784
+ readonly persona?: PersonaConfig;
2785
+ readonly metadata?: Readonly<Record<string, unknown>>;
2786
+ }
2787
+ declare function defineAgent(input: DefineAgentInput): Agent;
2788
+
2789
+ declare function createRuntime(options?: RuntimeOptions): Runtime;
2790
+
2791
+ interface ResolvePreCallContextInput {
2792
+ readonly memory: Memory;
2793
+ readonly resolver?: PreCallContextResolver;
2794
+ readonly userId?: UserId;
2795
+ readonly organizationId?: OrganizationId;
2796
+ readonly workflowId?: WorkflowId;
2797
+ readonly sessionId: SessionId;
2798
+ readonly clock: () => number;
2799
+ readonly timeoutMs?: number;
2800
+ /** Scopes permitted by the agent's memory policy. Omit to use all non-session scopes. */
2801
+ readonly scopes?: readonly MemoryScope[];
2802
+ /** Optional kind filter from `memoryPolicy.preCallLoad`. */
2803
+ readonly kind?: MemoryKind;
2804
+ /** Maximum entries read from each memory scope. */
2805
+ readonly maxEntries?: number;
2806
+ /** Maximum bytes rendered for the combined static and memory context. */
2807
+ readonly maxBytes?: number;
2808
+ /**
2809
+ * Optional provider of the static (non-memory) context. The default
2810
+ * resolver returns an empty static map; the user-provided resolver
2811
+ * (via `options.preCallContextResolver`) is the place to inject CRM,
2812
+ * feature flags, and tenant config.
2813
+ */
2814
+ readonly staticProvider?: () => Promise<ReadonlyMap<string, string>>;
2815
+ }
2816
+ declare function resolvePreCallContext(input: ResolvePreCallContextInput): Promise<PreCallContext>;
2817
+ /**
2818
+ * Format the pre-call context as a system-prompt block. Renders the
2819
+ * `<memory>...</memory>` block and (if non-empty or degraded) the
2820
+ * `<context>...</context>` block above it.
2821
+ */
2822
+ declare function formatPreCallContextAsSystemBlock(context: PreCallContext, maxBytes?: number): string;
2823
+ /**
2824
+ * @deprecated Use `resolvePreCallContext` and `formatPreCallContextAsSystemBlock`.
2825
+ * Kept for one compatibility release as a memory-only projection.
2826
+ */
2827
+ declare function resolvePreCallMemory(input: ResolvePreCallContextInput): Promise<PreCallMemoryContext>;
2828
+ declare function formatMemoryContextAsSystemBlock(context: PreCallMemoryContext, maxBytes?: number): string;
2829
+
2830
+ /**
2831
+ * Fails before a live session starts when the configured memory adapter cannot
2832
+ * provide the behavior the agent's policy promises. Memory adapters remain
2833
+ * pluggable; the adapter's instance-level declaration is the source of truth.
2834
+ */
2835
+ declare function assertMemoryPolicySupported(memory: Memory, policy: AgentMemoryPolicy): void;
2836
+ declare function assertMemoryCapability(memory: Memory, capability: "search.exact" | "write.explicit" | "write.implicit" | "write.sessionQuota" | "purge.perScope", operation: string): void;
2837
+
2838
+ interface SessionActivator {
2839
+ activate(input: {
2840
+ readonly sessionId: SessionId;
2841
+ readonly agent: Agent;
2842
+ readonly attachment: SessionAttachment;
2843
+ }): Promise<void>;
2844
+ }
2845
+ interface SessionRecoveryCoordinatorOptions {
2846
+ readonly runtime: Runtime;
2847
+ readonly durableStore: DurableRuntimeStore;
2848
+ readonly resolveAgent: (agentId: AgentId) => Promise<Agent | null>;
2849
+ readonly hasReconnectableTransport: (sessionId: SessionId) => Promise<boolean>;
2850
+ readonly activator: SessionActivator;
2851
+ readonly holderId: string;
2852
+ readonly policy?: Partial<DurableRuntimePolicy>;
2853
+ readonly pageSize?: number;
2854
+ readonly nowMs?: () => number;
2855
+ readonly onMetric?: (metric: {
2856
+ readonly name: string;
2857
+ readonly value: number;
2858
+ }) => void;
2859
+ }
2860
+ interface RecoveryPollResult {
2861
+ readonly candidates: number;
2862
+ readonly attached: number;
2863
+ readonly failed: number;
2864
+ }
2865
+ declare class SessionRecoveryCoordinator {
2866
+ #private;
2867
+ constructor(options: SessionRecoveryCoordinatorOptions);
2868
+ start(): void;
2869
+ stop(): void;
2870
+ pollOnce(): Promise<RecoveryPollResult>;
2871
+ }
2872
+ interface SessionReaperOptions {
2873
+ readonly runtime: Runtime;
2874
+ readonly durableStore: DurableRuntimeStore;
2875
+ readonly resolveAgent: (agentId: AgentId) => Promise<Agent | null>;
2876
+ readonly holderId: string;
2877
+ readonly hasReconnectableTransport: (sessionId: SessionId) => Promise<boolean>;
2878
+ readonly policy?: Partial<DurableRuntimePolicy>;
2879
+ readonly pageSize?: number;
2880
+ readonly nowMs?: () => number;
2881
+ readonly onMetric?: (metric: {
2882
+ readonly name: string;
2883
+ readonly value: number;
2884
+ }) => void;
2885
+ }
2886
+ declare class SessionReaper {
2887
+ #private;
2888
+ constructor(options: SessionReaperOptions);
2889
+ start(): void;
2890
+ stop(): void;
2891
+ reapOnce(): Promise<number>;
2892
+ }
2893
+
2894
+ interface ConversationPolicyOptions {
2895
+ readonly agent: Agent;
2896
+ /**
2897
+ * Pre-call context loaded from the runtime before the call starts.
2898
+ * Contains both the caller's prior memory (rendered as
2899
+ * `<memory>...</memory>`) and any non-memory static context like
2900
+ * CRM records and feature flags (rendered as `<context>...</context>`).
2901
+ */
2902
+ readonly preCallContext?: PreCallContext;
2903
+ /** Optional per-instance override for the agent's live context byte limit. */
2904
+ readonly maxHistoryBytes?: number;
2905
+ /** Optional per-instance override for the agent's live context message limit. */
2906
+ readonly maxHistoryMessages?: number;
2907
+ /** Optional per-instance override for the pre-call context byte limit. */
2908
+ readonly maxPreCallBytes?: number;
2909
+ }
2910
+ declare class ConversationPolicy {
2911
+ #private;
2912
+ constructor(options: ConversationPolicyOptions);
2913
+ hydrateTurns(turns: readonly Turn[]): void;
2914
+ /**
2915
+ * Set the session-level system instruction. Pre-call context and variables
2916
+ * remain attached because the system message is rebuilt from canonical state.
2917
+ */
2918
+ replaceSystemInstruction(instruction: string): void;
2919
+ /**
2920
+ * Set or clear a per-turn instruction. Clearing it restores the session-level
2921
+ * instruction without losing the tenant variables or pre-call context.
2922
+ */
2923
+ setTurnSystemInstruction(instruction: string | undefined): void;
2924
+ /**
2925
+ * Replace the static variables map. Values are substituted into `{{name}}`
2926
+ * placeholders and also rendered in a dedicated block so the model can see
2927
+ * variables that the base instruction did not reference explicitly.
2928
+ */
2929
+ setStaticVariables(variables: ReadonlyMap<string, string>): void;
2930
+ acceptTranscript(event: TranscriptEvent): string | null;
2931
+ get hasBufferedTranscript(): boolean;
2932
+ /** Commits buffered final segments for an endpoint, timeout, or stream shutdown. */
2933
+ flushBufferedTranscript(): string | null;
2934
+ messagesForTranscript(transcript: string): readonly LlmMessage[];
2935
+ messagesForToolContinuation(messages: readonly LlmMessage[], assistantText: string, toolMessages: readonly LlmMessage[]): readonly LlmMessage[];
2936
+ recordTurn(transcript: string, assistantText: string): void;
2937
+ recordInterruptedTurn(transcript: string, assistantText: string): void;
2938
+ findTool(call: LlmInlineToolCall): ToolDefinition | null;
2939
+ }
2940
+
2941
+ interface SttCommandController {
2942
+ /** Resolves only when this controller reaches a terminal state. */
2943
+ readonly failure: Promise<never>;
2944
+ /** Admit audio without waiting for provider I/O. */
2945
+ admitAudio(chunk: InputAudioChunk): Promise<void>;
2946
+ /** Admit an ordered barrier and settle it after provider command acceptance. */
2947
+ admitCommit(): Promise<void>;
2948
+ /** Drain admitted commands in order and then close the stream. */
2949
+ drain(): Promise<void>;
2950
+ /** Abandon queued work and close the stream immediately. */
2951
+ abort(error?: unknown): Promise<void>;
2952
+ }
2953
+
2954
+ type SttRecoveryState = "healthy" | "recovering" | "opening" | "probationary" | "failed" | "closed";
2955
+ interface SttRecoveryControl {
2956
+ readonly controller: SttCommandController;
2957
+ readonly state: () => SttRecoveryState;
2958
+ subscribe(listener: (state: SttRecoveryState) => void): () => void;
2959
+ }
2960
+ declare function getSttRecoveryControl(stream: SttStream): SttRecoveryControl | undefined;
2961
+
2962
+ interface SttReconnectOptions {
2963
+ readonly maxAttempts?: number;
2964
+ readonly connectTimeoutMs?: number;
2965
+ readonly maxRecoveryDurationMs?: number;
2966
+ readonly initialBackoffMs?: number;
2967
+ readonly maxBackoffMs?: number;
2968
+ readonly jitter?: boolean;
2969
+ readonly stableUptimeMs?: number;
2970
+ readonly maxQuickFailures?: number;
2971
+ readonly uncertainWindowMs?: number;
2972
+ readonly maxBufferedBytes?: number;
2973
+ readonly maxBufferedCommands?: number;
2974
+ readonly commitTimeoutMs?: number;
2975
+ }
2976
+ /**
2977
+ * Wraps one STT provider with bounded, best-effort reconnect and replay policy.
2978
+ * The wrapper is deliberately runtime-owned: adapters only need to expose honest
2979
+ * transport and timestamp semantics.
2980
+ */
2981
+ declare function withSttReconnect(provider: SpeechToTextProvider, options?: SttReconnectOptions): SpeechToTextProvider;
2982
+
2983
+ interface AssistantTextRecord {
2984
+ readonly sessionId: SessionId;
2985
+ readonly turnId: TurnId;
2986
+ readonly sequence: number;
2987
+ readonly status: "completed" | "cancelled" | "failed";
2988
+ readonly text: string;
2989
+ readonly delivered?: boolean;
2990
+ readonly audioError?: NormalizedError;
2991
+ }
2992
+
2993
+ type TextDeliveryMode = "auto" | "always" | "never";
2994
+ interface TextDeliveryDecision {
2995
+ readonly mode?: TextDeliveryMode;
2996
+ readonly audioDelivered: boolean;
2997
+ readonly hasTransport: boolean;
2998
+ readonly cancelledByBargeIn: boolean;
2999
+ }
3000
+ /** Decides whether participant-visible text should be attempted for a terminal turn. */
3001
+ declare function shouldDeliverText(decision: TextDeliveryDecision): boolean;
3002
+ declare function deliverAssistantText(options: {
3003
+ readonly callHandle: CallHandle;
3004
+ readonly turn: Turn;
3005
+ readonly text: string;
3006
+ readonly mode?: TextDeliveryMode;
3007
+ readonly audioDelivered: boolean;
3008
+ readonly cancelledByBargeIn: boolean;
3009
+ }): Promise<boolean | undefined>;
3010
+
3011
+ /**
3012
+ * Public-facing event emitted by a voice pipeline run. A discriminated
3013
+ * union of 8 kinds so consumers can `switch (event.kind)` without
3014
+ * runtime guessing.
3015
+ *
3016
+ * The shape is intentionally a *friendlier* view than the internal
3017
+ * `MediaEvent`. The runtime's `MediaEvent` includes IDs, sequence
3018
+ * numbers, and provider metadata; `VoiceEvent` strips those and exposes
3019
+ * the same data in a UX-friendly form.
3020
+ */
3021
+ type VoiceEvent = {
3022
+ readonly kind: "transcript_delta";
3023
+ readonly text: string;
3024
+ readonly turnId: TurnId;
3025
+ readonly isFinal: boolean;
3026
+ } | {
3027
+ readonly kind: "audio_output";
3028
+ readonly bytes: Uint8Array;
3029
+ readonly turnId: TurnId;
3030
+ readonly sequence: number;
3031
+ } | {
3032
+ readonly kind: "turn_started";
3033
+ readonly turnId: TurnId;
3034
+ readonly turnSequence: number;
3035
+ } | {
3036
+ readonly kind: "turn_completed";
3037
+ readonly turnId: TurnId;
3038
+ readonly status: "completed" | "cancelled" | "failed";
3039
+ readonly latencyMs: number;
3040
+ } | {
3041
+ readonly kind: "tool_call";
3042
+ readonly toolCallId: ToolCallId;
3043
+ readonly toolName: string;
3044
+ readonly input: unknown;
3045
+ } | {
3046
+ readonly kind: "tool_result";
3047
+ readonly toolCallId: ToolCallId;
3048
+ readonly output: unknown;
3049
+ readonly latencyMs: number;
3050
+ } | {
3051
+ readonly kind: "error";
3052
+ readonly error: NormalizedError;
3053
+ readonly recoverable: boolean;
3054
+ } | {
3055
+ readonly kind: "call_ended";
3056
+ readonly reason: "completed" | "cancelled" | "failed" | "remote_hangup";
3057
+ readonly totalTurns: number;
3058
+ };
3059
+ /**
3060
+ * The result of starting a `PipelineVoiceLoop`. Dual-protocol: awaits
3061
+ * to a final `PipelineVoiceLoopResult` AND iterates the per-event
3062
+ * stream.
3063
+ *
3064
+ * Implementation note: the class implements both `PromiseLike<PipelineVoiceLoopResult>`
3065
+ * and `AsyncIterable<VoiceEvent>`. Awaiting and iterating are independent
3066
+ * — the run starts on the first await/iteration. Subsequent awaits share
3067
+ * the same promise. Iteration drains buffered events before completing.
3068
+ */
3069
+ interface DualProtocolResult extends PromiseLike<PipelineVoiceLoopResult> {
3070
+ then<TResult1 = PipelineVoiceLoopResult, TResult2 = never>(onfulfilled?: ((value: PipelineVoiceLoopResult) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null | undefined): PromiseLike<TResult1 | TResult2>;
3071
+ catch<TResult = never>(onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null | undefined): PromiseLike<PipelineVoiceLoopResult | TResult>;
3072
+ finally(onfinally?: (() => void) | null | undefined): PromiseLike<PipelineVoiceLoopResult>;
3073
+ [Symbol.asyncIterator](): AsyncIterator<VoiceEvent>;
3074
+ /**
3075
+ * The session ID this result is for. Exposed for consumer convenience
3076
+ * (e.g., logging).
3077
+ */
3078
+ readonly sessionId: SessionId;
3079
+ }
3080
+ /**
3081
+ * Backward-compat alias. Older callers reference `PipelineVoiceLoopResultLike`
3082
+ * directly; new callers should use `PipelineVoiceLoopResult`.
3083
+ *
3084
+ * @deprecated Use `PipelineVoiceLoopResult` from `@tvic/runtime` instead.
3085
+ */
3086
+ type PipelineVoiceLoopResultLike = PipelineVoiceLoopResult;
3087
+
3088
+ /**
3089
+ * Fluent builder returned by {@link PipelineVoiceLoop.start}. Supports
3090
+ * `.abortSignal(sig)` to attach a per-call cancellation signal. Awaiting
3091
+ * the builder yields the final {@link PipelineVoiceLoopResult}; iterating
3092
+ * it exposes the per-event stream.
3093
+ *
3094
+ * Multiple `then` / `catch` / `finally` / `await` calls share a single
3095
+ * underlying run. Breaking out of the iteration cancels the run via the
3096
+ * supervisor controller.
3097
+ *
3098
+ * The double-set guard rejects a second `abortSignal` call. To compose
3099
+ * multiple signals, use `AbortSignal.any([a, b])` (Node 20+) before
3100
+ * passing them in.
3101
+ */
3102
+ declare class PipelineVoiceLoopBuilder {
3103
+ #private;
3104
+ constructor(loop: PipelineVoiceLoopLike, overrideSignal?: AbortSignal);
3105
+ /**
3106
+ * Attach an `AbortSignal` that cancels the run when triggered. Composed
3107
+ * with `attachment.signal` (both fire to cancel; the lease-loss reason
3108
+ * on the attachment is preserved). Throws if called twice on the same
3109
+ * builder.
3110
+ */
3111
+ abortSignal(signal: AbortSignal): this;
3112
+ then<TResult1 = PipelineVoiceLoopResultLike, TResult2 = never>(onfulfilled?: ((value: PipelineVoiceLoopResultLike) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null | undefined): PromiseLike<TResult1 | TResult2>;
3113
+ catch<TResult = never>(onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null | undefined): PromiseLike<PipelineVoiceLoopResultLike | TResult>;
3114
+ finally(onfinally?: (() => void) | null | undefined): PromiseLike<PipelineVoiceLoopResultLike>;
3115
+ [Symbol.asyncIterator](): AsyncIterator<VoiceEvent>;
3116
+ }
3117
+ /**
3118
+ * Subset of the `PipelineVoiceLoop` API the builder needs. Declared as
3119
+ * a structural type so the builder can live in its own file without
3120
+ * creating a circular import. The full `PipelineVoiceLoop` class
3121
+ * implements this.
3122
+ */
3123
+ interface PipelineVoiceLoopLike {
3124
+ _startInternal(options: {
3125
+ readonly overrideSignal?: AbortSignal;
3126
+ }): DualProtocolResult;
3127
+ }
3128
+
3129
+ interface TurnLatencyRecord {
3130
+ readonly sessionId: SessionId;
3131
+ readonly turnId: TurnId;
3132
+ readonly sequence: number;
3133
+ readonly status: "completed" | "cancelled" | "failed";
3134
+ readonly latency: TurnLatency;
3135
+ }
3136
+
3137
+ interface PipelineVoiceLoopOptions {
3138
+ readonly runtime: Runtime;
3139
+ readonly session: ActiveSession;
3140
+ readonly attachment?: SessionAttachment;
3141
+ readonly agent: Agent;
3142
+ readonly callHandle: CallHandle;
3143
+ readonly llmModel: string;
3144
+ readonly sttModel?: string;
3145
+ /** Allows a custom/self-hosted STT endpoint to accept a model outside TVIC's dated catalog. */
3146
+ readonly sttAllowUnknownModel?: boolean;
3147
+ readonly sttReconnect?: boolean | SttReconnectOptions;
3148
+ readonly sttLanguage?: string;
3149
+ readonly ttsVoice?: string;
3150
+ readonly ttsModel?: string;
3151
+ readonly idGenerator?: IdGenerator;
3152
+ readonly conversationPolicy?: ConversationPolicy;
3153
+ readonly memory?: Memory;
3154
+ readonly memoryUserId?: UserId;
3155
+ readonly organizationId?: OrganizationId;
3156
+ readonly workflowId?: WorkflowId;
3157
+ readonly preCallContext?: PreCallContext;
3158
+ readonly safetyIdentifier?: string;
3159
+ readonly textDelivery?: TextDeliveryMode;
3160
+ /** Max ms a provider stream may stall (no events) before the turn fails. */
3161
+ readonly streamStallTimeoutMs?: number;
3162
+ /** Inactivity debounce after the latest immutable final segment. */
3163
+ readonly turnEndpointTimeoutMs?: number;
3164
+ /** Absolute cap from the first immutable final segment in one utterance. */
3165
+ readonly turnMaxDurationMs?: number;
3166
+ /**
3167
+ * Receives one record per terminal turn. This is an observation seam, not part of
3168
+ * execution: it is invoked after the turn is already terminal, its return value is
3169
+ * ignored, and a throw from it is swallowed so an observer can never affect a call.
3170
+ */
3171
+ readonly onTurnLatency?: (record: TurnLatencyRecord) => void;
3172
+ readonly onAssistantText?: (record: AssistantTextRecord) => void;
3173
+ readonly sessionMetricsRecorder?: SessionMetricsRecorder;
3174
+ }
3175
+ interface PipelineVoiceLoopResult {
3176
+ readonly session: ActiveSession;
3177
+ readonly turnsHandled: number;
3178
+ readonly interruptions: number;
3179
+ readonly turnsFailed: number;
3180
+ readonly firstTurnError: NormalizedError | null;
3181
+ }
3182
+ /**
3183
+ * Runs a pipeline voice session: media -> STT -> LLM/tools -> TTS -> media.
3184
+ * This module owns only live execution and failure handling; telemetry belongs
3185
+ * outside the runtime's critical path.
3186
+ */
3187
+ declare class PipelineVoiceLoop {
3188
+ #private;
3189
+ constructor(options: PipelineVoiceLoopOptions);
3190
+ /** Start a call and expose an awaitable, iterable result. */
3191
+ start(options?: {
3192
+ readonly overrideSignal?: AbortSignal;
3193
+ }): PipelineVoiceLoopBuilder;
3194
+ /**
3195
+ * Legacy: returns `Promise<PipelineVoiceLoopResult>`. Equivalent to
3196
+ * `await this.start(options)`. Convenience wrapper preserved for backward
3197
+ * compatibility with the v0.0.x API.
3198
+ */
3199
+ run(options?: {
3200
+ readonly overrideSignal?: AbortSignal;
3201
+ }): Promise<PipelineVoiceLoopResult>;
3202
+ /**
3203
+ * Implementation hook for {@link PipelineVoiceLoopBuilder}. Returns a
3204
+ * `DualProtocolResult` that wraps the run promise and event queue.
3205
+ */
3206
+ _startInternal(options: {
3207
+ readonly overrideSignal?: AbortSignal;
3208
+ }): DualProtocolResult;
3209
+ }
3210
+
3211
+ type AudioNormalizationMode = "auto" | "never";
3212
+ interface SttSessionInputOptions {
3213
+ readonly format: AudioFormat;
3214
+ readonly normalization?: AudioNormalizationMode;
3215
+ }
3216
+ interface SttSessionOptions {
3217
+ readonly provider: SpeechToTextProvider;
3218
+ readonly sessionId?: SessionId;
3219
+ /** The exact format opened with the provider. */
3220
+ readonly format: AudioFormat;
3221
+ /** Optional source format. Source bytes are normalized to `format` when enabled. */
3222
+ readonly input?: SttSessionInputOptions;
3223
+ readonly model?: string;
3224
+ /** Allows a custom/self-hosted STT endpoint to accept a model outside the dated catalog. */
3225
+ readonly allowUnknownModel?: boolean;
3226
+ readonly language?: string;
3227
+ readonly interimResults?: boolean;
3228
+ readonly vocabulary?: readonly string[];
3229
+ readonly metadata?: Readonly<Record<string, unknown>>;
3230
+ readonly openTimeoutMs?: number;
3231
+ readonly signal?: AbortSignal;
3232
+ readonly clock?: Clock;
3233
+ readonly idGenerator?: IdGenerator;
3234
+ readonly sttReconnect?: boolean | SttReconnectOptions;
3235
+ }
3236
+ interface SttSession {
3237
+ readonly sessionId: SessionId;
3238
+ readonly format: AudioFormat;
3239
+ readonly inputFormat: AudioFormat;
3240
+ readonly events: AsyncIterable<TranscriptEvent>;
3241
+ pushAudioChunk(chunk: InputAudioChunk): Promise<void>;
3242
+ pushAudio(bytes: Uint8Array, format: AudioFormat, options?: {
3243
+ readonly monotonicOffsetMs?: number;
3244
+ }): Promise<void>;
3245
+ pushPcm16(bytes: Uint8Array, options?: {
3246
+ readonly monotonicOffsetMs?: number;
3247
+ }): Promise<void>;
3248
+ commit(): Promise<void>;
3249
+ close(): Promise<void>;
3250
+ }
3251
+ declare function createSttSession(options: SttSessionOptions): Promise<SttSession>;
3252
+
3253
+ type UpgradeAuthorization<TContext> = {
3254
+ readonly ok: true;
3255
+ readonly context: TContext;
3256
+ } | {
3257
+ readonly ok: false;
3258
+ readonly statusCode: number;
3259
+ readonly reason?: string;
3260
+ };
3261
+ interface NodeMediaPlaneConnection<TContext = unknown> {
3262
+ readonly socket: WebSocket;
3263
+ readonly request: IncomingMessage;
3264
+ readonly url: URL;
3265
+ readonly params: Readonly<Record<string, string>>;
3266
+ readonly upgradeContext?: TContext;
3267
+ }
3268
+ type NodeMediaPlaneConnectionHandler<TContext = unknown> = (connection: NodeMediaPlaneConnection<TContext>) => Promise<void> | void;
3269
+ type NodeMediaPlaneRequestHandler = (request: IncomingMessage, response: ServerResponse) => boolean | Promise<boolean>;
3270
+ type NodeMediaPlaneConnectionErrorHandler = (error: unknown, socket: WebSocket) => void | Promise<void>;
3271
+ interface NodeMediaPlaneOptions<TContext = unknown> {
3272
+ readonly host?: string;
3273
+ readonly port: number;
3274
+ readonly path: string;
3275
+ readonly healthPath?: string;
3276
+ /**
3277
+ * Optional health check. Called when the plane's `healthPath` endpoint
3278
+ * is hit. The plane responds 200 with `{ ok: true }` if the check
3279
+ * returns `ok: true`, and 503 with the check details otherwise. This
3280
+ * is where the runtime's `RuntimeOptions.healthCheck` plugs in so
3281
+ * the load balancer can drain traffic before a replica's Postgres
3282
+ * becomes unreachable. See `docs/operations/multi-replica.md`.
3283
+ */
3284
+ readonly healthCheck?: () => Promise<{
3285
+ readonly ok: boolean;
3286
+ readonly checks?: Readonly<Record<string, unknown>>;
3287
+ }>;
3288
+ readonly onConnection: NodeMediaPlaneConnectionHandler<TContext>;
3289
+ /** Handle non-WebSocket HTTP requests (e.g. the Twilio TwiML webhook). Return true if handled. */
3290
+ readonly onRequest?: NodeMediaPlaneRequestHandler;
3291
+ /** Routes failures from an async onConnection handler. Defaults to closing the socket. */
3292
+ readonly onConnectionError?: NodeMediaPlaneConnectionErrorHandler;
3293
+ /** Synchronous, pre-handshake authorization. Throwing rejects with HTTP 500. */
3294
+ readonly authorizeUpgrade?: (request: IncomingMessage, url: URL, params: Readonly<Record<string, string>>) => UpgradeAuthorization<TContext>;
3295
+ /** Releases state reserved by a successful authorization if the handshake aborts. */
3296
+ readonly onUpgradeAborted?: (context: TContext) => void;
3297
+ }
3298
+ declare class NodeMediaPlane<TContext = unknown> {
3299
+ #private;
3300
+ constructor(options: NodeMediaPlaneOptions<TContext>);
3301
+ get isRunning(): boolean;
3302
+ get address(): AddressInfo | null;
3303
+ start(): Promise<void>;
3304
+ stop(): Promise<void>;
3305
+ }
3306
+ declare function createNodeMediaPlane<TContext = unknown>(options: NodeMediaPlaneOptions<TContext>): NodeMediaPlane<TContext>;
3307
+ declare function matchPath(pattern: string, pathname: string): Readonly<Record<string, string>> | null;
3308
+
3309
+ type WithoutApiKey<T> = Omit<T, "apiKey">;
3310
+ type DeepgramProviderConfig = WithoutApiKey<DeepgramSttProviderOptions> & {
3311
+ readonly provider: "deepgram";
3312
+ readonly apiKey?: string;
3313
+ readonly model?: string;
3314
+ };
3315
+ type SarvamProviderConfig = WithoutApiKey<SarvamSttProviderOptions> & {
3316
+ readonly provider: "sarvam";
3317
+ readonly apiKey?: string;
3318
+ readonly model?: string;
3319
+ };
3320
+ type ElevenLabsSttProviderConfig = Omit<ElevenLabsSttProviderOptions, "apiKey" | "modelId"> & {
3321
+ readonly provider: "elevenlabs";
3322
+ readonly apiKey?: string;
3323
+ readonly model?: string;
3324
+ };
3325
+ type AssemblyAiProviderConfig = Omit<AssemblyAiSttProviderOptions, "apiKey" | "modelId"> & {
3326
+ readonly provider: "assemblyai";
3327
+ readonly apiKey?: string;
3328
+ readonly model?: string;
3329
+ };
3330
+ type SonioxProviderConfig = Omit<SonioxSttProviderOptions, "apiKey" | "modelId"> & {
3331
+ readonly provider: "soniox";
3332
+ readonly apiKey?: string;
3333
+ readonly model?: string;
3334
+ };
3335
+ type SttProviderConfig = DeepgramProviderConfig | SarvamProviderConfig | ElevenLabsSttProviderConfig | AssemblyAiProviderConfig | SonioxProviderConfig;
3336
+ type OpenAiProviderConfig = Omit<OpenAiResponsesLlmProviderOptions, "apiKey"> & {
3337
+ readonly provider: "openai" | "openai-responses";
3338
+ readonly apiKey?: string;
3339
+ readonly model?: string;
3340
+ /** Allows a custom OpenAI-compatible endpoint to use a model outside TVIC's dated catalog. */
3341
+ readonly allowUnknownModel?: boolean;
3342
+ };
3343
+ type LlmProviderConfig = OpenAiProviderConfig;
3344
+ type CartesiaProviderConfig = Omit<CartesiaTtsProviderOptions, "apiKey" | "voiceId" | "modelId"> & {
3345
+ readonly provider: "cartesia";
3346
+ readonly apiKey?: string;
3347
+ readonly model?: string;
3348
+ readonly voiceId?: string;
3349
+ };
3350
+ type ElevenLabsTtsProviderConfig = Omit<ElevenLabsTtsProviderOptions, "apiKey" | "voiceId" | "modelId"> & {
3351
+ readonly provider: "elevenlabs";
3352
+ readonly apiKey?: string;
3353
+ readonly model?: string;
3354
+ readonly voiceId?: string;
3355
+ };
3356
+ type TtsProviderConfig = CartesiaProviderConfig | ElevenLabsTtsProviderConfig;
3357
+ type TelephonyProviderConfig = {
3358
+ readonly provider: "web-client-audio";
3359
+ readonly options?: WebClientAudioProviderOptions;
3360
+ } | {
3361
+ readonly provider: "twilio";
3362
+ };
3363
+ type VoiceAgentProviders = {
3364
+ readonly telephony: TelephonyProvider | TelephonyProviderConfig;
3365
+ readonly stt: SpeechToTextProvider | SttProviderConfig;
3366
+ readonly llm: LLMProvider | LlmProviderConfig;
3367
+ readonly tts: TextToSpeechProvider | TtsProviderConfig;
3368
+ };
3369
+ interface VoiceAgentModels {
3370
+ readonly stt?: string;
3371
+ readonly llm?: string;
3372
+ readonly tts?: string;
3373
+ readonly ttsVoice?: string;
3374
+ }
3375
+ interface CreateVoiceAgentOptions {
3376
+ readonly prompt: string;
3377
+ readonly id?: string;
3378
+ readonly name?: string;
3379
+ readonly version?: string;
3380
+ readonly providers: VoiceAgentProviders;
3381
+ readonly models?: VoiceAgentModels;
3382
+ /** Tool generics are intentionally erased at the heterogeneous agent boundary. */
3383
+ readonly tools?: readonly ToolDefinition<any, any>[];
3384
+ readonly audio?: Partial<AgentAudioPolicy>;
3385
+ readonly memoryPolicy?: AgentMemoryPolicy;
3386
+ readonly contextPolicy?: AgentContextPolicy;
3387
+ readonly interruptionPolicy?: InterruptionPolicy;
3388
+ readonly runtime?: RuntimeOptions;
3389
+ }
3390
+ interface VoiceAgentRunOptions {
3391
+ /**
3392
+ * A handle created by a stable TVIC transport adapter or a custom adapter.
3393
+ * A factory is useful for transports whose events must be stamped with the
3394
+ * runtime-created session id; it runs after the session is created.
3395
+ */
3396
+ readonly callHandle: CallHandle | VoiceAgentCallHandleFactory;
3397
+ /** Optional complete call record. A safe inbound record is synthesized when omitted. */
3398
+ readonly call?: Call;
3399
+ readonly channel?: ChannelKind;
3400
+ readonly variables?: Readonly<Record<string, unknown>>;
3401
+ readonly metadata?: Readonly<Record<string, unknown>>;
3402
+ readonly memoryUserId?: UserId;
3403
+ readonly organizationId?: OrganizationId;
3404
+ readonly workflowId?: WorkflowId;
3405
+ readonly signal?: AbortSignal;
3406
+ readonly safetyIdentifier?: string;
3407
+ readonly sttReconnect?: boolean | SttReconnectOptions;
3408
+ readonly sttLanguage?: string;
3409
+ readonly textDelivery?: TextDeliveryMode;
3410
+ readonly streamStallTimeoutMs?: number;
3411
+ readonly turnEndpointTimeoutMs?: number;
3412
+ readonly turnMaxDurationMs?: number;
3413
+ }
3414
+ interface VoiceAgentCallHandleContext {
3415
+ readonly sessionId: SessionId;
3416
+ readonly call: Call;
3417
+ readonly channel: ChannelKind;
3418
+ }
3419
+ type VoiceAgentCallHandleFactory = (context: VoiceAgentCallHandleContext) => CallHandle | PromiseLike<CallHandle>;
3420
+ interface VoiceAgentRun extends DualProtocolResult {
3421
+ }
3422
+ interface VoiceAgentSession {
3423
+ readonly sessionId: SessionId;
3424
+ readonly run: VoiceAgentRun;
3425
+ }
3426
+ interface VoiceAgentProviderNames {
3427
+ readonly telephony: string;
3428
+ readonly stt: string;
3429
+ readonly llm: string;
3430
+ readonly tts: string;
3431
+ }
3432
+ interface VoiceAgent {
3433
+ readonly id: string;
3434
+ readonly name: string;
3435
+ readonly prompt: string;
3436
+ readonly providers: VoiceAgentProviderNames;
3437
+ start(options: VoiceAgentRunOptions): Promise<VoiceAgentSession>;
3438
+ run(options: VoiceAgentRunOptions): Promise<PipelineVoiceLoopResult>;
3439
+ stop(): Promise<void>;
3440
+ healthCheck(): Promise<HealthSnapshot>;
3441
+ }
3442
+ declare function createVoiceAgent(options: CreateVoiceAgentOptions): VoiceAgent;
3443
+
3444
+ declare function bytesToBase64(bytes: Uint8Array): string;
3445
+ declare function base64ToBytes(value: string): Uint8Array;
3446
+ declare function mulawToPcm16le(mulaw: Uint8Array): Uint8Array;
3447
+ declare function pcm16leToMulaw(pcm: Uint8Array): Uint8Array;
3448
+ /**
3449
+ * v0.1 normalized audio is frozen to **pcm_s16le, mono**. The frame/duration/
3450
+ * resample helpers below compute on interleaved-sample assumptions that only hold
3451
+ * for a single channel, so a stereo format would silently corrupt timing. We
3452
+ * reject it at the boundary rather than miscompute.
3453
+ */
3454
+ declare function assertPcm16leFormat(format: AudioFormat): void;
3455
+ declare function resamplePcm16le(pcm: Uint8Array, fromSampleRateHz: number, toSampleRateHz: number): Uint8Array;
3456
+ declare function frameCountForPcm16le(bytes: Uint8Array): number;
3457
+ declare function durationMsForPcm16le(bytes: Uint8Array, sampleRateHz: number): number;
3458
+ declare function splitPcm16leFrames(bytes: Uint8Array, format: AudioFormat, frameDurationMs: number): readonly Uint8Array[];
3459
+
3460
+ /**
3461
+ * A deliberately unbounded, single-consumer async queue: exactly one iterator
3462
+ * may drain it, and pushed values resolve waiters in FIFO order. Lives in
3463
+ * `@tvic/media` because it is the one package both `@tvic/providers` and
3464
+ * `@tvic/runtime` are allowed to depend on (see `scripts/check-architecture.mjs`),
3465
+ * so provider adapters and the runtime's own session/stream wrappers share this
3466
+ * one implementation instead of each keeping a private copy.
3467
+ */
3468
+ declare class AsyncQueue<T> implements AsyncIterable<T> {
3469
+ #private;
3470
+ constructor(options?: {
3471
+ readonly maxBuffered?: number;
3472
+ });
3473
+ push(value: T): boolean;
3474
+ fail(error: unknown): void;
3475
+ close(): void;
3476
+ [Symbol.asyncIterator](): AsyncIterator<T>;
3477
+ }
3478
+
3479
+ interface AudioNormalizerOptions {
3480
+ readonly inputFormat: AudioFormat;
3481
+ readonly outputFormat?: AudioFormat;
3482
+ readonly channelPolicy?: "average" | "left";
3483
+ }
3484
+ interface AudioNormalizer {
3485
+ readonly inputFormat: AudioFormat;
3486
+ readonly outputFormat: AudioFormat;
3487
+ push(bytes: Uint8Array): Uint8Array;
3488
+ finishSegment(): Uint8Array;
3489
+ finish(): Uint8Array;
3490
+ }
3491
+ /**
3492
+ * Converts supported PCM source frames into PCM16LE mono while preserving
3493
+ * sample-clock phase across arbitrary realtime chunk boundaries.
3494
+ *
3495
+ * The FIR is intentionally small and deterministic. It is causal with a
3496
+ * bounded look-ahead: ordinary pushes emit only samples whose filter support is
3497
+ * present, while an explicit segment/stream finish uses the final source sample
3498
+ * as the endpoint. This prevents packet boundaries from becoming resampling
3499
+ * boundaries.
3500
+ */
3501
+ declare function createAudioNormalizer(options: AudioNormalizerOptions): AudioNormalizer;
3502
+
3503
+ declare function isInputMediaEvent(event: MediaEvent): event is InputMediaEvent;
3504
+ declare function isOutputMediaEvent(event: MediaEvent): event is OutputMediaEvent;
3505
+
3506
+ declare class InMemorySessionStore implements SessionStore {
3507
+ #private;
3508
+ get(id: SessionId): Promise<StoredSessionRecord | null>;
3509
+ list(): Promise<readonly StoredSessionRecord[]>;
3510
+ put(record: StoredSessionRecord): Promise<void>;
3511
+ update(id: SessionId, updater: (record: StoredSessionRecord) => StoredSessionRecord): Promise<StoredSessionRecord>;
3512
+ close(): Promise<void>;
3513
+ /** Internal aggregate-transaction rollback hook. */
3514
+ restore(id: SessionId, record: StoredSessionRecord | null): void;
3515
+ }
3516
+ declare function createInMemorySessionStore(): InMemorySessionStore;
3517
+ declare class InMemoryTurnStore implements TurnStore {
3518
+ #private;
3519
+ get(sessionId: SessionId, id: TurnId): Promise<StoredTurnRecord | null>;
3520
+ listBySession(sessionId: SessionId): Promise<readonly StoredTurnRecord[]>;
3521
+ put(record: StoredTurnRecord): Promise<void>;
3522
+ update(sessionId: SessionId, id: TurnId, updater: (record: StoredTurnRecord) => StoredTurnRecord): Promise<StoredTurnRecord>;
3523
+ close(): Promise<void>;
3524
+ /** Internal aggregate-transaction rollback hook. */
3525
+ restore(sessionId: SessionId, records: readonly StoredTurnRecord[]): void;
3526
+ }
3527
+ declare function createInMemoryTurnStore(): InMemoryTurnStore;
3528
+ declare class InMemoryToolCallStore implements ToolCallStore {
3529
+ #private;
3530
+ get(sessionId: SessionId, id: ToolCallId): Promise<StoredToolCallRecord | null>;
3531
+ listBySession(sessionId: SessionId): Promise<readonly StoredToolCallRecord[]>;
3532
+ put(record: StoredToolCallRecord): Promise<void>;
3533
+ update(sessionId: SessionId, id: ToolCallId, updater: (record: StoredToolCallRecord) => StoredToolCallRecord): Promise<StoredToolCallRecord>;
3534
+ close(): Promise<void>;
3535
+ /** Internal aggregate-transaction rollback hook. */
3536
+ restore(sessionId: SessionId, records: readonly StoredToolCallRecord[]): void;
3537
+ }
3538
+ declare function createInMemoryToolCallStore(): InMemoryToolCallStore;
3539
+ declare class InMemorySessionLeaseStore implements SessionLeaseStore {
3540
+ #private;
3541
+ constructor(now?: () => number);
3542
+ acquire(sessionId: SessionId, holder: string, ttlMs: number): Promise<SessionLease | null>;
3543
+ renew(sessionId: SessionId, holder: string, fence: number, ttlMs: number): Promise<SessionLease | null>;
3544
+ release(sessionId: SessionId, holder: string, fence: number): Promise<void>;
3545
+ get(sessionId: SessionId): Promise<SessionLease | null>;
3546
+ listRecoveryCandidates(options: {
3547
+ readonly nowMs: number;
3548
+ readonly limit: number;
3549
+ readonly cursor?: string;
3550
+ }): Promise<{
3551
+ readonly sessionIds: readonly SessionId[];
3552
+ readonly nextCursor?: string;
3553
+ }>;
3554
+ close(): Promise<void>;
3555
+ }
3556
+
3557
+ interface InMemoryDurableRuntimeStore extends DurableRuntimeStore {
3558
+ readonly outbox: readonly DurableOutboxEvent[];
3559
+ }
3560
+ interface InMemoryDurableRuntimeStoreOptions {
3561
+ readonly nowMs?: () => number;
3562
+ /**
3563
+ * Legacy stores can be supplied while callers migrate to DurableRuntimeStore.
3564
+ * The aggregate transaction facade serializes access to them and uses the
3565
+ * optional internal restore hooks when the store provides one. Custom stores
3566
+ * without those hooks receive ordering isolation but cannot receive a full
3567
+ * rollback guarantee; production adapters should implement DurableRuntimeStore
3568
+ * directly when atomicity is required.
3569
+ */
3570
+ readonly sessionStore?: SessionStore;
3571
+ readonly turnStore?: TurnStore;
3572
+ readonly toolCallStore?: ToolCallStore;
3573
+ }
3574
+ declare function createInMemoryDurableRuntimeStore(options?: InMemoryDurableRuntimeStoreOptions): InMemoryDurableRuntimeStore;
3575
+
3576
+ interface InMemoryMemoryOptions {
3577
+ readonly now?: () => Date;
3578
+ readonly idPrefix?: string;
3579
+ /** Hard cap on the UTF-8 serialized memory value. Throws MemoryEntryTooLargeError. */
3580
+ readonly maxEntryBytes?: number;
3581
+ }
3582
+ declare class InMemoryMemory implements Memory {
3583
+ #private;
3584
+ readonly name = "in-memory";
3585
+ readonly version = "0.1.0";
3586
+ readonly capabilities: MemoryCapabilities;
3587
+ constructor(options?: InMemoryMemoryOptions);
3588
+ get<T = unknown>(ref: MemoryRef, key: string, kind?: MemoryKind): Promise<MemoryEntry<T> | null>;
3589
+ put<T = unknown>(ref: MemoryRef, key: string, kind: MemoryKind, value: T, options?: MemoryPutOptions): Promise<MemoryEntry<T>>;
3590
+ list<T = unknown>(ref: MemoryRef, query?: MemoryQuery): Promise<readonly MemoryEntry<T>[]>;
3591
+ delete(ref: MemoryRef, key: string, kind?: MemoryKind): Promise<boolean>;
3592
+ deleteAll(ref: MemoryRef): Promise<number>;
3593
+ deleteForUser(userId: UserId): Promise<number>;
3594
+ }
3595
+ declare function createInMemoryMemory(options?: InMemoryMemoryOptions): InMemoryMemory;
3596
+
3597
+ declare function stableStringify(value: unknown): string;
3598
+
3599
+ interface SchemaValidationResult {
3600
+ readonly valid: boolean;
3601
+ readonly errors: readonly string[];
3602
+ }
3603
+ /**
3604
+ * Validates a value against a deliberately small, well-defined subset of JSON Schema
3605
+ * (Draft-07 keywords): `type` (incl. "integer"), `enum`, `const`, object
3606
+ * `properties` / `required` / `additionalProperties: false`, array `items` /
3607
+ * `minItems` / `maxItems`, string `minLength` / `maxLength`, and numeric `minimum` /
3608
+ * `maximum`. It is intentionally NOT a full validator: unsupported keywords are
3609
+ * ignored. Swap in Ajv at the call site if full JSON Schema compliance is required.
3610
+ */
3611
+ declare function validateJsonSchemaSubset(value: unknown, schema: Readonly<Record<string, unknown>>, path?: string): SchemaValidationResult;
3612
+
3613
+ interface ToolRegistry {
3614
+ register(tool: ToolDefinition): void;
3615
+ get(id: ToolId): ToolDefinition | null;
3616
+ list(): readonly ToolDefinition[];
3617
+ }
3618
+ declare function createToolRegistry(tools?: readonly ToolDefinition[]): ToolRegistry;
3619
+ declare class InMemoryToolIdempotencyStore implements ToolIdempotencyStore {
3620
+ #private;
3621
+ constructor(now?: () => number, readLease?: (sessionId: SessionId) => Promise<{
3622
+ readonly holder: string;
3623
+ readonly fence: number;
3624
+ readonly expiresAtMs: number;
3625
+ } | null>);
3626
+ lookup(key: string, requestHash: string): Promise<ToolIdempotencyRecord | null>;
3627
+ claim(input: ToolIdempotencyClaim): Promise<ToolIdempotencyClaimResult>;
3628
+ complete(key: string, requestHash: string, outcome: ToolIdempotencyOutcome): Promise<void>;
3629
+ }
3630
+ interface ExecuteToolInput<TInput, TOutput> {
3631
+ readonly tool: ToolDefinition<TInput, TOutput>;
3632
+ readonly input: TInput;
3633
+ readonly sessionId: SessionId;
3634
+ readonly turnId: TurnId;
3635
+ readonly toolCallId: ToolCallId;
3636
+ /** Reuses the durable queue timestamp when the runtime has already persisted the call. */
3637
+ readonly queuedAt?: Timestamp;
3638
+ readonly attempt?: number;
3639
+ readonly logger?: ToolLogger;
3640
+ readonly now?: () => Date;
3641
+ /** Aborts the tool call (e.g. on barge-in). The tool's ctx.signal mirrors this. */
3642
+ readonly signal?: AbortSignal;
3643
+ /** Current process lease used to fence durable idempotency operations. */
3644
+ readonly lease?: ToolIdempotencyLease;
3645
+ /** Honours the tool's idempotency policy when provided. */
3646
+ readonly idempotencyStore?: ToolIdempotencyStore;
3647
+ /**
3648
+ * Tenant identity propagated into the tool's `ctx.tenant`. The runtime
3649
+ * populates this from the session attachment's `memoryUserId` /
3650
+ * `organizationId` / `workflowId` so the tool can enforce its own
3651
+ * auth/RBAC. TVIC ships no auth layer; the tool author reads
3652
+ * `ctx.tenant` and decides.
3653
+ */
3654
+ readonly tenant?: ToolTenant;
3655
+ }
3656
+ /**
3657
+ * Performs the input checks that must complete before a tool call reaches a
3658
+ * durable store. Keeping this at the tools boundary lets the runtime reject
3659
+ * provider-produced values before it creates queued/running records.
3660
+ */
3661
+ declare function toolInputError(value: unknown, schema: Readonly<Record<string, unknown>>): NormalizedError | null;
3662
+ declare function idempotencyKeyFor<TInput, TOutput>(input: ExecuteToolInput<TInput, TOutput>): string | null;
3663
+ /**
3664
+ * The request hash paired with idempotencyKeyFor. Keep this beside the key
3665
+ * builder so execution and crash recovery cannot silently hash different
3666
+ * request shapes.
3667
+ */
3668
+ declare function idempotencyRequestHashFor<TInput, TOutput>(input: ExecuteToolInput<TInput, TOutput>): string;
3669
+ declare function executeTool<TInput = unknown, TOutput = unknown>(input: ExecuteToolInput<TInput, TOutput>): Promise<ToolCall>;
3670
+
3671
+ declare class PostgresToolIdempotencyStore implements ToolIdempotencyStore {
3672
+ readonly client: SqlClient$1;
3673
+ constructor(client: SqlClient$1);
3674
+ lookup(key: string, requestHash: string): Promise<ToolIdempotencyRecord | null>;
3675
+ claim(input: ToolIdempotencyClaim): Promise<ToolIdempotencyClaimResult>;
3676
+ complete(key: string, requestHash: string, outcome: ToolIdempotencyOutcome): Promise<void>;
3677
+ }
3678
+
3679
+ interface PostgresOutboxWorkerOptions {
3680
+ readonly pool: SqlPool$1;
3681
+ readonly workerId: string;
3682
+ readonly deliver: (event: DurableOutboxEvent) => Promise<void>;
3683
+ readonly batchSize?: number;
3684
+ readonly claimTtlMs?: number;
3685
+ readonly onMetric?: (metric: {
3686
+ readonly name: string;
3687
+ readonly value: number;
3688
+ }) => void;
3689
+ }
3690
+
3691
+ interface PostgresMigration$1 {
3692
+ readonly version: number;
3693
+ readonly name: string;
3694
+ readonly sql: string;
3695
+ }
3696
+ /** Runs the ordered SQL files exactly once and records the applied filename. */
3697
+ declare function runPostgresMigrations(pool: SqlPool$1, migrations?: readonly PostgresMigration$1[]): Promise<readonly number[]>;
3698
+
3699
+ interface SqlResult<Row extends Record<string, unknown> = Record<string, unknown>> {
3700
+ readonly rows: readonly Row[];
3701
+ readonly rowCount?: number | null;
3702
+ }
3703
+ interface SqlClient$1 {
3704
+ query<Row extends Record<string, unknown> = Record<string, unknown>>(text: string, values?: readonly unknown[]): Promise<SqlResult<Row>>;
3705
+ }
3706
+ interface SqlPool$1 extends SqlClient$1 {
3707
+ connect(): Promise<SqlClient$1 & {
3708
+ readonly release: () => void;
3709
+ }>;
3710
+ /** If provided, the durable store owns this pool and calls it once on close. */
3711
+ end?(): Promise<void>;
3712
+ }
3713
+ interface PostgresDurableStoreOptions {
3714
+ readonly pool: SqlPool$1;
3715
+ }
3716
+ declare class PostgresSessionStore implements SessionStore {
3717
+ readonly client: SqlClient$1;
3718
+ constructor(client: SqlClient$1);
3719
+ get(id: SessionId): Promise<StoredSessionRecord | null>;
3720
+ list(): Promise<readonly StoredSessionRecord[]>;
3721
+ put(record: StoredSessionRecord): Promise<void>;
3722
+ update(id: SessionId, updater: (record: StoredSessionRecord) => StoredSessionRecord): Promise<StoredSessionRecord>;
3723
+ close(): Promise<void>;
3724
+ }
3725
+ declare class PostgresTurnStore implements TurnStore {
3726
+ readonly client: SqlClient$1;
3727
+ constructor(client: SqlClient$1);
3728
+ get(sessionId: SessionId, id: TurnId): Promise<StoredTurnRecord | null>;
3729
+ listBySession(sessionId: SessionId): Promise<readonly StoredTurnRecord[]>;
3730
+ put(record: StoredTurnRecord): Promise<void>;
3731
+ update(sessionId: SessionId, id: TurnId, updater: (record: StoredTurnRecord) => StoredTurnRecord): Promise<StoredTurnRecord>;
3732
+ close(): Promise<void>;
3733
+ }
3734
+ declare class PostgresToolCallStore implements ToolCallStore {
3735
+ readonly client: SqlClient$1;
3736
+ constructor(client: SqlClient$1);
3737
+ get(sessionId: SessionId, id: ToolCallId): Promise<StoredToolCallRecord | null>;
3738
+ listBySession(sessionId: SessionId): Promise<readonly StoredToolCallRecord[]>;
3739
+ put(record: StoredToolCallRecord): Promise<void>;
3740
+ update(sessionId: SessionId, id: ToolCallId, updater: (record: StoredToolCallRecord) => StoredToolCallRecord): Promise<StoredToolCallRecord>;
3741
+ close(): Promise<void>;
3742
+ }
3743
+ declare class PostgresSessionLeaseStore implements SessionLeaseStore {
3744
+ readonly client: SqlPool$1;
3745
+ constructor(client: SqlPool$1);
3746
+ acquire(sessionId: SessionId, holder: string, ttlMs: number): Promise<SessionLease | null>;
3747
+ renew(sessionId: SessionId, holder: string, fence: number, ttlMs: number): Promise<SessionLease | null>;
3748
+ release(sessionId: SessionId, holder: string, fence: number): Promise<void>;
3749
+ get(sessionId: SessionId): Promise<SessionLease | null>;
3750
+ listRecoveryCandidates(options: {
3751
+ readonly nowMs: number;
3752
+ readonly limit: number;
3753
+ readonly cursor?: string;
3754
+ }): Promise<{
3755
+ readonly sessionIds: readonly SessionId[];
3756
+ readonly nextCursor?: string;
3757
+ }>;
3758
+ close(): Promise<void>;
3759
+ }
3760
+ declare class PostgresDurableRuntimeStore implements DurableRuntimeStore {
3761
+ #private;
3762
+ readonly pool: SqlPool$1;
3763
+ readonly sessions: PostgresSessionStore;
3764
+ readonly turns: PostgresTurnStore;
3765
+ readonly toolCalls: PostgresToolCallStore;
3766
+ readonly leases: PostgresSessionLeaseStore;
3767
+ readonly toolIdempotencyStore: PostgresToolIdempotencyStore;
3768
+ constructor(pool: SqlPool$1);
3769
+ createSessionWithLease(record: StoredSessionRecord, holder: string, ttlMs: number, initialEvent?: (lease: SessionLease) => DurableOutboxEvent): Promise<SessionLease | null>;
3770
+ runSessionTransaction<T>(sessionId: SessionId, lease: Pick<SessionLease, "holder" | "fence">, operation: (tx: DurableSessionTransaction) => Promise<T>): Promise<T>;
3771
+ runUnfencedSessionTransaction<T>(sessionId: SessionId, operation: (tx: DurableSessionTransaction) => Promise<T>): Promise<T>;
3772
+ close(): Promise<void>;
3773
+ }
3774
+ declare function createPostgresDurableRuntimeStore(options: PostgresDurableStoreOptions): PostgresDurableRuntimeStore;
3775
+
3776
+ declare class RedisToolIdempotencyStore implements ToolIdempotencyStore {
3777
+ #private;
3778
+ readonly client: RedisClient;
3779
+ constructor(client: RedisClient, options?: RedisStoreOptions);
3780
+ lookup(key: string, _requestHash: string): Promise<ToolIdempotencyRecord | null>;
3781
+ claim(input: ToolIdempotencyClaim): Promise<ToolIdempotencyClaimResult>;
3782
+ complete(key: string, requestHash: string, outcome: ToolIdempotencyOutcome): Promise<void>;
3783
+ }
3784
+
3785
+ /**
3786
+ * Applies Postgres outbox events to Redis as a version/fence guarded cache.
3787
+ * Redis is intentionally not a second runtime authority: a cache event can be
3788
+ * replayed, duplicated, or delivered out of order without replacing a newer
3789
+ * value. Postgres remains the source of truth and the outbox worker supplies
3790
+ * the retry boundary.
3791
+ */
3792
+ declare class RedisOutboxCacheProjector {
3793
+ readonly client: RedisClient;
3794
+ readonly options: RedisStoreOptions;
3795
+ constructor(client: RedisClient, options?: RedisStoreOptions);
3796
+ apply(event: DurableOutboxEvent): Promise<void>;
3797
+ }
3798
+
3799
+ type RedisSetResult = "OK" | "ok" | boolean | null;
3800
+ interface RedisScanOptions {
3801
+ readonly MATCH?: string;
3802
+ readonly COUNT?: number;
3803
+ }
3804
+ interface RedisClient {
3805
+ get(key: string): Promise<string | null>;
3806
+ set(key: string, value: string, options?: {
3807
+ readonly NX?: boolean;
3808
+ }): Promise<RedisSetResult>;
3809
+ del(...keys: readonly string[]): Promise<number>;
3810
+ eval(script: string, keys: readonly string[], args: readonly string[]): Promise<unknown>;
3811
+ scan(cursor: string, options?: RedisScanOptions): Promise<readonly [string, readonly string[]]>;
3812
+ zrange(key: string, start: number, stop: number): Promise<readonly string[]>;
3813
+ zrangebyscore(key: string, min: number, max: number): Promise<readonly string[]>;
3814
+ time(): Promise<readonly [string, string]>;
3815
+ watch(...keys: readonly string[]): Promise<void>;
3816
+ unwatch(): Promise<void>;
3817
+ multi(): RedisMulti;
3818
+ quit?(): Promise<void>;
3819
+ }
3820
+ interface RedisMulti {
3821
+ set(key: string, value: string): RedisMulti;
3822
+ del(...keys: readonly string[]): RedisMulti;
3823
+ exec(): Promise<readonly unknown[] | null>;
3824
+ }
3825
+ interface RedisStoreOptions {
3826
+ readonly prefix?: string;
3827
+ readonly nowMs?: () => number;
3828
+ readonly maxTransactionRetries?: number;
3829
+ readonly closeClient?: boolean;
3830
+ }
3831
+
3832
+ declare class RedisSessionStore implements SessionStore {
3833
+ readonly client: RedisClient;
3834
+ readonly options: RedisStoreOptions;
3835
+ constructor(client: RedisClient, options?: RedisStoreOptions);
3836
+ get(id: SessionId): Promise<StoredSessionRecord | null>;
3837
+ list(): Promise<readonly StoredSessionRecord[]>;
3838
+ put(record: StoredSessionRecord): Promise<void>;
3839
+ update(id: SessionId, updater: (record: StoredSessionRecord) => StoredSessionRecord): Promise<StoredSessionRecord>;
3840
+ close(): Promise<void>;
3841
+ }
3842
+ declare class RedisTurnStore implements TurnStore {
3843
+ readonly client: RedisClient;
3844
+ readonly options: RedisStoreOptions;
3845
+ constructor(client: RedisClient, options?: RedisStoreOptions);
3846
+ get(sessionId: SessionId, id: TurnId): Promise<StoredTurnRecord | null>;
3847
+ listBySession(sessionId: SessionId): Promise<readonly StoredTurnRecord[]>;
3848
+ put(record: StoredTurnRecord): Promise<void>;
3849
+ update(sessionId: SessionId, id: TurnId, updater: (record: StoredTurnRecord) => StoredTurnRecord): Promise<StoredTurnRecord>;
3850
+ close(): Promise<void>;
3851
+ }
3852
+ declare class RedisToolCallStore implements ToolCallStore {
3853
+ readonly client: RedisClient;
3854
+ readonly options: RedisStoreOptions;
3855
+ constructor(client: RedisClient, options?: RedisStoreOptions);
3856
+ get(sessionId: SessionId, id: ToolCallId): Promise<StoredToolCallRecord | null>;
3857
+ listBySession(sessionId: SessionId): Promise<readonly StoredToolCallRecord[]>;
3858
+ put(record: StoredToolCallRecord): Promise<void>;
3859
+ update(sessionId: SessionId, id: ToolCallId, updater: (record: StoredToolCallRecord) => StoredToolCallRecord): Promise<StoredToolCallRecord>;
3860
+ close(): Promise<void>;
3861
+ }
3862
+ declare class RedisSessionLeaseStore implements SessionLeaseStore {
3863
+ #private;
3864
+ readonly client: RedisClient;
3865
+ constructor(client: RedisClient, options?: RedisStoreOptions);
3866
+ acquire(sessionId: SessionId, holder: string, ttlMs: number): Promise<SessionLease | null>;
3867
+ renew(sessionId: SessionId, holder: string, fence: number, ttlMs: number): Promise<SessionLease | null>;
3868
+ release(sessionId: SessionId, holder: string, fence: number): Promise<void>;
3869
+ get(sessionId: SessionId): Promise<SessionLease | null>;
3870
+ listRecoveryCandidates(options: {
3871
+ readonly nowMs: number;
3872
+ readonly limit: number;
3873
+ readonly cursor?: string;
3874
+ }): Promise<{
3875
+ readonly sessionIds: readonly SessionId[];
3876
+ readonly nextCursor?: string;
3877
+ }>;
3878
+ close(): Promise<void>;
3879
+ }
3880
+ declare class RedisDurableRuntimeStore implements DurableRuntimeStore {
3881
+ #private;
3882
+ readonly sessions: RedisSessionStore;
3883
+ readonly turns: RedisTurnStore;
3884
+ readonly toolCalls: RedisToolCallStore;
3885
+ readonly leases: RedisSessionLeaseStore;
3886
+ readonly toolIdempotencyStore: RedisToolIdempotencyStore;
3887
+ constructor(client: RedisClient, options?: RedisStoreOptions);
3888
+ createSessionWithLease(record: StoredSessionRecord, holder: string, ttlMs: number, initialEvent?: (lease: SessionLease) => DurableOutboxEvent): Promise<SessionLease | null>;
3889
+ runSessionTransaction<T>(sessionId: SessionId, lease: Pick<SessionLease, "holder" | "fence">, operation: (tx: DurableSessionTransaction) => Promise<T>): Promise<T>;
3890
+ runUnfencedSessionTransaction<T>(sessionId: SessionId, operation: (tx: DurableSessionTransaction) => Promise<T>): Promise<T>;
3891
+ close(): Promise<void>;
3892
+ }
3893
+ declare function createRedisDurableRuntimeStore(client: RedisClient, options?: RedisStoreOptions): RedisDurableRuntimeStore;
3894
+
3895
+ interface PostgresRedisDurableStoreOptions {
3896
+ readonly pool: SqlPool$1;
3897
+ readonly redis: RedisClient;
3898
+ readonly redisOptions?: RedisStoreOptions;
3899
+ }
3900
+ /**
3901
+ * The production composition: PostgreSQL owns all mutations, leases,
3902
+ * idempotency, and reads; the outbox asynchronously projects committed
3903
+ * envelopes into Redis. A Redis outage therefore cannot turn a committed
3904
+ * runtime mutation into a failed domain operation.
3905
+ */
3906
+ declare class PostgresRedisDurableRuntimeStore implements DurableRuntimeStore {
3907
+ #private;
3908
+ readonly primary: PostgresDurableRuntimeStore;
3909
+ readonly cacheProjector: RedisOutboxCacheProjector;
3910
+ readonly sessions: SessionStore;
3911
+ readonly turns: TurnStore;
3912
+ readonly toolCalls: ToolCallStore;
3913
+ readonly leases: PostgresSessionLeaseStore;
3914
+ readonly toolIdempotencyStore: ToolIdempotencyStore;
3915
+ constructor(options: PostgresRedisDurableStoreOptions);
3916
+ createSessionWithLease(record: StoredSessionRecord, holder: string, ttlMs: number, initialEvent?: (lease: SessionLease) => DurableOutboxEvent): Promise<SessionLease | null>;
3917
+ runSessionTransaction<T>(sessionId: SessionId, lease: Pick<SessionLease, "holder" | "fence">, operation: (tx: DurableSessionTransaction) => Promise<T>): Promise<T>;
3918
+ runUnfencedSessionTransaction<T>(sessionId: SessionId, operation: (tx: DurableSessionTransaction) => Promise<T>): Promise<T>;
3919
+ startOutboxWorker(options: Omit<PostgresOutboxWorkerOptions, "pool" | "deliver">): void;
3920
+ stopOutboxWorker(): Promise<void>;
3921
+ close(): Promise<void>;
3922
+ }
3923
+ declare function createPostgresRedisDurableRuntimeStore(options: PostgresRedisDurableStoreOptions): PostgresRedisDurableRuntimeStore;
3924
+
3925
+ /**
3926
+ * The minimum SQL surface a Postgres driver must provide for this package.
3927
+ * Drivers are injected — this package never imports `pg` directly. Matches
3928
+ * the contract used by `@tvic/dal-postgres` so a single shared `pg` pool can
3929
+ * be passed to both packages.
3930
+ */
3931
+ interface SqlClient {
3932
+ query<TRow extends Record<string, unknown> = Record<string, unknown>>(text: string, params?: readonly unknown[]): Promise<{
3933
+ rows: readonly TRow[];
3934
+ rowCount: number;
3935
+ }>;
3936
+ }
3937
+ interface SqlPool {
3938
+ query<TRow extends Record<string, unknown> = Record<string, unknown>>(text: string, params?: readonly unknown[]): Promise<{
3939
+ rows: readonly TRow[];
3940
+ rowCount: number;
3941
+ }>;
3942
+ connect(): Promise<SqlClient & {
3943
+ readonly release: () => void;
3944
+ }>;
3945
+ end?(): Promise<void>;
3946
+ }
3947
+
3948
+ interface PostgresMigration {
3949
+ readonly version: number;
3950
+ readonly name: string;
3951
+ readonly sql: string;
3952
+ }
3953
+ declare function runPostgresMemoryMigrations(pool: SqlPool, migrations?: readonly PostgresMigration[]): Promise<readonly number[]>;
3954
+
3955
+ interface PostgresMemoryOptions {
3956
+ readonly pool: SqlPool;
3957
+ /** UTF-8 serialized memory-value cap; throws MemoryEntryTooLargeError. */
3958
+ readonly maxEntryBytes?: number;
3959
+ }
3960
+ declare class PostgresMemory implements Memory {
3961
+ readonly pool: SqlPool;
3962
+ readonly name = "postgres";
3963
+ readonly version = "0.1.0";
3964
+ readonly capabilities: MemoryCapabilities;
3965
+ constructor(pool: SqlPool, options?: Omit<PostgresMemoryOptions, "pool">);
3966
+ get<T = unknown>(ref: MemoryRef, key: string, kind?: MemoryKind): Promise<MemoryEntry<T> | null>;
3967
+ put<T = unknown>(ref: MemoryRef, key: string, kind: MemoryKind, value: T, options?: MemoryPutOptions): Promise<MemoryEntry<T>>;
3968
+ list<T = unknown>(ref: MemoryRef, query?: MemoryQuery): Promise<readonly MemoryEntry<T>[]>;
3969
+ delete(ref: MemoryRef, key: string, kind?: MemoryKind): Promise<boolean>;
3970
+ deleteAll(ref: MemoryRef): Promise<number>;
3971
+ deleteForUser(userIdValue: UserId): Promise<number>;
3972
+ }
3973
+ declare function createPostgresMemory(options: PostgresMemoryOptions): PostgresMemory;
3974
+
3975
+ export { ADAPTER_DEFAULTS, AssemblyAiSttProvider, AsyncQueue, BackendUnavailableError, CartesiaTtsProvider, ConversationPolicy, CorruptRecordError, DEFAULT_DURABLE_RUNTIME_POLICY, DTMF_DIGITS, DeepgramSttProvider, DurableError, EVENT_KIND_MAP, ElevenLabsSttProvider, ElevenLabsTtsProvider, InMemoryMemory, InMemorySessionLeaseStore, InMemorySessionStore, InMemoryToolCallStore, InMemoryToolIdempotencyStore, InMemoryTurnStore, InvalidArgumentError, LeaseLostError, LeaseUnavailableError, MemoryBackendUnavailableError, MemoryEntryTooLargeError, MemorySessionQuotaExceededError, NodeMediaPlane, PCM16_16K_MONO, PCM16_8K_MONO, PROVIDER_API_VERSIONS, PROVIDER_CATALOG, PROVIDER_ERROR_CODES, PROVIDER_NAMES, PROVIDER_STABILITY, PipelineVoiceLoop, PipelineVoiceLoopBuilder, RUNTIME_SAMPLE_RATE_HZ, RecordConflictError, RecordNotFoundError, STT_ERROR_CODES, STT_STREAM_ENDED_REASON, SarvamSttProvider, SessionReaper, SessionRecoveryCoordinator, SonioxSttProvider, TELEPHONY_SAMPLE_RATE_HZ, TVIC_ERROR_MARKER, TVIC_ERROR_NAMES, TvicThrowableError, TwilioMediaStreamCallHandle, TwilioMediaStreamsProvider, WEB_CLIENT_AUDIO_CLOSE_CODES, WEB_CLIENT_AUDIO_DEFAULTS, WebClientAudioCallHandle, WebClientAudioProvider, assertMemoryCapability, assertMemoryPolicySupported, assertPcm16leFormat, authError, base64ToBytes, bytesToBase64, cancelledError, canonicalizeTwilioData, computeTwilioSignature, connectionError, counterIdGenerator, createAssemblyAiSttProvider, createAudioNormalizer, createCartesiaTtsProvider, createDeepgramSttProvider, createDefaultIdGenerator, createElevenLabsSttProvider, createElevenLabsTtsProvider, createInMemoryDurableRuntimeStore, createInMemoryMemory, createInMemorySessionStore, createInMemoryToolCallStore, createInMemoryTurnStore, createMediaEvent, createNodeMediaPlane, createOpenAiResponsesLlmProvider, createPostgresDurableRuntimeStore, createPostgresMemory, createPostgresRedisDurableRuntimeStore, createRedisDurableRuntimeStore, createRuntime, createSarvamSttProvider, createSonioxSttProvider, createSttSession, createSystemClock, createToolRegistry, createTwilioMediaStreamsProvider, createVoiceAgent, createWebClientAudioProvider, defineAgent, defineTool, deliverAssistantText, durationMsForPcm16le, evaluateProviderCompatibility, executeTool, formatMemoryContextAsSystemBlock, formatPreCallContextAsSystemBlock, frameCountForPcm16le, getSttRecoveryControl, idempotencyKeyFor, idempotencyRequestHashFor, internalError, interruptedError, isDtmfDigit, isIncrementalTextToSpeechProvider, isInputMediaEvent, isNormalizedError, isOutputMediaEvent, isProviderKind, isReconstructableAudioFormat, isSampleRateHz, isTerminalSession, isTerminalTurn, isTranscriptSegmentEvent, isTvicError, isTvicErrorName, kindForMediaEvent, matchPath, mediaError, monotonicOffsetMs, mulawToPcm16le, normalizeLegacyError, normalizeUnknownError, normalizedError, nowTimestamp, pcm16leToMulaw, providerError, rateLimitError, requireProviderKind, resamplePcm16le, resolvePreCallContext, resolvePreCallMemory, runPostgresMemoryMigrations, runPostgresMigrations, sameAudioFormat, shouldDeliverText, signVoiceSessionToken, signatureError, splitPcm16leFrames, stableStringify, supportsAudioFormat, supportsLanguage, supportsModel, terminalSessionFromRequest, terminalTurnFromRequest, timeoutError, toTimestamp, toolError, toolInputError, tvicErrorFromJSON, unknownErrorMessage, validateJsonSchemaSubset, validationError, verifyTwilioSignature, verifyVoiceSessionToken, withSttReconnect };
3976
+ export type { ActiveSession, ActiveTurn, Agent, AgentAudioPolicy, AgentContextPolicy, AgentId, AgentMemoryPolicy, AgentProviders, AnyMediaEventInit, AssemblyAiProviderConfig, AssemblyAiSttProviderOptions, AssistantTextRecord, AudioFormat, AudioNormalizationMode, AudioNormalizer, AudioNormalizerOptions, AudioPayload, BackoffStrategy, Brand, Call, CallControlCapability, CallDirection, CallHandle, CallId, CallStatus, CancelledSession, CancelledTurn, CartesiaProviderConfig, CartesiaTtsProviderOptions, ChannelKind, ChannelLayout, Clock, CompletedSession, CompletedTurn, ConnectedCall, ConnectionObservabilityEvent, ConversationPolicyOptions, CounterIdGenerator, CreateVoiceAgentOptions, CreatedCall, CreatedSession, DeepgramProviderConfig, DeepgramSttProviderOptions, DefineAgentInput, DefineToolInput, DtmfDigit, DtmfReceivedEvent, DualProtocolResult, DurableErrorCode, DurableOutboxEvent, DurableRuntimeMetric, DurableRuntimePolicy, DurableRuntimeStore, DurableSessionTransaction, ElevenLabsSttCommitStrategy, ElevenLabsSttProviderConfig, ElevenLabsSttProviderOptions, ElevenLabsTtsProviderConfig, ElevenLabsTtsProviderOptions, EndSessionReason, EndSessionRequest, EndTurnRequest, EndedCall, ErrorCategory, ExecuteToolInput, FailedCall, FailedSession, FailedToolCall, FailedToolCallStatus, FailedTurn, HealthCheckResult, HealthSnapshot, IdGenerator, IdempotencyPolicy, InMemoryDurableRuntimeStore, InMemoryDurableRuntimeStoreOptions, InMemoryMemoryOptions, InboundCallContext, InboundMediaEvent, IncrementalTextToSpeechProvider, InputAudioChunk, InputMediaEvent, InternalMediaEvent, InterruptRequestedEvent, InterruptionMode, InterruptionPolicy, JsonSchemaDocument, JsonValue, LLMProvider, LlmCompletion, LlmCompletionRequest, LlmInlineToolCall, LlmMessage, LlmMessageRole, LlmProviderConfig, LlmStreamEvent, LlmUsage, MediaAudioChunkEvent, MediaAudioCommittedEvent, MediaDirection, MediaErrorEvent, MediaEvent, MediaEventId, MediaEventKind, MediaEventType, MediaStreamEndedEvent, MediaStreamStartedEvent, MediaTransport, MediaTransportKind, Memory, MemoryCapabilities, MemoryEntry, MemoryEntryId, MemoryKind, MemoryPutOptions, MemoryQuery, MemoryRef, MemoryScope, NodeMediaPlaneConnection, NodeMediaPlaneConnectionErrorHandler, NodeMediaPlaneConnectionHandler, NodeMediaPlaneOptions, NodeMediaPlaneRequestHandler, NormalizedAudioEncoding, NormalizedError, OpenAiProviderConfig, OpenAiResponsesLlmProviderOptions, OrganizationId, OutboundCallRequest, OutputAudioChunk, OutputMediaEvent, PersonaConfig, PipelineVoiceLoopOptions, PipelineVoiceLoopResult, PostgresDurableStoreOptions, PostgresMemoryOptions, PostgresMigration, PostgresRedisDurableStoreOptions, PreCallContext, PreCallContextResolver, PreCallMemoryContext, PreCallMemoryResolver, Provider, ProviderAudioCapabilities, ProviderCancellationCapabilities, ProviderCapabilities, ProviderCatalogEntry, ProviderCompatibility, ProviderCompatibilityIssue, ProviderCompatibilityIssueCode, ProviderDataPolicy, ProviderEventId, ProviderForKind, ProviderKind, ProviderPlayoutCapabilities, ProviderRequirements, ProviderStability, ProviderStreamingCapabilities, ProviderToolCapabilities, ProviderTransport, QueuedToolCall, RecoveryPollResult, RedisClient, RedisMulti, RedisScanOptions, RedisSetResult, RedisStoreOptions, RequiredProviderCapabilities, RetryPolicy, RingingCall, RunningToolCall, Runtime, RuntimeOptions, RuntimeProvider, RuntimeServiceLifecycle, SampleRateHz, SarvamOutputMode, SarvamProviderConfig, SarvamSttProviderOptions, SchemaValidationResult, Session, SessionAttachment, SessionAttachmentHealth, SessionCancellationReason, SessionEndEvent, SessionEndMemorySnapshot, SessionId, SessionLease, SessionLeaseStore, SessionMemoryFinalization, SessionMetricsRecorder, SessionReaperOptions, SessionRecoveryCoordinatorOptions, SessionRuntimeMetadata, SessionSnapshot, SessionState, SessionStatus, SessionStore, SonioxProviderConfig, SonioxStructuredContext, SonioxSttProviderOptions, SpeechToTextProvider, SqlClient$1 as SqlClient, SqlPool$1 as SqlPool, SqlResult, StartAttachedSessionOptions, StartSessionOptions, StartTurnRequest, StartingSession, StoredSessionRecord, StoredToolCallRecord, StoredTurnRecord, StreamEndReason, SttCommitMode, SttOpenRequest, SttProviderConfig, SttReconnectOptions, SttRecoveryControl, SttRecoveryState, SttSession, SttSessionInputOptions, SttSessionOptions, SttStream, SttTimestampOrigin, SucceededToolCall, TelephonyProvider, TelephonyProviderConfig, TerminalSession, TerminalSessionDraft, TerminalSessionStatus, TerminalToolCall, TerminalTurn, TextDeliveryMode, TextToSpeechProvider, TimeoutAction, TimeoutPolicy, Timestamp, ToolCall, ToolCallId, ToolCallRuntimeMetadata, ToolCallStatus, ToolCallStore, ToolDefinition, ToolExecutionContext, ToolExecutor, ToolId, ToolIdempotencyClaim, ToolIdempotencyClaimResult, ToolIdempotencyLease, ToolIdempotencyOutcome, ToolIdempotencyRecord, ToolIdempotencyStatus, ToolIdempotencyStore, ToolLogger, ToolName, ToolRegistry, ToolTenant, TranscriptEndpointEvent, TranscriptEndpointReason, TranscriptEvent, TranscriptEventType, TranscriptSegmentEvent, TranscriptSegmentEventType, TranscriptSpeechStartedEvent, TransportAudioEncoding, TransportAudioFormat, TtsAlignmentEvent, TtsAlignmentUnit, TtsEvent, TtsFlushAcknowledgement, TtsFlushCompletedEvent, TtsFlushId, TtsFlushResult, TtsProviderConfig, TtsSession, TtsSessionOpenRequest, TtsStream, TtsSynthesisRequest, Turn, TurnCancellationReason, TurnCommitRequestedEvent, TurnDetectionMode, TurnId, TurnInput, TurnLatency, TurnLatencyRecord, TurnOutput, TurnRuntimeMetadata, TurnStatus, TurnStore, TvicErrorName, TwilioFlatParams, TwilioMediaStreamCallHandleOptions, TwilioMediaStreamSocket, TwilioParamValue, TwilioParams, UserId, VerifyTwilioOptions, VerifyVoiceSessionTokenOptions, VoiceAgent, VoiceAgentCallHandleContext, VoiceAgentCallHandleFactory, VoiceAgentModels, VoiceAgentProviderNames, VoiceAgentProviders, VoiceAgentRun, VoiceAgentRunOptions, VoiceAgentSession, VoiceEvent, WebClientAudioCallHandleOptions, WebClientAudioConnectionEvent, WebClientAudioProviderOptions, WebClientAudioSocket, WorkflowId };