nexus-ai-pro 1.1.0 → 1.2.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/CHANGELOG.md +25 -0
- package/README.md +81 -2
- package/dist/core/nexus.d.ts +22 -1
- package/dist/core/nexus.js +31 -0
- package/dist/telephony/index.d.ts +3 -1
- package/dist/telephony/index.js +1 -0
- package/dist/telephony/manager.d.ts +6 -1
- package/dist/telephony/manager.js +59 -0
- package/dist/telephony/providers/twilio.d.ts +9 -1
- package/dist/telephony/providers/twilio.js +165 -12
- package/dist/telephony/realtime-bridge.d.ts +58 -0
- package/dist/telephony/realtime-bridge.js +180 -0
- package/dist/types/telephony.d.ts +87 -0
- package/dist-cjs/agent/loop.js +93 -0
- package/dist-cjs/agent/tool.js +50 -0
- package/dist-cjs/cache/adapters.js +84 -0
- package/dist-cjs/cache/memory-cache.js +106 -0
- package/dist-cjs/cache/semantic-cache.js +75 -0
- package/dist-cjs/connectors/web.js +401 -0
- package/dist-cjs/context/index.js +5 -0
- package/dist-cjs/context/window.js +284 -0
- package/dist-cjs/core/config-builder.js +203 -0
- package/dist-cjs/core/create-nexus.js +184 -0
- package/dist-cjs/core/nexus.js +897 -0
- package/dist-cjs/core/response-format.js +109 -0
- package/dist-cjs/core/secure-stream.js +153 -0
- package/dist-cjs/core/streaming.js +35 -0
- package/dist-cjs/embeddings/providers.js +65 -0
- package/dist-cjs/evals/index.js +24 -0
- package/dist-cjs/evals/judge.js +158 -0
- package/dist-cjs/evals/metrics.js +171 -0
- package/dist-cjs/evals/runner.js +64 -0
- package/dist-cjs/hallucination/consistency.js +92 -0
- package/dist-cjs/hallucination/factual.js +37 -0
- package/dist-cjs/hallucination/knowledge-graph.js +74 -0
- package/dist-cjs/hallucination/rag.js +53 -0
- package/dist-cjs/hallucination/retrieval.js +78 -0
- package/dist-cjs/hallucination/verification.js +139 -0
- package/dist-cjs/images/assets.js +351 -0
- package/dist-cjs/images/errors.js +90 -0
- package/dist-cjs/images/index.js +14 -0
- package/dist-cjs/images/manager.js +578 -0
- package/dist-cjs/images/mock.js +241 -0
- package/dist-cjs/images/openai.js +360 -0
- package/dist-cjs/index.js +230 -0
- package/dist-cjs/jobs/batch.js +35 -0
- package/dist-cjs/jobs/durable-adapters.js +55 -0
- package/dist-cjs/jobs/queue.js +61 -0
- package/dist-cjs/models/registry.js +52 -0
- package/dist-cjs/next/route-handler.js +38 -0
- package/dist-cjs/ops/audit-logger.js +51 -0
- package/dist-cjs/ops/health.js +71 -0
- package/dist-cjs/ops/metrics.js +131 -0
- package/dist-cjs/ops/otel-tracing.js +54 -0
- package/dist-cjs/ops/rate-limiter.js +38 -0
- package/dist-cjs/optimizer/budget.js +93 -0
- package/dist-cjs/optimizer/cost.js +37 -0
- package/dist-cjs/optimizer/densifier.js +84 -0
- package/dist-cjs/optimizer/index.js +65 -0
- package/dist-cjs/package.json +3 -0
- package/dist-cjs/pipeline/pipeline.js +111 -0
- package/dist-cjs/pipeline/types.js +2 -0
- package/dist-cjs/providers/anthropic.js +253 -0
- package/dist-cjs/providers/azure-openai.js +30 -0
- package/dist-cjs/providers/base.js +172 -0
- package/dist-cjs/providers/cohere.js +123 -0
- package/dist-cjs/providers/deepseek.js +18 -0
- package/dist-cjs/providers/errors.js +141 -0
- package/dist-cjs/providers/google.js +251 -0
- package/dist-cjs/providers/groq.js +54 -0
- package/dist-cjs/providers/llamacpp.js +19 -0
- package/dist-cjs/providers/lmstudio.js +19 -0
- package/dist-cjs/providers/mistral.js +54 -0
- package/dist-cjs/providers/ollama.js +163 -0
- package/dist-cjs/providers/openai.js +474 -0
- package/dist-cjs/providers/openrouter.js +23 -0
- package/dist-cjs/providers/type-guards.js +43 -0
- package/dist-cjs/rag/file-ingestion.js +60 -0
- package/dist-cjs/rag/ingestion.js +52 -0
- package/dist-cjs/realtime/conversation.js +414 -0
- package/dist-cjs/realtime/errors.js +79 -0
- package/dist-cjs/realtime/events.js +43 -0
- package/dist-cjs/realtime/id.js +12 -0
- package/dist-cjs/realtime/index.js +41 -0
- package/dist-cjs/realtime/mock.transport.js +173 -0
- package/dist-cjs/realtime/openai-events.js +409 -0
- package/dist-cjs/realtime/openai-server.js +143 -0
- package/dist-cjs/realtime/openai-webrtc.transport.js +875 -0
- package/dist-cjs/realtime/openai-websocket.transport.js +424 -0
- package/dist-cjs/realtime/provider.js +64 -0
- package/dist-cjs/realtime/session.js +967 -0
- package/dist-cjs/realtime/tools.js +364 -0
- package/dist-cjs/realtime/transport-utils.js +259 -0
- package/dist-cjs/realtime/types.js +2 -0
- package/dist-cjs/router/auto-router.js +255 -0
- package/dist-cjs/router/failover.js +382 -0
- package/dist-cjs/router/index.js +36 -0
- package/dist-cjs/router/rules-router.js +72 -0
- package/dist-cjs/router/types.js +2 -0
- package/dist-cjs/security/index.js +223 -0
- package/dist-cjs/security/injection-calibration.js +56 -0
- package/dist-cjs/security/injection-detector.js +84 -0
- package/dist-cjs/security/input-guard.js +127 -0
- package/dist-cjs/security/output-guard.js +238 -0
- package/dist-cjs/security/pii-detector.js +70 -0
- package/dist-cjs/security/policies.js +65 -0
- package/dist-cjs/security/prompt-hardening.js +22 -0
- package/dist-cjs/security/schema-validator.js +69 -0
- package/dist-cjs/security/semantic-injection-classifier.js +79 -0
- package/dist-cjs/security/upload-scanner.js +79 -0
- package/dist-cjs/telephony/errors.js +21 -0
- package/dist-cjs/telephony/index.js +13 -0
- package/dist-cjs/telephony/manager.js +141 -0
- package/dist-cjs/telephony/providers/twilio.js +435 -0
- package/dist-cjs/telephony/realtime-bridge.js +184 -0
- package/dist-cjs/telephony/twiml.js +90 -0
- package/dist-cjs/testing/image-provider-conformance.js +328 -0
- package/dist-cjs/testing/provider-conformance.js +158 -0
- package/dist-cjs/types/agent.js +2 -0
- package/dist-cjs/types/config.js +2 -0
- package/dist-cjs/types/context-window.js +2 -0
- package/dist-cjs/types/images.js +2 -0
- package/dist-cjs/types/messages.js +2 -0
- package/dist-cjs/types/optimizer.js +2 -0
- package/dist-cjs/types/planning.js +2 -0
- package/dist-cjs/types/providers.js +604 -0
- package/dist-cjs/types/response.js +2 -0
- package/dist-cjs/types/security.js +2 -0
- package/dist-cjs/types/telephony.js +2 -0
- package/dist-cjs/types/voice.js +2 -0
- package/dist-cjs/utils/ids.js +7 -0
- package/dist-cjs/utils/logger.js +71 -0
- package/dist-cjs/utils/tokenizer.js +45 -0
- package/dist-cjs/voice/errors.js +21 -0
- package/dist-cjs/voice/index.js +10 -0
- package/dist-cjs/voice/manager.js +110 -0
- package/dist-cjs/voice/providers/openai.js +287 -0
- package/dist-cjs/voice/session.js +230 -0
- package/dist-cjs/workflow/chains.js +135 -0
- package/dist-cjs/workflow/domain.js +118 -0
- package/package.json +121 -59
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.2.0] - 2026-08-10
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- A `telephony/realtime-bridge` subpath joining a provider media stream to a realtime session:
|
|
12
|
+
inbound-only caller audio forwarding, outbound audio framing in emission order, barge-in that clears
|
|
13
|
+
provider-queued audio before cancelling the response, playback marks that resolve
|
|
14
|
+
`markAudioPlayed()`, stream lifecycle, and custom stream parameters for tenant routing.
|
|
15
|
+
- `twilioRealtimeAudioOptions()` pinning both directions to 8 kHz G.711 mu-law so telephony audio
|
|
16
|
+
reaches the model without transcoding.
|
|
17
|
+
- Call control and usage metering on `TelephonyProvider` and the Twilio adapter: `getCall`, `endCall`,
|
|
18
|
+
and `parseStatusCallback`, surfaced on `NexusAI` as `getCall`, `endCall`, and
|
|
19
|
+
`parseTelephonyStatusCallback`. Status callbacks carry the authoritative billable call duration.
|
|
20
|
+
- Phone-number management on `TelephonyProvider` and the Twilio adapter: `listPhoneNumbers` and
|
|
21
|
+
`updatePhoneNumber`, so an app can point a number at its own voice webhook and status callback.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- The package now ships parallel ESM and CommonJS builds. Every export subpath gained a `require`
|
|
26
|
+
condition, so NestJS and other `"module": "commonjs"` consumers can load it without a dynamic-import
|
|
27
|
+
shim. Types stay shared between both builds, and the packed-size guards were raised to account for the
|
|
28
|
+
duplicated JS payload.
|
|
29
|
+
|
|
30
|
+
## [1.1.0] - 2026-07-28
|
|
31
|
+
|
|
7
32
|
### Added
|
|
8
33
|
|
|
9
34
|
- An experimental provider-neutral image operation family with portable asset locations,
|
package/README.md
CHANGED
|
@@ -15,7 +15,9 @@ Use the whole pipeline for production AI features, or turn pieces off when you o
|
|
|
15
15
|
npm install nexus-ai-pro
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Requires Node.js 22 or newer.
|
|
18
|
+
Requires Node.js 22 or newer. The package is authored as ESM and ships both an ESM and a CommonJS build,
|
|
19
|
+
so `import` and `require()` both work — including from NestJS and other apps compiled with
|
|
20
|
+
`"module": "commonjs"`. Types are shared between both builds.
|
|
19
21
|
The main Nexus pipeline is Node-oriented; the isolated realtime subpaths use portable structural
|
|
20
22
|
interfaces and can be bundled for modern browsers.
|
|
21
23
|
|
|
@@ -791,6 +793,80 @@ const call = await ai.createCall({
|
|
|
791
793
|
|
|
792
794
|
For smaller apps, skip Twilio entirely and register your own `TelephonyProvider`.
|
|
793
795
|
|
|
796
|
+
### Phone Agents: Bridging a Call to a Realtime Session
|
|
797
|
+
|
|
798
|
+
A phone agent needs the telephony media stream and a realtime session joined together. The bridge owns
|
|
799
|
+
that audio path — caller audio in, assistant audio out, barge-in, playback marks, and stream lifecycle —
|
|
800
|
+
while session configuration, tools, and authorization stay in application code.
|
|
801
|
+
|
|
802
|
+
```ts
|
|
803
|
+
import { createTelephonyRealtimeBridge, twilioRealtimeAudioOptions } from 'nexus-ai-pro/telephony/realtime-bridge';
|
|
804
|
+
import { OpenAIWebSocketTransport } from 'nexus-ai-pro/realtime/openai-websocket';
|
|
805
|
+
import { createRealtimeSession } from 'nexus-ai-pro/realtime/session';
|
|
806
|
+
|
|
807
|
+
// One socket per call, from your WebSocket server.
|
|
808
|
+
function handleCallSocket(socket: AppWebSocket) {
|
|
809
|
+
const session = createRealtimeSession({
|
|
810
|
+
provider: 'openai',
|
|
811
|
+
model: 'gpt-realtime',
|
|
812
|
+
transport: new OpenAIWebSocketTransport({ apiKey: process.env.OPENAI_API_KEY!, webSocketFactory }),
|
|
813
|
+
modalities: ['audio'],
|
|
814
|
+
instructions: 'You are a scheduling assistant.',
|
|
815
|
+
tools: [checkAvailability, createBooking],
|
|
816
|
+
// Telephony audio is 8 kHz G.711 mu-law; this passes it through untranscoded.
|
|
817
|
+
audio: twilioRealtimeAudioOptions({ output: { voice: 'alloy' } }),
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
const bridge = createTelephonyRealtimeBridge({
|
|
821
|
+
session,
|
|
822
|
+
telephony,
|
|
823
|
+
send: (message) => socket.send(message.body),
|
|
824
|
+
onStart: (event) => logger.info(`call ${event.callId} started for ${event.parameters?.tenant}`),
|
|
825
|
+
onStop: () => persistTranscript(session.export('json')),
|
|
826
|
+
onError: (error) => logger.error(error),
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
socket.on('message', (raw) => bridge.handleMessage(String(raw)));
|
|
830
|
+
socket.on('close', () => bridge.close());
|
|
831
|
+
}
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
The bridge feeds only inbound caller audio to the model, so an echoed outbound track never loops back.
|
|
835
|
+
On caller speech it clears audio already queued at the provider and cancels the in-flight response, which
|
|
836
|
+
is what stops the assistant talking over an interruption. Custom `<Parameter>` values from the stream
|
|
837
|
+
instruction are exposed as `bridge.parameters` for tenant and worker routing.
|
|
838
|
+
|
|
839
|
+
Pass `bargeIn: false` to keep queued audio playing, `autoConnect: false` to connect the session yourself,
|
|
840
|
+
and `await bridge.flush()` when you need outstanding sends to settle.
|
|
841
|
+
|
|
842
|
+
### Call Control and Usage Metering
|
|
843
|
+
|
|
844
|
+
Meter billable time from the provider's own record rather than from stream lifecycle events, which do not
|
|
845
|
+
account for ring time or provider-side teardown:
|
|
846
|
+
|
|
847
|
+
```ts
|
|
848
|
+
// From your status webhook.
|
|
849
|
+
const status = ai.parseTelephonyStatusCallback('twilio', requestBody);
|
|
850
|
+
if (status?.status === 'completed') {
|
|
851
|
+
await usage.recordCallMinutes({ callId: status.callId, seconds: status.durationSeconds ?? 0 });
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Or read it directly, and hang up from a tool.
|
|
855
|
+
const details = await ai.getCall({ callId });
|
|
856
|
+
await ai.endCall({ callId });
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
### Pointing a Number at Your Webhook
|
|
860
|
+
|
|
861
|
+
```ts
|
|
862
|
+
const [number] = await ai.listPhoneNumbers({ phoneNumber: '+15557650000' });
|
|
863
|
+
await ai.updatePhoneNumber({
|
|
864
|
+
id: number.id,
|
|
865
|
+
voiceUrl: 'https://api.example.com/voice/incoming',
|
|
866
|
+
statusCallbackUrl: 'https://api.example.com/voice/status',
|
|
867
|
+
});
|
|
868
|
+
```
|
|
869
|
+
|
|
794
870
|
## Security
|
|
795
871
|
|
|
796
872
|
Security is enabled by default with `standard` mode.
|
|
@@ -955,9 +1031,12 @@ import { MockImageProvider } from 'nexus-ai-pro/images/mock';
|
|
|
955
1031
|
import { OpenAIImageProvider } from 'nexus-ai-pro/images/openai';
|
|
956
1032
|
import { createRealtimeSession } from 'nexus-ai-pro/realtime/session';
|
|
957
1033
|
import { OpenAIWebRTCTransport } from 'nexus-ai-pro/realtime/openai-webrtc';
|
|
1034
|
+
import { TelephonyManager } from 'nexus-ai-pro/telephony';
|
|
1035
|
+
import { TwilioTelephonyProvider } from 'nexus-ai-pro/telephony/twilio';
|
|
1036
|
+
import { createTelephonyRealtimeBridge } from 'nexus-ai-pro/telephony/realtime-bridge';
|
|
958
1037
|
```
|
|
959
1038
|
|
|
960
|
-
Provider SDKs are optional peer dependencies. The package
|
|
1039
|
+
Provider SDKs are optional peer dependencies. The package ships ESM and CommonJS builds, supports Node.js 22+, and is marked with `sideEffects: false`. Only the entry points listed in the package export map are public; deep imports into `dist`, `dist-cjs`, or `src` are unsupported.
|
|
961
1040
|
|
|
962
1041
|
## Examples
|
|
963
1042
|
|
package/dist/core/nexus.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { NexusResponse, NexusStream } from '../types/response.js';
|
|
|
5
5
|
import type { AgentConfig, AgentResult } from '../types/agent.js';
|
|
6
6
|
import type { SpeechRequest, SpeechResponse, TranscriptionRequest, TranscriptionResponse, VoiceProvider, VoiceSessionConfig, VoiceTurnRequest, VoiceTurnResponse } from '../types/voice.js';
|
|
7
7
|
import type { ImageProvider } from '../types/images.js';
|
|
8
|
-
import type { CreateCallRequest, CreateCallResponse, TelephonyMediaStreamEvent, TelephonyOutboundAudioMessage, TelephonyProvider, TelephonyResponseRequest, TelephonyWebhookResponse, TelephonyWebhookValidationRequest } from '../types/telephony.js';
|
|
8
|
+
import type { CreateCallRequest, CreateCallResponse, EndCallRequest, GetCallRequest, ListPhoneNumbersRequest, TelephonyCallDetails, TelephonyMediaStreamEvent, TelephonyOutboundAudioMessage, TelephonyPhoneNumber, TelephonyProvider, TelephonyResponseRequest, TelephonyStatusCallback, TelephonyWebhookResponse, TelephonyWebhookValidationRequest, UpdatePhoneNumberRequest } from '../types/telephony.js';
|
|
9
9
|
import type { PipelineStep } from '../pipeline/types.js';
|
|
10
10
|
import type { BaseProvider } from '../providers/base.js';
|
|
11
11
|
import { type VoiceSession } from '../voice/index.js';
|
|
@@ -108,6 +108,27 @@ export declare class NexusAI {
|
|
|
108
108
|
event?: 'media' | 'mark' | 'clear';
|
|
109
109
|
markName?: string;
|
|
110
110
|
}): TelephonyOutboundAudioMessage;
|
|
111
|
+
/**
|
|
112
|
+
* Reads a call's current provider-side state, including the duration usage metering bills on.
|
|
113
|
+
*/
|
|
114
|
+
getCall(request: GetCallRequest): Promise<TelephonyCallDetails>;
|
|
115
|
+
/**
|
|
116
|
+
* Hangs up a live call through the provider's call-control API.
|
|
117
|
+
*/
|
|
118
|
+
endCall(request: EndCallRequest): Promise<TelephonyCallDetails>;
|
|
119
|
+
/**
|
|
120
|
+
* Parses a provider status webhook into a normalized record. Prefer this over media-stream
|
|
121
|
+
* lifecycle events when metering usage.
|
|
122
|
+
*/
|
|
123
|
+
parseTelephonyStatusCallback(providerName: string, body: string | URLSearchParams | Record<string, string | number | boolean | undefined>): TelephonyStatusCallback | undefined;
|
|
124
|
+
/**
|
|
125
|
+
* Lists phone numbers owned by the provider account.
|
|
126
|
+
*/
|
|
127
|
+
listPhoneNumbers(request?: ListPhoneNumbersRequest): Promise<TelephonyPhoneNumber[]>;
|
|
128
|
+
/**
|
|
129
|
+
* Points a phone number at a voice webhook and/or status callback.
|
|
130
|
+
*/
|
|
131
|
+
updatePhoneNumber(request: UpdatePhoneNumberRequest): Promise<TelephonyPhoneNumber>;
|
|
111
132
|
/**
|
|
112
133
|
* Registers a custom text provider at runtime.
|
|
113
134
|
*/
|
package/dist/core/nexus.js
CHANGED
|
@@ -443,6 +443,37 @@ export class NexusAI {
|
|
|
443
443
|
formatTelephonyAudioMessage(providerName, streamId, payload, options) {
|
|
444
444
|
return this.telephonyManager.formatAudioMessage(providerName, streamId, payload, options);
|
|
445
445
|
}
|
|
446
|
+
/**
|
|
447
|
+
* Reads a call's current provider-side state, including the duration usage metering bills on.
|
|
448
|
+
*/
|
|
449
|
+
async getCall(request) {
|
|
450
|
+
return this.telephonyManager.getCall(request);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Hangs up a live call through the provider's call-control API.
|
|
454
|
+
*/
|
|
455
|
+
async endCall(request) {
|
|
456
|
+
return this.telephonyManager.endCall(request);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Parses a provider status webhook into a normalized record. Prefer this over media-stream
|
|
460
|
+
* lifecycle events when metering usage.
|
|
461
|
+
*/
|
|
462
|
+
parseTelephonyStatusCallback(providerName, body) {
|
|
463
|
+
return this.telephonyManager.parseStatusCallback(providerName, body);
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Lists phone numbers owned by the provider account.
|
|
467
|
+
*/
|
|
468
|
+
async listPhoneNumbers(request) {
|
|
469
|
+
return this.telephonyManager.listPhoneNumbers(request);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Points a phone number at a voice webhook and/or status callback.
|
|
473
|
+
*/
|
|
474
|
+
async updatePhoneNumber(request) {
|
|
475
|
+
return this.telephonyManager.updatePhoneNumber(request);
|
|
476
|
+
}
|
|
446
477
|
/**
|
|
447
478
|
* Registers a custom text provider at runtime.
|
|
448
479
|
*/
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { TelephonyManager } from './manager.js';
|
|
2
2
|
export { TelephonyProviderError, TelephonyCapabilityError } from './errors.js';
|
|
3
3
|
export { createVoiceTwiML } from './twiml.js';
|
|
4
|
-
export
|
|
4
|
+
export { createTelephonyRealtimeBridge, twilioRealtimeAudioOptions } from './realtime-bridge.js';
|
|
5
|
+
export type { TelephonyRealtimeBridge, TelephonyRealtimeBridgeOptions } from './realtime-bridge.js';
|
|
6
|
+
export type { CreateCallRequest, CreateCallResponse, EndCallRequest, GetCallRequest, ListPhoneNumbersRequest, TelephonyAudioEncoding, TelephonyCallDetails, TelephonyCallDirection, TelephonyCallStatus, TelephonyConfig, TelephonyConnectedEvent, TelephonyDtmfEvent, TelephonyGatherConfig, TelephonyHttpMethod, TelephonyMarkEvent, TelephonyMediaEvent, TelephonyMediaStreamEvent, TelephonyOutboundAudioMessage, TelephonyPhoneNumber, TelephonyProvider, TelephonyProviderInfo, TelephonyResponseRequest, TelephonyStartEvent, TelephonyStatusCallback, TelephonyStopEvent, TelephonyStreamConfig, TelephonyStreamMode, TelephonyStreamTrack, TelephonyWebhookResponse, TelephonyWebhookValidationRequest, UpdatePhoneNumberRequest, } from '../types/telephony.js';
|
package/dist/telephony/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { TelephonyManager } from './manager.js';
|
|
2
2
|
export { TelephonyProviderError, TelephonyCapabilityError } from './errors.js';
|
|
3
3
|
export { createVoiceTwiML } from './twiml.js';
|
|
4
|
+
export { createTelephonyRealtimeBridge, twilioRealtimeAudioOptions } from './realtime-bridge.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CreateCallRequest, CreateCallResponse, TelephonyConfig, TelephonyMediaStreamEvent, TelephonyOutboundAudioMessage, TelephonyProvider, TelephonyResponseRequest, TelephonyWebhookResponse, TelephonyWebhookValidationRequest } from '../types/telephony.js';
|
|
1
|
+
import type { CreateCallRequest, CreateCallResponse, EndCallRequest, GetCallRequest, ListPhoneNumbersRequest, TelephonyCallDetails, TelephonyConfig, TelephonyMediaStreamEvent, TelephonyOutboundAudioMessage, TelephonyPhoneNumber, TelephonyProvider, TelephonyResponseRequest, TelephonyStatusCallback, TelephonyWebhookResponse, TelephonyWebhookValidationRequest, UpdatePhoneNumberRequest } from '../types/telephony.js';
|
|
2
2
|
export declare class TelephonyManager {
|
|
3
3
|
private config;
|
|
4
4
|
private providers;
|
|
@@ -9,6 +9,11 @@ export declare class TelephonyManager {
|
|
|
9
9
|
createCall(request: CreateCallRequest): Promise<CreateCallResponse>;
|
|
10
10
|
createWebhookResponse(request: TelephonyResponseRequest): Promise<TelephonyWebhookResponse>;
|
|
11
11
|
validateWebhook(request: TelephonyWebhookValidationRequest): Promise<boolean>;
|
|
12
|
+
getCall(request: GetCallRequest): Promise<TelephonyCallDetails>;
|
|
13
|
+
endCall(request: EndCallRequest): Promise<TelephonyCallDetails>;
|
|
14
|
+
parseStatusCallback(providerName: string, body: string | URLSearchParams | Record<string, string | number | boolean | undefined>): TelephonyStatusCallback | undefined;
|
|
15
|
+
listPhoneNumbers(request?: ListPhoneNumbersRequest): Promise<TelephonyPhoneNumber[]>;
|
|
16
|
+
updatePhoneNumber(request: UpdatePhoneNumberRequest): Promise<TelephonyPhoneNumber>;
|
|
12
17
|
parseMediaStreamEvent(providerName: string, message: string | Record<string, unknown>): TelephonyMediaStreamEvent | undefined;
|
|
13
18
|
formatAudioMessage(providerName: string, streamId: string, payload: string, options?: {
|
|
14
19
|
event?: 'media' | 'mark' | 'clear';
|
|
@@ -50,6 +50,65 @@ export class TelephonyManager {
|
|
|
50
50
|
throw new TelephonyCapabilityError(provider.info.name, 'webhook validation');
|
|
51
51
|
return provider.validateWebhook(request);
|
|
52
52
|
}
|
|
53
|
+
async getCall(request) {
|
|
54
|
+
const provider = this.resolveProvider('getCall', request.provider || this.config.defaultProvider);
|
|
55
|
+
if (!provider.getCall)
|
|
56
|
+
throw new TelephonyCapabilityError(provider.info.name, 'call lookup');
|
|
57
|
+
try {
|
|
58
|
+
return await provider.getCall(request);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (error instanceof TelephonyProviderError)
|
|
62
|
+
throw error;
|
|
63
|
+
throw new TelephonyProviderError(`Telephony call lookup failed for provider "${provider.info.name}"`, provider.info.name, error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async endCall(request) {
|
|
67
|
+
const provider = this.resolveProvider('endCall', request.provider || this.config.defaultProvider);
|
|
68
|
+
if (!provider.endCall)
|
|
69
|
+
throw new TelephonyCapabilityError(provider.info.name, 'call control');
|
|
70
|
+
try {
|
|
71
|
+
return await provider.endCall(request);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error instanceof TelephonyProviderError)
|
|
75
|
+
throw error;
|
|
76
|
+
throw new TelephonyProviderError(`Telephony call hangup failed for provider "${provider.info.name}"`, provider.info.name, error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
parseStatusCallback(providerName, body) {
|
|
80
|
+
const provider = this.resolveProvider('parseStatusCallback', providerName);
|
|
81
|
+
if (!provider.parseStatusCallback) {
|
|
82
|
+
throw new TelephonyCapabilityError(provider.info.name, 'status callback parsing');
|
|
83
|
+
}
|
|
84
|
+
return provider.parseStatusCallback(body);
|
|
85
|
+
}
|
|
86
|
+
async listPhoneNumbers(request = {}) {
|
|
87
|
+
const provider = this.resolveProvider('listPhoneNumbers', request.provider || this.config.defaultProvider);
|
|
88
|
+
if (!provider.listPhoneNumbers)
|
|
89
|
+
throw new TelephonyCapabilityError(provider.info.name, 'phone number listing');
|
|
90
|
+
try {
|
|
91
|
+
return await provider.listPhoneNumbers(request);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (error instanceof TelephonyProviderError)
|
|
95
|
+
throw error;
|
|
96
|
+
throw new TelephonyProviderError(`Telephony phone number listing failed for provider "${provider.info.name}"`, provider.info.name, error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async updatePhoneNumber(request) {
|
|
100
|
+
const provider = this.resolveProvider('updatePhoneNumber', request.provider || this.config.defaultProvider);
|
|
101
|
+
if (!provider.updatePhoneNumber)
|
|
102
|
+
throw new TelephonyCapabilityError(provider.info.name, 'phone number updates');
|
|
103
|
+
try {
|
|
104
|
+
return await provider.updatePhoneNumber(request);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (error instanceof TelephonyProviderError)
|
|
108
|
+
throw error;
|
|
109
|
+
throw new TelephonyProviderError(`Telephony phone number update failed for provider "${provider.info.name}"`, provider.info.name, error);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
53
112
|
parseMediaStreamEvent(providerName, message) {
|
|
54
113
|
const provider = this.resolveProvider('parseMediaStreamEvent', providerName);
|
|
55
114
|
if (!provider.parseMediaStreamEvent)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CreateCallRequest, CreateCallResponse, TelephonyMediaStreamEvent, TelephonyOutboundAudioMessage, TelephonyProvider, TelephonyProviderInfo, TelephonyResponseRequest, TelephonyWebhookResponse, TelephonyWebhookValidationRequest } from '../../types/telephony.js';
|
|
1
|
+
import type { CreateCallRequest, CreateCallResponse, EndCallRequest, GetCallRequest, ListPhoneNumbersRequest, TelephonyCallDetails, TelephonyMediaStreamEvent, TelephonyOutboundAudioMessage, TelephonyPhoneNumber, TelephonyProvider, TelephonyProviderInfo, TelephonyResponseRequest, TelephonyStatusCallback, TelephonyWebhookResponse, TelephonyWebhookValidationRequest, UpdatePhoneNumberRequest } from '../../types/telephony.js';
|
|
2
2
|
export interface TwilioTelephonyProviderConfig {
|
|
3
3
|
accountSid?: string;
|
|
4
4
|
authToken?: string;
|
|
@@ -12,11 +12,19 @@ export declare class TwilioTelephonyProvider implements TelephonyProvider {
|
|
|
12
12
|
createCall(request: CreateCallRequest): Promise<CreateCallResponse>;
|
|
13
13
|
createWebhookResponse(request: TelephonyResponseRequest): Promise<TelephonyWebhookResponse>;
|
|
14
14
|
validateWebhook(request: TelephonyWebhookValidationRequest): boolean;
|
|
15
|
+
getCall(request: GetCallRequest): Promise<TelephonyCallDetails>;
|
|
16
|
+
endCall(request: EndCallRequest): Promise<TelephonyCallDetails>;
|
|
17
|
+
parseStatusCallback(body: string | URLSearchParams | Record<string, string | number | boolean | undefined>): TelephonyStatusCallback | undefined;
|
|
18
|
+
listPhoneNumbers(request?: ListPhoneNumbersRequest): Promise<TelephonyPhoneNumber[]>;
|
|
19
|
+
updatePhoneNumber(request: UpdatePhoneNumberRequest): Promise<TelephonyPhoneNumber>;
|
|
15
20
|
parseMediaStreamEvent(message: string | Record<string, unknown>): TelephonyMediaStreamEvent | undefined;
|
|
16
21
|
formatAudioMessage(streamId: string, payload: string, options?: {
|
|
17
22
|
event?: 'media' | 'mark' | 'clear';
|
|
18
23
|
markName?: string;
|
|
19
24
|
}): TelephonyOutboundAudioMessage;
|
|
25
|
+
private request;
|
|
26
|
+
private toCallDetails;
|
|
27
|
+
private toPhoneNumber;
|
|
20
28
|
private requireConfig;
|
|
21
29
|
private fetchImpl;
|
|
22
30
|
private baseUrl;
|
|
@@ -12,14 +12,14 @@ export class TwilioTelephonyProvider {
|
|
|
12
12
|
mediaStreams: true,
|
|
13
13
|
bidirectionalStreams: true,
|
|
14
14
|
webhookValidation: true,
|
|
15
|
+
callControl: true,
|
|
16
|
+
phoneNumbers: true,
|
|
15
17
|
},
|
|
16
18
|
};
|
|
17
19
|
constructor(config = {}) {
|
|
18
20
|
this.config = config;
|
|
19
21
|
}
|
|
20
22
|
async createCall(request) {
|
|
21
|
-
const accountSid = this.requireConfig('accountSid');
|
|
22
|
-
const authToken = this.requireConfig('authToken');
|
|
23
23
|
const body = new URLSearchParams();
|
|
24
24
|
body.set('To', request.to);
|
|
25
25
|
body.set('From', request.from);
|
|
@@ -49,20 +49,12 @@ export class TwilioTelephonyProvider {
|
|
|
49
49
|
if (!body.has('Url') && !body.has('Twiml') && !body.has('ApplicationSid')) {
|
|
50
50
|
throw new TelephonyProviderError('Twilio calls require webhookUrl, twiml, mediaStreamUrl, or applicationSid', 'twilio');
|
|
51
51
|
}
|
|
52
|
-
const
|
|
52
|
+
const raw = await this.request('/Calls.json', {
|
|
53
53
|
method: 'POST',
|
|
54
54
|
body,
|
|
55
|
-
headers: {
|
|
56
|
-
authorization: `Basic ${Buffer.from(`${accountSid}:${authToken}`).toString('base64')}`,
|
|
57
|
-
'content-type': 'application/x-www-form-urlencoded',
|
|
58
|
-
},
|
|
59
55
|
signal: request.signal,
|
|
56
|
+
errorPrefix: 'Twilio call request failed',
|
|
60
57
|
});
|
|
61
|
-
if (!response.ok) {
|
|
62
|
-
const message = await response.text().catch(() => response.statusText);
|
|
63
|
-
throw new TelephonyProviderError(`Twilio call request failed: ${response.status} ${message}`, 'twilio');
|
|
64
|
-
}
|
|
65
|
-
const raw = (await response.json());
|
|
66
58
|
return {
|
|
67
59
|
callId: stringValue(raw.sid) || '',
|
|
68
60
|
providerUsed: 'twilio',
|
|
@@ -98,6 +90,85 @@ export class TwilioTelephonyProvider {
|
|
|
98
90
|
const expected = createHmac('sha1', token).update(payload).digest('base64');
|
|
99
91
|
return safeEqual(signature, expected);
|
|
100
92
|
}
|
|
93
|
+
async getCall(request) {
|
|
94
|
+
const raw = await this.request(`/Calls/${encodeURIComponent(request.callId)}.json`, {
|
|
95
|
+
method: 'GET',
|
|
96
|
+
signal: request.signal,
|
|
97
|
+
errorPrefix: 'Twilio call lookup failed',
|
|
98
|
+
});
|
|
99
|
+
return this.toCallDetails(raw, request.callId);
|
|
100
|
+
}
|
|
101
|
+
async endCall(request) {
|
|
102
|
+
const body = new URLSearchParams();
|
|
103
|
+
body.set('Status', request.status || 'completed');
|
|
104
|
+
const raw = await this.request(`/Calls/${encodeURIComponent(request.callId)}.json`, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
body,
|
|
107
|
+
signal: request.signal,
|
|
108
|
+
errorPrefix: 'Twilio call hangup failed',
|
|
109
|
+
});
|
|
110
|
+
return this.toCallDetails(raw, request.callId);
|
|
111
|
+
}
|
|
112
|
+
parseStatusCallback(body) {
|
|
113
|
+
const params = toParamRecord(body);
|
|
114
|
+
const callId = params.CallSid || params.callSid || params.sid;
|
|
115
|
+
if (!callId)
|
|
116
|
+
return undefined;
|
|
117
|
+
// Twilio sends CallDuration on completed-call callbacks and Duration on recording callbacks.
|
|
118
|
+
const durationSeconds = numberValue(params.CallDuration ?? params.Duration ?? params.duration);
|
|
119
|
+
return {
|
|
120
|
+
providerUsed: 'twilio',
|
|
121
|
+
callId,
|
|
122
|
+
status: normalizeStatus(params.CallStatus || params.status),
|
|
123
|
+
direction: normalizeDirection(params.Direction || params.direction),
|
|
124
|
+
from: params.From || params.from,
|
|
125
|
+
to: params.To || params.to,
|
|
126
|
+
durationSeconds,
|
|
127
|
+
// Twilio only sets SipResponseCode/ErrorCode when a call failed to complete normally.
|
|
128
|
+
endedReason: params.ErrorCode || params.SipResponseCode || undefined,
|
|
129
|
+
raw: params,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
async listPhoneNumbers(request = {}) {
|
|
133
|
+
const query = new URLSearchParams();
|
|
134
|
+
if (request.phoneNumber)
|
|
135
|
+
query.set('PhoneNumber', request.phoneNumber);
|
|
136
|
+
if (request.pageSize !== undefined)
|
|
137
|
+
query.set('PageSize', String(request.pageSize));
|
|
138
|
+
const suffix = query.size ? `?${query.toString()}` : '';
|
|
139
|
+
const raw = await this.request(`/IncomingPhoneNumbers.json${suffix}`, {
|
|
140
|
+
method: 'GET',
|
|
141
|
+
signal: request.signal,
|
|
142
|
+
errorPrefix: 'Twilio phone number lookup failed',
|
|
143
|
+
});
|
|
144
|
+
const list = raw.incoming_phone_numbers;
|
|
145
|
+
if (!Array.isArray(list))
|
|
146
|
+
return [];
|
|
147
|
+
return list.map((entry) => this.toPhoneNumber(objectValue(entry)));
|
|
148
|
+
}
|
|
149
|
+
async updatePhoneNumber(request) {
|
|
150
|
+
const body = new URLSearchParams();
|
|
151
|
+
if (request.friendlyName !== undefined)
|
|
152
|
+
body.set('FriendlyName', request.friendlyName);
|
|
153
|
+
if (request.voiceUrl !== undefined)
|
|
154
|
+
body.set('VoiceUrl', request.voiceUrl);
|
|
155
|
+
if (request.voiceMethod !== undefined)
|
|
156
|
+
body.set('VoiceMethod', request.voiceMethod);
|
|
157
|
+
if (request.statusCallbackUrl !== undefined)
|
|
158
|
+
body.set('StatusCallback', request.statusCallbackUrl);
|
|
159
|
+
if (request.statusCallbackMethod !== undefined)
|
|
160
|
+
body.set('StatusCallbackMethod', request.statusCallbackMethod);
|
|
161
|
+
if (!body.size) {
|
|
162
|
+
throw new TelephonyProviderError('Twilio phone number update requires at least one field to change', 'twilio');
|
|
163
|
+
}
|
|
164
|
+
const raw = await this.request(`/IncomingPhoneNumbers/${encodeURIComponent(request.id)}.json`, {
|
|
165
|
+
method: 'POST',
|
|
166
|
+
body,
|
|
167
|
+
signal: request.signal,
|
|
168
|
+
errorPrefix: 'Twilio phone number update failed',
|
|
169
|
+
});
|
|
170
|
+
return this.toPhoneNumber(raw);
|
|
171
|
+
}
|
|
101
172
|
parseMediaStreamEvent(message) {
|
|
102
173
|
const data = typeof message === 'string' ? JSON.parse(message) : message;
|
|
103
174
|
const event = stringValue(data.event);
|
|
@@ -180,6 +251,59 @@ export class TwilioTelephonyProvider {
|
|
|
180
251
|
: JSON.stringify({ event: 'clear', streamSid: streamId });
|
|
181
252
|
return { event, streamId, body };
|
|
182
253
|
}
|
|
254
|
+
async request(path, options) {
|
|
255
|
+
const accountSid = this.requireConfig('accountSid');
|
|
256
|
+
const authToken = this.requireConfig('authToken');
|
|
257
|
+
const response = await this.fetchImpl()(`${this.baseUrl()}/Accounts/${accountSid}${path}`, {
|
|
258
|
+
method: options.method,
|
|
259
|
+
body: options.body,
|
|
260
|
+
headers: {
|
|
261
|
+
authorization: `Basic ${Buffer.from(`${accountSid}:${authToken}`).toString('base64')}`,
|
|
262
|
+
...(options.body ? { 'content-type': 'application/x-www-form-urlencoded' } : {}),
|
|
263
|
+
},
|
|
264
|
+
signal: options.signal,
|
|
265
|
+
});
|
|
266
|
+
if (!response.ok) {
|
|
267
|
+
const message = await response.text().catch(() => response.statusText);
|
|
268
|
+
throw new TelephonyProviderError(`${options.errorPrefix}: ${response.status} ${message}`, 'twilio');
|
|
269
|
+
}
|
|
270
|
+
return (await response.json());
|
|
271
|
+
}
|
|
272
|
+
toCallDetails(raw, fallbackCallId) {
|
|
273
|
+
return {
|
|
274
|
+
callId: stringValue(raw.sid) || fallbackCallId,
|
|
275
|
+
providerUsed: 'twilio',
|
|
276
|
+
status: normalizeStatus(stringValue(raw.status)),
|
|
277
|
+
direction: normalizeDirection(stringValue(raw.direction)),
|
|
278
|
+
from: stringValue(raw.from),
|
|
279
|
+
to: stringValue(raw.to),
|
|
280
|
+
durationSeconds: numberValue(raw.duration),
|
|
281
|
+
startedAt: stringValue(raw.start_time),
|
|
282
|
+
endedAt: stringValue(raw.end_time),
|
|
283
|
+
price: numberValue(raw.price),
|
|
284
|
+
priceUnit: stringValue(raw.price_unit),
|
|
285
|
+
raw,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
toPhoneNumber(raw) {
|
|
289
|
+
const capabilities = objectValue(raw.capabilities);
|
|
290
|
+
return {
|
|
291
|
+
id: stringValue(raw.sid) || '',
|
|
292
|
+
providerUsed: 'twilio',
|
|
293
|
+
phoneNumber: stringValue(raw.phone_number) || '',
|
|
294
|
+
friendlyName: stringValue(raw.friendly_name),
|
|
295
|
+
voiceUrl: stringValue(raw.voice_url),
|
|
296
|
+
voiceMethod: normalizeHttpMethod(stringValue(raw.voice_method)),
|
|
297
|
+
statusCallbackUrl: stringValue(raw.status_callback),
|
|
298
|
+
statusCallbackMethod: normalizeHttpMethod(stringValue(raw.status_callback_method)),
|
|
299
|
+
capabilities: {
|
|
300
|
+
voice: capabilities.voice === true,
|
|
301
|
+
sms: capabilities.sms === true,
|
|
302
|
+
mms: capabilities.mms === true,
|
|
303
|
+
},
|
|
304
|
+
raw,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
183
307
|
requireConfig(key) {
|
|
184
308
|
const value = this.config[key];
|
|
185
309
|
if (!value)
|
|
@@ -247,6 +371,35 @@ function normalizeStatus(status) {
|
|
|
247
371
|
}
|
|
248
372
|
return 'unknown';
|
|
249
373
|
}
|
|
374
|
+
function normalizeDirection(direction) {
|
|
375
|
+
if (!direction)
|
|
376
|
+
return undefined;
|
|
377
|
+
// Twilio reports outbound calls as `outbound-api` or `outbound-dial` depending on how they started.
|
|
378
|
+
return direction.startsWith('outbound') ? 'outbound' : 'inbound';
|
|
379
|
+
}
|
|
380
|
+
function normalizeHttpMethod(method) {
|
|
381
|
+
if (!method)
|
|
382
|
+
return undefined;
|
|
383
|
+
return method.toUpperCase() === 'GET' ? 'GET' : 'POST';
|
|
384
|
+
}
|
|
385
|
+
function toParamRecord(body) {
|
|
386
|
+
const params = {};
|
|
387
|
+
if (body instanceof URLSearchParams) {
|
|
388
|
+
for (const [key, value] of body.entries())
|
|
389
|
+
params[key] = value;
|
|
390
|
+
}
|
|
391
|
+
else if (typeof body === 'string') {
|
|
392
|
+
for (const [key, value] of new URLSearchParams(body).entries())
|
|
393
|
+
params[key] = value;
|
|
394
|
+
}
|
|
395
|
+
else if (body && typeof body === 'object') {
|
|
396
|
+
for (const [key, value] of Object.entries(body)) {
|
|
397
|
+
if (value !== undefined)
|
|
398
|
+
params[key] = String(value);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return params;
|
|
402
|
+
}
|
|
250
403
|
function normalizeTrack(track) {
|
|
251
404
|
if (track === 'outbound' || track === 'outbound_track')
|
|
252
405
|
return 'outbound';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { RealtimeSession } from '../realtime/session.js';
|
|
2
|
+
import type { RealtimeAudioOptions } from '../realtime/types.js';
|
|
3
|
+
import type { TelephonyDtmfEvent, TelephonyOutboundAudioMessage, TelephonyStartEvent, TelephonyStopEvent } from '../types/telephony.js';
|
|
4
|
+
import type { TelephonyManager } from './manager.js';
|
|
5
|
+
/**
|
|
6
|
+
* Audio settings that match what Twilio Media Streams actually send and accept: 8 kHz G.711 µ-law.
|
|
7
|
+
*
|
|
8
|
+
* Passing these to a realtime session lets the provider consume and produce telephony audio directly,
|
|
9
|
+
* so the bridge forwards payloads without resampling or transcoding. Overriding them means the app
|
|
10
|
+
* becomes responsible for converting every frame in both directions.
|
|
11
|
+
*/
|
|
12
|
+
export declare function twilioRealtimeAudioOptions(overrides?: RealtimeAudioOptions): RealtimeAudioOptions;
|
|
13
|
+
export interface TelephonyRealtimeBridgeOptions {
|
|
14
|
+
/** The realtime session driving the call. The bridge does not create it, so tools and instructions stay app-owned. */
|
|
15
|
+
session: RealtimeSession;
|
|
16
|
+
telephony: TelephonyManager;
|
|
17
|
+
/** Registered provider name. Defaults to `twilio`. */
|
|
18
|
+
provider?: string;
|
|
19
|
+
/** Sends one framed message back over the provider's media socket. Awaited, so ordering is preserved. */
|
|
20
|
+
send(message: TelephonyOutboundAudioMessage): void | Promise<void>;
|
|
21
|
+
/** Connect the session on the provider's `start` event. Defaults to true. */
|
|
22
|
+
autoConnect?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* On caller speech, discard audio already buffered at the provider and cancel the in-flight response.
|
|
25
|
+
* Defaults to true. Without it the assistant keeps talking over the caller for as long as the
|
|
26
|
+
* provider has audio queued.
|
|
27
|
+
*/
|
|
28
|
+
bargeIn?: boolean;
|
|
29
|
+
/** Disconnect the session when the provider's stream stops. Defaults to true. */
|
|
30
|
+
autoDisconnect?: boolean;
|
|
31
|
+
onStart?(event: TelephonyStartEvent): void | Promise<void>;
|
|
32
|
+
onStop?(event: TelephonyStopEvent): void | Promise<void>;
|
|
33
|
+
onDtmf?(event: TelephonyDtmfEvent): void | Promise<void>;
|
|
34
|
+
/** Receives errors from message handling and outbound sends, which are never thrown at the socket. */
|
|
35
|
+
onError?(error: unknown): void;
|
|
36
|
+
}
|
|
37
|
+
export interface TelephonyRealtimeBridge {
|
|
38
|
+
/** Provider call identifier, available once the stream has started. */
|
|
39
|
+
readonly callId: string | undefined;
|
|
40
|
+
readonly streamId: string | undefined;
|
|
41
|
+
/** Custom `<Parameter>` values from the provider's stream instruction. */
|
|
42
|
+
readonly parameters: Record<string, string>;
|
|
43
|
+
readonly closed: boolean;
|
|
44
|
+
/** Feeds one raw media-socket message through the bridge. Safe to call before `start`. */
|
|
45
|
+
handleMessage(message: string | Record<string, unknown>): Promise<void>;
|
|
46
|
+
/** Resolves once every queued outbound message has been sent. Send failures surface via `onError`. */
|
|
47
|
+
flush(): Promise<void>;
|
|
48
|
+
/** Detaches session listeners and, unless disabled, disconnects the session. Idempotent. */
|
|
49
|
+
close(): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Connects a provider media stream to a realtime session: caller audio in, assistant audio out,
|
|
53
|
+
* barge-in, playback marks, and stream lifecycle.
|
|
54
|
+
*
|
|
55
|
+
* The bridge owns only the audio path. Session configuration, tools, authorization, and any
|
|
56
|
+
* post-call persistence stay with the application.
|
|
57
|
+
*/
|
|
58
|
+
export declare function createTelephonyRealtimeBridge(options: TelephonyRealtimeBridgeOptions): TelephonyRealtimeBridge;
|