@fonoster/voice 0.27.0 → 0.31.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/README.md +32 -419
- package/dist/Session.d.ts +161 -0
- package/dist/Session.js +576 -0
- package/dist/VoiceServer.d.ts +70 -5
- package/dist/VoiceServer.js +149 -43
- package/dist/{verbs/Dial.d.ts → errors.d.ts} +9 -13
- package/dist/{verbs/Answer.js → errors.js} +18 -7
- package/dist/index.d.ts +4 -6
- package/dist/index.js +4 -15
- package/dist/signature.d.ts +31 -0
- package/dist/signature.js +93 -0
- package/dist/types.d.ts +88 -10
- package/dist/types.js +0 -21
- package/package.json +13 -19
- package/session.d.ts +1 -0
- package/session.js +2 -0
- package/dist/VoiceResponse.d.ts +0 -304
- package/dist/VoiceResponse.js +0 -461
- package/dist/createSession.d.ts +0 -22
- package/dist/createSession.js +0 -66
- package/dist/defaultServerConfig.d.ts +0 -21
- package/dist/defaultServerConfig.js +0 -10
- package/dist/demo.d.ts +0 -1
- package/dist/demo.js +0 -83
- package/dist/filters.d.ts +0 -44
- package/dist/filters.js +0 -17
- package/dist/serviceDefinition.d.ts +0 -2
- package/dist/serviceDefinition.js +0 -29
- package/dist/verbs/Answer.d.ts +0 -24
- package/dist/verbs/Dial.js +0 -60
- package/dist/verbs/Gather.d.ts +0 -25
- package/dist/verbs/Gather.js +0 -57
- package/dist/verbs/Hangup.d.ts +0 -24
- package/dist/verbs/Hangup.js +0 -29
- package/dist/verbs/Mute.d.ts +0 -25
- package/dist/verbs/Mute.js +0 -36
- package/dist/verbs/Play.d.ts +0 -25
- package/dist/verbs/Play.js +0 -33
- package/dist/verbs/PlayDtmf.d.ts +0 -25
- package/dist/verbs/PlayDtmf.js +0 -32
- package/dist/verbs/PlaybackControl.d.ts +0 -25
- package/dist/verbs/PlaybackControl.js +0 -35
- package/dist/verbs/Record.d.ts +0 -25
- package/dist/verbs/Record.js +0 -49
- package/dist/verbs/Say.d.ts +0 -25
- package/dist/verbs/Say.js +0 -33
- package/dist/verbs/SetAudioFilters.d.ts +0 -25
- package/dist/verbs/SetAudioFilters.js +0 -16
- package/dist/verbs/StopSay.d.ts +0 -25
- package/dist/verbs/StopSay.js +0 -11
- package/dist/verbs/Stream.d.ts +0 -39
- package/dist/verbs/Stream.js +0 -80
- package/dist/verbs/StreamGather.d.ts +0 -37
- package/dist/verbs/StreamGather.js +0 -66
- package/dist/verbs/Unmute.d.ts +0 -22
- package/dist/verbs/Unmute.js +0 -26
- package/dist/verbs/Verb.d.ts +0 -28
- package/dist/verbs/Verb.js +0 -103
- package/dist/verbs/getExpectedContent.d.ts +0 -2
- package/dist/verbs/getExpectedContent.js +0 -32
- package/dist/verbs/index.d.ts +0 -35
- package/dist/verbs/index.js +0 -51
- package/dist/verbs/validateRequest.d.ts +0 -20
- package/dist/verbs/validateRequest.js +0 -13
package/README.md
CHANGED
|
@@ -6,11 +6,14 @@ voice
|
|
|
6
6
|
[](https://npmjs.org/package/@fonoster/voice)
|
|
7
7
|
[](https://github.com/fonoster/fonoster/blob/main/package.json)
|
|
8
8
|
|
|
9
|
-
The
|
|
9
|
+
The SDK for Fonoster **streams**: your own WebSocket server, speaking the `fonoster` protocol. For
|
|
10
|
+
every call, Fonoster opens one signed WebSocket **session** to your stream; `VoiceServer` accepts it
|
|
11
|
+
and runs your handler with the session. The protocol is in `docs/session-protocol.md`, the full SDK guide in
|
|
12
|
+
`docs/voice-sdk.md`, and runnable examples in `examples/`.
|
|
10
13
|
|
|
11
14
|
* [Installation](#installation)
|
|
12
15
|
* [Example](#example)
|
|
13
|
-
* [
|
|
16
|
+
* [The session](#the-session)
|
|
14
17
|
|
|
15
18
|
## Installation
|
|
16
19
|
|
|
@@ -20,431 +23,41 @@ $ npm install --save @fonoster/voice
|
|
|
20
23
|
|
|
21
24
|
## Example
|
|
22
25
|
|
|
23
|
-
A Voice Application is a server that controls a call's flow. A Voice Application can use any combination of the following verbs:
|
|
24
|
-
|
|
25
|
-
- `Answer` - Accepts an incoming call
|
|
26
|
-
- `Dial` - Passes the call to an Agent or a Number at the PSTN
|
|
27
|
-
- `Hangup` - Closes the call
|
|
28
|
-
- `Play` - Takes a URL or file and streams the sound back to the calling party
|
|
29
|
-
- `Say` - Takes a text, synthesizes the text into audio, and streams back the result
|
|
30
|
-
- `Gather` - Waits for DTMF or speech events and returns back the result
|
|
31
|
-
- `SGather` - Returns a stream for future DTMF and speech results
|
|
32
|
-
- `Stream` - Starts a stream to read and write audio into the call
|
|
33
|
-
- `Record` - It records the voice of the calling party and saves the audio on the Storage sub-system
|
|
34
|
-
- `Mute` - It tells the channel to stop sending media, effectively muting the channel
|
|
35
|
-
- `Unmute` - It tells the channel to allow media flow
|
|
36
|
-
- `SetAudioFilters` - Filters the caller's audio in the Media Server before it reaches speech recognition and your media stream
|
|
37
|
-
|
|
38
|
-
### Audio filters
|
|
39
|
-
|
|
40
|
-
Background voices — a television, a radio, someone else in the room — get transcribed too, and
|
|
41
|
-
your application ends up answering them. Ask the Media Server to filter the caller's audio before
|
|
42
|
-
it reaches speech recognition and your media stream:
|
|
43
|
-
|
|
44
26
|
```typescript
|
|
45
|
-
const { aiCoustics } = require("@fonoster/voice");
|
|
46
|
-
|
|
47
|
-
new VoiceServer().listen(async (req: VoiceRequest, voice: VoiceResponse) => {
|
|
48
|
-
// Set filters before answering; they apply for the rest of the session
|
|
49
|
-
await voice.setAudioFilters([aiCoustics({ enhancementLevel: 0.8 })]);
|
|
50
|
-
|
|
51
|
-
await voice.answer();
|
|
52
|
-
// ...
|
|
53
|
-
});
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Notes:
|
|
57
|
-
|
|
58
|
-
- The filter runs in the Media Server, which needs the optional
|
|
59
|
-
`@ai-coustics/aic-sdk` package and an `AIC_SDK_LICENSE` key
|
|
60
|
-
([developers.ai-coustics.com](https://developers.ai-coustics.com)). Credentials never leave the
|
|
61
|
-
Media Server, so your application never handles them.
|
|
62
|
-
- `enhancementLevel` goes from 0 to 1; higher values also suppress competing speech.
|
|
63
|
-
- If the filter cannot be applied, `setAudioFilters` throws and **the call continues with
|
|
64
|
-
unfiltered audio**. Catch it if a failure should not interrupt your flow.
|
|
65
|
-
- Filtering adds about 30 ms of delay to the audio your application hears.
|
|
66
|
-
- Sending a new list replaces the previous one, and an empty list removes all filters.
|
|
67
|
-
|
|
68
|
-
Voice Application Example:
|
|
69
|
-
|
|
70
|
-
```typescript
|
|
71
|
-
const VoiceServer = require("@fonoster/voice").default;
|
|
72
|
-
const {
|
|
73
|
-
GatherSource,
|
|
74
|
-
VoiceRequest,
|
|
75
|
-
VoiceResponse
|
|
76
|
-
} = require("@fonoster/voice");
|
|
77
|
-
|
|
78
|
-
new VoiceServer().listen(async (req: VoiceRequest, voice: VoiceResponse) => {
|
|
79
|
-
const { ingressNumber, sessionRef, appRef } = req;
|
|
80
|
-
|
|
81
|
-
await voice.answer();
|
|
82
|
-
|
|
83
|
-
await voice.say("Hi there! What's your name?");
|
|
84
|
-
|
|
85
|
-
const { speech: name } = await res.gather({
|
|
86
|
-
source: GatherSource.SPEECH
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
await voice.say("Nice to meet you " + name + "!");
|
|
90
|
-
|
|
91
|
-
await voice.say("Please enter your 4 digit pin.");
|
|
92
|
-
|
|
93
|
-
const { digits } = await voice.gather({
|
|
94
|
-
maxDigits: 4,
|
|
95
|
-
finishOnKey: "#"
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
await voice.say("Your pin is " + digits);
|
|
99
|
-
|
|
100
|
-
await voice.hangup();
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// Your app will live at tcp://127.0.0.1:50061
|
|
104
|
-
// and you can easily publish it to the Internet with:
|
|
105
|
-
// ngrok tcp 50061
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
<a name="VoiceResponse"></a>
|
|
109
|
-
|
|
110
|
-
## VoiceResponse ⇐ <code>Verb</code>
|
|
111
|
-
Use the VoiceResponse object, to construct advance Interactive
|
|
112
|
-
Voice Response (IVR) applications.
|
|
113
|
-
|
|
114
|
-
**Kind**: global class
|
|
115
|
-
**Extends**: <code>Verb</code>
|
|
116
|
-
**See**: module:core:APIClient
|
|
117
|
-
|
|
118
|
-
* [VoiceResponse](#VoiceResponse) ⇐ <code>Verb</code>
|
|
119
|
-
* [new VoiceResponse(request, voice)](#new_VoiceResponse_new)
|
|
120
|
-
* [.answer()](#VoiceResponse+answer)
|
|
121
|
-
* [.hangup()](#VoiceResponse+hangup)
|
|
122
|
-
* [.play(url, options)](#VoiceResponse+play)
|
|
123
|
-
* [.playDtmf(digits)](#VoiceResponse+playDtmf)
|
|
124
|
-
* [.playbackControl(playbackRef, action)](#VoiceResponse+playbackControl)
|
|
125
|
-
* [.gather(options)](#VoiceResponse+gather)
|
|
126
|
-
* [.say(text, options)](#VoiceResponse+say)
|
|
127
|
-
* [.record(options)](#VoiceResponse+record) ⇒ <code>RecordResponse</code>
|
|
128
|
-
* [.dial(destination, options)](#VoiceResponse+dial) ⇒ <code>Promise.<DialStatusStream></code>
|
|
129
|
-
* [.stream(options)](#VoiceResponse+stream) ⇒ <code>Promise.<Stream></code>
|
|
130
|
-
* [.sgather(options)](#VoiceResponse+sgather) ⇒ <code>Promise.<StreamGatherStream></code>
|
|
131
|
-
* [.mute(options)](#VoiceResponse+mute)
|
|
132
|
-
* [.unmute(options)](#VoiceResponse+unmute)
|
|
133
|
-
* [.on(event, listener)](#VoiceResponse+on)
|
|
134
|
-
|
|
135
|
-
<a name="new_VoiceResponse_new"></a>
|
|
136
|
-
|
|
137
|
-
### new VoiceResponse(request, voice)
|
|
138
|
-
Constructs a new VoiceResponse object.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
| Param | Type | Description |
|
|
142
|
-
| --- | --- | --- |
|
|
143
|
-
| request | <code>VoiceRequest</code> | Options to indicate the objects endpoint |
|
|
144
|
-
| voice | <code>VoiceSessionStream</code> | The voice session stream |
|
|
145
|
-
|
|
146
|
-
**Example**
|
|
147
|
-
```js
|
|
148
27
|
import { VoiceServer } from "@fonoster/voice";
|
|
149
28
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
<a name="VoiceResponse+answer"></a>
|
|
158
|
-
|
|
159
|
-
### voiceResponse.answer()
|
|
160
|
-
Answer the call. Before running any other verb you
|
|
161
|
-
must run the answer command.
|
|
162
|
-
|
|
163
|
-
**Kind**: instance method of [<code>VoiceResponse</code>](#VoiceResponse)
|
|
164
|
-
**Example**
|
|
165
|
-
```js
|
|
166
|
-
async function handler (request, response) {
|
|
167
|
-
await response.answer();
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
<a name="VoiceResponse+hangup"></a>
|
|
171
|
-
|
|
172
|
-
### voiceResponse.hangup()
|
|
173
|
-
Hangup the call.
|
|
174
|
-
|
|
175
|
-
**Kind**: instance method of [<code>VoiceResponse</code>](#VoiceResponse)
|
|
176
|
-
**Example**
|
|
177
|
-
```js
|
|
178
|
-
async function handler (request, response) {
|
|
179
|
-
await response.hangup();
|
|
180
|
-
}
|
|
181
|
-
```
|
|
182
|
-
<a name="VoiceResponse+play"></a>
|
|
183
|
-
|
|
184
|
-
### voiceResponse.play(url, options)
|
|
185
|
-
Play an audio in the call.
|
|
186
|
-
|
|
187
|
-
**Kind**: instance method of [<code>VoiceResponse</code>](#VoiceResponse)
|
|
188
|
-
**See**: Playback
|
|
189
|
-
|
|
190
|
-
| Param | Type | Description |
|
|
191
|
-
| --- | --- | --- |
|
|
192
|
-
| url | <code>string</code> | The URL of the media to play |
|
|
193
|
-
| options | <code>PlayOptions</code> | Options to control the playback |
|
|
194
|
-
| options.playbackRef | <code>string</code> | Playback identifier to use in Playback operations |
|
|
195
|
-
|
|
196
|
-
**Example**
|
|
197
|
-
```js
|
|
198
|
-
async function handler (request, response) {
|
|
199
|
-
await response.answer();
|
|
200
|
-
await response.play("https://soundsserver:9000/sounds/hello-world.wav");
|
|
201
|
-
}
|
|
202
|
-
```
|
|
203
|
-
<a name="VoiceResponse+playDtmf"></a>
|
|
204
|
-
|
|
205
|
-
### voiceResponse.playDtmf(digits)
|
|
206
|
-
Play a series of DTMF digits in a call.
|
|
207
|
-
|
|
208
|
-
**Kind**: instance method of [<code>VoiceResponse</code>](#VoiceResponse)
|
|
209
|
-
|
|
210
|
-
| Param | Type | Description |
|
|
211
|
-
| --- | --- | --- |
|
|
212
|
-
| digits | <code>string</code> | The DTMF digits to play (0-9, #, or *) |
|
|
213
|
-
|
|
214
|
-
**Example**
|
|
215
|
-
```js
|
|
216
|
-
async function handler (request, response) {
|
|
217
|
-
await response.answer();
|
|
218
|
-
await response.playDtmf("1234");
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
<a name="VoiceResponse+playbackControl"></a>
|
|
222
|
-
|
|
223
|
-
### voiceResponse.playbackControl(playbackRef, action)
|
|
224
|
-
Control the playback of the currently playing media.
|
|
225
|
-
|
|
226
|
-
**Kind**: instance method of [<code>VoiceResponse</code>](#VoiceResponse)
|
|
227
|
-
**See**: play
|
|
228
|
-
|
|
229
|
-
| Param | Type | Description |
|
|
230
|
-
| --- | --- | --- |
|
|
231
|
-
| playbackRef | <code>string</code> | The playback identifier |
|
|
232
|
-
| action | <code>PlaybackControlAction</code> | The action to perform (STOP, RESTART, PAUSE, UNPAUSE, FORWARD) |
|
|
29
|
+
// AppBridge signs every connection with the app's signing secret.
|
|
30
|
+
new VoiceServer({ port: 3000, signingSecret: process.env.APP_SIGNING_SECRET }).listen(
|
|
31
|
+
async (session) => {
|
|
32
|
+
await session.say("Hi there! What's your name?");
|
|
33
|
+
const { speech: name } = await session.gather();
|
|
34
|
+
await session.say(`Nice to meet you ${name}!`);
|
|
233
35
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
await response.answer();
|
|
238
|
-
await response.play("https://s3.fonoster.io/uuid/hello-world.wav", { playbackRef: "playback-01" });
|
|
36
|
+
await session.say("Please enter your 4 digit pin.");
|
|
37
|
+
const { digits } = await session.gather({ maxDigits: 4, finishOnKey: "#" });
|
|
38
|
+
await session.say(`Your pin is ${digits}`);
|
|
239
39
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
40
|
+
await session.hangup();
|
|
41
|
+
}
|
|
42
|
+
);
|
|
243
43
|
```
|
|
244
|
-
<a name="VoiceResponse+gather"></a>
|
|
245
|
-
|
|
246
|
-
### voiceResponse.gather(options)
|
|
247
|
-
Waits for data entry from the user's keypad or from a speech provider.
|
|
248
|
-
|
|
249
|
-
**Kind**: instance method of [<code>VoiceResponse</code>](#VoiceResponse)
|
|
250
|
-
**Note**: When including `SPEECH` the default timeout is 10000 (10s).
|
|
251
|
-
|
|
252
|
-
| Param | Type | Description |
|
|
253
|
-
| --- | --- | --- |
|
|
254
|
-
| options | <code>GatherOptions</code> | Options to select the maximum number of digits, final character, and timeout |
|
|
255
|
-
| options.maxDigits | <code>number</code> | Maximum number of digits to collect. Defaults to 1 |
|
|
256
|
-
| options.timeout | <code>number</code> | Milliseconds to wait before timeout. Defaults to 4000. Use zero for no timeout. |
|
|
257
|
-
| options.finishOnKey | <code>string</code> | Optional last character to wait for. Defaults to '#'. It will not be included in the returned digits |
|
|
258
|
-
| options.source | <code>GatherSource</code> | Where to listen as input source. This option accepts `DTMF` and `SPEECH`. A speech provider must be configure when including the `SPEECH` source. You might include both with `SPEECH_AND_DTMF`. Defaults to `SPEECH_AND_DTMF` |
|
|
259
|
-
|
|
260
|
-
**Example**
|
|
261
|
-
```js
|
|
262
|
-
async function handler (request, response) {
|
|
263
|
-
await response.answer();
|
|
264
|
-
const speech = await response.gather({ source: GatherSource.SPEECH, numDigits: 3 });
|
|
265
|
-
console.log("speech: " + speech);
|
|
266
|
-
await response.hangup();
|
|
267
|
-
}
|
|
268
|
-
```
|
|
269
|
-
<a name="VoiceResponse+say"></a>
|
|
270
|
-
|
|
271
|
-
### voiceResponse.say(text, options)
|
|
272
|
-
Send a text for a TTS engine to convert to speech.
|
|
273
|
-
|
|
274
|
-
**Kind**: instance method of [<code>VoiceResponse</code>](#VoiceResponse)
|
|
275
|
-
**See**: Say
|
|
276
|
-
|
|
277
|
-
| Param | Type | Description |
|
|
278
|
-
| --- | --- | --- |
|
|
279
|
-
| text | <code>string</code> | The text to convert to speech |
|
|
280
|
-
| options | <code>SayOptions</code> | Options to control the TTS engine |
|
|
281
|
-
| options.playbackRef | <code>string</code> | Playback identifier to use in Playback operations |
|
|
282
|
-
| options.ttsOptions | <code>TTSOptions</code> | Options to control the TTS engine (specific to the TTS engine) |
|
|
283
|
-
|
|
284
|
-
**Example**
|
|
285
|
-
```js
|
|
286
|
-
async function handler (request, response) {
|
|
287
|
-
await response.answer();
|
|
288
|
-
const playbackRef = await response.say("Hello World");
|
|
289
|
-
|
|
290
|
-
// Like the play verb, you can control the playback
|
|
291
|
-
await response.playbackControl(playbackRef, PlaybackControlAction.STOP);
|
|
292
|
-
}
|
|
293
|
-
```
|
|
294
|
-
<a name="VoiceResponse+record"></a>
|
|
295
|
-
|
|
296
|
-
### voiceResponse.record(options) ⇒ <code>RecordResponse</code>
|
|
297
|
-
Record the audio of the call.
|
|
298
|
-
|
|
299
|
-
**Kind**: instance method of [<code>VoiceResponse</code>](#VoiceResponse)
|
|
300
|
-
**Returns**: <code>RecordResponse</code> - The record response
|
|
301
|
-
|
|
302
|
-
| Param | Type | Description |
|
|
303
|
-
| --- | --- | --- |
|
|
304
|
-
| options | <code>RecordOptions</code> | Options to control the record operation |
|
|
305
|
-
| options.maxDuration | <code>number</code> | The maximum duration of the recording in seconds. Default is 60 |
|
|
306
|
-
| options.maxSilence | <code>number</code> | The maximum duration of silence in seconds. Default is 5 |
|
|
307
|
-
| options.beep | <code>boolean</code> | Play a beep before recording. Default is true |
|
|
308
|
-
| options.finishOnKey | <code>string</code> | Stop recording when a DTMF digit is received. Default is '#' |
|
|
309
|
-
|
|
310
|
-
**Example**
|
|
311
|
-
```js
|
|
312
|
-
async function handler (request, response) {
|
|
313
|
-
await response.answer();
|
|
314
|
-
const record = await response.record();
|
|
315
|
-
console.log("Recording: %s", record.name);
|
|
316
|
-
}
|
|
317
|
-
```
|
|
318
|
-
<a name="VoiceResponse+dial"></a>
|
|
319
|
-
|
|
320
|
-
### voiceResponse.dial(destination, options) ⇒ <code>Promise.<DialStatusStream></code>
|
|
321
|
-
Dials a destination and returns a stream of status.
|
|
322
|
-
|
|
323
|
-
**Kind**: instance method of [<code>VoiceResponse</code>](#VoiceResponse)
|
|
324
|
-
**Returns**: <code>Promise.<DialStatusStream></code> - The dial status stream
|
|
325
|
-
|
|
326
|
-
| Param | Type | Description |
|
|
327
|
-
| --- | --- | --- |
|
|
328
|
-
| destination | <code>string</code> | The destination to dial |
|
|
329
|
-
| options | <code>DialOptions</code> | Options to control the dial operation |
|
|
330
|
-
| options.timeout | <code>number</code> | The timeout in seconds. Default is 60 |
|
|
331
|
-
| options.recordDirection | <code>RecordDirection</code> | The direction to record the call (IN, OUT, BOTH). Default is BOTH |
|
|
332
|
-
|
|
333
|
-
<a name="VoiceResponse+stream"></a>
|
|
334
|
-
|
|
335
|
-
### voiceResponse.stream(options) ⇒ <code>Promise.<Stream></code>
|
|
336
|
-
Starts a bidirectional audio stream between the call and the application.
|
|
337
44
|
|
|
338
|
-
|
|
339
|
-
**Returns**: <code>Promise.<Stream></code> - The stream object
|
|
45
|
+
`attach(server, handler)` mounts the same server on an existing HTTP or Express server.
|
|
340
46
|
|
|
341
|
-
|
|
342
|
-
| --- | --- | --- |
|
|
343
|
-
| options | <code>StreamOptions</code> | Options to control the stream operation |
|
|
344
|
-
| options.direction | <code>StreamDirection</code> | The direction to stream the audio (IN, OUT, BOTH). Default is BOTH |
|
|
345
|
-
| options.format | <code>StreamAudioFormat</code> | The audio format to stream (WAV). Default is WAV |
|
|
47
|
+
## The session
|
|
346
48
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
49
|
+
| Method | Does |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `say(text \| asyncIterable, options?)` | Speaks; resolves with `{ interrupted, heardText, heardMs }` |
|
|
52
|
+
| `gather(options?)` | Waits for speech or keypresses (`maxDigits`, `finishOnKey`, `timeout`) |
|
|
53
|
+
| `play(url)`, `playDtmf(digits)` | Plays media or DTMF tones |
|
|
54
|
+
| `transfer(to, { mode })`, `completeTransfer()` | Cold, warm or `refer` transfer |
|
|
55
|
+
| `hold()`, `resume()`, `handoff(app, payload)`, `hangup()` | Call control |
|
|
56
|
+
| `audio.onFrame`, `audio.send`, `audio.mark`, `audio.clear` | Raw call audio |
|
|
57
|
+
| `on(event, cb)`, `subscribe(events)` | Turns, partial transcripts, keypresses |
|
|
351
58
|
|
|
352
|
-
|
|
353
|
-
direction: StreamDirection.BOTH
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
stream.onPayload((payload) => {
|
|
357
|
-
// Use the payload
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
// Or write to the stream
|
|
361
|
-
// stream.write({ type: StreamMessageType.AUDIO_OUT, payload: "\x00\x01\x02" });
|
|
362
|
-
}
|
|
363
|
-
```
|
|
364
|
-
<a name="VoiceResponse+sgather"></a>
|
|
365
|
-
|
|
366
|
-
### voiceResponse.sgather(options) ⇒ <code>Promise.<StreamGatherStream></code>
|
|
367
|
-
Starts a server-side stream gather operation which sends transcription data to the voice server.
|
|
368
|
-
|
|
369
|
-
**Kind**: instance method of [<code>VoiceResponse</code>](#VoiceResponse)
|
|
370
|
-
**Returns**: <code>Promise.<StreamGatherStream></code> - The stream gather object
|
|
371
|
-
**See**: Gather
|
|
372
|
-
|
|
373
|
-
| Param | Type | Description |
|
|
374
|
-
| --- | --- | --- |
|
|
375
|
-
| options | <code>StreamGatherOptions</code> | Options to control the stream gather operation |
|
|
376
|
-
| options.source | <code>StreamGatherSource</code> | The source to gather data from (DTMF, SPEECH, SPEECH_AND_DTMF). Default is SPEECH |
|
|
377
|
-
|
|
378
|
-
**Example**
|
|
379
|
-
```js
|
|
380
|
-
async function handler (request, response) {
|
|
381
|
-
await response.answer();
|
|
382
|
-
const sGather = await response.sgather({ source: StreamGatherSource.SPEECH });
|
|
383
|
-
sGather.onPayload((payload) => {
|
|
384
|
-
console.log("Payload: %s", payload);
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
```
|
|
388
|
-
<a name="VoiceResponse+mute"></a>
|
|
389
|
-
|
|
390
|
-
### voiceResponse.mute(options)
|
|
391
|
-
Mutes a call.
|
|
392
|
-
|
|
393
|
-
**Kind**: instance method of [<code>VoiceResponse</code>](#VoiceResponse)
|
|
394
|
-
**See**: unmute
|
|
395
|
-
|
|
396
|
-
| Param | Type | Description |
|
|
397
|
-
| --- | --- | --- |
|
|
398
|
-
| options | <code>MuteOptions</code> | Options to control the mute operation |
|
|
399
|
-
| options.direction | <code>MuteDirection</code> | The direction to mute the channel (IN, OUT, BOTH). Default is BOTH |
|
|
400
|
-
|
|
401
|
-
**Example**
|
|
402
|
-
```js
|
|
403
|
-
async function handler (request, response) {
|
|
404
|
-
await response.answer();
|
|
405
|
-
await response.mute(); // Will mute both directions
|
|
406
|
-
}
|
|
407
|
-
```
|
|
408
|
-
<a name="VoiceResponse+unmute"></a>
|
|
409
|
-
|
|
410
|
-
### voiceResponse.unmute(options)
|
|
411
|
-
Unmute a call.
|
|
412
|
-
|
|
413
|
-
**Kind**: instance method of [<code>VoiceResponse</code>](#VoiceResponse)
|
|
414
|
-
**See**: mute
|
|
415
|
-
|
|
416
|
-
| Param | Type | Description |
|
|
417
|
-
| --- | --- | --- |
|
|
418
|
-
| options | <code>MuteOptions</code> | Options to control the unmute operation |
|
|
419
|
-
| options.direction | <code>MuteDirection</code> | The direction to unmute the call (IN, OUT, BOTH). Default is BOTH |
|
|
420
|
-
|
|
421
|
-
**Example**
|
|
422
|
-
```js
|
|
423
|
-
async function handler (request, response) {
|
|
424
|
-
await response.answer();
|
|
425
|
-
await response.unmute(); // Will unmute both directions
|
|
426
|
-
}
|
|
427
|
-
```
|
|
428
|
-
<a name="VoiceResponse+on"></a>
|
|
429
|
-
|
|
430
|
-
### voiceResponse.on(event, listener)
|
|
431
|
-
Register a listener for the given event.
|
|
432
|
-
|
|
433
|
-
**Kind**: instance method of [<code>VoiceResponse</code>](#VoiceResponse)
|
|
434
|
-
|
|
435
|
-
| Param | Type | Description |
|
|
436
|
-
| --- | --- | --- |
|
|
437
|
-
| event | <code>StreamEvent</code> | The event to listen for |
|
|
438
|
-
| listener | <code>function</code> | The callback function |
|
|
439
|
-
|
|
440
|
-
**Example**
|
|
441
|
-
```js
|
|
442
|
-
async function handler (request, response) {
|
|
443
|
-
...
|
|
444
|
-
|
|
445
|
-
response.on(StreamEvent.END, () => {
|
|
446
|
-
console.log("Call ended");
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
```
|
|
59
|
+
Fields: `callRef`, `appRef`, `from`, `to`, `direction`, `metadata`.
|
|
450
60
|
|
|
61
|
+
Coming from the gRPC SDK (`(req, res)` handlers, `VoiceResponse`, `stream`, `sgather`, `dial`)?
|
|
62
|
+
Those are gone; see `docs/migrating-to-sessions.md`. `@fonoster/voice/session` resolves to this
|
|
63
|
+
package for one more release.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
3
|
+
* http://github.com/fonoster/fonoster
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Fonoster
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the MIT License (the "License");
|
|
8
|
+
* you may not use this file except in compliance with
|
|
9
|
+
* the License. You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* https://opensource.org/licenses/MIT
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { SessionStart } from "@fonoster/common";
|
|
20
|
+
import { GatherOptions, GatherResult, PlayOptions, PlayResult, SayOptions, SayResult, SessionEventMap, SessionEventName, TransferOptions } from "./types";
|
|
21
|
+
/** What the session needs from its socket. */
|
|
22
|
+
export type SessionTransport = {
|
|
23
|
+
sendJson(message: Record<string, unknown>): void;
|
|
24
|
+
sendBinary(data: Buffer): void;
|
|
25
|
+
close(): void;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* One call's session, built on the fonoster.session.v1 messages (voice-sdk S8). Verbs are
|
|
29
|
+
* conveniences: each sends one command and resolves on the event that answers it.
|
|
30
|
+
*/
|
|
31
|
+
export declare class Session {
|
|
32
|
+
private readonly transport;
|
|
33
|
+
readonly sessionRef: string;
|
|
34
|
+
readonly callRef: string;
|
|
35
|
+
readonly appRef: string;
|
|
36
|
+
readonly direction: SessionStart["direction"];
|
|
37
|
+
readonly from: string;
|
|
38
|
+
readonly to: string;
|
|
39
|
+
readonly metadata: Record<string, unknown>;
|
|
40
|
+
/** The W3C traceparent of the call's span (session.start `traceparent`), when sent. */
|
|
41
|
+
readonly traceparent?: string;
|
|
42
|
+
/** The payload of the handoff that started this session (session.start `handoff`), or null. */
|
|
43
|
+
readonly handoffPayload: Record<string, unknown> | null;
|
|
44
|
+
readonly speech: SessionStart["speech"];
|
|
45
|
+
readonly audioFormat: SessionStart["audio"];
|
|
46
|
+
readonly tracks: SessionStart["tracks"];
|
|
47
|
+
/**
|
|
48
|
+
* The app and workspace AppBridge's app-bound signature named, when the server verified one
|
|
49
|
+
* (VoiceServer); undefined otherwise.
|
|
50
|
+
*/
|
|
51
|
+
readonly signedFor?: {
|
|
52
|
+
appRef: string;
|
|
53
|
+
workspaceRef: string;
|
|
54
|
+
};
|
|
55
|
+
/** Raw audio (the Audio layer). */
|
|
56
|
+
readonly audio: {
|
|
57
|
+
onFrame(cb: (frame: Buffer, trackId: number) => void): void;
|
|
58
|
+
send(frame: Buffer): void;
|
|
59
|
+
mark(name: string): Promise<{
|
|
60
|
+
cleared: boolean;
|
|
61
|
+
}>;
|
|
62
|
+
clear(): void;
|
|
63
|
+
};
|
|
64
|
+
private seq;
|
|
65
|
+
private outputs;
|
|
66
|
+
private commands;
|
|
67
|
+
private marks;
|
|
68
|
+
private endWaiters;
|
|
69
|
+
private listeners;
|
|
70
|
+
private frameListeners;
|
|
71
|
+
private gatherState?;
|
|
72
|
+
private buffered;
|
|
73
|
+
private lastWarmTransfer?;
|
|
74
|
+
/** A pending completeTransfer(), rejected when AppBridge refuses it. */
|
|
75
|
+
private completing?;
|
|
76
|
+
private endedReason?;
|
|
77
|
+
private listening;
|
|
78
|
+
constructor(start: SessionStart, transport: SessionTransport, signedFor?: {
|
|
79
|
+
appRef: string;
|
|
80
|
+
workspaceRef: string;
|
|
81
|
+
});
|
|
82
|
+
get ended(): boolean;
|
|
83
|
+
/** Speaks text, or a stream of text pieces as one utterance, and reports what was heard. */
|
|
84
|
+
say(input: string | AsyncIterable<string>, options?: SayOptions): Promise<SayResult>;
|
|
85
|
+
/** Cuts the agent off: every pending say and play resolves as interrupted. */
|
|
86
|
+
stopSay(): Promise<void>;
|
|
87
|
+
play(url: string, options?: PlayOptions): Promise<PlayResult>;
|
|
88
|
+
/** Waits for speech (turn.ended) or digits (dtmf), whichever comes first. */
|
|
89
|
+
gather(options?: GatherOptions): Promise<GatherResult>;
|
|
90
|
+
/**
|
|
91
|
+
* Starts listening or changes how AppBridge listens (`listen.update`): recognition language,
|
|
92
|
+
* hints, and the turn-taking patience knob (`low`, `normal` or `high`).
|
|
93
|
+
*/
|
|
94
|
+
listen(options?: {
|
|
95
|
+
language?: string;
|
|
96
|
+
hints?: string[];
|
|
97
|
+
patience?: "low" | "normal" | "high";
|
|
98
|
+
}): Promise<void>;
|
|
99
|
+
/** Requests opt-in events: "turn.estimate", "transcript.partial". */
|
|
100
|
+
subscribe(events: ("turn.estimate" | "transcript.partial")[]): Promise<void>;
|
|
101
|
+
on<E extends SessionEventName>(event: E, cb: (data: SessionEventMap[E]) => void): this;
|
|
102
|
+
off<E extends SessionEventName>(event: E, cb: (data: SessionEventMap[E]) => void): this;
|
|
103
|
+
playDtmf(digits: string): Promise<void>;
|
|
104
|
+
/** Transfers the call; resolves when AppBridge ends the session with reason transfer. */
|
|
105
|
+
transfer(to: string, options?: TransferOptions): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Completes the pending warm transfer: the agent leaves, caller and new party stay. Resolves
|
|
108
|
+
* when the session ends with reason transfer; rejects with a SessionError
|
|
109
|
+
* (`transfer_not_answered`) when the new party has not answered yet, which leaves the transfer
|
|
110
|
+
* pending: wait for the `transfer.answered` event and call it again.
|
|
111
|
+
*/
|
|
112
|
+
completeTransfer(): Promise<void>;
|
|
113
|
+
hold(options?: {
|
|
114
|
+
music?: string;
|
|
115
|
+
}): Promise<void>;
|
|
116
|
+
resume(): Promise<void>;
|
|
117
|
+
/** Hands the call to another app with a payload; resolves when this session ends. */
|
|
118
|
+
handoff(app: string, payload?: Record<string, unknown>): Promise<void>;
|
|
119
|
+
/** Hangs up; resolves when the session ends. */
|
|
120
|
+
hangup(): Promise<void>;
|
|
121
|
+
/**
|
|
122
|
+
* Adds a note to the call record (record.note).
|
|
123
|
+
*
|
|
124
|
+
* @internal Reserved for Fonoster's own apps (Autopilot); not part of the public SDK and may change.
|
|
125
|
+
*/
|
|
126
|
+
note(data: Record<string, unknown>): Promise<void>;
|
|
127
|
+
/**
|
|
128
|
+
* Adds model token usage to the call (usage.report, add-usage-metering).
|
|
129
|
+
*
|
|
130
|
+
* @internal Reserved for Fonoster's own Autopilot; AppBridge refuses it from other apps.
|
|
131
|
+
*/
|
|
132
|
+
reportUsage(lines: Record<string, unknown>[]): Promise<void>;
|
|
133
|
+
/** Resolves at once: AppBridge answers the call on connect (voice-sdk S2). */
|
|
134
|
+
answer(): Promise<void>;
|
|
135
|
+
/** Feeds one frame from the socket. */
|
|
136
|
+
receive(data: Buffer, isBinary: boolean): void;
|
|
137
|
+
/** The socket closed. */
|
|
138
|
+
closed(): void;
|
|
139
|
+
private onMessage;
|
|
140
|
+
private onError;
|
|
141
|
+
private input;
|
|
142
|
+
private feedGather;
|
|
143
|
+
private armGatherTimer;
|
|
144
|
+
private endGather;
|
|
145
|
+
private nextId;
|
|
146
|
+
private send;
|
|
147
|
+
/** Sends a command whose only answer is a possible error (logged as a session error). */
|
|
148
|
+
private command;
|
|
149
|
+
private awaitOutput;
|
|
150
|
+
/**
|
|
151
|
+
* The app owns interruption (appbridge B22): when the caller starts talking over an interruptible
|
|
152
|
+
* say or play, the SDK cuts it off with clear; a non-interruptible one keeps playing.
|
|
153
|
+
*/
|
|
154
|
+
private bargeIn;
|
|
155
|
+
private take;
|
|
156
|
+
private untilEnd;
|
|
157
|
+
private finish;
|
|
158
|
+
private emit;
|
|
159
|
+
private compact;
|
|
160
|
+
private without;
|
|
161
|
+
}
|