twilio-agent-connect 2.3.0 → 2.4.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.
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export { z } from 'zod';
3
3
  import pino from 'pino';
4
4
  import { AxiosInstance } from 'axios';
5
5
  import { WebSocket } from 'ws';
6
+ import Twilio from 'twilio';
6
7
  import VoiceResponse from 'twilio/lib/twiml/VoiceResponse.js';
7
8
  import { CallListInstanceCreateOptions } from 'twilio/lib/rest/api/v2010/account/call.js';
8
9
  import { FastifyInstance, FastifyServerOptions } from 'fastify';
@@ -1737,7 +1738,8 @@ type InterruptMode = z.infer<typeof InterruptModeSchema>;
1737
1738
  *
1738
1739
  * Distinct from {@link LanguageAttributes} (the Twilio-SDK-shaped type used by
1739
1740
  * `connectConversationRelay`): this is the customization-facing model used in
1740
- * {@link TwiMLOptions}, mirroring the Python SDK's `LanguageConfig`.
1741
+ * {@link VoiceTwiMLOptionsConversationRelay}, mirroring the Python SDK's
1742
+ * `LanguageConfig`.
1741
1743
  */
1742
1744
  declare const LanguageConfigSchema: z.ZodObject<{
1743
1745
  code: z.ZodString;
@@ -1747,6 +1749,25 @@ declare const LanguageConfigSchema: z.ZodObject<{
1747
1749
  speechModel: z.ZodOptional<z.ZodString>;
1748
1750
  }, z.core.$strip>;
1749
1751
  type LanguageConfig = z.infer<typeof LanguageConfigSchema>;
1752
+ /**
1753
+ * Provider-agnostic base for a voice provider's inbound-call TwiML
1754
+ * customization options.
1755
+ *
1756
+ * Each provider answers the inbound-call webhook with its own TwiML shape
1757
+ * ({@link VoiceTwiMLOptionsConversationRelaySchema} for `<ConversationRelay>`,
1758
+ * {@link VoiceTwiMLOptionsMediaStreamsSchema} for `<Connect><Stream>`), so
1759
+ * `VoiceProvider.handleIncomingCall`'s
1760
+ * `hostTwimlOptions` is typed against this base rather than a specific
1761
+ * provider's options. It carries only what every provider shares: the
1762
+ * `<Connect action>` URL, the transport URL, and `<Parameter>` children.
1763
+ *
1764
+ * Mirrors the Python SDK's `VoiceTwiMLOptions`.
1765
+ */
1766
+ declare const VoiceTwiMLOptionsSchema: z.ZodObject<{
1767
+ customParameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1768
+ actionUrl: z.ZodOptional<z.ZodString>;
1769
+ websocketUrl: z.ZodOptional<z.ZodString>;
1770
+ }, z.core.$strict>;
1750
1771
  /**
1751
1772
  * Options for the TwiML inside `<ConversationRelay>` (plus the
1752
1773
  * `<Connect action>` URL).
@@ -1759,10 +1780,12 @@ type LanguageConfig = z.infer<typeof LanguageConfigSchema>;
1759
1780
  *
1760
1781
  * This is the customization-facing counterpart to {@link ConversationRelayConfig}
1761
1782
  * (which is the Twilio-SDK-shaped emit model and carries the required `url`).
1762
- * Mirrors the Python SDK's `TwiMLOptions`.
1783
+ * Mirrors the Python SDK's `VoiceTwiMLOptionsConversationRelay`.
1763
1784
  */
1764
- declare const TwiMLOptionsSchema: z.ZodObject<{
1785
+ declare const VoiceTwiMLOptionsConversationRelaySchema: z.ZodObject<{
1765
1786
  customParameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1787
+ actionUrl: z.ZodOptional<z.ZodString>;
1788
+ websocketUrl: z.ZodOptional<z.ZodString>;
1766
1789
  welcomeGreeting: z.ZodOptional<z.ZodString>;
1767
1790
  welcomeGreetingInterruptible: z.ZodOptional<z.ZodEnum<{
1768
1791
  any: "any";
@@ -1770,9 +1793,108 @@ declare const TwiMLOptionsSchema: z.ZodObject<{
1770
1793
  none: "none";
1771
1794
  dtmf: "dtmf";
1772
1795
  }>>;
1773
- actionUrl: z.ZodOptional<z.ZodString>;
1774
1796
  conversationConfiguration: z.ZodOptional<z.ZodString>;
1797
+ language: z.ZodOptional<z.ZodString>;
1798
+ ttsLanguage: z.ZodOptional<z.ZodString>;
1799
+ transcriptionLanguage: z.ZodOptional<z.ZodString>;
1800
+ voice: z.ZodOptional<z.ZodString>;
1801
+ ttsProvider: z.ZodOptional<z.ZodString>;
1802
+ transcriptionProvider: z.ZodOptional<z.ZodString>;
1803
+ speechModel: z.ZodOptional<z.ZodString>;
1804
+ elevenlabsTextNormalization: z.ZodOptional<z.ZodEnum<{
1805
+ on: "on";
1806
+ auto: "auto";
1807
+ off: "off";
1808
+ }>>;
1809
+ eotThreshold: z.ZodOptional<z.ZodNumber>;
1810
+ partialPrompts: z.ZodOptional<z.ZodBoolean>;
1811
+ deepgramSmartFormat: z.ZodOptional<z.ZodBoolean>;
1812
+ speechTimeout: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodLiteral<"auto">]>>;
1813
+ interruptible: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
1814
+ any: "any";
1815
+ speech: "speech";
1816
+ none: "none";
1817
+ dtmf: "dtmf";
1818
+ }>, z.ZodBoolean]>>;
1819
+ interruptSensitivity: z.ZodOptional<z.ZodEnum<{
1820
+ low: "low";
1821
+ medium: "medium";
1822
+ high: "high";
1823
+ }>>;
1824
+ reportInputDuringAgentSpeech: z.ZodOptional<z.ZodEnum<{
1825
+ any: "any";
1826
+ speech: "speech";
1827
+ none: "none";
1828
+ dtmf: "dtmf";
1829
+ }>>;
1830
+ ignoreBackchannel: z.ZodOptional<z.ZodBoolean>;
1831
+ preemptible: z.ZodOptional<z.ZodBoolean>;
1832
+ dtmfDetection: z.ZodOptional<z.ZodBoolean>;
1833
+ hints: z.ZodOptional<z.ZodString>;
1834
+ events: z.ZodOptional<z.ZodString>;
1835
+ debug: z.ZodOptional<z.ZodString>;
1836
+ intelligenceService: z.ZodOptional<z.ZodString>;
1837
+ languages: z.ZodOptional<z.ZodArray<z.ZodObject<{
1838
+ code: z.ZodString;
1839
+ voice: z.ZodOptional<z.ZodString>;
1840
+ ttsProvider: z.ZodOptional<z.ZodString>;
1841
+ transcriptionProvider: z.ZodOptional<z.ZodString>;
1842
+ speechModel: z.ZodOptional<z.ZodString>;
1843
+ }, z.core.$strip>>>;
1844
+ extra: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodBoolean, z.ZodNumber]>>>;
1845
+ }, z.core.$strict>;
1846
+ type VoiceTwiMLOptions = z.infer<typeof VoiceTwiMLOptionsSchema>;
1847
+ type VoiceTwiMLOptionsConversationRelay = z.infer<typeof VoiceTwiMLOptionsConversationRelaySchema>;
1848
+ /**
1849
+ * Options for the TwiML inside `<Connect><Stream>`.
1850
+ *
1851
+ * Fields map to the attributes documented at
1852
+ * https://www.twilio.com/docs/voice/twiml/stream (the `<Stream>` verb) and
1853
+ * https://www.twilio.com/docs/voice/twiml/connect (the `<Connect>` verb it is
1854
+ * nested in). `track` is deliberately absent: a bidirectional `<Connect>`
1855
+ * stream only ever carries `inbound_track`, so it is not settable.
1856
+ *
1857
+ * Inherits `customParameters`, `actionUrl` and `websocketUrl` from the shared
1858
+ * base. Mirrors the Python SDK's `VoiceTwiMLOptionsMediaStreams`.
1859
+ */
1860
+ declare const VoiceTwiMLOptionsMediaStreamsSchema: z.ZodObject<{
1861
+ customParameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1862
+ actionUrl: z.ZodOptional<z.ZodString>;
1863
+ websocketUrl: z.ZodOptional<z.ZodString>;
1864
+ name: z.ZodOptional<z.ZodString>;
1865
+ statusCallback: z.ZodOptional<z.ZodString>;
1866
+ statusCallbackMethod: z.ZodOptional<z.ZodEnum<{
1867
+ POST: "POST";
1868
+ GET: "GET";
1869
+ }>>;
1870
+ actionMethod: z.ZodOptional<z.ZodEnum<{
1871
+ POST: "POST";
1872
+ GET: "GET";
1873
+ }>>;
1874
+ }, z.core.$strict>;
1875
+ type VoiceTwiMLOptionsMediaStreams = z.infer<typeof VoiceTwiMLOptionsMediaStreamsSchema>;
1876
+ /** @deprecated Use {@link VoiceTwiMLOptionsConversationRelay} instead. */
1877
+ type TwiMLOptions = VoiceTwiMLOptionsConversationRelay;
1878
+ /**
1879
+ * Unlike Python, this emits no runtime warning. It is a plain alias so that
1880
+ * `TwiMLOptionsSchema === VoiceTwiMLOptionsConversationRelaySchema`; wrapping it
1881
+ * in a warning proxy would break that identity, and callers commonly compare or
1882
+ * `.extend()` the schema.
1883
+ *
1884
+ * @deprecated Use {@link VoiceTwiMLOptionsConversationRelaySchema} instead.
1885
+ */
1886
+ declare const TwiMLOptionsSchema: z.ZodObject<{
1887
+ customParameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1888
+ actionUrl: z.ZodOptional<z.ZodString>;
1775
1889
  websocketUrl: z.ZodOptional<z.ZodString>;
1890
+ welcomeGreeting: z.ZodOptional<z.ZodString>;
1891
+ welcomeGreetingInterruptible: z.ZodOptional<z.ZodEnum<{
1892
+ any: "any";
1893
+ speech: "speech";
1894
+ none: "none";
1895
+ dtmf: "dtmf";
1896
+ }>>;
1897
+ conversationConfiguration: z.ZodOptional<z.ZodString>;
1776
1898
  language: z.ZodOptional<z.ZodString>;
1777
1899
  ttsLanguage: z.ZodOptional<z.ZodString>;
1778
1900
  transcriptionLanguage: z.ZodOptional<z.ZodString>;
@@ -1822,13 +1944,12 @@ declare const TwiMLOptionsSchema: z.ZodObject<{
1822
1944
  }, z.core.$strip>>>;
1823
1945
  extra: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodBoolean, z.ZodNumber]>>>;
1824
1946
  }, z.core.$strict>;
1825
- type TwiMLOptions = z.infer<typeof TwiMLOptionsSchema>;
1826
1947
  /**
1827
1948
  * Framework-neutral view of the Twilio TwiML webhook form.
1828
1949
  *
1829
1950
  * Populated by `TACServer` from the incoming Twilio webhook, then passed to a
1830
1951
  * customizer registered via `VoiceChannel.onInboundCallTwiml(...)` so the
1831
- * application can produce per-call {@link TwiMLOptions} overrides without
1952
+ * application can produce per-call {@link VoiceTwiMLOptions} overrides without
1832
1953
  * depending on Fastify types. Mirrors the Python SDK's `TwiMLRequest`.
1833
1954
  */
1834
1955
  declare const TwiMLRequestSchema: z.ZodObject<{
@@ -1887,6 +2008,12 @@ declare const ConversationRelayCallbackPayloadSchema: z.ZodObject<{
1887
2008
  SessionDuration: z.ZodOptional<z.ZodString>;
1888
2009
  }, z.core.$strip>;
1889
2010
  type ConversationRelayCallbackPayload = z.infer<typeof ConversationRelayCallbackPayloadSchema>;
2011
+ /** HTTP response a provider's out-of-band lifecycle webhook handler produces. */
2012
+ interface TwilioProviderCallbackResponse {
2013
+ status: number;
2014
+ content: string;
2015
+ contentType: string;
2016
+ }
1890
2017
  /**
1891
2018
  * A Twilio `statusCallback` webhook — call progress and disposition.
1892
2019
  *
@@ -2121,10 +2248,17 @@ interface InitiateVoiceConversationOptions {
2121
2248
  */
2122
2249
  websocketUrl?: string | undefined;
2123
2250
  /**
2124
- * Per-call overrides for the TwiML inside `<ConversationRelay>`. Merged over
2251
+ * Per-call overrides for the outbound TwiML. Merged over
2125
2252
  * `VoiceChannelConfig.defaultTwimlOptions` and TAC defaults.
2253
+ *
2254
+ * A union of the provider subtypes rather than the provider-agnostic base:
2255
+ * the base type carries only the three shared fields, so TypeScript's
2256
+ * excess-property check would reject a fresh object literal carrying any
2257
+ * provider-specific field. Each provider still parses this against its own
2258
+ * schema at the channel boundary, so passing the wrong subtype for the
2259
+ * configured provider fails at runtime.
2126
2260
  */
2127
- twimlOptions?: TwiMLOptions | undefined;
2261
+ twimlOptions?: VoiceTwiMLOptionsConversationRelay | VoiceTwiMLOptionsMediaStreams | undefined;
2128
2262
  /**
2129
2263
  * Parameters for Twilio's `calls.create()` — AMD, recording, status
2130
2264
  * callbacks, timeout (see {@link CallOptions}). Callback URLs auto-wire when
@@ -2132,7 +2266,94 @@ interface InitiateVoiceConversationOptions {
2132
2266
  */
2133
2267
  callOptions?: CallOptions | undefined;
2134
2268
  }
2269
+ /**
2270
+ * Validates outbound options for `ConversationRelayProvider`.
2271
+ *
2272
+ * Despite the union on {@link InitiateVoiceConversationOptions.twimlOptions},
2273
+ * this schema accepts only the ConversationRelay subtype — use
2274
+ * {@link InitiateVoiceConversationOptionsOpenAIRealtimeSchema} for
2275
+ * `<Connect><Stream>`.
2276
+ */
2135
2277
  declare const InitiateVoiceConversationOptionsSchema: z.ZodType<InitiateVoiceConversationOptions>;
2278
+ /**
2279
+ * Outbound options for `OpenAIRealtimeProvider`, adding a per-call
2280
+ * `sessionConfig` on top of {@link InitiateVoiceConversationOptionsSchema}.
2281
+ * Also narrows `twimlOptions` to {@link VoiceTwiMLOptionsMediaStreamsSchema}.
2282
+ *
2283
+ * Mirrors the Python SDK's `InitiateVoiceConversationOptionsOpenAIRealtime`.
2284
+ */
2285
+ declare const InitiateVoiceConversationOptionsOpenAIRealtimeSchema: z.ZodObject<{
2286
+ to: z.ZodString;
2287
+ websocketUrl: z.ZodOptional<z.ZodURL>;
2288
+ callOptions: z.ZodOptional<z.ZodType<CallOptions, unknown, z.core.$ZodTypeInternals<CallOptions, unknown>>>;
2289
+ twimlOptions: z.ZodOptional<z.ZodObject<{
2290
+ customParameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
2291
+ actionUrl: z.ZodOptional<z.ZodString>;
2292
+ websocketUrl: z.ZodOptional<z.ZodString>;
2293
+ name: z.ZodOptional<z.ZodString>;
2294
+ statusCallback: z.ZodOptional<z.ZodString>;
2295
+ statusCallbackMethod: z.ZodOptional<z.ZodEnum<{
2296
+ POST: "POST";
2297
+ GET: "GET";
2298
+ }>>;
2299
+ actionMethod: z.ZodOptional<z.ZodEnum<{
2300
+ POST: "POST";
2301
+ GET: "GET";
2302
+ }>>;
2303
+ }, z.core.$strict>>;
2304
+ sessionConfig: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
2305
+ }, z.core.$strict>;
2306
+ type InitiateVoiceConversationOptionsOpenAIRealtime = z.infer<typeof InitiateVoiceConversationOptionsOpenAIRealtimeSchema>;
2307
+ /**
2308
+ * Outbound options for `GPTLiveProvider`, adding a per-call `sessionConfig` on
2309
+ * top of {@link InitiateVoiceConversationOptionsSchema}. Also narrows
2310
+ * `twimlOptions` to {@link VoiceTwiMLOptionsMediaStreamsSchema}.
2311
+ *
2312
+ * Mirrors the Python SDK's `InitiateVoiceConversationOptionsGPTLive`.
2313
+ */
2314
+ declare const InitiateVoiceConversationOptionsGPTLiveSchema: z.ZodObject<{
2315
+ to: z.ZodString;
2316
+ websocketUrl: z.ZodOptional<z.ZodURL>;
2317
+ callOptions: z.ZodOptional<z.ZodType<CallOptions, unknown, z.core.$ZodTypeInternals<CallOptions, unknown>>>;
2318
+ twimlOptions: z.ZodOptional<z.ZodObject<{
2319
+ customParameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
2320
+ actionUrl: z.ZodOptional<z.ZodString>;
2321
+ websocketUrl: z.ZodOptional<z.ZodString>;
2322
+ name: z.ZodOptional<z.ZodString>;
2323
+ statusCallback: z.ZodOptional<z.ZodString>;
2324
+ statusCallbackMethod: z.ZodOptional<z.ZodEnum<{
2325
+ POST: "POST";
2326
+ GET: "GET";
2327
+ }>>;
2328
+ actionMethod: z.ZodOptional<z.ZodEnum<{
2329
+ POST: "POST";
2330
+ GET: "GET";
2331
+ }>>;
2332
+ }, z.core.$strict>>;
2333
+ sessionConfig: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
2334
+ }, z.core.$strict>;
2335
+ type InitiateVoiceConversationOptionsGPTLive = z.infer<typeof InitiateVoiceConversationOptionsGPTLiveSchema>;
2336
+
2337
+ /**
2338
+ * The `start` field of Twilio's Media Stream `start` event.
2339
+ *
2340
+ * This is an inbound, read-only message Twilio pushes over the
2341
+ * `<Connect><Stream>` WebSocket, so unlike the outbound TwiML options it
2342
+ * carries no snake_case alias layer — the wire names are parsed directly.
2343
+ *
2344
+ * Mirrors the Python SDK's `StreamStartMessage`. Python exposes a
2345
+ * `conversation_id` property aliasing `call_sid`; here callers read `callSid`
2346
+ * directly.
2347
+ *
2348
+ * @see https://www.twilio.com/docs/voice/media-streams/websocket-messages
2349
+ */
2350
+ declare const StreamStartMessageSchema: z.ZodObject<{
2351
+ streamSid: z.ZodString;
2352
+ callSid: z.ZodString;
2353
+ mediaFormat: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
2354
+ customParameters: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
2355
+ }, z.core.$strip>;
2356
+ type StreamStartMessage = z.infer<typeof StreamStartMessageSchema>;
2136
2357
 
2137
2358
  /**
2138
2359
  * Structured payload generated during a handoff.
@@ -2231,6 +2452,33 @@ declare const AnthropicToolSchema: z.ZodObject<{
2231
2452
  }, z.core.$strip>;
2232
2453
  }, z.core.$strip>;
2233
2454
  type AnthropicTool = z.infer<typeof AnthropicToolSchema>;
2455
+ /**
2456
+ * OpenAI Realtime tool format.
2457
+ *
2458
+ * Realtime's `session.tools` expects the fields flat on the tool object,
2459
+ * unlike Chat Completions ({@link OpenAIToolSchema}), which nests them under a
2460
+ * `function` key.
2461
+ */
2462
+ declare const OpenAIRealtimeToolSchema: z.ZodObject<{
2463
+ type: z.ZodLiteral<"function">;
2464
+ name: z.ZodString;
2465
+ description: z.ZodString;
2466
+ parameters: z.ZodObject<{
2467
+ type: z.ZodEnum<{
2468
+ string: "string";
2469
+ number: "number";
2470
+ boolean: "boolean";
2471
+ object: "object";
2472
+ array: "array";
2473
+ }>;
2474
+ properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
2475
+ required: z.ZodOptional<z.ZodArray<z.ZodString>>;
2476
+ items: z.ZodOptional<z.ZodAny>;
2477
+ enum: z.ZodOptional<z.ZodArray<z.ZodAny>>;
2478
+ description: z.ZodOptional<z.ZodString>;
2479
+ }, z.core.$strip>;
2480
+ }, z.core.$strip>;
2481
+ type OpenAIRealtimeTool = z.infer<typeof OpenAIRealtimeToolSchema>;
2234
2482
  /**
2235
2483
  * Tool execution context
2236
2484
  */
@@ -3182,6 +3430,22 @@ declare class TAC {
3182
3430
  shutdown(): void;
3183
3431
  }
3184
3432
 
3433
+ type EventProperties = {
3434
+ account_sid: string;
3435
+ } & Record<string, unknown>;
3436
+ /**
3437
+ * Record a telemetry event. Never throws.
3438
+ *
3439
+ * @internal
3440
+ */
3441
+ declare function trackEvent(event: string, properties: EventProperties): void;
3442
+ /**
3443
+ * Flush pending events and close the client. Called by `TAC.shutdown()`.
3444
+ *
3445
+ * @internal
3446
+ */
3447
+ declare function shutdownAnalytics(): Promise<void>;
3448
+
3185
3449
  /**
3186
3450
  * Messaging channel configuration options.
3187
3451
  * Alias for BaseChannelOptions that can be extended by specific channel implementations.
@@ -3238,6 +3502,14 @@ declare abstract class MessagingChannel extends BaseChannel {
3238
3502
  * per-conversation channelId) to build the address.
3239
3503
  */
3240
3504
  protected abstract getAgentAddress(conversationId: ConversationId): ConversationAddress;
3505
+ /**
3506
+ * Report a delivered response.
3507
+ *
3508
+ * Each channel calls this from its own `sendResponse` rather than the base
3509
+ * wrapping the call: `sendResponse` is the public extension point, so making
3510
+ * it a template method would break subclasses defined outside this package.
3511
+ */
3512
+ protected trackResponseSent(conversationId: ConversationId): void;
3241
3513
  /**
3242
3514
  * Check if a message is from the bot itself (2-tier).
3243
3515
  *
@@ -3507,15 +3779,154 @@ declare class ChatChannel extends MessagingChannel {
3507
3779
  }
3508
3780
 
3509
3781
  /**
3510
- * Configuration for the Voice channel.
3782
+ * Base class for a {@link VoiceChannel}'s real-time media provider.
3783
+ *
3784
+ * Holds the owning `channel` (Calls API lifecycle, conversation bookkeeping,
3785
+ * `TAC`). Every method here has a default that either declines the capability
3786
+ * or does nothing, so a provider only implements the transport it actually
3787
+ * supports — ConversationRelay serves inbound TwiML and a WebSocket, while a
3788
+ * provider with no inbound story simply inherits the refusal.
3789
+ */
3790
+ declare class VoiceProvider {
3791
+ /** The `VoiceChannel` that owns this provider. */
3792
+ readonly channel: VoiceChannel;
3793
+ /** Logger named after the concrete provider class. */
3794
+ protected readonly logger: Logger;
3795
+ constructor(channel: VoiceChannel);
3796
+ /**
3797
+ * Stable snake_case identifier for this provider, reported on voice
3798
+ * telemetry events so emissions from different transports are
3799
+ * distinguishable. Built-in providers override it; a provider defined outside
3800
+ * the SDK inherits `"custom"`.
3801
+ *
3802
+ * @internal
3803
+ */
3804
+ get providerId(): string;
3805
+ /**
3806
+ * Channel name identifier, e.g. `"VOICE"`.
3807
+ *
3808
+ * A label for this provider's transport; it does not change how
3809
+ * `VoiceChannel` reports its `ChannelType`.
3810
+ */
3811
+ get channelName(): string;
3812
+ /**
3813
+ * Build the response for an inbound call. Default: not supported.
3814
+ *
3815
+ * @param _twimlRequest - Parsed Twilio webhook fields for the inbound call.
3816
+ * @param _options - Additional per-call inputs.
3817
+ * @param _options.hostTwimlOptions - Per-call TwiML supplied by a custom
3818
+ * in-process host.
3819
+ */
3820
+ handleIncomingCall(_twimlRequest?: TwiMLRequest, _options?: {
3821
+ hostTwimlOptions?: VoiceTwiMLOptions;
3822
+ }): Promise<string>;
3823
+ /**
3824
+ * Handle this provider's own out-of-band lifecycle webhook, if it has one.
3825
+ *
3826
+ * Not every provider has an equivalent — Twilio's ConversationRelay posts to
3827
+ * `<Connect action=...>` when the session ends (`ConversationRelayProvider`
3828
+ * uses this as a WebSocket-disconnect backup); Media Streams instead has its
3829
+ * own independent `statusCallback` (`stream-started` / `stream-stopped` /
3830
+ * `stream-error`), which is purely informational and doesn't gate call flow.
3831
+ * Default acknowledges with an empty 200 for providers with nothing to do
3832
+ * here.
3833
+ */
3834
+ handleTwilioProviderCallback(_payload: Record<string, unknown>): Promise<TwilioProviderCallbackResponse>;
3835
+ /**
3836
+ * Drive one WebSocket connection from accept to disconnect.
3837
+ *
3838
+ * Implementations may be synchronous or asynchronous: a provider that only
3839
+ * attaches event handlers can return `void`, while one that awaits an
3840
+ * upstream handshake before serving traffic returns a `Promise<void>`. The
3841
+ * owning `VoiceChannel` is responsible for handling a returned promise's
3842
+ * rejection, so an async override never produces an unhandled rejection.
3843
+ */
3844
+ handleWebSocket(_websocket: WebSocket): void | Promise<void>;
3845
+ /** Place an outbound call. Default: not supported. */
3846
+ initiateOutboundConversation(_options: InitiateVoiceConversationOptions): Promise<InitiateVoiceConversationResult>;
3847
+ /** Send a text response back through this provider's transport, if supported. */
3848
+ sendResponse(_conversationId: ConversationId, _message: string, _metadata?: Record<string, unknown>): Promise<void>;
3849
+ /**
3850
+ * Stream a text response back through this provider's transport, token by
3851
+ * token, if supported. Default: not supported.
3852
+ *
3853
+ * @param _conversationId - Conversation whose transport receives the tokens.
3854
+ * @param _stream - Async iterable of text chunks to relay as they arrive.
3855
+ * @param _options - Additional per-call inputs.
3856
+ * @param _options.signal - Aborts the stream mid-flight, e.g. when the caller
3857
+ * interrupts.
3858
+ * @returns The accumulated response text.
3859
+ */
3860
+ sendStreamingResponse(_conversationId: ConversationId, _stream: AsyncIterable<string>, _options?: {
3861
+ signal?: AbortSignal;
3862
+ }): Promise<string>;
3863
+ /** Return the Twilio-facing WebSocket for a conversation, if tracked. */
3864
+ getWebSocket(_conversationId: ConversationId): WebSocket | null;
3865
+ /**
3866
+ * Drop this provider's transport state on channel shutdown.
3867
+ *
3868
+ * Called by {@link VoiceChannel.shutdown} before the channel clears its own
3869
+ * conversation bookkeeping. Default no-op — providers override this to drop
3870
+ * whatever transport state they track. Live WebSocket connections are owned
3871
+ * and closed by the server, so an override only clears in-process tracking.
3872
+ */
3873
+ shutdown(): void;
3874
+ /**
3875
+ * Set callback URLs on `callParams` for every registered call-event handler.
3876
+ *
3877
+ * A URL is derived only when its handler is registered — an unwanted
3878
+ * call-event URL would otherwise surface as silent 11200 alerts for a
3879
+ * feature nobody asked for. If TAC isn't serving these routes, set the URLs
3880
+ * explicitly via `CallOptions` (or the provider config's default call
3881
+ * options, where available). An explicit URL from either options layer is
3882
+ * never overwritten.
3883
+ */
3884
+ protected applyCallEventCallbacks(callParams: Record<string, unknown>): Record<string, unknown>;
3885
+ }
3886
+ /**
3887
+ * Base configuration for a {@link VoiceChannel}'s real-time media provider.
3888
+ *
3889
+ * Subclasses add their provider's own settings and implement
3890
+ * {@link VoiceProviderConfig.createProvider} to build the provider they
3891
+ * configure.
3892
+ */
3893
+ declare class VoiceProviderConfig {
3894
+ /** Memory retrieval mode for this channel. Defaults to `'never'`. */
3895
+ memoryMode: MemoryMode;
3896
+ /**
3897
+ * The {@link BaseChannelOptions} this config was constructed with, retained
3898
+ * verbatim so `VoiceChannel` can hand them to `BaseChannel`.
3899
+ *
3900
+ * A provider config carries channel-level options (`dedupCapacity` and
3901
+ * friends) alongside its own provider settings; without this they would be
3902
+ * dropped on the config path while still applying on the plain-object path,
3903
+ * which is the same channel configured two ways.
3904
+ *
3905
+ * @internal
3906
+ */
3907
+ readonly channelOptions: BaseChannelOptions;
3908
+ constructor(options?: BaseChannelOptions);
3909
+ /**
3910
+ * Build the {@link VoiceProvider} this config configures.
3911
+ *
3912
+ * @param _channel - The owning `VoiceChannel`.
3913
+ * @param _tacConfig - `TACConfig` — providers that talk TwiML need it to
3914
+ * derive default URLs (`voicePublicDomain` etc.).
3915
+ */
3916
+ createProvider(_channel: VoiceChannel, _tacConfig: TACConfig): VoiceProvider;
3917
+ }
3918
+
3919
+ /**
3920
+ * Options accepted by {@link ConversationRelayProviderConfig}.
3511
3921
  *
3512
3922
  * `defaultTwimlOptions` is one of several TwiML layers that merge per-field;
3513
- * see `handleIncomingCall` (inbound) and `initiateOutboundConversation`
3514
- * (outbound) for the full precedence order.
3923
+ * see `ConversationRelayProvider.handleIncomingCall` (inbound) and
3924
+ * `ConversationRelayProvider.initiateOutboundConversation` (outbound) for the
3925
+ * full precedence order.
3515
3926
  */
3516
- interface VoiceChannelConfig extends BaseChannelOptions {
3927
+ interface ConversationRelayProviderConfigOptions extends BaseChannelOptions {
3517
3928
  /**
3518
- * Static `TwiMLOptions` applied to every call (inbound and outbound).
3929
+ * Static `VoiceTwiMLOptionsConversationRelay` applied to every call (inbound and outbound).
3519
3930
  * Controls the TwiML inside `<ConversationRelay>` — voice, language,
3520
3931
  * transcription provider, welcomeGreeting, `<Language>` children, etc. Use
3521
3932
  * this when the same ConversationRelay configuration is correct for every call.
@@ -3526,7 +3937,7 @@ interface VoiceChannelConfig extends BaseChannelOptions {
3526
3937
  * Note: `customParameters` and `languages` replace wholesale when a
3527
3938
  * higher-priority layer sets them.
3528
3939
  */
3529
- defaultTwimlOptions?: TwiMLOptions;
3940
+ defaultTwimlOptions?: VoiceTwiMLOptionsConversationRelay;
3530
3941
  /**
3531
3942
  * Static {@link CallOptions} applied to every outbound call — the
3532
3943
  * `calls.create` parameters, including the call-event callback URLs. This is
@@ -3537,242 +3948,95 @@ interface VoiceChannelConfig extends BaseChannelOptions {
3537
3948
  defaultCallOptions?: CallOptions;
3538
3949
  }
3539
3950
  /**
3540
- * Callback that produces per-call overrides for the TwiML inside
3541
- * `<ConversationRelay>` on inbound calls. Receives a framework-neutral
3542
- * {@link TwiMLRequest} and returns {@link TwiMLOptions}.
3951
+ * Configuration for {@link ConversationRelayProvider}, the default
3952
+ * {@link VoiceChannel} provider.
3953
+ *
3954
+ * TwiML configuration layers (highest precedence first):
3955
+ *
3956
+ * Inbound calls (`handleIncomingCall`):
3957
+ * 1. Output of the customizer registered via
3958
+ * `VoiceChannel.onInboundCallTwiml(...)` [optional]
3959
+ * 2. `defaultTwimlOptions` [optional]
3960
+ * 3. `handleIncomingCall(hostTwimlOptions)` [optional]
3961
+ * 4. TAC defaults
3962
+ *
3963
+ * Outbound calls (`initiateOutboundConversation`):
3964
+ * 1. `InitiateVoiceConversationOptions.twimlOptions` [optional]
3965
+ * 2. `defaultTwimlOptions` [optional]
3966
+ * 3. TAC defaults
3967
+ *
3968
+ * Calls-API parameters (`initiateOutboundConversation`):
3969
+ * 1. `InitiateVoiceConversationOptions.callOptions` [optional]
3970
+ * 2. `defaultCallOptions` [optional]
3971
+ * 3. Callback URLs derived from `TACConfig.voicePublicDomain` +
3972
+ * `voiceCallEventPath`, for handlers that are registered
3973
+ *
3974
+ * All layers merge per-field via key presence — only fields a layer explicitly
3975
+ * sets override lower layers. Arrays (`languages`) and nested objects
3976
+ * (`customParameters`) replace wholesale when set.
3977
+ *
3978
+ * Per-call inbound customization is registered via
3979
+ * `VoiceChannel.onInboundCallTwiml(...)` (not on this config).
3543
3980
  */
3544
- type InboundCallTwimlHandler = (req: TwiMLRequest) => Promise<TwiMLOptions>;
3545
- /** Handler for Twilio `statusCallback` webhooks. */
3546
- type CallStatusHandler = (event: CallStatusEvent) => Promise<void> | void;
3547
- /** Handler for Twilio `asyncAmdStatusCallback` webhooks. */
3548
- type AmdHandler = (event: AmdEvent) => Promise<void> | void;
3549
- /** Handler for Twilio `recordingStatusCallback` webhooks. */
3550
- type RecordingHandler = (event: RecordingEvent) => Promise<void> | void;
3551
- /** One ConversationRelay keypress, as delivered to a {@link DtmfHandler}. */
3552
- interface DtmfEvent {
3981
+ declare class ConversationRelayProviderConfig extends VoiceProviderConfig {
3553
3982
  /**
3554
- * Undefined only when the keypress beat conversation setup: `dtmf` before
3555
- * ConversationRelay's `setup`, or an orchestrated-mode lookup that failed.
3983
+ * Static `VoiceTwiMLOptionsConversationRelay` for the TwiML inside `<ConversationRelay>`, applied
3984
+ * to every call (inbound and outbound).
3556
3985
  */
3557
- conversationId: ConversationId | undefined;
3558
- /** Undefined only before ConversationRelay's `setup` message. */
3559
- callSid: string | undefined;
3560
- /** The key pressed: `0`-`9`, `*`, `#`, or `A`-`D`. */
3561
- digit: string;
3562
- /** Present whenever `conversationId` is. */
3563
- session?: ConversationSession;
3986
+ readonly defaultTwimlOptions?: VoiceTwiMLOptionsConversationRelay;
3987
+ /** Static {@link CallOptions} applied to every outbound call. */
3988
+ readonly defaultCallOptions?: CallOptions;
3989
+ constructor(options?: ConversationRelayProviderConfigOptions);
3990
+ createProvider(channel: VoiceChannel, tacConfig: TACConfig): VoiceProvider;
3564
3991
  }
3565
- /** Handler for ConversationRelay `dtmf` messages (caller keypresses). */
3566
- type DtmfHandler = (event: DtmfEvent) => Promise<void> | void;
3567
3992
  /**
3568
- * Voice channel event callbacks extending base callbacks
3993
+ * Pre-provider-split name for {@link ConversationRelayProviderConfigOptions} —
3994
+ * the shape `new VoiceChannel(tac, {...})` accepts. Kept so the shipped public
3995
+ * export stays valid across the provider refactor.
3996
+ *
3997
+ * @deprecated Use {@link ConversationRelayProviderConfigOptions} instead.
3569
3998
  */
3570
- interface VoiceChannelEvents extends BaseChannelEvents {
3571
- onSetup?: (data: {
3572
- callSid: string;
3573
- from: string;
3574
- to: string;
3575
- customParameters: Record<string, unknown> | undefined;
3576
- }) => void;
3577
- onPrompt?: (data: {
3578
- conversationId: ConversationId;
3579
- transcript: string;
3580
- userMemory?: TACMemoryResponse;
3581
- session?: ConversationSession;
3582
- abortSignal: AbortSignal;
3583
- }) => Promise<void> | void;
3584
- onInterrupt?: (data: {
3585
- conversationId: ConversationId;
3586
- utteranceUntilInterrupt: string | undefined;
3587
- durationUntilInterruptMs: number | undefined;
3588
- }) => void;
3589
- /** Caller keypress. See {@link VoiceChannel.onDtmf}. */
3590
- onDtmf?: DtmfHandler;
3591
- /**
3592
- * Fired once the session and WebSocket registration exist — in orchestrated
3593
- * mode possibly before the first prompt, since the lookup starts at setup.
3594
- */
3595
- onWebSocketConnected?: (data: {
3596
- conversationId: ConversationId;
3597
- }) => void;
3598
- onWebSocketDisconnected?: (data: {
3599
- conversationId: ConversationId;
3600
- }) => void;
3601
- }
3999
+ type VoiceChannelConfig = ConversationRelayProviderConfigOptions;
4000
+
3602
4001
  /**
3603
- * Voice Channel implementation for Twilio ConversationRelay
3604
- *
3605
- * Handles voice conversations through WebSocket connections.
3606
- * Manages real-time audio streaming and conversation state.
4002
+ * A single in-flight streaming response, with the {@link AbortController} that
4003
+ * cancels it and whether any token has been written to the transport yet.
3607
4004
  */
3608
4005
  interface StreamTask {
3609
4006
  controller: AbortController;
3610
4007
  hasSentTokens: boolean;
3611
4008
  }
3612
- declare class VoiceChannel extends BaseChannel {
4009
+ /**
4010
+ * Twilio ConversationRelay: Twilio handles ASR/TTS and exchanges JSON
4011
+ * `setup`/`prompt`/`interrupt` messages over one WebSocket.
4012
+ *
4013
+ * This is the default provider {@link VoiceChannel} builds when none is passed
4014
+ * explicitly.
4015
+ */
4016
+ declare class ConversationRelayProvider extends VoiceProvider {
4017
+ /** @internal */
4018
+ get providerId(): string;
4019
+ /**
4020
+ * The owning channel's logger, so relocated ConversationRelay logic keeps
4021
+ * logging exactly as it did when it lived on `VoiceChannel`.
4022
+ */
4023
+ protected readonly logger: Logger;
4024
+ /** In-flight streaming responses, keyed by conversation. */
4025
+ protected readonly streamTasks: Map<ConversationId, StreamTask>;
4026
+ private readonly config;
4027
+ private readonly tacConfig;
4028
+ private readonly twimlBuilder;
3613
4029
  private readonly webSocketConnections;
3614
- private readonly voiceCallbacks;
3615
- private readonly streamTasks;
3616
4030
  private readonly promptQueues;
3617
4031
  private readonly initializationRetries;
3618
4032
  private readonly callSidToConversationId;
3619
4033
  private readonly MAX_INITIALIZATION_RETRIES;
3620
- private twilioClient;
3621
- private readonly voiceConfig;
3622
- private onInboundCallTwimlHandler;
3623
- private onCallStatusHandler;
3624
- private onAmdHandler;
3625
- private onRecordingHandler;
3626
- constructor(tac: TAC, options?: VoiceChannelConfig);
3627
- /**
3628
- * Register a callback that produces per-call overrides for the TwiML inside
3629
- * `<ConversationRelay>` on inbound calls.
3630
- *
3631
- * The callback receives a framework-neutral {@link TwiMLRequest} (parsed from
3632
- * the Twilio webhook form) and returns {@link TwiMLOptions}. Fields the
3633
- * callback explicitly sets override `defaultTwimlOptions` and TAC defaults;
3634
- * unset fields fall through.
3635
- *
3636
- * @example
3637
- * ```typescript
3638
- * voiceChannel.onInboundCallTwiml(async req => {
3639
- * if (req.callerCountry === 'MX') {
3640
- * return { language: 'es-MX', welcomeGreeting: '¡Hola!' };
3641
- * }
3642
- * return {};
3643
- * });
3644
- * ```
3645
- *
3646
- * Outbound calls don't use this — pass per-call TwiML via
3647
- * `InitiateVoiceConversationOptions.twimlOptions` directly.
3648
- */
3649
- onInboundCallTwiml(callback: InboundCallTwimlHandler): void;
3650
- /**
3651
- * Register a handler for Twilio `statusCallback` webhooks.
3652
- *
3653
- * This is the Calls-API status callback (call disposition), not the
3654
- * ConversationRelay session callback — see
3655
- * {@link handleConversationRelayCallback}.
3656
- *
3657
- * Registering does two things: it stores the handler, and it makes later
3658
- * outbound calls pass `statusCallback` to `calls.create`. With no handler
3659
- * registered TAC omits that parameter, so Twilio has nowhere to post and the
3660
- * event never arrives.
3661
- *
3662
- * Twilio reports only the terminal event by default, which covers every
3663
- * disposition; set `CallOptions.statusCallbackEvent` for ringing/answered.
3664
- *
3665
- * @example
3666
- * ```typescript
3667
- * voiceChannel.onCallStatus(async event => {
3668
- * if (event.isUnreached) {
3669
- * // queue a retry
3670
- * }
3671
- * });
3672
- * ```
3673
- */
3674
- onCallStatus(callback: CallStatusHandler): void;
3675
- /**
3676
- * Register a handler for Twilio `asyncAmdStatusCallback` webhooks.
3677
- *
3678
- * Registering makes later outbound calls pass `asyncAmdStatusCallback` to
3679
- * `calls.create`; without a handler TAC omits it and Twilio has nowhere to
3680
- * post the result. It does not enable detection — that's per-call, via
3681
- * `CallOptions.machineDetection` and `asyncAmd`, both of which are required
3682
- * for this to fire (at most once per call).
3683
- *
3684
- * @example
3685
- * ```typescript
3686
- * voiceChannel.onAmd(async event => {
3687
- * if (event.isMachine) {
3688
- * await voiceChannel.endCall(event.callSid); // voicemail → hang up
3689
- * }
3690
- * });
3691
- * ```
3692
- */
3693
- onAmd(callback: AmdHandler): void;
3694
- /**
3695
- * Register a handler for Twilio `recordingStatusCallback` webhooks.
3696
- *
3697
- * Registering makes later outbound calls pass `recordingStatusCallback` to
3698
- * `calls.create`; without a handler TAC omits it and Twilio has nowhere to
3699
- * post. It does not start recording — that's `CallOptions.record`, which is
3700
- * required for this to fire.
3701
- *
3702
- * @example
3703
- * ```typescript
3704
- * voiceChannel.onRecording(async event => {
3705
- * if (event.recordingStatus === 'completed') {
3706
- * // store event.recordingUrl
3707
- * }
3708
- * });
3709
- * ```
3710
- */
3711
- onRecording(callback: RecordingHandler): void;
3712
- /**
3713
- * Register a handler for DTMF keypresses, called once per key in order.
3714
- *
3715
- * Requires `dtmfDetection: true` on the ConversationRelay config — without it
3716
- * Twilio sends nothing and this never fires. Digits aren't buffered, so
3717
- * accumulating a multi-digit entry is the handler's job.
3718
- *
3719
- * A keypress initializes the conversation just as a prompt does, since a
3720
- * caller can type without ever speaking; if that fails the digit still
3721
- * arrives, with `conversationId` and `session` undefined. Keypresses don't
3722
- * cancel in-flight streaming on their own — that's a separate `interrupt`
3723
- * message, sent when `interruptible` includes `dtmf`.
3724
- *
3725
- * @example
3726
- * ```typescript
3727
- * const digits = new Map<string, string>();
3728
- *
3729
- * voiceChannel.onDtmf(({ conversationId, digit }) => {
3730
- * if (!conversationId) return;
3731
- * digits.set(conversationId, (digits.get(conversationId) ?? '') + digit);
3732
- * });
3733
- * ```
3734
- */
3735
- onDtmf(callback: DtmfHandler): void;
3736
- /**
3737
- * Resolve the public WebSocket URL from `TACConfig.voicePublicDomain` +
3738
- * `TACConfig.voiceWebsocketPath`. Throws if `voicePublicDomain` isn't set.
3739
- */
3740
- private resolveWebsocketUrl;
3741
- /**
3742
- * Resolve the default `<Connect action=...>` cleanup URL.
3743
- *
3744
- * Returns undefined if `voicePublicDomain` isn't set; that's fine because
3745
- * actionUrl has higher-priority layers (customizer, twimlOptions, Studio
3746
- * handoff) above this fallback.
3747
- */
3748
- private resolveDefaultActionUrl;
3749
- private getTwilioClient;
3750
- get channelType(): ChannelType;
3751
- /**
3752
- * Register event callbacks (override for Voice-specific events)
3753
- */
3754
- on(event: string, callback: (...args: any[]) => void): void;
3755
- /**
3756
- * Process conversation webhooks for cleanup.
3757
- *
3758
- * Voice channel processes CONVERSATION_UPDATED events:
3759
- * - CLOSED status: Clean up local session state
3760
- *
3761
- * Note: Conversation tracking uses instance-local memory. In multi-instance
3762
- * deployments, webhooks may route to a different instance, preventing cleanup.
3763
- *
3764
- * @param payload - Raw webhook event data from Twilio
3765
- * @param idempotencyToken - Optional Twilio idempotency token from request headers
3766
- */
3767
- processWebhook(payload: unknown, idempotencyToken?: string): Promise<void>;
3768
- /**
3769
- * Handle conversation updated event
3770
- */
3771
- private handleConversationUpdated;
4034
+ constructor(channel: VoiceChannel, tacConfig: TACConfig, config: ConversationRelayProviderConfig);
4035
+ get channelName(): string;
3772
4036
  /**
3773
4037
  * Get active WebSocket connection for a conversation
3774
4038
  */
3775
- getWebsocket(conversationId: ConversationId): WebSocket | null;
4039
+ getWebSocket(conversationId: ConversationId): WebSocket | null;
3776
4040
  /**
3777
4041
  * Poll Conversation Orchestrator for the conversation ConversationRelay
3778
4042
  * created for `callSid`, then register the local session and WebSocket.
@@ -3783,7 +4047,7 @@ declare class VoiceChannel extends BaseChannel {
3783
4047
  /**
3784
4048
  * Handle WebSocket connection from ConversationRelay
3785
4049
  */
3786
- handleWebSocketConnection(ws: WebSocket): void;
4050
+ handleWebSocket(ws: WebSocket): void;
3787
4051
  /**
3788
4052
  * Handle WebSocket prompt message (user speech)
3789
4053
  */
@@ -3820,6 +4084,33 @@ declare class VoiceChannel extends BaseChannel {
3820
4084
  sendStreamingResponse(conversationId: ConversationId, stream: AsyncIterable<string>, options?: {
3821
4085
  signal?: AbortSignal;
3822
4086
  }): Promise<string>;
4087
+ /**
4088
+ * Start tracking a streaming task for a conversation
4089
+ *
4090
+ * @param conversationId - The conversation ID
4091
+ * @returns The stream task with its AbortController
4092
+ */
4093
+ startStreamTask(conversationId: ConversationId): StreamTask;
4094
+ /**
4095
+ * Cancel an active streaming task
4096
+ *
4097
+ * @param conversationId - The conversation ID
4098
+ * @returns true if a task was cancelled, false otherwise
4099
+ */
4100
+ cancelStreamTask(conversationId: ConversationId): boolean;
4101
+ /**
4102
+ * Complete a streaming task (remove from tracking)
4103
+ *
4104
+ * @param conversationId - The conversation ID
4105
+ */
4106
+ completeStreamTask(conversationId: ConversationId): void;
4107
+ /**
4108
+ * Check if a stream task is active
4109
+ *
4110
+ * @param conversationId - The conversation ID
4111
+ * @returns true if an active task exists
4112
+ */
4113
+ hasActiveStreamTask(conversationId: ConversationId): boolean;
3823
4114
  /**
3824
4115
  * Generate the TwiML response for an incoming voice call.
3825
4116
  *
@@ -3834,7 +4125,8 @@ declare class VoiceChannel extends BaseChannel {
3834
4125
  * 1. Output of the customizer registered via
3835
4126
  * `VoiceChannel.onInboundCallTwiml(...)` if configured and `twimlRequest`
3836
4127
  * is given. (Application-owned.)
3837
- * 2. `VoiceChannelConfig.defaultTwimlOptions` — per-channel defaults.
4128
+ * 2. `ConversationRelayProviderConfig.defaultTwimlOptions` — per-channel
4129
+ * defaults.
3838
4130
  * 3. `hostTwimlOptions` — per-call transport facts supplied by the host (the
3839
4131
  * code owning the route), e.g. a per-call `websocketUrl` with an affinity
3840
4132
  * token.
@@ -3857,73 +4149,42 @@ declare class VoiceChannel extends BaseChannel {
3857
4149
  * `websocketUrl`), layered below `defaultTwimlOptions` and the application
3858
4150
  * customizer but above the TAC defaults.
3859
4151
  * @returns TwiML XML string for call connection.
4152
+ * @throws {Error} if either options layer isn't a
4153
+ * {@link VoiceTwiMLOptionsConversationRelay}.
3860
4154
  */
3861
4155
  handleIncomingCall(twimlRequest?: TwiMLRequest, options?: {
3862
- hostTwimlOptions?: TwiMLOptions;
4156
+ hostTwimlOptions?: VoiceTwiMLOptions;
3863
4157
  }): Promise<string>;
3864
4158
  /**
3865
- * Layer TwiML options, lowest precedence first: TAC defaults → `host`
3866
- * (calling host's per-call values) → channel `defaultTwimlOptions` → `perCall`
3867
- * (application customizer output for inbound, or
3868
- * `InitiateVoiceConversationOptions.twimlOptions` for outbound).
3869
- */
3870
- private buildTwimlOptions;
3871
- /**
3872
- * Apply fields explicitly present on `source` onto `target`.
3873
- *
3874
- * Nested objects (`customParameters`), arrays (`languages`), and dicts
3875
- * (`extra`) replace wholesale — there's no per-key merging.
3876
- *
3877
- * `actionUrl` is skipped here on purpose — it's resolved once via
3878
- * `resolveActionUrl` looking at every layer at once, and that resolved value
3879
- * is written into `target` before this overlay runs. Letting it through here
3880
- * would let a higher-priority layer that didn't set actionUrl silently clobber
3881
- * a lower layer that did.
3882
- *
3883
- * "Explicitly present" is detected via key presence (`key in source`), which
3884
- * mirrors Python's `model_fields_set`: a key set to `undefined` is still
3885
- * "present" and overrides lower layers, while an absent key falls through.
3886
- */
3887
- private overlayFields;
3888
- /**
3889
- * Resolve the TwiML `<Connect action=...>` URL.
4159
+ * Narrow provider-agnostic {@link VoiceTwiMLOptions} to this provider's
4160
+ * concrete shape. `VoiceProvider.handleIncomingCall` is typed against the
4161
+ * base so every provider can accept its own TwiML options, so the
4162
+ * ConversationRelay shape has to be established at runtime.
3890
4163
  *
3891
- * Precedence (highest to lowest):
3892
- * 1. application customizer
3893
- * 2. channel `defaultTwimlOptions`
3894
- * 3. `host` (calling host's per-call options)
3895
- * 4. Studio handoff (when `studioHandoffFlowSid` is configured)
3896
- * 5. Channel default — derived from `TACConfig.voicePublicDomain` +
3897
- * `TACConfig.voiceActionPath`.
3898
- *
3899
- * User-expressed intent (Studio handoff is configured explicitly on
3900
- * `TACConfig`) beats the SDK's generated cleanup default.
3901
- *
3902
- * Explicit `actionUrl: undefined` on a layer (key present, value undefined)
3903
- * suppresses `<Connect action=...>` entirely — all lower layers are skipped.
3904
- * `actionUrl` left absent (key not present) falls through to the next layer.
4164
+ * @param value - Options from a caller or the application customizer.
4165
+ * @param label - What produced `value`, for the error message.
3905
4166
  */
3906
- private resolveActionUrl;
4167
+ private narrowTwimlOptions;
3907
4168
  /**
3908
- * Overlay `perCall` onto `VoiceChannelConfig.defaultCallOptions`.
4169
+ * Overlay `perCall` onto `ConversationRelayProviderConfig.defaultCallOptions`.
3909
4170
  *
3910
- * Per-field via key presence, the same convention {@link overlayFields} uses
3911
- * for TwiML options — so a per-call `{ machineDetection: undefined }`
4171
+ * Per-field via key presence, the same convention `TwiMLBuilderBase.overlayFields`
4172
+ * uses for TwiML options — so a per-call `{ machineDetection: undefined }`
3912
4173
  * explicitly clears the channel default rather than falling through to it.
3913
4174
  *
3914
4175
  * The result is always validated, for two reasons: a combination only
3915
4176
  * reachable by layering — per-call clearing `machineDetection` while the
3916
4177
  * default set `asyncAmd` — must still fail instead of reaching Twilio, and
3917
- * `VoiceChannelConfig` is a plain interface, so `defaultCallOptions` has had
3918
- * no runtime validation of its own.
4178
+ * `ConversationRelayProviderConfigOptions` is a plain interface, so
4179
+ * `defaultCallOptions` has had no runtime validation of its own.
3919
4180
  */
3920
4181
  private mergeCallOptions;
3921
4182
  /**
3922
4183
  * Build the extra arguments for `client.calls.create`.
3923
4184
  *
3924
4185
  * Layers, highest precedence first: this call's `callOptions`,
3925
- * `VoiceChannelConfig.defaultCallOptions`, then callback URLs derived from
3926
- * `voicePublicDomain` + `voiceCallEventPath`.
4186
+ * `ConversationRelayProviderConfig.defaultCallOptions`, then callback URLs
4187
+ * derived from `voicePublicDomain` + `voiceCallEventPath`.
3927
4188
  *
3928
4189
  * A URL is derived only when its handler is registered. That's a deliberate
3929
4190
  * deviation from `websocketUrl` / `actionUrl`, which derive unconditionally:
@@ -3943,14 +4204,16 @@ declare class VoiceChannel extends BaseChannel {
3943
4204
  *
3944
4205
  * TwiML fields are merged per-field, highest precedence first:
3945
4206
  * 1. `options.twimlOptions` — per-call overrides
3946
- * 2. `VoiceChannelConfig.defaultTwimlOptions` — channel-wide defaults
4207
+ * 2. `ConversationRelayProviderConfig.defaultTwimlOptions` — channel-wide
4208
+ * defaults
3947
4209
  * 3. TAC defaults: welcome greeting, `conversationConfiguration` from
3948
4210
  * `TACConfig`, and `actionUrl` from Studio handoff (if configured), else
3949
4211
  * derived from `TACConfig.voicePublicDomain` + `voiceActionPath`.
3950
4212
  *
3951
4213
  * Calls-API parameters merge the same way:
3952
4214
  * 1. `options.callOptions` — per-call overrides
3953
- * 2. `VoiceChannelConfig.defaultCallOptions` — channel-wide defaults
4215
+ * 2. `ConversationRelayProviderConfig.defaultCallOptions` — channel-wide
4216
+ * defaults
3954
4217
  * 3. Callback URLs derived from `TACConfig.voicePublicDomain` +
3955
4218
  * `voiceCallEventPath`, for handlers that are registered
3956
4219
  *
@@ -3963,203 +4226,1849 @@ declare class VoiceChannel extends BaseChannel {
3963
4226
  * Handle ConversationRelay callback from Twilio. Cleans up on call completion
3964
4227
  * in voice-only mode; in orchestrated mode the CO webhook owns cleanup.
3965
4228
  *
3966
- * @param payload - Callback payload from Twilio
4229
+ * @param rawPayload - Callback payload from Twilio
3967
4230
  * @returns Response with status, content, and content type
3968
4231
  */
3969
- handleConversationRelayCallback(payload: ConversationRelayCallbackPayload): Promise<{
3970
- status: number;
3971
- content: string;
3972
- contentType: string;
3973
- }>;
4232
+ handleTwilioProviderCallback(rawPayload: Record<string, unknown>): Promise<TwilioProviderCallbackResponse>;
3974
4233
  /**
3975
- * Whether a call-webhook payload belongs to the configured account.
3976
- *
3977
- * Twilio signature validation already gates the route; this is defense in
3978
- * depth. A payload with no `AccountSid` is allowed through.
4234
+ * Generate TwiML to connect a call to ConversationRelay.
4235
+ * Validates configuration with Zod before generating TwiML.
3979
4236
  *
3980
- * Subaccounts: events carry the SID the call was placed on, so configure TAC
3981
- * with that account or its events get dropped here.
4237
+ * @param config - ConversationRelay configuration (url, transcription, TTS, etc.)
4238
+ * @param options - Optional settings for parameters and the Connect verb
4239
+ * @returns TwiML XML string
4240
+ * @throws {Error} if config validation fails
3982
4241
  */
3983
- private callEventAccountOk;
4242
+ connectConversationRelay(config: ConversationRelayConfig, options?: {
4243
+ parameters?: CustomParameters;
4244
+ actionUrl?: string;
4245
+ }): string;
3984
4246
  /**
3985
- * Parse a call-event webhook form and dispatch it to its handler.
4247
+ * Drop this provider's ConversationRelay transport state on channel shutdown.
3986
4248
  *
3987
- * Returns 400 when the payload can't be parsed (no `CallSid`) or the handler
3988
- * throws — better than handing Twilio a 200 for an event that wasn't
3989
- * processed. Everything else, including no handler registered and an
3990
- * account mismatch, is a 200 no-op.
4249
+ * Note: WebSocket connections are managed by the server and closed there.
4250
+ * This method only cleans up internal provider state.
3991
4251
  */
3992
- private dispatchCallEvent;
4252
+ shutdown(): void;
4253
+ }
4254
+
4255
+ /**
4256
+ * Callback that produces per-call overrides for the TwiML inside
4257
+ * `<ConversationRelay>` on inbound calls. Receives a framework-neutral
4258
+ * {@link TwiMLRequest} and returns {@link VoiceTwiMLOptionsConversationRelay}.
4259
+ *
4260
+ * Stays typed against the ConversationRelay subtype rather than the
4261
+ * provider-agnostic base ({@link VoiceProvider.handleIncomingCall} is widened
4262
+ * to the base, this consumer-facing surface is not): the base has only
4263
+ * optional shared fields, so TypeScript's object-literal freshness check
4264
+ * rejects the documented inline form `async () => ({ voice: '...' })` for
4265
+ * having no properties in common with it. It widens once a second
4266
+ * inbound-capable provider exists.
4267
+ */
4268
+ type InboundCallTwimlHandler = (req: TwiMLRequest) => Promise<VoiceTwiMLOptionsConversationRelay>;
4269
+ /** Handler for Twilio `statusCallback` webhooks. */
4270
+ type CallStatusHandler = (event: CallStatusEvent) => Promise<void> | void;
4271
+ /** Handler for Twilio `asyncAmdStatusCallback` webhooks. */
4272
+ type AmdHandler = (event: AmdEvent) => Promise<void> | void;
4273
+ /** Handler for Twilio `recordingStatusCallback` webhooks. */
4274
+ type RecordingHandler = (event: RecordingEvent) => Promise<void> | void;
4275
+ /** One ConversationRelay keypress, as delivered to a {@link DtmfHandler}. */
4276
+ interface DtmfEvent {
3993
4277
  /**
3994
- * Handle a Twilio `statusCallback` webhook.
3995
- *
3996
- * The developer routes the request here (`TACServer` does this automatically
3997
- * for its `/status` call-event route). Parsed into a {@link CallStatusEvent}
3998
- * and dispatched to the {@link onCallStatus} handler. No-op if no handler is
3999
- * registered.
4000
- *
4001
- * @param form - Raw form data from the webhook request.
4278
+ * Undefined only when the keypress beat conversation setup: `dtmf` before
4279
+ * ConversationRelay's `setup`, or an orchestrated-mode lookup that failed.
4002
4280
  */
4003
- handleCallStatusEvent(form: Record<string, string>): Promise<{
4004
- status: number;
4005
- content: string;
4006
- contentType: string;
4007
- }>;
4281
+ conversationId: ConversationId | undefined;
4282
+ /** Undefined only before ConversationRelay's `setup` message. */
4283
+ callSid: string | undefined;
4284
+ /** The key pressed: `0`-`9`, `*`, `#`, or `A`-`D`. */
4285
+ digit: string;
4286
+ /** Present whenever `conversationId` is. */
4287
+ session?: ConversationSession;
4288
+ }
4289
+ /** Handler for ConversationRelay `dtmf` messages (caller keypresses). */
4290
+ type DtmfHandler = (event: DtmfEvent) => Promise<void> | void;
4291
+ /**
4292
+ * Voice channel event callbacks extending base callbacks
4293
+ */
4294
+ interface VoiceChannelEvents extends BaseChannelEvents {
4295
+ onSetup?: (data: {
4296
+ callSid: string;
4297
+ from: string;
4298
+ to: string;
4299
+ customParameters: Record<string, unknown> | undefined;
4300
+ }) => void;
4301
+ onPrompt?: (data: {
4302
+ conversationId: ConversationId;
4303
+ transcript: string;
4304
+ userMemory?: TACMemoryResponse;
4305
+ session?: ConversationSession;
4306
+ abortSignal: AbortSignal;
4307
+ }) => Promise<void> | void;
4308
+ onInterrupt?: (data: {
4309
+ conversationId: ConversationId;
4310
+ utteranceUntilInterrupt: string | undefined;
4311
+ durationUntilInterruptMs: number | undefined;
4312
+ }) => void;
4313
+ /** Caller keypress. See {@link VoiceChannel.onDtmf}. */
4314
+ onDtmf?: DtmfHandler;
4008
4315
  /**
4009
- * Handle a Twilio `asyncAmdStatusCallback` webhook.
4010
- *
4011
- * The developer routes the request here (`TACServer` does this automatically
4012
- * for its `/amd` call-event route). Parsed into an {@link AmdEvent} and
4013
- * dispatched to the {@link onAmd} handler. No-op if no handler is registered.
4014
- *
4015
- * @param form - Raw form data from the webhook request.
4316
+ * Fired once the session and WebSocket registration exist — in orchestrated
4317
+ * mode possibly before the first prompt, since the lookup starts at setup.
4016
4318
  */
4017
- handleAmdEvent(form: Record<string, string>): Promise<{
4018
- status: number;
4019
- content: string;
4020
- contentType: string;
4021
- }>;
4319
+ onWebSocketConnected?: (data: {
4320
+ conversationId: ConversationId;
4321
+ }) => void;
4322
+ onWebSocketDisconnected?: (data: {
4323
+ conversationId: ConversationId;
4324
+ }) => void;
4325
+ }
4326
+ /**
4327
+ * Voice Channel for handling voice-based conversations over a WebSocket.
4328
+ *
4329
+ * The real-time media transport lives on a {@link VoiceProvider} — by default
4330
+ * {@link ConversationRelayProvider}. `VoiceChannel` keeps the Calls-API
4331
+ * lifecycle (call events, hangup), conversation bookkeeping and webhook
4332
+ * processing, and delegates everything transport-shaped to the provider.
4333
+ */
4334
+ declare class VoiceChannel extends BaseChannel {
4335
+ private readonly provider;
4336
+ private readonly voiceCallbacks;
4337
+ private twilioClient;
4338
+ private onInboundCallTwimlHandler;
4339
+ private onCallStatusHandler;
4340
+ private onAmdHandler;
4341
+ private onRecordingHandler;
4022
4342
  /**
4023
- * Handle a Twilio `recordingStatusCallback` webhook.
4343
+ * @param tac - The owning {@link TAC} instance.
4344
+ * @param options - Either a {@link VoiceProviderConfig} selecting the media
4345
+ * provider, or a plain object — shorthand for
4346
+ * {@link ConversationRelayProviderConfig}, which is what TAC builds when no
4347
+ * provider config is given.
4348
+ */
4349
+ constructor(tac: TAC, options?: VoiceProviderConfig | ConversationRelayProviderConfigOptions);
4350
+ /**
4351
+ * The {@link BaseChannelOptions} to hand `BaseChannel`. A provider config
4352
+ * replays the options it retained (with its resolved `memoryMode`, which is
4353
+ * writable after construction); a plain options object is passed through. Both
4354
+ * paths therefore honour `dedupCapacity` and friends identically.
4355
+ */
4356
+ private static toBaseOptions;
4357
+ /**
4358
+ * Resolve the provider config: an explicit {@link VoiceProviderConfig} as-is,
4359
+ * anything else wrapped as a {@link ConversationRelayProviderConfig}.
4360
+ */
4361
+ private static toProviderConfig;
4362
+ /**
4363
+ * Register a callback that produces per-call overrides for the TwiML inside
4364
+ * `<ConversationRelay>` on inbound calls.
4024
4365
  *
4025
- * The developer routes the request here (`TACServer` does this automatically
4026
- * for its `/recording` call-event route). Parsed into a
4027
- * {@link RecordingEvent} and dispatched to the {@link onRecording} handler.
4028
- * No-op if no handler is registered.
4366
+ * The callback receives a framework-neutral {@link TwiMLRequest} (parsed from
4367
+ * the Twilio webhook form) and returns
4368
+ * {@link VoiceTwiMLOptionsConversationRelay}. Fields the
4369
+ * callback explicitly sets override `defaultTwimlOptions` and TAC defaults;
4370
+ * unset fields fall through.
4029
4371
  *
4030
- * @param form - Raw form data from the webhook request.
4372
+ * @example
4373
+ * ```typescript
4374
+ * voiceChannel.onInboundCallTwiml(async req => {
4375
+ * if (req.callerCountry === 'MX') {
4376
+ * return { language: 'es-MX', welcomeGreeting: '¡Hola!' };
4377
+ * }
4378
+ * return {};
4379
+ * });
4380
+ * ```
4381
+ *
4382
+ * Outbound calls don't use this — pass per-call TwiML via
4383
+ * `InitiateVoiceConversationOptions.twimlOptions` directly.
4031
4384
  */
4032
- handleRecordingEvent(form: Record<string, string>): Promise<{
4033
- status: number;
4034
- content: string;
4035
- contentType: string;
4036
- }>;
4385
+ onInboundCallTwiml(callback: InboundCallTwimlHandler): void;
4037
4386
  /**
4038
- * Hang up a call and clean up its ConversationRelay session.
4387
+ * Register a handler for Twilio `statusCallback` webhooks.
4039
4388
  *
4040
- * Works on `callSid` alone, whether or not a session exists yet. No-ops the
4041
- * session cleanup if none is tracked.
4389
+ * This is the Calls-API status callback (call disposition), not the
4390
+ * ConversationRelay session callback — see
4391
+ * {@link handleTwilioProviderCallback}.
4042
4392
  *
4043
- * Does not throw — hanging up an already-ended call is routine (the callee
4044
- * hangs up while AMD is still resolving), and handlers shouldn't have to
4045
- * guard against it.
4393
+ * Registering does two things: it stores the handler, and it makes later
4394
+ * outbound calls pass `statusCallback` to `calls.create`. With no handler
4395
+ * registered TAC omits that parameter, so Twilio has nowhere to post and the
4396
+ * event never arrives.
4046
4397
  *
4047
- * @param callSid - Twilio Call SID (from a call event, the outbound result, or
4048
- * `ConversationSession.callSid`).
4049
- * @returns True if Twilio accepted the hangup, false if it failed (logged).
4050
- * Session cleanup runs either way.
4398
+ * Twilio reports only the terminal event by default, which covers every
4399
+ * disposition; set `CallOptions.statusCallbackEvent` for ringing/answered.
4400
+ *
4401
+ * @example
4402
+ * ```typescript
4403
+ * voiceChannel.onCallStatus(async event => {
4404
+ * if (event.isUnreached) {
4405
+ * // queue a retry
4406
+ * }
4407
+ * });
4408
+ * ```
4051
4409
  */
4052
- endCall(callSid: string): Promise<boolean>;
4410
+ onCallStatus(callback: CallStatusHandler): void;
4053
4411
  /**
4054
- * Look up the active voice session for a Twilio Call SID.
4412
+ * Register a handler for Twilio `asyncAmdStatusCallback` webhooks.
4055
4413
  *
4056
- * Out-of-band code holding a CallSid — a dashboard route, an operator action,
4057
- * a call-event handler — can't reach the session-facing methods, which are
4058
- * keyed by conversation id: the Orchestrator conversation id in orchestrator
4059
- * mode, the CallSid only in ConversationRelay-only mode.
4414
+ * Registering makes later outbound calls pass `asyncAmdStatusCallback` to
4415
+ * `calls.create`; without a handler TAC omits it and Twilio has nowhere to
4416
+ * post the result. It does not enable detection — that's per-call, via
4417
+ * `CallOptions.machineDetection` and `asyncAmd`, both of which are required
4418
+ * for this to fire (at most once per call).
4060
4419
  *
4061
- * Relay-only mode creates the session on the first prompt; orchestrated
4062
- * mode creates it when the lookup started at setup finishes, so it may
4063
- * exist before the caller speaks — including before `onAmd` fires. Treat it
4064
- * as racy and hang up with {@link endCall}, which needs no session.
4420
+ * @example
4421
+ * ```typescript
4422
+ * voiceChannel.onAmd(async event => {
4423
+ * if (event.isMachine) {
4424
+ * await voiceChannel.endCall(event.callSid); // voicemail → hang up
4425
+ * }
4426
+ * });
4427
+ * ```
4428
+ */
4429
+ onAmd(callback: AmdHandler): void;
4430
+ /**
4431
+ * Register a handler for Twilio `recordingStatusCallback` webhooks.
4065
4432
  *
4066
- * At the other end, orchestrator mode keeps the session until Conversation
4067
- * Orchestrator's CLOSED webhook, so it outlives the call and `onCallStatus` /
4068
- * `onRecording` do resolve. Relay-only mode tears down on the
4069
- * ConversationRelay callback instead, which races them.
4433
+ * Registering makes later outbound calls pass `recordingStatusCallback` to
4434
+ * `calls.create`; without a handler TAC omits it and Twilio has nowhere to
4435
+ * post. It does not start recording — that's `CallOptions.record`, which is
4436
+ * required for this to fire.
4070
4437
  *
4071
4438
  * @example
4072
4439
  * ```typescript
4073
- * async function nudge(callSid: string): Promise<void> {
4074
- * const session = voiceChannel.getConversationSessionByCallSid(callSid);
4075
- * if (session) {
4076
- * await voiceChannel.sendResponse(session.conversationId, 'Still there?');
4440
+ * voiceChannel.onRecording(async event => {
4441
+ * if (event.recordingStatus === 'completed') {
4442
+ * // store event.recordingUrl
4077
4443
  * }
4078
- * }
4444
+ * });
4079
4445
  * ```
4446
+ */
4447
+ onRecording(callback: RecordingHandler): void;
4448
+ /**
4449
+ * Register a handler for DTMF keypresses, called once per key in order.
4080
4450
  *
4081
- * @param callSid - Twilio Call SID, e.g. from
4082
- * `InitiateVoiceConversationResult.callSid` or a call event.
4083
- * @returns The session, or `undefined` — not created yet, the call ended, or
4084
- * it landed on another instance (see the horizontal-scaling note in
4085
- * CLAUDE.md).
4451
+ * Requires `dtmfDetection: true` on the ConversationRelay config — without it
4452
+ * Twilio sends nothing and this never fires. Digits aren't buffered, so
4453
+ * accumulating a multi-digit entry is the handler's job.
4454
+ *
4455
+ * A keypress initializes the conversation just as a prompt does, since a
4456
+ * caller can type without ever speaking; if that fails the digit still
4457
+ * arrives, with `conversationId` and `session` undefined. Keypresses don't
4458
+ * cancel in-flight streaming on their own — that's a separate `interrupt`
4459
+ * message, sent when `interruptible` includes `dtmf`.
4460
+ *
4461
+ * @example
4462
+ * ```typescript
4463
+ * const digits = new Map<string, string>();
4464
+ *
4465
+ * voiceChannel.onDtmf(({ conversationId, digit }) => {
4466
+ * if (!conversationId) return;
4467
+ * digits.set(conversationId, (digits.get(conversationId) ?? '') + digit);
4468
+ * });
4469
+ * ```
4086
4470
  */
4087
- getConversationSessionByCallSid(callSid: string): ConversationSession | undefined;
4471
+ onDtmf(callback: DtmfHandler): void;
4088
4472
  /**
4089
- * Start tracking a streaming task for a conversation
4473
+ * The registered call-event handlers, for a `VoiceProvider` deciding which
4474
+ * callback URLs to derive. A provider is not a subclass of `VoiceChannel`,
4475
+ * so the `private` fields are genuinely out of reach without this.
4090
4476
  *
4091
- * @param conversationId - The conversation ID
4092
- * @returns The stream task with its AbortController
4477
+ * @internal
4093
4478
  */
4094
- startStreamTask(conversationId: ConversationId): StreamTask;
4479
+ getCallEventHandlers(): {
4480
+ status: CallStatusHandler | undefined;
4481
+ amd: AmdHandler | undefined;
4482
+ recording: RecordingHandler | undefined;
4483
+ };
4095
4484
  /**
4096
- * Cancel an active streaming task
4485
+ * This channel's `TACConfig`, for a `VoiceProvider` deriving default URLs.
4486
+ * `BaseChannel.config` is `protected`, and a provider is not a subclass.
4097
4487
  *
4098
- * @param conversationId - The conversation ID
4099
- * @returns true if a task was cancelled, false otherwise
4488
+ * @internal
4100
4489
  */
4101
- cancelStreamTask(conversationId: ConversationId): boolean;
4490
+ getTacConfig(): TACConfig;
4102
4491
  /**
4103
- * Complete a streaming task (remove from tracking)
4492
+ * This channel's logger, so a provider's relocated logic keeps logging under
4493
+ * the same name it did when it lived on `VoiceChannel`.
4104
4494
  *
4105
- * @param conversationId - The conversation ID
4495
+ * @internal
4106
4496
  */
4107
- completeStreamTask(conversationId: ConversationId): void;
4497
+ getLoggerInternal(): Logger;
4108
4498
  /**
4109
- * Check if a stream task is active
4499
+ * The Conversation Orchestrator client, or `null` in ConversationRelay-only
4500
+ * mode.
4501
+ *
4502
+ * @internal
4503
+ */
4504
+ getConversationClientInternal(): ConversationClient | null;
4505
+ /**
4506
+ * The lazily built Twilio REST client, for a provider placing outbound calls.
4507
+ *
4508
+ * @internal
4509
+ */
4510
+ getTwilioClientInternal(): ReturnType<typeof Twilio>;
4511
+ /**
4512
+ * Whether Conversation Orchestrator is configured. Providers branch on this
4513
+ * to decide who owns conversation cleanup.
4514
+ *
4515
+ * @internal
4516
+ */
4517
+ isOrchestratorEnabledInternal(): boolean;
4518
+ /**
4519
+ * Voice event callbacks registered via {@link on}, for a provider to fire.
4520
+ *
4521
+ * @internal
4522
+ */
4523
+ getVoiceCallbacks(): VoiceChannelEvents;
4524
+ /**
4525
+ * The inbound-TwiML customizer registered via {@link onInboundCallTwiml}, for
4526
+ * a provider building the inbound response.
4527
+ *
4528
+ * @internal
4529
+ */
4530
+ getInboundCallTwimlHandler(): InboundCallTwimlHandler | undefined;
4531
+ /**
4532
+ * Start tracking a conversation session. Forwards to
4533
+ * `BaseChannel.startConversation`.
4534
+ *
4535
+ * @internal
4536
+ */
4537
+ startConversationInternal(conversationId: ConversationId, profileId?: ProfileId): ConversationSession;
4538
+ /**
4539
+ * End a tracked conversation session. Forwards to
4540
+ * `BaseChannel.endConversation`.
4541
+ *
4542
+ * @internal
4543
+ */
4544
+ endConversationInternal(conversationId: ConversationId): Promise<void>;
4545
+ /**
4546
+ * Retrieve memory when `memoryMode` calls for it. Forwards to
4547
+ * `BaseChannel.retrieveMemoryIfEnabled`.
4548
+ *
4549
+ * @internal
4550
+ */
4551
+ retrieveMemoryInternal(session: ConversationSession, query?: string): Promise<TACMemoryResponse | undefined>;
4552
+ /**
4553
+ * Report an error through the channel's `onError` callback and logger.
4554
+ * Forwards to `BaseChannel.handleError`.
4555
+ *
4556
+ * @internal
4557
+ */
4558
+ handleErrorInternal(error: Error, context?: Record<string, unknown>): void;
4559
+ private getTwilioClient;
4560
+ get channelType(): ChannelType;
4561
+ /**
4562
+ * Register event callbacks (override for Voice-specific events)
4563
+ */
4564
+ on(event: string, callback: (...args: any[]) => void): void;
4565
+ /**
4566
+ * Process conversation webhooks for cleanup.
4567
+ *
4568
+ * Voice channel processes CONVERSATION_UPDATED events:
4569
+ * - CLOSED status: Clean up local session state
4570
+ *
4571
+ * Note: Conversation tracking uses instance-local memory. In multi-instance
4572
+ * deployments, webhooks may route to a different instance, preventing cleanup.
4573
+ *
4574
+ * @param payload - Raw webhook event data from Twilio
4575
+ * @param idempotencyToken - Optional Twilio idempotency token from request headers
4576
+ */
4577
+ processWebhook(payload: unknown, idempotencyToken?: string): Promise<void>;
4578
+ /**
4579
+ * Handle conversation updated event
4580
+ */
4581
+ private handleConversationUpdated;
4582
+ /**
4583
+ * Get active WebSocket connection for a conversation
4584
+ */
4585
+ getWebsocket(conversationId: ConversationId): WebSocket | null;
4586
+ /**
4587
+ * Hand one WebSocket connection to the active provider, which drives its
4588
+ * lifecycle from accept to disconnect.
4589
+ *
4590
+ * @param ws - The accepted WebSocket, from ConversationRelay or whatever
4591
+ * transport the active provider serves.
4592
+ */
4593
+ handleWebSocketConnection(ws: WebSocket): void;
4594
+ /**
4595
+ * Send voice response via WebSocket
4596
+ */
4597
+ sendResponse(conversationId: ConversationId, message: string, metadata?: Record<string, unknown>): Promise<void>;
4598
+ /**
4599
+ * Send a streaming voice response through the active provider's transport,
4600
+ * token by token. Delegates to the active provider — see
4601
+ * {@link ConversationRelayProvider.sendStreamingResponse} for the token
4602
+ * protocol and abort semantics.
4603
+ *
4604
+ * @param conversationId - Conversation whose transport receives the tokens.
4605
+ * @param stream - Async iterable of text chunks to relay as they arrive.
4606
+ * @param options - Additional per-call inputs.
4607
+ * @param options.signal - Aborts the stream mid-flight, e.g. when the caller
4608
+ * interrupts.
4609
+ * @returns The accumulated full response text.
4610
+ */
4611
+ sendStreamingResponse(conversationId: ConversationId, stream: AsyncIterable<string>, options?: {
4612
+ signal?: AbortSignal;
4613
+ }): Promise<string>;
4614
+ /**
4615
+ * Generate the response for an incoming voice call. Delegates to the active
4616
+ * provider — see {@link ConversationRelayProvider.handleIncomingCall} for the
4617
+ * full TwiML merge/precedence rules (only meaningful for that provider; a
4618
+ * provider with no inbound story declines instead).
4619
+ *
4620
+ * @param twimlRequest - Parsed Twilio webhook fields. Passed to the customizer
4621
+ * registered via {@link onInboundCallTwiml}, if one is configured.
4622
+ * @param options - Additional per-call inputs.
4623
+ * @param options.hostTwimlOptions - Per-call TwiML supplied by a custom
4624
+ * in-process host (e.g. an affinity-routed deployment injecting a per-call
4625
+ * `websocketUrl`). Typed against the ConversationRelay subtype for the same
4626
+ * reason as {@link InboundCallTwimlHandler}.
4627
+ * @returns TwiML XML string for call connection.
4628
+ */
4629
+ handleIncomingCall(twimlRequest?: TwiMLRequest, options?: {
4630
+ hostTwimlOptions?: VoiceTwiMLOptionsConversationRelay;
4631
+ }): Promise<string>;
4632
+ /**
4633
+ * Initiate an outbound voice conversation. Delegates to the active provider —
4634
+ * see {@link ConversationRelayProvider.initiateOutboundConversation} for the
4635
+ * TwiML and Calls-API merge/precedence rules.
4636
+ *
4637
+ * {@link ConversationRelayProvider} and `OpenAIRealtimeProvider` place
4638
+ * outbound calls; any other provider declines.
4639
+ *
4640
+ * @param options - Destination, per-call TwiML and Calls-API overrides.
4641
+ * `OpenAIRealtimeProvider` additionally accepts a per-call `sessionConfig`.
4642
+ * Each provider validates against its own schema and rejects the other's.
4643
+ * @returns The placed call's `callSid`.
4644
+ */
4645
+ initiateOutboundConversation(options: InitiateVoiceConversationOptions): Promise<InitiateVoiceConversationResult>;
4646
+ /**
4647
+ * Handle the provider's own out-of-band lifecycle webhook from Twilio.
4648
+ *
4649
+ * Not every provider has one; those that don't inherit a plain 200
4650
+ * acknowledgement. ConversationRelay posts here when a session ends, and
4651
+ * cleans up on call completion in voice-only mode — in orchestrated mode the
4652
+ * CO webhook owns cleanup.
4653
+ *
4654
+ * @param payload - Raw callback payload from Twilio; the provider validates it.
4655
+ * @returns Response with status, content, and content type
4656
+ */
4657
+ handleTwilioProviderCallback(payload: Record<string, unknown>): Promise<TwilioProviderCallbackResponse>;
4658
+ /**
4659
+ * @deprecated Use {@link VoiceChannel.handleTwilioProviderCallback} instead.
4660
+ */
4661
+ handleConversationRelayCallback(payload: ConversationRelayCallbackPayload): Promise<TwilioProviderCallbackResponse>;
4662
+ /**
4663
+ * Whether a call-webhook payload belongs to the configured account.
4664
+ *
4665
+ * Twilio signature validation already gates the route; this is defense in
4666
+ * depth. A payload with no `AccountSid` is allowed through.
4667
+ *
4668
+ * Subaccounts: events carry the SID the call was placed on, so configure TAC
4669
+ * with that account or its events get dropped here.
4670
+ */
4671
+ private callEventAccountOk;
4672
+ /**
4673
+ * Parse a call-event webhook form and dispatch it to its handler.
4674
+ *
4675
+ * Returns 400 when the payload can't be parsed (no `CallSid`) or the handler
4676
+ * throws — better than handing Twilio a 200 for an event that wasn't
4677
+ * processed. Everything else, including no handler registered and an
4678
+ * account mismatch, is a 200 no-op.
4679
+ */
4680
+ private dispatchCallEvent;
4681
+ /**
4682
+ * Handle a Twilio `statusCallback` webhook.
4683
+ *
4684
+ * The developer routes the request here (`TACServer` does this automatically
4685
+ * for its `/status` call-event route). Parsed into a {@link CallStatusEvent}
4686
+ * and dispatched to the {@link onCallStatus} handler. No-op if no handler is
4687
+ * registered.
4688
+ *
4689
+ * @param form - Raw form data from the webhook request.
4690
+ */
4691
+ handleCallStatusEvent(form: Record<string, string>): Promise<{
4692
+ status: number;
4693
+ content: string;
4694
+ contentType: string;
4695
+ }>;
4696
+ /**
4697
+ * Handle a Twilio `asyncAmdStatusCallback` webhook.
4698
+ *
4699
+ * The developer routes the request here (`TACServer` does this automatically
4700
+ * for its `/amd` call-event route). Parsed into an {@link AmdEvent} and
4701
+ * dispatched to the {@link onAmd} handler. No-op if no handler is registered.
4702
+ *
4703
+ * @param form - Raw form data from the webhook request.
4704
+ */
4705
+ handleAmdEvent(form: Record<string, string>): Promise<{
4706
+ status: number;
4707
+ content: string;
4708
+ contentType: string;
4709
+ }>;
4710
+ /**
4711
+ * Handle a Twilio `recordingStatusCallback` webhook.
4712
+ *
4713
+ * The developer routes the request here (`TACServer` does this automatically
4714
+ * for its `/recording` call-event route). Parsed into a
4715
+ * {@link RecordingEvent} and dispatched to the {@link onRecording} handler.
4716
+ * No-op if no handler is registered.
4717
+ *
4718
+ * @param form - Raw form data from the webhook request.
4719
+ */
4720
+ handleRecordingEvent(form: Record<string, string>): Promise<{
4721
+ status: number;
4722
+ content: string;
4723
+ contentType: string;
4724
+ }>;
4725
+ /**
4726
+ * Hang up a call and clean up its ConversationRelay session.
4727
+ *
4728
+ * Works on `callSid` alone, whether or not a session exists yet. No-ops the
4729
+ * session cleanup if none is tracked.
4730
+ *
4731
+ * Does not throw — hanging up an already-ended call is routine (the callee
4732
+ * hangs up while AMD is still resolving), and handlers shouldn't have to
4733
+ * guard against it.
4734
+ *
4735
+ * @param callSid - Twilio Call SID (from a call event, the outbound result, or
4736
+ * `ConversationSession.callSid`).
4737
+ * @returns True if Twilio accepted the hangup, false if it failed (logged).
4738
+ * Session cleanup runs either way.
4739
+ */
4740
+ endCall(callSid: string): Promise<boolean>;
4741
+ /**
4742
+ * Look up the active voice session for a Twilio Call SID.
4743
+ *
4744
+ * Out-of-band code holding a CallSid — a dashboard route, an operator action,
4745
+ * a call-event handler — can't reach the session-facing methods, which are
4746
+ * keyed by conversation id: the Orchestrator conversation id in orchestrator
4747
+ * mode, the CallSid only in ConversationRelay-only mode.
4748
+ *
4749
+ * Relay-only mode creates the session on the first prompt; orchestrated
4750
+ * mode creates it when the lookup started at setup finishes, so it may
4751
+ * exist before the caller speaks — including before `onAmd` fires. Treat it
4752
+ * as racy and hang up with {@link endCall}, which needs no session.
4753
+ *
4754
+ * At the other end, orchestrator mode keeps the session until Conversation
4755
+ * Orchestrator's CLOSED webhook, so it outlives the call and `onCallStatus` /
4756
+ * `onRecording` do resolve. Relay-only mode tears down on the
4757
+ * ConversationRelay callback instead, which races them.
4758
+ *
4759
+ * @example
4760
+ * ```typescript
4761
+ * async function nudge(callSid: string): Promise<void> {
4762
+ * const session = voiceChannel.getConversationSessionByCallSid(callSid);
4763
+ * if (session) {
4764
+ * await voiceChannel.sendResponse(session.conversationId, 'Still there?');
4765
+ * }
4766
+ * }
4767
+ * ```
4768
+ *
4769
+ * @param callSid - Twilio Call SID, e.g. from
4770
+ * `InitiateVoiceConversationResult.callSid` or a call event.
4771
+ * @returns The session, or `undefined` — not created yet, the call ended, or
4772
+ * it landed on another instance (see the horizontal-scaling note in
4773
+ * CLAUDE.md).
4774
+ */
4775
+ getConversationSessionByCallSid(callSid: string): ConversationSession | undefined;
4776
+ /**
4777
+ * Narrow `provider` to the ConversationRelay implementation for the
4778
+ * ConversationRelay-only forwarders on this channel.
4779
+ *
4780
+ * Throws whenever a consumer supplies a non-ConversationRelay provider, which
4781
+ * is the point of the guard. The provider callback used to narrow here too; it
4782
+ * now rides {@link VoiceProvider.handleTwilioProviderCallback}'s
4783
+ * base-compatible signature instead, which is the eventual shape for these
4784
+ * forwarders.
4785
+ *
4786
+ * @throws {Error} if this channel's provider is not ConversationRelay-based.
4787
+ */
4788
+ private requireConversationRelayProvider;
4789
+ /**
4790
+ * Start tracking a streaming task for a conversation
4791
+ *
4792
+ * @param conversationId - The conversation ID
4793
+ * @returns The stream task with its AbortController
4794
+ */
4795
+ startStreamTask(conversationId: ConversationId): StreamTask;
4796
+ /**
4797
+ * Cancel an active streaming task
4798
+ *
4799
+ * @param conversationId - The conversation ID
4800
+ * @returns true if a task was cancelled, false otherwise
4801
+ */
4802
+ cancelStreamTask(conversationId: ConversationId): boolean;
4803
+ /**
4804
+ * Complete a streaming task (remove from tracking)
4805
+ *
4806
+ * @param conversationId - The conversation ID
4807
+ */
4808
+ completeStreamTask(conversationId: ConversationId): void;
4809
+ /**
4810
+ * Check if a stream task is active
4811
+ *
4812
+ * @param conversationId - The conversation ID
4813
+ * @returns true if an active task exists
4814
+ */
4815
+ hasActiveStreamTask(conversationId: ConversationId): boolean;
4816
+ /**
4817
+ * Generate TwiML to connect a call to ConversationRelay. Delegates to
4818
+ * {@link ConversationRelayProvider.connectConversationRelay}, which validates
4819
+ * the configuration with Zod before generating TwiML.
4820
+ *
4821
+ * @param config - ConversationRelay configuration (url, transcription, TTS, etc.)
4822
+ * @param options - Optional settings for parameters and the Connect verb
4823
+ * @returns TwiML XML string
4824
+ * @throws {Error} if config validation fails, or if this channel's provider is
4825
+ * not ConversationRelay-based.
4826
+ */
4827
+ connectConversationRelay(config: ConversationRelayConfig, options?: {
4828
+ parameters?: CustomParameters;
4829
+ actionUrl?: string;
4830
+ }): string;
4831
+ /**
4832
+ * Cleanup channel state on shutdown
4833
+ *
4834
+ * Note: WebSocket connections are managed by the server and closed there.
4835
+ * This method only cleans up internal channel state.
4836
+ */
4837
+ shutdown(): void;
4838
+ }
4839
+
4840
+ /**
4841
+ * Common construction and option-layering helpers shared by every provider's
4842
+ * TwiML builder.
4843
+ *
4844
+ * Subclasses take `TACConfig` and their provider's channel config wholesale
4845
+ * (not individual derived values) so a later change to either — a new field, a
4846
+ * new default — is a change to the builder alone, not a change to what
4847
+ * `VoiceChannel` has to compute and hand over.
4848
+ *
4849
+ * @internal
4850
+ */
4851
+ declare abstract class TwiMLBuilderBase<TChannelConfig = unknown> {
4852
+ protected readonly tacConfig: TACConfig;
4853
+ protected readonly channelConfig: TChannelConfig;
4854
+ protected readonly logger: Logger;
4855
+ constructor(tacConfig: TACConfig, channelConfig: TChannelConfig, logger: Logger);
4856
+ /**
4857
+ * Apply fields explicitly present on `source` onto `target`, except those
4858
+ * named in `skip`.
4859
+ *
4860
+ * Nested objects, arrays, and dicts replace wholesale — there's no per-key
4861
+ * merging.
4862
+ *
4863
+ * "Explicitly present" is detected via key presence (`Object.keys`), which
4864
+ * mirrors Python's `model_fields_set`: a key set to `undefined` is still
4865
+ * "present" and overrides lower layers, while an absent key falls through.
4866
+ */
4867
+ protected overlayFields(target: Record<string, unknown>, source: Record<string, unknown>, skip?: readonly string[]): void;
4868
+ /**
4869
+ * The error thrown when no layer and no `TACConfig`-derived default supplies
4870
+ * a WebSocket URL. `caller` names the API the developer actually called.
4871
+ */
4872
+ protected missingWebsocketUrlError(caller: string): Error;
4873
+ /**
4874
+ * The WebSocket URL derived from `TACConfig.voicePublicDomain` +
4875
+ * `TACConfig.voiceWebsocketPath`, or undefined when `voicePublicDomain` is
4876
+ * unset.
4877
+ */
4878
+ protected defaultWebsocketUrl(): string | undefined;
4879
+ /**
4880
+ * Resolve the default `<Connect action=...>` cleanup URL from
4881
+ * `TACConfig.voicePublicDomain` + `TACConfig.voiceActionPath`.
4882
+ *
4883
+ * Returns undefined if `voicePublicDomain` isn't set; that's fine because
4884
+ * actionUrl has higher-priority layers (customizer, twimlOptions, Studio
4885
+ * handoff) above this fallback.
4886
+ */
4887
+ protected defaultActionUrl(): string | undefined;
4888
+ }
4889
+
4890
+ /**
4891
+ * The slice of a Media Streams provider's config this builder reads. Declared
4892
+ * structurally rather than importing the provider's config class, so the
4893
+ * builder does not depend on which Media Streams provider owns it.
4894
+ */
4895
+ interface MediaStreamsTwiMLBuilderConfig {
4896
+ defaultTwimlOptions?: VoiceTwiMLOptionsMediaStreams | undefined;
4897
+ }
4898
+ /**
4899
+ * Generate TwiML that connects the call to a bidirectional Media Stream.
4900
+ *
4901
+ * See https://www.twilio.com/docs/voice/twiml/stream for the `<Stream>` verb.
4902
+ *
4903
+ * The WebSocket URL may be passed positionally or as `options.websocketUrl`
4904
+ * (positional wins when both are given), so a caller can pass everything in one
4905
+ * object: `generateStreamTwiml(undefined, { websocketUrl: ... })`.
4906
+ *
4907
+ * @param websocketUrl - Public `wss://` URL of the WebSocket endpoint Twilio
4908
+ * should stream call audio to (the `<Stream url=...>` attribute). Optional if
4909
+ * `options.websocketUrl` is set.
4910
+ * @param options - Optional Media Streams TwiML options.
4911
+ * @returns TwiML XML string ready to return to Twilio.
4912
+ * @throws {Error} if no WebSocket URL is provided via either source.
4913
+ */
4914
+ declare function generateStreamTwiml(websocketUrl?: string, options?: VoiceTwiMLOptionsMediaStreams): string;
4915
+ /** Per-call inputs to {@link TwiMLBuilderMediaStreams.build}. */
4916
+ interface BuildStreamTwiMLInputs {
4917
+ /**
4918
+ * Per-call overrides from the host owning the route (e.g. a per-call
4919
+ * `websocketUrl` with an affinity token). Lowest of the option layers.
4920
+ */
4921
+ host?: VoiceTwiMLOptionsMediaStreams | undefined;
4922
+ /**
4923
+ * Per-call overrides — the `onInboundCallTwiml` customizer's output for
4924
+ * inbound, or `InitiateVoiceConversationOptions.twimlOptions` for outbound.
4925
+ * Highest layer.
4926
+ */
4927
+ perCall?: VoiceTwiMLOptionsMediaStreams | undefined;
4928
+ /**
4929
+ * Dedicated per-call WebSocket override that wins over any `websocketUrl`
4930
+ * coming through the option layers. Used by outbound, which takes it as its
4931
+ * own argument.
4932
+ */
4933
+ websocketUrl?: string | undefined;
4934
+ }
4935
+ /**
4936
+ * Builds the TwiML for a Media Streams call, owning the layering and WebSocket
4937
+ * URL resolution so the provider doesn't have to.
4938
+ */
4939
+ declare class TwiMLBuilderMediaStreams extends TwiMLBuilderBase<MediaStreamsTwiMLBuilderConfig> {
4940
+ /**
4941
+ * Build the TwiML XML for one call.
4942
+ *
4943
+ * TwiML fields are merged per-field, highest precedence first:
4944
+ * 1. `perCall` — the `onInboundCallTwiml` customizer's output for inbound,
4945
+ * or `InitiateVoiceConversationOptions.twimlOptions` for outbound
4946
+ * 2. the provider config's `defaultTwimlOptions` — channel-wide defaults
4947
+ * 3. `host` — per-call transport facts supplied by the host
4948
+ * 4. TAC defaults: the WebSocket URL derived from
4949
+ * `TACConfig.voicePublicDomain` + `voiceWebsocketPath`
4950
+ *
4951
+ * @param caller - Name of the calling method, used in the "no WebSocket URL"
4952
+ * error so it points at the API the developer actually called.
4953
+ * @param options - Per-call option layers and WebSocket override.
4954
+ * @throws {Error} if no layer and no `TACConfig`-derived default supplies a
4955
+ * WebSocket URL.
4956
+ */
4957
+ build(caller: string, options?: BuildStreamTwiMLInputs): string;
4958
+ /**
4959
+ * Layer TwiML options, lowest precedence first: `host` →
4960
+ * `defaultTwimlOptions` → `perCall`.
4961
+ *
4962
+ * `customParameters` replaces wholesale when set at a higher-priority layer —
4963
+ * there's no per-key merging.
4964
+ */
4965
+ protected buildTwimlOptions(host: VoiceTwiMLOptionsMediaStreams | undefined, perCall: VoiceTwiMLOptionsMediaStreams | undefined): VoiceTwiMLOptionsMediaStreams;
4966
+ }
4967
+
4968
+ /** Options accepted by {@link MediaStreamsProviderConfig}. */
4969
+ interface MediaStreamsProviderConfigOptions extends BaseChannelOptions {
4970
+ /**
4971
+ * Static `VoiceTwiMLOptionsMediaStreams` applied to every inbound call.
4972
+ * Per-call customization is registered via
4973
+ * `VoiceChannel.onInboundCallTwiml(...)`, which takes precedence over this.
4974
+ */
4975
+ defaultTwimlOptions?: VoiceTwiMLOptionsMediaStreams;
4976
+ }
4977
+ /**
4978
+ * Base configuration for a Media Streams (`<Connect><Stream>`) provider.
4979
+ *
4980
+ * Holds the transport-level settings every Media Streams provider shares,
4981
+ * independent of which model or protocol runs over the stream.
4982
+ */
4983
+ declare class MediaStreamsProviderConfig extends VoiceProviderConfig {
4984
+ /**
4985
+ * Static `VoiceTwiMLOptionsMediaStreams` applied to every inbound call.
4986
+ * Per-call customization is registered via
4987
+ * `VoiceChannel.onInboundCallTwiml(...)`, which takes precedence over this.
4988
+ */
4989
+ readonly defaultTwimlOptions?: VoiceTwiMLOptionsMediaStreams;
4990
+ constructor(options?: MediaStreamsProviderConfigOptions);
4991
+ }
4992
+
4993
+ /**
4994
+ * TAC Tool class with helper methods for LLM integration
4995
+ *
4996
+ * Matches Python's TACTool dataclass with conversion methods.
4997
+ */
4998
+ declare class TACTool<TParams = unknown, TResult = unknown> {
4999
+ readonly name: string;
5000
+ readonly description: string;
5001
+ readonly parameters: JSONSchema;
5002
+ readonly implementation: ToolFunction<TParams, TResult>;
5003
+ constructor(name: string, description: string, parameters: JSONSchema, implementation: ToolFunction<TParams, TResult>);
5004
+ /**
5005
+ * Convert to OpenAI function calling format
5006
+ */
5007
+ toOpenAIFormat(): OpenAITool;
5008
+ /**
5009
+ * Convert to OpenAI Realtime function calling format.
5010
+ *
5011
+ * Unlike {@link TACTool.toOpenAIFormat} (Chat Completions, which nests the
5012
+ * schema under a `function` key), Realtime's `session.tools` expects the
5013
+ * fields flat on the tool object.
5014
+ */
5015
+ toRealtimeFormat(): OpenAIRealtimeTool;
5016
+ /**
5017
+ * Convert to Anthropic tool calling format
5018
+ */
5019
+ toAnthropicFormat(): AnthropicTool;
5020
+ /**
5021
+ * Convert to JSON string (OpenAI format by default)
5022
+ */
5023
+ toJSON(): string;
5024
+ /**
5025
+ * Convert this tool to an OpenAI Agents SDK `FunctionTool` instance.
5026
+ *
5027
+ * Unlike `toOpenAIFormat` and `toAnthropicFormat` (which return plain
5028
+ * objects consumed by HTTP APIs), the OpenAI Agents SDK dispatches on tool
5029
+ * *type*, so this returns a live `tool(...)` object with an invoke callback
5030
+ * that calls this tool and JSON-encodes the result.
5031
+ *
5032
+ * Requires the `@openai/agents` package:
5033
+ *
5034
+ * npm install @openai/agents
5035
+ *
5036
+ * @returns A FunctionTool ready to pass to `new Agent({ tools: [...] })`.
5037
+ */
5038
+ toOpenAIAgentsSDKTool(): Promise<any>;
5039
+ }
5040
+ /**
5041
+ * Create a tool directly with all parameters
5042
+ *
5043
+ * Simplified approach matching Python's create_tool function.
5044
+ * No builder pattern - just a simple function call.
5045
+ */
5046
+ declare function defineTool<TParams = unknown, TResult = unknown>(name: string, description: string, parameters: JSONSchema, implementation: ToolFunction<TParams, TResult>): TACTool<TParams, TResult>;
5047
+
5048
+ /**
5049
+ * Parameters for memory retrieval tool
5050
+ */
5051
+ interface MemoryRetrievalParams {
5052
+ query?: string;
5053
+ beginDate?: string;
5054
+ endDate?: string;
5055
+ observationsLimit?: number;
5056
+ summariesLimit?: number;
5057
+ communicationsLimit?: number;
5058
+ relevanceThreshold?: number;
5059
+ }
5060
+ /**
5061
+ * Create memory retrieval tool.
5062
+ *
5063
+ * @param memoryClient - Memory client instance (must be initialized with storeId)
5064
+ * @param profileId - Optional profile ID for memory retrieval
5065
+ * @param conversationId - Optional conversation ID for memory retrieval
5066
+ * @param options - Optional overrides for tool metadata.
5067
+ * @param options.name - Tool name exposed to the LLM. Defaults to `retrieve_profile_memory`.
5068
+ * @param options.description - Tool description exposed to the LLM. Defaults to a
5069
+ * generic "retrieve memories" prompt.
5070
+ */
5071
+ declare function createMemoryRetrievalTool(memoryClient: MemoryClient, profileId?: string, conversationId?: string, options?: {
5072
+ name?: string;
5073
+ description?: string;
5074
+ }): TACTool<MemoryRetrievalParams, MemoryRetrievalResponse>;
5075
+ /**
5076
+ * Create factory function for memory tools
5077
+ *
5078
+ * @param memoryClient - Memory client instance (must be initialized with storeId)
5079
+ */
5080
+ declare function createMemoryTools(memoryClient: MemoryClient): {
5081
+ forProfile: (profileId: string, conversationId?: string) => TACTool<MemoryRetrievalParams, MemoryRetrievalResponse>;
5082
+ forSession: (profileId?: string, conversationId?: string) => TACTool<MemoryRetrievalParams, MemoryRetrievalResponse>;
5083
+ };
5084
+
5085
+ /**
5086
+ * Parameters for send message tool
5087
+ */
5088
+ interface SendMessageParams {
5089
+ message: string;
5090
+ metadata?: Record<string, unknown>;
5091
+ }
5092
+ /**
5093
+ * Result from send message tool
5094
+ */
5095
+ interface SendMessageResult {
5096
+ success: boolean;
5097
+ message_id?: string;
5098
+ error?: string;
5099
+ }
5100
+ /**
5101
+ * Create send message tool
5102
+ */
5103
+ declare function createSendMessageTool(channel: BaseChannel, conversationId: ConversationId): TACTool<SendMessageParams, SendMessageResult>;
5104
+ /**
5105
+ * Create factory function for messaging tools
5106
+ */
5107
+ declare function createMessagingTools(): {
5108
+ forConversation: (channel: BaseChannel, conversationId: ConversationId) => TACTool<SendMessageParams, SendMessageResult>;
5109
+ };
5110
+
5111
+ /**
5112
+ * Handoff tool for the Twilio Agent Connect.
5113
+ *
5114
+ * Generic Studio-backed handoff that routes a conversation to a human agent.
5115
+ * Produces a structured HandoffPayload and delivers it as a Twilio Studio
5116
+ * Execution (voice via `<Connect action>`, digital channels via direct POST).
5117
+ */
5118
+
5119
+ /**
5120
+ * Build a HandoffPayload from session context and attributes.
5121
+ *
5122
+ * Useful for custom handoff tools that want TAC's payload shape without
5123
+ * the Studio-specific delivery in `postStudioHandoff`.
5124
+ */
5125
+ declare function buildHandoffPayload(session: ConversationSession, memoryStoreId: string, attributes: Record<string, unknown>): HandoffPayload;
5126
+ /**
5127
+ * POST a handoff payload to a Twilio Studio Flow Executions endpoint.
5128
+ *
5129
+ * Emits the Twilio Studio Executions API wire format: form-encoded
5130
+ * `To` / `From` / `Parameters` fields with HTTP Basic auth.
5131
+ * `Parameters` is a JSON string keyed under `HandoffData` so Studio
5132
+ * can reference it via `{{flow.data.HandoffData.*}}`.
5133
+ */
5134
+ declare function postStudioHandoff(payload: HandoffPayload, session: ConversationSession, options: {
5135
+ handoffUrl: string;
5136
+ fromAddress: string;
5137
+ apiKey: string;
5138
+ apiSecret: string;
5139
+ }): Promise<void>;
5140
+ /**
5141
+ * Result returned by the handoff tool.
5142
+ */
5143
+ interface HandoffResult {
5144
+ status: 'handoff_initiated' | 'handoff_failed';
5145
+ channel: string;
5146
+ error?: string;
5147
+ }
5148
+ interface HandoffParams {
5149
+ reason: string;
5150
+ }
5151
+ /**
5152
+ * Create a handoff tool that delivers in the Twilio Studio Executions API shape.
5153
+ *
5154
+ * The returned tool exposes only `handoff({ reason })` to the LLM. All other
5155
+ * dependencies (TAC instance, session, static attributes) are captured in the
5156
+ * closure.
5157
+ *
5158
+ * On digital channels, the tool POSTs to the Studio Flow Executions endpoint
5159
+ * derived from `tac.getConfig().studioHandoffFlowSid`. For voice channels,
5160
+ * the payload is stored on the session and the voice channel automatically
5161
+ * sends the WS `end` message with `handoffData` after the LLM's final
5162
+ * response is delivered.
5163
+ *
5164
+ * The tool also sets the conversation to INACTIVE and clears status callbacks
5165
+ * to prevent further webhook events from being routed to TAC.
5166
+ *
5167
+ * **Not available in voice-only mode.** This tool requires Conversation
5168
+ * Orchestrator for conversation state management and Conversation Memory for
5169
+ * the handoff payload. In voice-only mode, implement your own handoff by
5170
+ * setting `session.pendingHandoffData` directly — the voice channel will
5171
+ * send the WS `end` message with your payload, and your `<Connect action>`
5172
+ * URL handler can route the call accordingly.
5173
+ *
5174
+ * @throws Error if `tac.getConfig().studioHandoffFlowSid` is unset, if
5175
+ * Conversation Orchestrator is not configured (voice-only mode), or if
5176
+ * the memory store ID was not resolved at startup.
5177
+ */
5178
+ declare function createStudioHandoffTool(tac: TAC, session: ConversationSession, options?: {
5179
+ attributes?: Record<string, unknown>;
5180
+ name?: string;
5181
+ description?: string;
5182
+ }): TACTool<HandoffParams, HandoffResult>;
5183
+
5184
+ /**
5185
+ * Parameters for knowledge search tool (visible to LLM)
5186
+ */
5187
+ interface KnowledgeSearchParams {
5188
+ query: string;
5189
+ }
5190
+ /**
5191
+ * Configuration for knowledge search tool
5192
+ */
5193
+ interface KnowledgeToolConfig {
5194
+ name?: string;
5195
+ description?: string;
5196
+ topK?: number;
5197
+ }
5198
+ /**
5199
+ * Create knowledge search tool with explicit name and description
5200
+ *
5201
+ * @param knowledgeClient - The Knowledge client instance
5202
+ * @param knowledgeBaseId - The knowledge base ID to search
5203
+ * @param config - Configuration with required name and description
5204
+ * @returns TACTool configured for knowledge search
5205
+ */
5206
+ declare function createKnowledgeSearchTool(knowledgeClient: KnowledgeClient, knowledgeBaseId: string, config: {
5207
+ name: string;
5208
+ description: string;
5209
+ topK?: number;
5210
+ }): TACTool<KnowledgeSearchParams, KnowledgeChunkResult[]>;
5211
+ /**
5212
+ * Create knowledge search tool with auto-fetched metadata from knowledge base
5213
+ *
5214
+ * This async version fetches the knowledge base metadata to auto-generate
5215
+ * the tool name and description if not provided.
5216
+ *
5217
+ * @param knowledgeClient - The Knowledge client instance
5218
+ * @param knowledgeBaseId - The knowledge base ID to search
5219
+ * @param config - Optional configuration (name/description auto-generated if not provided)
5220
+ * @returns Promise containing TACTool configured for knowledge search
5221
+ */
5222
+ declare function createKnowledgeSearchToolAsync(knowledgeClient: KnowledgeClient, knowledgeBaseId: string, config?: KnowledgeToolConfig): Promise<TACTool<KnowledgeSearchParams, KnowledgeChunkResult[]>>;
5223
+ /**
5224
+ * Create factory for knowledge tools
5225
+ *
5226
+ * @param knowledgeClient - The Knowledge client instance
5227
+ * @returns Factory object with methods to create knowledge tools
5228
+ */
5229
+ declare function createKnowledgeTools(knowledgeClient: KnowledgeClient): {
5230
+ forKnowledgeBase: (knowledgeBaseId: string, config: {
5231
+ name: string;
5232
+ description: string;
5233
+ topK?: number;
5234
+ }) => TACTool<KnowledgeSearchParams, KnowledgeChunkResult[]>;
5235
+ forKnowledgeBaseAsync: (knowledgeBaseId: string, config?: KnowledgeToolConfig) => Promise<TACTool<KnowledgeSearchParams, KnowledgeChunkResult[]>>;
5236
+ };
5237
+
5238
+ /**
5239
+ * Options accepted by {@link MediaStreamsOpenAIProviderConfig}.
5240
+ */
5241
+ interface MediaStreamsOpenAIProviderConfigOptions extends MediaStreamsProviderConfigOptions {
5242
+ /**
5243
+ * OpenAI API key. Defaults to the `OPENAI_API_KEY` environment variable.
5244
+ */
5245
+ openaiApiKey?: string;
5246
+ /**
5247
+ * Executable `TACTool` implementations, looked up by name to run mid-call
5248
+ * tool requests. This alone does not tell the model these tools exist — the
5249
+ * session config sent to OpenAI must separately declare each tool's schema.
5250
+ */
5251
+ tools?: TACTool<never, unknown>[];
5252
+ /**
5253
+ * Session configuration sent to OpenAI once the model connects — used for any
5254
+ * call that doesn't supply its own via
5255
+ * {@link MediaStreamsOpenAIProviderConfigOptions.onInboundCallSessionConfig}
5256
+ * or per-call outbound options.
5257
+ *
5258
+ * If using {@link MediaStreamsOpenAIProviderConfigOptions.tools}, this config
5259
+ * must separately list each tool's schema — it is passed to OpenAI as-is,
5260
+ * with no tool schemas merged in.
5261
+ */
5262
+ defaultSessionConfig?: Record<string, unknown>;
5263
+ /**
5264
+ * Per-inbound-call override for
5265
+ * {@link MediaStreamsOpenAIProviderConfigOptions.defaultSessionConfig},
5266
+ * called with the `TwiMLRequest`. Its return value is used verbatim (not
5267
+ * merged with `defaultSessionConfig`); return `null` to fall back to it.
5268
+ * Outbound calls don't use this — they pass their session config per call.
5269
+ */
5270
+ onInboundCallSessionConfig?: (req: TwiMLRequest) => Promise<Record<string, unknown> | null>;
5271
+ }
5272
+ /**
5273
+ * Base configuration for an OpenAI-backed Media Streams provider.
5274
+ *
5275
+ * Holds what every OpenAI-backed Media Streams provider needs — credentials,
5276
+ * executable tools, and the session config sent when the model connects —
5277
+ * independent of which OpenAI API runs over the stream.
5278
+ */
5279
+ declare class MediaStreamsOpenAIProviderConfig extends MediaStreamsProviderConfig {
5280
+ /** OpenAI API key. Defaults to the `OPENAI_API_KEY` environment variable. */
5281
+ readonly openaiApiKey: string;
5282
+ /**
5283
+ * Executable `TACTool` implementations, looked up by name to run mid-call
5284
+ * tool requests. This alone does not tell the model these tools exist — the
5285
+ * session config sent to OpenAI must separately declare each tool's schema.
5286
+ */
5287
+ readonly tools: TACTool<never, unknown>[];
5288
+ /**
5289
+ * Session configuration sent to OpenAI once the model connects — used for any
5290
+ * call that doesn't supply its own via
5291
+ * {@link MediaStreamsOpenAIProviderConfig.onInboundCallSessionConfig} or
5292
+ * per-call outbound options.
5293
+ *
5294
+ * If using {@link MediaStreamsOpenAIProviderConfig.tools}, this config must
5295
+ * separately list each tool's schema — it is passed to OpenAI as-is, with no
5296
+ * tool schemas merged in.
5297
+ */
5298
+ readonly defaultSessionConfig?: Record<string, unknown>;
5299
+ /**
5300
+ * Per-inbound-call override for
5301
+ * {@link MediaStreamsOpenAIProviderConfig.defaultSessionConfig}, called with
5302
+ * the `TwiMLRequest`. Its return value is used verbatim (not merged with
5303
+ * `defaultSessionConfig`); return `null` to fall back to it. Outbound calls
5304
+ * don't use this — they pass their session config per call.
5305
+ */
5306
+ readonly onInboundCallSessionConfig?: (req: TwiMLRequest) => Promise<Record<string, unknown> | null>;
5307
+ constructor(options?: MediaStreamsOpenAIProviderConfigOptions);
5308
+ }
5309
+
5310
+ /**
5311
+ * Both legs of one call's audio bridge — the Twilio-facing socket and the
5312
+ * model-facing socket — live here together, rather than in two parallel maps
5313
+ * keyed by conversation id that could drift out of sync.
5314
+ *
5315
+ * `streamSid` and `transcript` live on `ConversationSession.metadata` instead,
5316
+ * not here: this map is deleted before `onConversationEnded` fires, so anything
5317
+ * a handler needs to read after the call ends must survive on the session.
5318
+ *
5319
+ * @internal
5320
+ */
5321
+ declare class MediaStreamsOpenAICallState {
5322
+ twilioWs: WebSocket | null;
5323
+ modelWs: WebSocket | null;
5324
+ /**
5325
+ * Resolves `true` once this call's model socket is open and its session
5326
+ * config has been sent, or `false` if that handshake failed. `null` until the
5327
+ * handshake has been started.
5328
+ *
5329
+ * Twilio begins streaming caller audio as soon as the media stream opens,
5330
+ * which is well before the OpenAI handshake completes. Caller audio waits on
5331
+ * this promise rather than being written to a socket that does not exist
5332
+ * yet, so a caller who speaks the instant the call connects is not clipped.
5333
+ * Every frame awaits this same promise, so the frames resume in the order
5334
+ * they arrived.
5335
+ *
5336
+ * It resolves rather than rejects: a failed handshake is reported once, by
5337
+ * the code that opened the socket, not once per waiting frame.
5338
+ */
5339
+ modelReady: Promise<boolean> | null;
5340
+ }
5341
+
5342
+ /**
5343
+ * Identifies this SDK to OpenAI on every WebSocket connection, per OpenAI's
5344
+ * requested User-Agent pattern: [Company/Library name]/[Language] [Version].
5345
+ *
5346
+ * Deliberately unlike the Twilio-facing User-Agent in `clients/base.ts`, which
5347
+ * follows Twilio's own convention instead.
5348
+ */
5349
+ declare const OPENAI_USER_AGENT: string;
5350
+ /**
5351
+ * Shared scaffolding for a {@link VoiceProvider} bridging Twilio Media Streams
5352
+ * to an OpenAI real-time voice API.
5353
+ *
5354
+ * Holds only what's identical across every such provider; each subclass still
5355
+ * owns its own `initiateOutboundConversation`, `handleWebSocket`,
5356
+ * `registerCall`, `connectModel`, {@link dispatchModelEvent},
5357
+ * `handleFunctionCall`, and `cleanupCall`.
5358
+ *
5359
+ * Generic over `TCallState` (bound to `MediaStreamsOpenAICallState`) so
5360
+ * {@link calls} keeps each subclass's own call-state shape instead of widening
5361
+ * to the shared base everywhere it's read.
5362
+ */
5363
+ declare abstract class MediaStreamsOpenAIProvider<TCallState extends MediaStreamsOpenAICallState> extends VoiceProvider {
5364
+ /**
5365
+ * The owning channel's logger, so this provider logs under the same name the
5366
+ * rest of the voice channel does.
5367
+ */
5368
+ protected readonly logger: Logger;
5369
+ /** Executable tools from the config, looked up by the name the model sends. */
5370
+ protected readonly toolsByName: Map<string, TACTool>;
5371
+ /** Per-call transport state, keyed by conversation id. */
5372
+ protected readonly calls: Map<ConversationId, TCallState>;
5373
+ protected readonly config: MediaStreamsOpenAIProviderConfig;
5374
+ protected readonly tacConfig: TACConfig;
5375
+ protected readonly twimlBuilder: TwiMLBuilderMediaStreams;
5376
+ /**
5377
+ * Session config overrides awaiting the call they belong to.
5378
+ *
5379
+ * Inbound entries are keyed by call SID (known when the TwiML webhook is
5380
+ * answered); a subclass keys its outbound entries by whatever token it round
5381
+ * trips through the stream's custom parameters.
5382
+ */
5383
+ protected readonly pendingSessionConfigs: Map<string, Record<string, unknown>>;
5384
+ /**
5385
+ * Expiry timers for the inbound {@link pendingSessionConfigs} entries, keyed
5386
+ * by the same call SID. Each fires once to purge a stash whose Media Stream
5387
+ * never connected; a normal connect cancels it in the subclass's
5388
+ * `registerCall`. Outbound entries are keyed by token instead and are not
5389
+ * tracked here.
5390
+ */
5391
+ private readonly pendingInboundExpiries;
5392
+ constructor(channel: VoiceChannel, tacConfig: TACConfig, config: MediaStreamsOpenAIProviderConfig);
5393
+ /** The Twilio-facing WebSocket for a conversation, if one is tracked. */
5394
+ getWebSocket(conversationId: ConversationId): WebSocket | null;
5395
+ /**
5396
+ * Open a WebSocket and resolve once it is ready to carry traffic.
5397
+ *
5398
+ * Isolated from each subclass's `connectModel` so tests can substitute a
5399
+ * socket without reaching the network.
5400
+ *
5401
+ * A resolved socket always carries at least one `'error'` listener, whatever
5402
+ * the caller does with it next.
5403
+ *
5404
+ * @internal
5405
+ */
5406
+ openModelSocket(url: string, headers: Record<string, string>): Promise<WebSocket>;
5407
+ /**
5408
+ * The transcript captured so far for an in-progress call.
5409
+ *
5410
+ * It lives on `ConversationSession.metadata.transcript`, so once the call
5411
+ * ends and the session is dropped it is no longer reachable here — read it
5412
+ * from the session an `onConversationEnded` handler receives instead.
5413
+ */
5414
+ getTranscript(conversationId: ConversationId): Record<string, string>[];
5415
+ /**
5416
+ * The session config stashed for `key` — a call SID for inbound calls, a
5417
+ * token for outbound ones.
5418
+ *
5419
+ * @internal
5420
+ */
5421
+ peekPendingSessionConfig(key: string): Record<string, unknown> | undefined;
5422
+ /**
5423
+ * How many session config overrides are waiting for their call.
5424
+ *
5425
+ * @internal
5426
+ */
5427
+ pendingSessionConfigCount(): number;
5428
+ /**
5429
+ * Start the clock on an inbound stash, so a call whose Media Stream never
5430
+ * connects cannot strand its override in {@link pendingSessionConfigs} until
5431
+ * shutdown.
5432
+ *
5433
+ * Unref'd: a two-minute timer must not be what keeps the process alive after
5434
+ * the call it belongs to is long over. Re-arming replaces any prior timer for
5435
+ * the same call SID, so a duplicate inbound webhook can't orphan one.
5436
+ */
5437
+ private armInboundConfigExpiry;
5438
+ /**
5439
+ * Stop the clock on an inbound stash, once the call it belongs to has
5440
+ * connected and is about to consume the entry. A no-op for outbound calls,
5441
+ * which key their stash by token and never arm one — the subclass calls this
5442
+ * with the call SID for every `start`, inbound or not.
5443
+ *
5444
+ * @internal
5445
+ */
5446
+ protected cancelInboundConfigExpiry(callSid: string): void;
5447
+ /**
5448
+ * The executable tool the model would run for `name`, if the config supplied
5449
+ * one.
5450
+ *
5451
+ * @internal
5452
+ */
5453
+ peekTool(name: string): TACTool | undefined;
5454
+ /**
5455
+ * Build the `<Connect><Stream>` TwiML for an inbound call.
5456
+ *
5457
+ * TwiML fields are merged per-field, highest precedence first:
5458
+ * 1. Output of the customizer registered via
5459
+ * `VoiceChannel.onInboundCallTwiml(...)`, if configured and
5460
+ * `twimlRequest` is given
5461
+ * 2. `MediaStreamsProviderConfig.defaultTwimlOptions` — channel-wide
5462
+ * defaults
5463
+ * 3. `options.hostTwimlOptions` — per-call transport facts supplied by the
5464
+ * host
5465
+ * 4. TAC defaults: the WebSocket URL derived from
5466
+ * `TACConfig.voicePublicDomain` + `voiceWebsocketPath`
5467
+ *
5468
+ * Also runs `MediaStreamsOpenAIProviderConfig.onInboundCallSessionConfig`, if
5469
+ * set, and stashes its result for the call to pick up once it connects. The
5470
+ * hook runs only after the TwiML builds, so a call that never connects
5471
+ * leaves nothing stashed behind it.
5472
+ *
5473
+ * @param twimlRequest - Parsed Twilio webhook fields for the inbound call.
5474
+ * @param options - Additional per-call inputs.
5475
+ * @param options.hostTwimlOptions - Per-call TwiML supplied by a custom
5476
+ * in-process host.
5477
+ * @throws {TypeError} if either the host options or the customizer's output
5478
+ * is not a `VoiceTwiMLOptionsMediaStreams`.
5479
+ * @throws {Error} if no WebSocket URL can be resolved — none of the TwiML
5480
+ * layers set one and `TACConfig.voicePublicDomain` is unset.
5481
+ */
5482
+ handleIncomingCall(twimlRequest?: TwiMLRequest, options?: {
5483
+ hostTwimlOptions?: VoiceTwiMLOptions;
5484
+ }): Promise<string>;
5485
+ /**
5486
+ * Narrow provider-agnostic {@link VoiceTwiMLOptions} to this provider's
5487
+ * concrete shape. `VoiceProvider.handleIncomingCall` is typed against the
5488
+ * base so every provider can accept its own TwiML options, so the Media
5489
+ * Streams shape has to be established at runtime.
5490
+ *
5491
+ * @param value - Options from a caller or the application customizer.
5492
+ * @param caller - Name of the calling method, for the error message.
5493
+ * @param label - What produced `value`, for the error message.
5494
+ */
5495
+ protected narrowTwimlOptions(value: VoiceTwiMLOptions | undefined, caller: string, label: string): VoiceTwiMLOptionsMediaStreams | undefined;
5496
+ /**
5497
+ * Parse one frame off the model socket and hand it to
5498
+ * {@link dispatchModelEvent}.
5499
+ *
5500
+ * A failure here is logged and skipped rather than ending the call: one
5501
+ * malformed delta must not hang up on the caller.
5502
+ *
5503
+ * @internal
5504
+ */
5505
+ handleModelMessage(conversationId: ConversationId, raw: Buffer | string): Promise<void>;
5506
+ /**
5507
+ * Interpret one event received from the model. Protocol-specific —
5508
+ * implemented by each subclass.
5509
+ */
5510
+ protected abstract dispatchModelEvent(conversationId: ConversationId, session: ConversationSession, event: Record<string, unknown>): Promise<void>;
5511
+ /**
5512
+ * Look up a model-requested tool by name, run it, and return its output.
5513
+ *
5514
+ * Errors are returned as part of the output rather than thrown, so a bad
5515
+ * tool call does not kill the call.
5516
+ *
5517
+ * @internal
5518
+ */
5519
+ runToolCall(conversationId: ConversationId, name: string, argumentsJson: unknown): Promise<unknown>;
5520
+ /** Write one event to this call's model socket, if it still has one. */
5521
+ protected modelSend(conversationId: ConversationId, payload: Record<string, unknown>): void;
5522
+ /** Write one message to this call's Twilio socket, if it still has one. */
5523
+ protected twilioSend(conversationId: ConversationId, payload: Record<string, unknown>): void;
5524
+ /**
5525
+ * Always throws: the model streams its reply as audio straight to Twilio, so
5526
+ * this transport has no text response to send.
5527
+ */
5528
+ sendResponse(_conversationId: ConversationId, _message: string, _metadata?: Record<string, unknown>): Promise<void>;
5529
+ /**
5530
+ * Drop this provider's Media Streams transport state on channel shutdown.
5531
+ *
5532
+ * Note: WebSocket connections are managed by the server and closed there.
5533
+ * This method only cleans up internal provider state — including session
5534
+ * config overrides stashed for calls that were placed but never connected.
5535
+ */
5536
+ shutdown(): void;
5537
+ }
5538
+
5539
+ /**
5540
+ * Options accepted by {@link OpenAIRealtimeProviderConfig}.
5541
+ */
5542
+ interface OpenAIRealtimeProviderConfigOptions extends MediaStreamsOpenAIProviderConfigOptions {
5543
+ /**
5544
+ * Executable `TACTool` implementations, looked up by name to run mid-call
5545
+ * tool requests. This alone does not tell the model these tools exist — also
5546
+ * add each tool's `toRealtimeFormat()` schema to
5547
+ * {@link OpenAIRealtimeProviderConfigOptions.defaultSessionConfig}'s `tools`
5548
+ * entry.
5549
+ */
5550
+ tools?: TACTool<never, unknown>[];
5551
+ /**
5552
+ * If set, sent verbatim as `response.create`'s `response` payload when the
5553
+ * call connects — e.g. `{ instructions: 'Hi there!' }`. No SDK-added wrapping
5554
+ * text or language assumption.
5555
+ */
5556
+ welcomeGreetingResponse?: Record<string, unknown>;
5557
+ /**
5558
+ * The `session.update` payload's `session` body, sent once the model connects
5559
+ * — used for any call that doesn't supply its own via
5560
+ * {@link OpenAIRealtimeProviderConfigOptions.onInboundCallSessionConfig} or
5561
+ * `InitiateVoiceConversationOptionsOpenAIRealtime`.
5562
+ *
5563
+ * See
5564
+ * https://developers.openai.com/api/reference/resources/realtime/client-events#session.update
5565
+ * for the schema. If using {@link OpenAIRealtimeProviderConfigOptions.tools},
5566
+ * its `tools` entry must separately list each tool's `toRealtimeFormat()`
5567
+ * schema — this config is passed to OpenAI as-is, with no tool schemas merged
5568
+ * in.
5569
+ */
5570
+ defaultSessionConfig?: Record<string, unknown>;
5571
+ /**
5572
+ * Per-inbound-call override for
5573
+ * {@link OpenAIRealtimeProviderConfigOptions.defaultSessionConfig}, called
5574
+ * with the `TwiMLRequest`. Its return value is used verbatim (not merged with
5575
+ * `defaultSessionConfig`); return `null` to fall back to it. Outbound calls
5576
+ * don't use this — see `InitiateVoiceConversationOptionsOpenAIRealtime`.
5577
+ */
5578
+ onInboundCallSessionConfig?: (req: TwiMLRequest) => Promise<Record<string, unknown> | null>;
5579
+ }
5580
+ /**
5581
+ * Configuration for `OpenAIRealtimeProvider`.
5582
+ */
5583
+ declare class OpenAIRealtimeProviderConfig extends MediaStreamsOpenAIProviderConfig {
5584
+ /**
5585
+ * Executable `TACTool` implementations, looked up by name to run mid-call
5586
+ * tool requests. This alone does not tell the model these tools exist — also
5587
+ * add each tool's `toRealtimeFormat()` schema to
5588
+ * {@link OpenAIRealtimeProviderConfig.defaultSessionConfig}'s `tools` entry.
5589
+ */
5590
+ readonly tools: TACTool<never, unknown>[];
5591
+ /**
5592
+ * If set, sent verbatim as `response.create`'s `response` payload when the
5593
+ * call connects — e.g. `{ instructions: 'Hi there!' }`. No SDK-added wrapping
5594
+ * text or language assumption.
5595
+ */
5596
+ readonly welcomeGreetingResponse?: Record<string, unknown>;
5597
+ /**
5598
+ * The `session.update` payload's `session` body, sent once the model connects
5599
+ * — used for any call that doesn't supply its own via
5600
+ * {@link OpenAIRealtimeProviderConfig.onInboundCallSessionConfig} or
5601
+ * `InitiateVoiceConversationOptionsOpenAIRealtime`.
5602
+ *
5603
+ * See
5604
+ * https://developers.openai.com/api/reference/resources/realtime/client-events#session.update
5605
+ * for the schema. If using {@link OpenAIRealtimeProviderConfig.tools}, its
5606
+ * `tools` entry must separately list each tool's `toRealtimeFormat()` schema
5607
+ * — this config is passed to OpenAI as-is, with no tool schemas merged in.
5608
+ */
5609
+ readonly defaultSessionConfig?: Record<string, unknown>;
5610
+ /**
5611
+ * Per-inbound-call override for
5612
+ * {@link OpenAIRealtimeProviderConfig.defaultSessionConfig}, called with the
5613
+ * `TwiMLRequest`. Its return value is used verbatim (not merged with
5614
+ * `defaultSessionConfig`); return `null` to fall back to it. Outbound calls
5615
+ * don't use this — see `InitiateVoiceConversationOptionsOpenAIRealtime`.
5616
+ */
5617
+ readonly onInboundCallSessionConfig?: (req: TwiMLRequest) => Promise<Record<string, unknown> | null>;
5618
+ constructor(options?: OpenAIRealtimeProviderConfigOptions);
5619
+ createProvider(channel: VoiceChannel, tacConfig: TACConfig): VoiceProvider;
5620
+ }
5621
+
5622
+ /**
5623
+ * Per-call barge-in bookkeeping.
5624
+ *
5625
+ * For every delta that carries an `item_id`, `currentItemAudioMs` is never
5626
+ * more than the duration of audio actually sent to Twilio for
5627
+ * `lastAssistantItem`: it comes from delta byte counts rather than a
5628
+ * wall-clock estimate, floored per delta, so it can understate by up to a
5629
+ * millisecond per delta. `conversation.item.truncate` rejects an `audioEndMs`
5630
+ * beyond the item's real content, so understating is the safe direction.
5631
+ *
5632
+ * `responseActive` tracks whether a response is still being generated (set on
5633
+ * `response.created`, cleared on `response.done` or once barge-in cancels it) —
5634
+ * `response.cancel` with nothing in flight is itself an error event, so this
5635
+ * gates whether to send it.
5636
+ *
5637
+ * `mutedItemId` is the assistant item truncated by the last barge-in —
5638
+ * `response.output_audio.delta` events whose `item_id` matches it are dropped
5639
+ * as stale audio for a reply the caller already talked over. It is never
5640
+ * cleared; the next barge-in overwrites it.
5641
+ *
5642
+ * @internal
5643
+ */
5644
+ declare class BargeInState {
5645
+ lastAssistantItem: string | null;
5646
+ currentItemAudioMs: number;
5647
+ mutedItemId: string | null;
5648
+ responseActive: boolean;
5649
+ }
5650
+ /**
5651
+ * Per-call bookkeeping the OpenAI Realtime provider needs beyond
5652
+ * `ConversationSession` and the sockets its base class holds.
5653
+ *
5654
+ * @internal
5655
+ */
5656
+ declare class CallState$1 extends MediaStreamsOpenAICallState {
5657
+ /**
5658
+ * Tail of this call's model-event chain: each incoming OpenAI Realtime event
5659
+ * is appended to it rather than dispatched on arrival.
5660
+ *
5661
+ * The Python SDK reads model events in a sequential loop, so event N is fully
5662
+ * handled before N+1 is even read. `ws` delivers each event on its own
5663
+ * `'message'` emission with nothing serializing them, so without this chain a
5664
+ * `response.output_audio.delta` could advance the barge-in bookkeeping while
5665
+ * dispatch is suspended on the `handleFunctionCall` await, producing a
5666
+ * truncate that overruns the item it names.
5667
+ */
5668
+ modelEvents: Promise<void>;
5669
+ readonly bargeIn: BargeInState;
5670
+ }
5671
+
5672
+ /**
5673
+ * The audio format both directions of a call must use.
5674
+ *
5675
+ * Twilio Media Streams always sends and expects 8kHz G.711 u-law — see
5676
+ * https://www.twilio.com/docs/voice/media-streams/websocket-messages. Not
5677
+ * configurable. No `rate` key: Realtime's `session.audio.*.format` schema
5678
+ * rejects it as unknown, since g711 is inherently fixed-rate.
5679
+ */
5680
+ declare const TWILIO_AUDIO_FORMAT_FOR_REALTIME: {
5681
+ readonly type: "audio/pcmu";
5682
+ };
5683
+ /**
5684
+ * A {@link VoiceProvider} bridging Twilio Media Streams to OpenAI's Realtime
5685
+ * API.
5686
+ *
5687
+ * Twilio streams call audio to TAC's own WebSocket via `<Connect><Stream>`, and
5688
+ * this provider relays it to and from a second WebSocket it opens to OpenAI's
5689
+ * Realtime API.
5690
+ *
5691
+ * ```ts
5692
+ * const channel = new VoiceChannel(
5693
+ * tac,
5694
+ * new OpenAIRealtimeProviderConfig({ defaultSessionConfig: { instructions: '...' } })
5695
+ * );
5696
+ * ```
5697
+ */
5698
+ declare class OpenAIRealtimeProvider extends MediaStreamsOpenAIProvider<CallState$1> {
5699
+ /** @internal */
5700
+ get providerId(): string;
5701
+ /**
5702
+ * The Realtime-specific config this provider was built with.
5703
+ *
5704
+ * `declare`: `target: ES2022` implies `useDefineForClassFields`, so a plain
5705
+ * redeclaration would emit a field definition that runs after `super()` and
5706
+ * overwrite the value the base constructor assigned with `undefined`. This
5707
+ * exists only to narrow the base's `MediaStreamsOpenAIProviderConfig` to the
5708
+ * Realtime shape `connectModel` reads `welcomeGreetingResponse` from.
5709
+ *
5710
+ * No `override`: TypeScript rejects it alongside `declare` (TS1243), and
5711
+ * `noImplicitOverride` does not require it for a `declare`d field.
5712
+ */
5713
+ protected readonly config: OpenAIRealtimeProviderConfig;
5714
+ constructor(channel: VoiceChannel, tacConfig: TACConfig, config: OpenAIRealtimeProviderConfig);
5715
+ get channelName(): string;
5716
+ /**
5717
+ * Initiate an outbound voice conversation.
5718
+ *
5719
+ * Places an outbound call with inline TwiML that connects to a Media Stream.
5720
+ * Unlike inbound, there is no local session yet at this point — one is
5721
+ * created when Twilio's WebSocket `start` event arrives.
5722
+ *
5723
+ * TwiML fields are merged per-field — see
5724
+ * {@link TwiMLBuilderMediaStreams.build}. The WebSocket URL is derived from
5725
+ * `TACConfig.voicePublicDomain` + `TACConfig.voiceWebsocketPath` unless
5726
+ * overridden per-call via `options.websocketUrl`.
5727
+ *
5728
+ * Pass `InitiateVoiceConversationOptionsOpenAIRealtime` with `sessionConfig`
5729
+ * set to override `OpenAIRealtimeProviderConfig.defaultSessionConfig` for
5730
+ * this call.
5731
+ *
5732
+ * @param options - Outbound call options, validated in full against
5733
+ * `InitiateVoiceConversationOptionsOpenAIRealtimeSchema`.
5734
+ * @throws {TypeError} if `options` is not a valid
5735
+ * `InitiateVoiceConversationOptionsOpenAIRealtime` — including an unknown
5736
+ * key, a missing `to`, or a `twimlOptions` that is not a
5737
+ * `VoiceTwiMLOptionsMediaStreams`.
5738
+ * @throws {Error} if no WebSocket URL can be resolved — neither
5739
+ * `options.websocketUrl` nor any TwiML layer sets one and
5740
+ * `TACConfig.voicePublicDomain` is unset.
5741
+ */
5742
+ initiateOutboundConversation(options: InitiateVoiceConversationOptions | InitiateVoiceConversationOptionsOpenAIRealtime): Promise<InitiateVoiceConversationResult>;
5743
+ /**
5744
+ * Drive one Twilio Media Stream connection from `start` to disconnect.
5745
+ *
5746
+ * Twilio's `start` event names the call, which opens the matching OpenAI
5747
+ * Realtime socket; from then on caller audio is relayed to the model and the
5748
+ * model's audio back to Twilio, until either side goes away. Whichever leg
5749
+ * closes first takes the other down with it, so a caller is never left
5750
+ * connected to silence.
5751
+ *
5752
+ * Twilio streams audio without waiting for the OpenAI socket to finish
5753
+ * connecting, so audio that arrives during that handshake is held and
5754
+ * forwarded, in order, once the model is ready — a caller who speaks the
5755
+ * instant the call connects is heard in full.
5756
+ *
5757
+ * Called by `VoiceChannel.handleWebSocketConnection`; hosts serve the socket
5758
+ * rather than calling this directly.
5759
+ *
5760
+ * @param ws - The accepted Twilio-facing WebSocket.
5761
+ */
5762
+ handleWebSocket(ws: WebSocket): void;
5763
+ /**
5764
+ * Handle Twilio's `start` event: track the call and open its session.
5765
+ *
5766
+ * @param start - The event's `start` body, parsed against
5767
+ * `StreamStartMessageSchema`.
5768
+ * @param ws - The Twilio-facing socket this call arrived on.
5769
+ * @returns The conversation id — which is the call SID — and the call's
5770
+ * freshly tracked transport state.
5771
+ */
5772
+ private registerCall;
5773
+ /**
5774
+ * Open this call's OpenAI Realtime socket and send its session config.
5775
+ *
5776
+ * @internal
5777
+ */
5778
+ connectModel(conversationId: ConversationId): Promise<void>;
5779
+ /**
5780
+ * The validated session config for this call: its own stashed override if it
5781
+ * has one, else the channel-wide default.
5782
+ *
5783
+ * @throws {Error} if neither exists, if it has no `model`, or if either audio
5784
+ * direction is set to a format Twilio can't carry.
5785
+ */
5786
+ private resolveSessionConfig;
5787
+ /**
5788
+ * Wire up the model socket: dispatch its events, and tear the call down when
5789
+ * it goes away.
5790
+ *
5791
+ * The Python SDK races its two read loops so the Twilio leg dies with the
5792
+ * model leg; `ws` is event-driven, so the same guarantee is a close/error
5793
+ * handler instead. Python's sequential read loop also handles each model
5794
+ * event to completion before reading the next, which `ws` does not — see
5795
+ * {@link CallState.modelEvents} for the chain that restores it.
5796
+ */
5797
+ private attachModelHandlers;
5798
+ /**
5799
+ * Hang up the Twilio leg because the model leg is gone, then clean up. A
5800
+ * no-op once the call has already been cleaned up, so both the model socket's
5801
+ * `close` and its `error` can call it.
5802
+ */
5803
+ private endCallFromModel;
5804
+ /** Apply one parsed OpenAI Realtime event to the call. */
5805
+ protected dispatchModelEvent(conversationId: ConversationId, session: ConversationSession, event: Record<string, unknown>): Promise<void>;
5806
+ /** Record one turn on the session's running transcript. */
5807
+ private appendTranscript;
5808
+ /**
5809
+ * The caller started talking. Cancel any response still generating, truncate
5810
+ * the model's memory of the last reply at the point actually heard, then
5811
+ * clear Twilio's buffered audio so playback stops immediately.
5812
+ *
5813
+ * If no assistant audio has been sent since the last barge-in this is a
5814
+ * no-op: there is nothing queued at Twilio to clear, no item id to name in a
5815
+ * truncate, and any response still generating is left to run.
5816
+ */
5817
+ private handleBargeIn;
5818
+ /**
5819
+ * Run a model-requested tool call and hand the result back.
5820
+ *
5821
+ * Always sends a `function_call_output` once a `call_id` is present — even a
5822
+ * tool that ran successfully can return something `JSON.stringify` throws on
5823
+ * (a circular object, a `BigInt`) or has no JSON form at all, which
5824
+ * `JSON.stringify` reports by returning `undefined` rather than throwing (a
5825
+ * void tool, a bare function, a `Symbol`). Either way the model would
5826
+ * otherwise be left waiting on a `call_id` it never gets a result for.
5827
+ * Without a `call_id` there is nothing to reply to, so the item is dropped
5828
+ * instead.
5829
+ */
5830
+ private handleFunctionCall;
5831
+ /**
5832
+ * Drop this call's transport state, close the model socket, and end the
5833
+ * session.
5834
+ *
5835
+ * Both legs can report the call ending, and the first one to arrive tears
5836
+ * down the other, so this runs at most once per call: a second invocation
5837
+ * finds nothing tracked and returns.
5838
+ */
5839
+ private cleanupCall;
5840
+ }
5841
+
5842
+ interface GPTLiveProviderConfigOptions extends MediaStreamsOpenAIProviderConfigOptions {
5843
+ /**
5844
+ * If set, sent verbatim as a `session.commentary.append` once `session.started`
5845
+ * arrives. Word it as an instruction, not just a greeting — e.g. "Greet the
5846
+ * caller immediately using: Hi, how can I help you today?". A bare greeting
5847
+ * will not make the model speak first.
5848
+ */
5849
+ welcomeInstruction?: string;
5850
+ }
5851
+ /**
5852
+ * Configuration for `GPTLiveProvider`.
5853
+ *
5854
+ * Two GPT-Live-specific notes on inherited members:
5855
+ *
5856
+ * - `tools` holds executable implementations looked up by name. It does **not**
5857
+ * tell the model the tools exist — also add each tool's `toRealtimeFormat()`
5858
+ * schema to `defaultSessionConfig.delegation.responses.tools`.
5859
+ * - `defaultSessionConfig` is the `session.start` payload's `session` body. It
5860
+ * must include `model` (e.g. `'gpt-live-1'`), and `audio.format`
5861
+ * must be `TWILIO_AUDIO_FORMAT_FOR_GPT_LIVE`.
5862
+ */
5863
+ declare class GPTLiveProviderConfig extends MediaStreamsOpenAIProviderConfig {
5864
+ readonly welcomeInstruction: string | null;
5865
+ constructor(opts?: GPTLiveProviderConfigOptions);
5866
+ createProvider(channel: VoiceChannel, tacConfig: TACConfig): VoiceProvider;
5867
+ }
5868
+
5869
+ /**
5870
+ * Per-call bookkeeping `GPTLiveProvider` needs beyond `ConversationSession`.
5871
+ *
5872
+ * @internal
5873
+ */
5874
+ declare class CallState extends MediaStreamsOpenAICallState {
5875
+ /**
5876
+ * Settles once `session.closed` arrives, so teardown can wait for graceful
5877
+ * finalization before tearing the socket down.
5878
+ */
5879
+ readonly closed: Promise<void>;
5880
+ private readonly resolveClosed;
5881
+ constructor();
5882
+ markClosed(): void;
5883
+ }
5884
+
5885
+ /**
5886
+ * The audio format both directions of a call must use.
5887
+ *
5888
+ * Twilio Media Streams always sends and expects 8kHz G.711 u-law — see
5889
+ * https://www.twilio.com/docs/voice/media-streams/websocket-messages. Not
5890
+ * configurable. GPT-Live speaks it natively on both legs, so a single
5891
+ * `session.audio.format` covers input and output and nothing transcodes.
5892
+ *
5893
+ * Spelled with an explicit `rate`, which GPT-Live's schema wants and Realtime's
5894
+ * rejects — that is why this is a separate constant from
5895
+ * `TWILIO_AUDIO_FORMAT_FOR_REALTIME` rather than a shared one.
5896
+ */
5897
+ declare const TWILIO_AUDIO_FORMAT_FOR_GPT_LIVE: {
5898
+ readonly type: "audio/pcmu";
5899
+ readonly rate: 8000;
5900
+ };
5901
+ /**
5902
+ * `ConversationSession.metadata` key holding OpenAI's id for the GPT-Live
5903
+ * session behind this call, set once `session.started` arrives. Quote it to
5904
+ * OpenAI support when reporting a session.
5905
+ *
5906
+ * Opaque: the prefix differs across GPT-Live's alpha (`rtc_`) and GA (`live_`),
5907
+ * so never parse it, validate it, or branch on it.
5908
+ */
5909
+ declare const GPT_LIVE_SESSION_ID_METADATA_KEY = "gpt_live_session_id";
5910
+ /**
5911
+ * A {@link VoiceProvider} bridging Twilio Media Streams to OpenAI's GPT-Live
5912
+ * API.
5913
+ */
5914
+ declare class GPTLiveProvider extends MediaStreamsOpenAIProvider<CallState> {
5915
+ /** @internal */
5916
+ get providerId(): string;
5917
+ /**
5918
+ * No `override`: TypeScript rejects it alongside `declare` (TS1243), and
5919
+ * `declare` is what keeps this a pure type narrowing of the base's field
5920
+ * rather than a second field that shadows it.
5921
+ */
5922
+ protected readonly config: GPTLiveProviderConfig;
5923
+ private readonly pendingTokenExpiries;
5924
+ /** Calls whose teardown has begun but is still awaiting `session.closed`. */
5925
+ private readonly closingCalls;
5926
+ get channelName(): string;
5927
+ /**
5928
+ * Initiate an outbound voice conversation.
5929
+ *
5930
+ * Places an outbound call with inline TwiML that connects to a Media Stream.
5931
+ * Unlike inbound, there is no local session yet at this point — one is
5932
+ * created when Twilio's WebSocket `start` event arrives.
5933
+ *
5934
+ * TwiML fields are merged per-field — see
5935
+ * {@link TwiMLBuilderMediaStreams.build}. The WebSocket URL is derived from
5936
+ * `TACConfig.voicePublicDomain` + `TACConfig.voiceWebsocketPath` unless
5937
+ * overridden per-call via `options.websocketUrl`.
5938
+ *
5939
+ * Pass `InitiateVoiceConversationOptionsGPTLive` with `sessionConfig` set to
5940
+ * override `GPTLiveProviderConfig.defaultSessionConfig` for this call.
5941
+ *
5942
+ * @param options - Outbound call options.
5943
+ * @throws {TypeError} if `options` does not satisfy
5944
+ * `InitiateVoiceConversationOptionsGPTLiveSchema`.
5945
+ * @throws {Error} if no WebSocket URL can be resolved — neither
5946
+ * `options.websocketUrl` nor any TwiML layer sets one and
5947
+ * `TACConfig.voicePublicDomain` is unset.
5948
+ */
5949
+ initiateOutboundConversation(options: InitiateVoiceConversationOptions | InitiateVoiceConversationOptionsGPTLive): Promise<InitiateVoiceConversationResult>;
5950
+ /**
5951
+ * Start the clock on a stashed token, so a call that never connects cannot
5952
+ * strand its override in {@link pendingSessionConfigs} forever.
5953
+ *
5954
+ * Unref'd: a two-minute timer must not be what keeps the process alive after
5955
+ * the call it belongs to is long over.
5956
+ */
5957
+ private armTokenExpiry;
5958
+ /**
5959
+ * Stop the clock on a token, once the call it belongs to has claimed it.
5960
+ *
5961
+ * Without this a two-minute timer outlives every call that connected
5962
+ * normally, waiting to purge an entry that is already gone.
5963
+ */
5964
+ private cancelTokenExpiry;
5965
+ /**
5966
+ * Drive one Twilio Media Stream connection from `start` to disconnect.
5967
+ *
5968
+ * Twilio's `start` event names the call, which opens the matching GPT-Live
5969
+ * socket; from then on caller audio is relayed to the model and the model's
5970
+ * audio back to Twilio, until either side goes away. Whichever leg closes
5971
+ * first takes the other down with it, so a caller is never left connected to
5972
+ * silence.
5973
+ *
5974
+ * Twilio streams audio without waiting for the GPT-Live socket to finish
5975
+ * connecting, so audio that arrives during that handshake is held and
5976
+ * forwarded, in order, once the model is ready — a caller who speaks the
5977
+ * instant the call connects is heard in full.
5978
+ *
5979
+ * Called by `VoiceChannel.handleWebSocketConnection`; hosts serve the socket
5980
+ * rather than calling this directly.
5981
+ *
5982
+ * @param ws - The accepted Twilio-facing WebSocket.
5983
+ */
5984
+ handleWebSocket(ws: WebSocket): void;
5985
+ /**
5986
+ * Handle Twilio's `start` event: track the call and open its session.
5987
+ *
5988
+ * @param start - The event's `start` body, parsed against
5989
+ * `StreamStartMessageSchema`.
5990
+ * @param ws - The Twilio-facing socket this call arrived on.
5991
+ * @returns The conversation id — which is the call SID — and the call's
5992
+ * freshly tracked transport state.
5993
+ */
5994
+ private registerCall;
5995
+ /**
5996
+ * Open this call's GPT-Live socket and send its session config.
5997
+ *
5998
+ * @internal
5999
+ */
6000
+ connectModel(conversationId: ConversationId): Promise<void>;
6001
+ /**
6002
+ * The validated session config for this call: its own stashed override if it
6003
+ * has one, else the channel-wide default.
6004
+ *
6005
+ * @throws {Error} if neither exists, if the audio format is one Twilio can't
6006
+ * carry, or if it names no model.
6007
+ */
6008
+ private resolveSessionConfig;
6009
+ /**
6010
+ * Wire up the model socket: dispatch its events, and tear the call down when
6011
+ * it goes away.
4110
6012
  *
4111
- * @param conversationId - The conversation ID
4112
- * @returns true if an active task exists
6013
+ * The Python SDK races its Twilio read against its model-event reader so the
6014
+ * Twilio leg dies with the model leg; `ws` is event-driven, so the same
6015
+ * guarantee is a close/error handler instead.
4113
6016
  */
4114
- hasActiveStreamTask(conversationId: ConversationId): boolean;
6017
+ private attachModelHandlers;
4115
6018
  /**
4116
- * Field names on {@link TwiMLOptions} that map directly to `<ConversationRelay>`
4117
- * attributes (camelCase, emitted as-is). Excludes the fields handled specially
4118
- * by {@link generateTwiml}: websocketUrl (resolved through the layered merge and
4119
- * emitted as the `url` attribute), actionUrl, languages, customParameters, extra.
6019
+ * Hang up the Twilio leg because the model leg is gone, then clean up. A
6020
+ * no-op once the call has already been cleaned up, so both the model socket's
6021
+ * `close` and its `error` can call it.
4120
6022
  */
4121
- private static readonly RELAY_ATTR_FIELDS;
6023
+ private endCallFromModel;
4122
6024
  /**
4123
- * Generate TwiML XML for ConversationRelay from a merged {@link TwiMLOptions}.
6025
+ * Close this call's GPT-Live session gracefully, drop its transport state,
6026
+ * and end the session.
4124
6027
  *
4125
- * This is the low-level emitter used by `handleIncomingCall` and
4126
- * `initiateOutboundConversation` after layering. It mirrors the Python SDK's
4127
- * `generate_twiml`. The WebSocket URL may be passed as `websocketUrl` or via
4128
- * `options.websocketUrl` (the explicit argument wins when both are given), so a
4129
- * channel-less caller can pass everything in one object.
6028
+ * `session.close` asks the server to finalize the session, and teardown waits
6029
+ * up to {@link CLOSE_TIMEOUT_MS} for the `session.closed` answering it before
6030
+ * the socket goes away — otherwise the socket would be gone before the server
6031
+ * could finish.
4130
6032
  *
4131
- * @param websocketUrl - Public WebSocket URL (e.g. 'wss://example.ngrok.app/ws').
4132
- * Optional if `options.websocketUrl` is set.
4133
- * @param options - Merged TwiMLOptions to emit.
4134
- * @returns TwiML XML string ready to return to Twilio.
4135
- * @throws {Error} if no WebSocket URL is provided via either source.
6033
+ * Both legs can report the call ending, and the first one to arrive tears
6034
+ * down the other, so this runs at most once per call: a second invocation
6035
+ * finds the call either untracked or already closing, and returns.
4136
6036
  */
4137
- private generateTwiml;
6037
+ private cleanupCall;
4138
6038
  /**
4139
- * Generate TwiML to connect a call to ConversationRelay.
4140
- * Validates configuration with Zod before generating TwiML.
6039
+ * Drop this provider's transport state on channel shutdown, including the
6040
+ * bookkeeping it keeps beyond the base class's.
4141
6041
  *
4142
- * @param config - ConversationRelay configuration (url, transcription, TTS, etc.)
4143
- * @param options - Optional settings for parameters and the Connect verb
4144
- * @returns TwiML XML string
4145
- * @throws {Error} if config validation fails
6042
+ * The token expiry timers are unref'd and delete themselves, so nothing hangs
6043
+ * without this — but a shut-down provider must not still be holding entries
6044
+ * for calls that can no longer arrive.
4146
6045
  */
4147
- connectConversationRelay(config: ConversationRelayConfig, options?: {
4148
- parameters?: CustomParameters;
4149
- actionUrl?: string;
4150
- }): string;
6046
+ shutdown(): void;
6047
+ /** Apply one parsed GPT-Live event to the call. */
6048
+ protected dispatchModelEvent(conversationId: ConversationId, session: ConversationSession, event: Record<string, unknown>): Promise<void>;
4151
6049
  /**
4152
- * Filter out undefined values from configuration object.
4153
- * Keeps null, false, 0, and empty strings as they are valid values.
6050
+ * Run a Responses-delegated tool call and hand the result back.
6051
+ *
6052
+ * Always sends a `function_call_output` once a `call_id` is present — even a
6053
+ * tool that ran successfully can return something `JSON.stringify` throws on
6054
+ * (a circular object, a `BigInt`) or has no JSON form at all, which
6055
+ * `JSON.stringify` reports by returning `undefined` rather than throwing (a
6056
+ * void tool, a bare function, a `Symbol`). Either way the model would
6057
+ * otherwise be left waiting on a `call_id` it never gets a result for.
6058
+ * Without a `call_id` there is nothing to reply to, so the item is dropped
6059
+ * instead.
4154
6060
  */
4155
- private filterUnsetValues;
6061
+ private handleFunctionCall;
4156
6062
  /**
4157
- * Cleanup channel state on shutdown
6063
+ * Surface the GPT-Live session id from a session-snapshot event.
4158
6064
  *
4159
- * Note: WebSocket connections are managed by the server and closed there.
4160
- * This method only cleans up internal channel state.
6065
+ * OpenAI support asks for this id when investigating a session, so it goes
6066
+ * where a caller can reach it — `session.metadata`, which outlives the call
6067
+ * into `onConversationEnded` — and is logged once per call.
4161
6068
  */
4162
- shutdown(): void;
6069
+ private recordGptLiveSessionId;
6070
+ /** Accumulate one transcript delta into the in-progress turn. */
6071
+ private static appendTranscriptDelta;
4163
6072
  }
4164
6073
 
4165
6074
  declare function scrubPii(value: string): string;
@@ -4354,243 +6263,6 @@ declare class MemoryPromptBuilder {
4354
6263
  private static assemblePrompt;
4355
6264
  }
4356
6265
 
4357
- /**
4358
- * TAC Tool class with helper methods for LLM integration
4359
- *
4360
- * Matches Python's TACTool dataclass with conversion methods.
4361
- */
4362
- declare class TACTool<TParams = unknown, TResult = unknown> {
4363
- readonly name: string;
4364
- readonly description: string;
4365
- readonly parameters: JSONSchema;
4366
- readonly implementation: ToolFunction<TParams, TResult>;
4367
- constructor(name: string, description: string, parameters: JSONSchema, implementation: ToolFunction<TParams, TResult>);
4368
- /**
4369
- * Convert to OpenAI function calling format
4370
- */
4371
- toOpenAIFormat(): OpenAITool;
4372
- /**
4373
- * Convert to Anthropic tool calling format
4374
- */
4375
- toAnthropicFormat(): AnthropicTool;
4376
- /**
4377
- * Convert to JSON string (OpenAI format by default)
4378
- */
4379
- toJSON(): string;
4380
- /**
4381
- * Convert this tool to an OpenAI Agents SDK `FunctionTool` instance.
4382
- *
4383
- * Unlike `toOpenAIFormat` and `toAnthropicFormat` (which return plain
4384
- * objects consumed by HTTP APIs), the OpenAI Agents SDK dispatches on tool
4385
- * *type*, so this returns a live `tool(...)` object with an invoke callback
4386
- * that calls this tool and JSON-encodes the result.
4387
- *
4388
- * Requires the `@openai/agents` package:
4389
- *
4390
- * npm install @openai/agents
4391
- *
4392
- * @returns A FunctionTool ready to pass to `new Agent({ tools: [...] })`.
4393
- */
4394
- toOpenAIAgentsSDKTool(): Promise<any>;
4395
- }
4396
- /**
4397
- * Create a tool directly with all parameters
4398
- *
4399
- * Simplified approach matching Python's create_tool function.
4400
- * No builder pattern - just a simple function call.
4401
- */
4402
- declare function defineTool<TParams = unknown, TResult = unknown>(name: string, description: string, parameters: JSONSchema, implementation: ToolFunction<TParams, TResult>): TACTool<TParams, TResult>;
4403
-
4404
- /**
4405
- * Parameters for memory retrieval tool
4406
- */
4407
- interface MemoryRetrievalParams {
4408
- query?: string;
4409
- beginDate?: string;
4410
- endDate?: string;
4411
- observationsLimit?: number;
4412
- summariesLimit?: number;
4413
- communicationsLimit?: number;
4414
- relevanceThreshold?: number;
4415
- }
4416
- /**
4417
- * Create memory retrieval tool.
4418
- *
4419
- * @param memoryClient - Memory client instance (must be initialized with storeId)
4420
- * @param profileId - Optional profile ID for memory retrieval
4421
- * @param conversationId - Optional conversation ID for memory retrieval
4422
- * @param options - Optional overrides for tool metadata.
4423
- * @param options.name - Tool name exposed to the LLM. Defaults to `retrieve_profile_memory`.
4424
- * @param options.description - Tool description exposed to the LLM. Defaults to a
4425
- * generic "retrieve memories" prompt.
4426
- */
4427
- declare function createMemoryRetrievalTool(memoryClient: MemoryClient, profileId?: string, conversationId?: string, options?: {
4428
- name?: string;
4429
- description?: string;
4430
- }): TACTool<MemoryRetrievalParams, MemoryRetrievalResponse>;
4431
- /**
4432
- * Create factory function for memory tools
4433
- *
4434
- * @param memoryClient - Memory client instance (must be initialized with storeId)
4435
- */
4436
- declare function createMemoryTools(memoryClient: MemoryClient): {
4437
- forProfile: (profileId: string, conversationId?: string) => TACTool<MemoryRetrievalParams, MemoryRetrievalResponse>;
4438
- forSession: (profileId?: string, conversationId?: string) => TACTool<MemoryRetrievalParams, MemoryRetrievalResponse>;
4439
- };
4440
-
4441
- /**
4442
- * Parameters for send message tool
4443
- */
4444
- interface SendMessageParams {
4445
- message: string;
4446
- metadata?: Record<string, unknown>;
4447
- }
4448
- /**
4449
- * Result from send message tool
4450
- */
4451
- interface SendMessageResult {
4452
- success: boolean;
4453
- message_id?: string;
4454
- error?: string;
4455
- }
4456
- /**
4457
- * Create send message tool
4458
- */
4459
- declare function createSendMessageTool(channel: BaseChannel, conversationId: ConversationId): TACTool<SendMessageParams, SendMessageResult>;
4460
- /**
4461
- * Create factory function for messaging tools
4462
- */
4463
- declare function createMessagingTools(): {
4464
- forConversation: (channel: BaseChannel, conversationId: ConversationId) => TACTool<SendMessageParams, SendMessageResult>;
4465
- };
4466
-
4467
- /**
4468
- * Handoff tool for the Twilio Agent Connect.
4469
- *
4470
- * Generic Studio-backed handoff that routes a conversation to a human agent.
4471
- * Produces a structured HandoffPayload and delivers it as a Twilio Studio
4472
- * Execution (voice via `<Connect action>`, digital channels via direct POST).
4473
- */
4474
-
4475
- /**
4476
- * Build a HandoffPayload from session context and attributes.
4477
- *
4478
- * Useful for custom handoff tools that want TAC's payload shape without
4479
- * the Studio-specific delivery in `postStudioHandoff`.
4480
- */
4481
- declare function buildHandoffPayload(session: ConversationSession, memoryStoreId: string, attributes: Record<string, unknown>): HandoffPayload;
4482
- /**
4483
- * POST a handoff payload to a Twilio Studio Flow Executions endpoint.
4484
- *
4485
- * Emits the Twilio Studio Executions API wire format: form-encoded
4486
- * `To` / `From` / `Parameters` fields with HTTP Basic auth.
4487
- * `Parameters` is a JSON string keyed under `HandoffData` so Studio
4488
- * can reference it via `{{flow.data.HandoffData.*}}`.
4489
- */
4490
- declare function postStudioHandoff(payload: HandoffPayload, session: ConversationSession, options: {
4491
- handoffUrl: string;
4492
- fromAddress: string;
4493
- apiKey: string;
4494
- apiSecret: string;
4495
- }): Promise<void>;
4496
- /**
4497
- * Result returned by the handoff tool.
4498
- */
4499
- interface HandoffResult {
4500
- status: 'handoff_initiated' | 'handoff_failed';
4501
- channel: string;
4502
- error?: string;
4503
- }
4504
- interface HandoffParams {
4505
- reason: string;
4506
- }
4507
- /**
4508
- * Create a handoff tool that delivers in the Twilio Studio Executions API shape.
4509
- *
4510
- * The returned tool exposes only `handoff({ reason })` to the LLM. All other
4511
- * dependencies (TAC instance, session, static attributes) are captured in the
4512
- * closure.
4513
- *
4514
- * On digital channels, the tool POSTs to the Studio Flow Executions endpoint
4515
- * derived from `tac.getConfig().studioHandoffFlowSid`. For voice channels,
4516
- * the payload is stored on the session and the voice channel automatically
4517
- * sends the WS `end` message with `handoffData` after the LLM's final
4518
- * response is delivered.
4519
- *
4520
- * The tool also sets the conversation to INACTIVE and clears status callbacks
4521
- * to prevent further webhook events from being routed to TAC.
4522
- *
4523
- * **Not available in voice-only mode.** This tool requires Conversation
4524
- * Orchestrator for conversation state management and Conversation Memory for
4525
- * the handoff payload. In voice-only mode, implement your own handoff by
4526
- * setting `session.pendingHandoffData` directly — the voice channel will
4527
- * send the WS `end` message with your payload, and your `<Connect action>`
4528
- * URL handler can route the call accordingly.
4529
- *
4530
- * @throws Error if `tac.getConfig().studioHandoffFlowSid` is unset, if
4531
- * Conversation Orchestrator is not configured (voice-only mode), or if
4532
- * the memory store ID was not resolved at startup.
4533
- */
4534
- declare function createStudioHandoffTool(tac: TAC, session: ConversationSession, options?: {
4535
- attributes?: Record<string, unknown>;
4536
- name?: string;
4537
- description?: string;
4538
- }): TACTool<HandoffParams, HandoffResult>;
4539
-
4540
- /**
4541
- * Parameters for knowledge search tool (visible to LLM)
4542
- */
4543
- interface KnowledgeSearchParams {
4544
- query: string;
4545
- }
4546
- /**
4547
- * Configuration for knowledge search tool
4548
- */
4549
- interface KnowledgeToolConfig {
4550
- name?: string;
4551
- description?: string;
4552
- topK?: number;
4553
- }
4554
- /**
4555
- * Create knowledge search tool with explicit name and description
4556
- *
4557
- * @param knowledgeClient - The Knowledge client instance
4558
- * @param knowledgeBaseId - The knowledge base ID to search
4559
- * @param config - Configuration with required name and description
4560
- * @returns TACTool configured for knowledge search
4561
- */
4562
- declare function createKnowledgeSearchTool(knowledgeClient: KnowledgeClient, knowledgeBaseId: string, config: {
4563
- name: string;
4564
- description: string;
4565
- topK?: number;
4566
- }): TACTool<KnowledgeSearchParams, KnowledgeChunkResult[]>;
4567
- /**
4568
- * Create knowledge search tool with auto-fetched metadata from knowledge base
4569
- *
4570
- * This async version fetches the knowledge base metadata to auto-generate
4571
- * the tool name and description if not provided.
4572
- *
4573
- * @param knowledgeClient - The Knowledge client instance
4574
- * @param knowledgeBaseId - The knowledge base ID to search
4575
- * @param config - Optional configuration (name/description auto-generated if not provided)
4576
- * @returns Promise containing TACTool configured for knowledge search
4577
- */
4578
- declare function createKnowledgeSearchToolAsync(knowledgeClient: KnowledgeClient, knowledgeBaseId: string, config?: KnowledgeToolConfig): Promise<TACTool<KnowledgeSearchParams, KnowledgeChunkResult[]>>;
4579
- /**
4580
- * Create factory for knowledge tools
4581
- *
4582
- * @param knowledgeClient - The Knowledge client instance
4583
- * @returns Factory object with methods to create knowledge tools
4584
- */
4585
- declare function createKnowledgeTools(knowledgeClient: KnowledgeClient): {
4586
- forKnowledgeBase: (knowledgeBaseId: string, config: {
4587
- name: string;
4588
- description: string;
4589
- topK?: number;
4590
- }) => TACTool<KnowledgeSearchParams, KnowledgeChunkResult[]>;
4591
- forKnowledgeBaseAsync: (knowledgeBaseId: string, config?: KnowledgeToolConfig) => Promise<TACTool<KnowledgeSearchParams, KnowledgeChunkResult[]>>;
4592
- };
4593
-
4594
6266
  /**
4595
6267
  * Server configuration options
4596
6268
  */
@@ -4712,4 +6384,4 @@ declare class TACServer {
4712
6384
  stop(): Promise<void>;
4713
6385
  }
4714
6386
 
4715
- export { type ActionChannelSettings, ActionChannelSettingsSchema, type ActionParticipantRef, ActionParticipantRefSchema, type ActionResponse, ActionResponseSchema, type ActionTextContent, ActionTextContentSchema, type AdapterOptions, type AmdEvent, AmdEventSchema, type AmdHandler, type AnthropicTool, AnthropicToolSchema, type AuthorInfo, AuthorInfoSchema, BaseChannel, type BaseChannelEvents, type BaseChannelOptions, BaseClient, type BuiltInToolName, BuiltInTools, CALL_EVENT_KINDS, type CallEventKind, CallEventKindSchema, type CallOptions, CallOptionsSchema, type CallStatusEvent, CallStatusEventSchema, type CallStatusHandler, type CaptureRule, CaptureRuleSchema, type ChannelSettings, ChannelSettingsSchema, type ChannelType, ChannelTypeSchema, ChatChannel, type ChatChannelConfig, type CintelParticipant, CintelParticipantSchema, type Communication, type CommunicationContent, CommunicationContentSchema, type CommunicationParticipant, CommunicationParticipantSchema, CommunicationSchema, type ConversationAddress, ConversationAddressSchema, ConversationClient, type ConversationConfiguration, ConversationConfigurationSchema, type ConversationEndedCallback, type ConversationGroupingType, ConversationGroupingTypeSchema, type ConversationId, type ConversationIntelligenceConfig, ConversationIntelligenceConfigSchema, type ConversationParticipant, ConversationParticipantSchema, type ConversationRelayAttributes, ConversationRelayAttributesSchema, type ConversationRelayCallbackPayload, ConversationRelayCallbackPayloadSchema, type ConversationRelayConfig, ConversationRelayConfigSchema, type ConversationResponse, ConversationResponseSchema, type ConversationSession, ConversationSessionSchema, type ConversationSummaryItem, ConversationSummaryItemSchema, type ConversationWebhookPayload, type CreateConversationSummariesResponse, CreateConversationSummariesResponseSchema, type CreateObservationResponse, CreateObservationResponseSchema, type CreateObservationsRequest, CreateObservationsRequestSchema, type CustomParameters, CustomParametersSchema, type DtmfEvent, type DtmfHandler, type DtmfMessage, DtmfMessageSchema, EMPTY_MEMORY_RESPONSE, EnvironmentVariables, type ExecutionDetails, ExecutionDetailsSchema, type HandoffPayload, HandoffPayloadSchema, type HandoffResult, type InboundCallTwimlHandler, type InitiateChatConversationOptions, type InitiateConversationResult, type InitiateMessagingConversationOptions, InitiateMessagingConversationOptionsSchema, type InitiateVoiceConversationOptions, InitiateVoiceConversationOptionsSchema, type InitiateVoiceConversationResult, type IntelligenceConfiguration, IntelligenceConfigurationSchema, type InterruptCallback, type InterruptMessage, InterruptMessageSchema, type InterruptMode, InterruptModeSchema, type JSONSchema, JSONSchemaSchema, type KnowledgeBase, KnowledgeBaseSchema, type KnowledgeBaseStatus, KnowledgeBaseStatusSchema, type KnowledgeChunkResult, KnowledgeChunkResultSchema, KnowledgeClient, type KnowledgeSearchResponse, KnowledgeSearchResponseSchema, type LanguageAttributes, LanguageAttributesSchema, type LanguageConfig, LanguageConfigSchema, type ListCommunicationsResponse, ListCommunicationsResponseSchema, type ListConversationsResponse, ListConversationsResponseSchema, type ListParticipantsResponse, ListParticipantsResponseSchema, type Logger, type MemoryChannelType, MemoryChannelTypeSchema, MemoryClient, type MemoryCommunication, type MemoryCommunicationContent, MemoryCommunicationContentSchema, MemoryCommunicationSchema, type MemoryDeliveryStatus, MemoryDeliveryStatusSchema, type MemoryMode, MemoryModeSchema, type MemoryParticipant, MemoryParticipantSchema, type MemoryParticipantType, MemoryParticipantTypeSchema, MemoryPromptBuilder, type MemoryRetrievalRequest, MemoryRetrievalRequestSchema, type MemoryRetrievalResponse, MemoryRetrievalResponseSchema, type MessageDirection, MessageDirectionSchema, type MessageReadyCallback, MessagingChannel, type MessagingChannelConfig, type MessagingChannelEvents, type ObservationCreateRequest, ObservationCreateRequestSchema, type ObservationInfo, ObservationInfoSchema, type OpenAITool, OpenAIToolSchema, type Operator, type OperatorProcessingResult, OperatorProcessingResultSchema, type OperatorResult, type OperatorResultEvent, OperatorResultEventSchema, OperatorResultProcessor, OperatorResultSchema, OperatorSchema, type ParticipantAddress, ParticipantAddressSchema, type ParticipantAddressType, ParticipantAddressTypeSchema, type ParticipantId, type PendingHandoffData, PendingHandoffDataSchema, type Profile, type ProfileId, type ProfileLookupResponse, ProfileLookupResponseSchema, type ProfileResponse, ProfileResponseSchema, type PromptMessage, PromptMessageSchema, RCSChannel, type RecordingEvent, RecordingEventSchema, type RecordingHandler, SMSChannel, type SendMessageActionPayload, SendMessageActionPayloadSchema, type SendMessageActionRequest, SendMessageActionRequestSchema, type SessionInfo, SessionInfoSchema, type SessionMessage, SessionMessageSchema, type SetupMessage, SetupMessageSchema, type StatusCallback, StatusCallbackSchema, type StatusTimeouts, StatusTimeoutsSchema, type StreamTask, type SummaryInfo, SummaryInfoSchema, TAC, type TACChannelType, TACChannelTypeSchema, type TACCommunication, type TACCommunicationAuthor, TACCommunicationAuthorSchema, type TACCommunicationContent, TACCommunicationContentSchema, TACCommunicationSchema, TACConfig, type TACConfigData, TACConfigSchema, type TACDeliveryStatus, TACDeliveryStatusSchema, TACMemoryResponse, type TACOptions, type TACParticipantType, TACParticipantTypeSchema, TACServer, type TACServerConfig, TACTool, type TextTokenMessage, TextTokenMessageSchema, type ToolContext, type ToolExecutionResult, ToolExecutionResultSchema, type ToolFunction, type Transcription, TranscriptionSchema, type TranscriptionWord, TranscriptionWordSchema, type TwiMLOptions, TwiMLOptionsSchema, type TwiMLRequest, TwiMLRequestSchema, type TwilioMemoryConfig, TwilioMemoryConfigSchema, type TypedCallOptions, VoiceChannel, type VoiceChannelConfig, type VoiceChannelEvents, type WebSocketMessage, WebSocketMessageSchema, WhatsAppChannel, type _CallsCreateDriftGuards, type _SDKDriftGuards, amdEventFromForm, buildHandoffPayload, callOptionsToCreateParams, callStatusEventFromForm, createKnowledgeSearchTool, createKnowledgeSearchToolAsync, createKnowledgeTools, createLogger, createMemoryRetrievalTool, createMemoryTools, createMessagingTools, createSendMessageTool, createStudioHandoffTool, defineTool, isConversationId, isParticipantId, isProfileId, maskAddress, maskEmail, maskPhone, postStudioHandoff, recordingEventFromForm, redactTwimlParameters, scrubObject, scrubPii, studioExecutionsUrl, studioVoiceHandoffUrl, twiMLRequestFromForm };
6387
+ export { type ActionChannelSettings, ActionChannelSettingsSchema, type ActionParticipantRef, ActionParticipantRefSchema, type ActionResponse, ActionResponseSchema, type ActionTextContent, ActionTextContentSchema, type AdapterOptions, type AmdEvent, AmdEventSchema, type AmdHandler, type AnthropicTool, AnthropicToolSchema, type AuthorInfo, AuthorInfoSchema, BaseChannel, type BaseChannelEvents, type BaseChannelOptions, BaseClient, type BuildStreamTwiMLInputs, type BuiltInToolName, BuiltInTools, CALL_EVENT_KINDS, type CallEventKind, CallEventKindSchema, type CallOptions, CallOptionsSchema, type CallStatusEvent, CallStatusEventSchema, type CallStatusHandler, type CaptureRule, CaptureRuleSchema, type ChannelSettings, ChannelSettingsSchema, type ChannelType, ChannelTypeSchema, ChatChannel, type ChatChannelConfig, type CintelParticipant, CintelParticipantSchema, type Communication, type CommunicationContent, CommunicationContentSchema, type CommunicationParticipant, CommunicationParticipantSchema, CommunicationSchema, type ConversationAddress, ConversationAddressSchema, ConversationClient, type ConversationConfiguration, ConversationConfigurationSchema, type ConversationEndedCallback, type ConversationGroupingType, ConversationGroupingTypeSchema, type ConversationId, type ConversationIntelligenceConfig, ConversationIntelligenceConfigSchema, type ConversationParticipant, ConversationParticipantSchema, type ConversationRelayAttributes, ConversationRelayAttributesSchema, type ConversationRelayCallbackPayload, ConversationRelayCallbackPayloadSchema, type ConversationRelayConfig, ConversationRelayConfigSchema, ConversationRelayProvider, ConversationRelayProviderConfig, type ConversationRelayProviderConfigOptions, type ConversationResponse, ConversationResponseSchema, type ConversationSession, ConversationSessionSchema, type ConversationSummaryItem, ConversationSummaryItemSchema, type ConversationWebhookPayload, type CreateConversationSummariesResponse, CreateConversationSummariesResponseSchema, type CreateObservationResponse, CreateObservationResponseSchema, type CreateObservationsRequest, CreateObservationsRequestSchema, type CustomParameters, CustomParametersSchema, type DtmfEvent, type DtmfHandler, type DtmfMessage, DtmfMessageSchema, EMPTY_MEMORY_RESPONSE, EnvironmentVariables, type ExecutionDetails, ExecutionDetailsSchema, GPTLiveProvider, GPTLiveProviderConfig, type GPTLiveProviderConfigOptions, GPT_LIVE_SESSION_ID_METADATA_KEY, type HandoffPayload, HandoffPayloadSchema, type HandoffResult, type InboundCallTwimlHandler, type InitiateChatConversationOptions, type InitiateConversationResult, type InitiateMessagingConversationOptions, InitiateMessagingConversationOptionsSchema, type InitiateVoiceConversationOptions, type InitiateVoiceConversationOptionsGPTLive, InitiateVoiceConversationOptionsGPTLiveSchema, type InitiateVoiceConversationOptionsOpenAIRealtime, InitiateVoiceConversationOptionsOpenAIRealtimeSchema, InitiateVoiceConversationOptionsSchema, type InitiateVoiceConversationResult, type IntelligenceConfiguration, IntelligenceConfigurationSchema, type InterruptCallback, type InterruptMessage, InterruptMessageSchema, type InterruptMode, InterruptModeSchema, type JSONSchema, JSONSchemaSchema, type KnowledgeBase, KnowledgeBaseSchema, type KnowledgeBaseStatus, KnowledgeBaseStatusSchema, type KnowledgeChunkResult, KnowledgeChunkResultSchema, KnowledgeClient, type KnowledgeSearchResponse, KnowledgeSearchResponseSchema, type LanguageAttributes, LanguageAttributesSchema, type LanguageConfig, LanguageConfigSchema, type ListCommunicationsResponse, ListCommunicationsResponseSchema, type ListConversationsResponse, ListConversationsResponseSchema, type ListParticipantsResponse, ListParticipantsResponseSchema, type Logger, MediaStreamsOpenAICallState, MediaStreamsOpenAIProvider, MediaStreamsOpenAIProviderConfig, type MediaStreamsOpenAIProviderConfigOptions, MediaStreamsProviderConfig, type MediaStreamsProviderConfigOptions, type MediaStreamsTwiMLBuilderConfig, type MemoryChannelType, MemoryChannelTypeSchema, MemoryClient, type MemoryCommunication, type MemoryCommunicationContent, MemoryCommunicationContentSchema, MemoryCommunicationSchema, type MemoryDeliveryStatus, MemoryDeliveryStatusSchema, type MemoryMode, MemoryModeSchema, type MemoryParticipant, MemoryParticipantSchema, type MemoryParticipantType, MemoryParticipantTypeSchema, MemoryPromptBuilder, type MemoryRetrievalRequest, MemoryRetrievalRequestSchema, type MemoryRetrievalResponse, MemoryRetrievalResponseSchema, type MessageDirection, MessageDirectionSchema, type MessageReadyCallback, MessagingChannel, type MessagingChannelConfig, type MessagingChannelEvents, OPENAI_USER_AGENT, type ObservationCreateRequest, ObservationCreateRequestSchema, type ObservationInfo, ObservationInfoSchema, OpenAIRealtimeProvider, OpenAIRealtimeProviderConfig, type OpenAIRealtimeProviderConfigOptions, type OpenAIRealtimeTool, OpenAIRealtimeToolSchema, type OpenAITool, OpenAIToolSchema, type Operator, type OperatorProcessingResult, OperatorProcessingResultSchema, type OperatorResult, type OperatorResultEvent, OperatorResultEventSchema, OperatorResultProcessor, OperatorResultSchema, OperatorSchema, type ParticipantAddress, ParticipantAddressSchema, type ParticipantAddressType, ParticipantAddressTypeSchema, type ParticipantId, type PendingHandoffData, PendingHandoffDataSchema, type Profile, type ProfileId, type ProfileLookupResponse, ProfileLookupResponseSchema, type ProfileResponse, ProfileResponseSchema, type PromptMessage, PromptMessageSchema, RCSChannel, type RecordingEvent, RecordingEventSchema, type RecordingHandler, SMSChannel, type SendMessageActionPayload, SendMessageActionPayloadSchema, type SendMessageActionRequest, SendMessageActionRequestSchema, type SessionInfo, SessionInfoSchema, type SessionMessage, SessionMessageSchema, type SetupMessage, SetupMessageSchema, type StatusCallback, StatusCallbackSchema, type StatusTimeouts, StatusTimeoutsSchema, type StreamStartMessage, StreamStartMessageSchema, type StreamTask, type SummaryInfo, SummaryInfoSchema, TAC, type TACChannelType, TACChannelTypeSchema, type TACCommunication, type TACCommunicationAuthor, TACCommunicationAuthorSchema, type TACCommunicationContent, TACCommunicationContentSchema, TACCommunicationSchema, TACConfig, type TACConfigData, TACConfigSchema, type TACDeliveryStatus, TACDeliveryStatusSchema, TACMemoryResponse, type TACOptions, type TACParticipantType, TACParticipantTypeSchema, TACServer, type TACServerConfig, TACTool, TWILIO_AUDIO_FORMAT_FOR_GPT_LIVE, TWILIO_AUDIO_FORMAT_FOR_REALTIME, type TextTokenMessage, TextTokenMessageSchema, type ToolContext, type ToolExecutionResult, ToolExecutionResultSchema, type ToolFunction, type Transcription, TranscriptionSchema, type TranscriptionWord, TranscriptionWordSchema, TwiMLBuilderMediaStreams, type TwiMLOptions, TwiMLOptionsSchema, type TwiMLRequest, TwiMLRequestSchema, type TwilioMemoryConfig, TwilioMemoryConfigSchema, type TwilioProviderCallbackResponse, type TypedCallOptions, VoiceChannel, type VoiceChannelConfig, type VoiceChannelEvents, VoiceProvider, VoiceProviderConfig, type VoiceTwiMLOptions, type VoiceTwiMLOptionsConversationRelay, VoiceTwiMLOptionsConversationRelaySchema, type VoiceTwiMLOptionsMediaStreams, VoiceTwiMLOptionsMediaStreamsSchema, VoiceTwiMLOptionsSchema, type WebSocketMessage, WebSocketMessageSchema, WhatsAppChannel, type _CallsCreateDriftGuards, type _SDKDriftGuards, amdEventFromForm, buildHandoffPayload, callOptionsToCreateParams, callStatusEventFromForm, createKnowledgeSearchTool, createKnowledgeSearchToolAsync, createKnowledgeTools, createLogger, createMemoryRetrievalTool, createMemoryTools, createMessagingTools, createSendMessageTool, createStudioHandoffTool, defineTool, generateStreamTwiml, isConversationId, isParticipantId, isProfileId, maskAddress, maskEmail, maskPhone, postStudioHandoff, recordingEventFromForm, redactTwimlParameters, scrubObject, scrubPii, shutdownAnalytics, studioExecutionsUrl, studioVoiceHandoffUrl, trackEvent, twiMLRequestFromForm };