@pinecall/skills 0.1.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 +65 -0
- package/build.mjs +204 -0
- package/package.json +29 -0
- package/skills/pinecall-concepts/SKILL.md +41 -0
- package/skills/pinecall-concepts/references/concepts/agents-and-channels.md +155 -0
- package/skills/pinecall-concepts/references/concepts/deployment-topologies.md +120 -0
- package/skills/pinecall-concepts/references/concepts/hot-reload.md +119 -0
- package/skills/pinecall-concepts/references/concepts/philosophy.md +100 -0
- package/skills/pinecall-concepts/references/concepts/server-vs-client-llm.md +119 -0
- package/skills/pinecall-examples/SKILL.md +59 -0
- package/skills/pinecall-examples/references/examples/browser-widget.md +206 -0
- package/skills/pinecall-examples/references/examples/chat-bot.md +184 -0
- package/skills/pinecall-examples/references/examples/headless-agent.md +121 -0
- package/skills/pinecall-examples/references/examples/index.md +183 -0
- package/skills/pinecall-examples/references/examples/multi-channel-bot.md +173 -0
- package/skills/pinecall-examples/references/examples/outbound-dispatch.md +109 -0
- package/skills/pinecall-examples/references/examples/turn-detection.md +150 -0
- package/skills/pinecall-guides/SKILL.md +68 -0
- package/skills/pinecall-guides/references/guides/call-ringing.md +149 -0
- package/skills/pinecall-guides/references/guides/conversation-history.md +377 -0
- package/skills/pinecall-guides/references/guides/dev-mode.md +130 -0
- package/skills/pinecall-guides/references/guides/events.md +677 -0
- package/skills/pinecall-guides/references/guides/human-takeover.md +184 -0
- package/skills/pinecall-guides/references/guides/inbound-voice.md +201 -0
- package/skills/pinecall-guides/references/guides/knowledge-bases.md +166 -0
- package/skills/pinecall-guides/references/guides/live-listening.md +199 -0
- package/skills/pinecall-guides/references/guides/multi-tenant.md +158 -0
- package/skills/pinecall-guides/references/guides/outbound-calls.md +279 -0
- package/skills/pinecall-guides/references/guides/sse-streaming.md +207 -0
- package/skills/pinecall-guides/references/guides/testing-agents.md +272 -0
- package/skills/pinecall-guides/references/guides/tools-and-functions.md +254 -0
- package/skills/pinecall-guides/references/guides/webrtc-browser.md +200 -0
- package/skills/pinecall-guides/references/guides/whatsapp.md +370 -0
- package/skills/pinecall-guides/references/guides/ws-streaming.md +235 -0
- package/skills/pinecall-quickstart/SKILL.md +54 -0
- package/skills/pinecall-quickstart/references/index.md +123 -0
- package/skills/pinecall-quickstart/references/quickstart.md +185 -0
- package/skills/pinecall-reference/SKILL.md +43 -0
- package/skills/pinecall-reference/references/reference/cli.md +578 -0
- package/skills/pinecall-reference/references/reference/events.md +366 -0
- package/skills/pinecall-reference/references/reference/llm-providers.md +263 -0
- package/skills/pinecall-reference/references/reference/rest-api.md +122 -0
- package/skills/pinecall-reference/references/reference/session-limits.md +119 -0
- package/skills/pinecall-reference/references/reference/stt-providers.md +174 -0
- package/skills/pinecall-reference/references/reference/tts-providers.md +149 -0
- package/skills/pinecall-sdk-api/SKILL.md +56 -0
- package/skills/pinecall-sdk-api/references/api/agent.md +328 -0
- package/skills/pinecall-sdk-api/references/api/call.md +324 -0
- package/skills/pinecall-sdk-api/references/api/pinecall.md +186 -0
- package/skills/pinecall-sdk-api/references/api/reply-stream.md +148 -0
- package/skills/pinecall-security/SKILL.md +37 -0
- package/skills/pinecall-security/references/security.md +138 -0
- package/skills/pinecall-web-chat/SKILL.md +38 -0
- package/skills/pinecall-web-chat/references/web/chat/chat-session.md +178 -0
- package/skills/pinecall-web-chat/references/web/chat/overview.md +98 -0
- package/skills/pinecall-web-components/SKILL.md +37 -0
- package/skills/pinecall-web-components/references/web/components/overview.md +128 -0
- package/skills/pinecall-web-voice/SKILL.md +40 -0
- package/skills/pinecall-web-voice/references/web/core/datachannel-protocol.md +149 -0
- package/skills/pinecall-web-voice/references/web/core/overview.md +70 -0
- package/skills/pinecall-web-voice/references/web/core/state-and-phases.md +153 -0
- package/skills/pinecall-web-voice/references/web/core/voice-session.md +279 -0
- package/skills/pinecall-web-widget/SKILL.md +41 -0
- package/skills/pinecall-web-widget/references/web/widget/overview.md +67 -0
- package/skills/pinecall-web-widget/references/web/widget/props.md +291 -0
- package/skills/pinecall-web-widget/references/web/widget/theming.md +131 -0
- package/skills/pinecall-web-widget/references/web/widget/tools-api.md +381 -0
- package/skills/pinecall-web-widget/references/web/widget/use-voice-session-hook.md +130 -0
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Events Guide"
|
|
3
|
+
description: "Complete guide to every event in the Pinecall SDK — lifecycle, speech, turn, bot, tools, session, WhatsApp, and more."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Events Guide
|
|
7
|
+
|
|
8
|
+
Every event the SDK emits, organized by category. Subscribe via `agent.on(event, handler)` — all call-scoped events include the `Call` as the final argument.
|
|
9
|
+
|
|
10
|
+
> **Quick reference:** For just the type signatures and payload shapes, see [Events Reference](/reference/events).
|
|
11
|
+
|
|
12
|
+
## How events work
|
|
13
|
+
|
|
14
|
+
Events flow from the **voice server** to your **SDK agent** over WebSocket. The server emits raw wire events (snake_case), and the SDK normalizes them to camelCase before invoking your handlers.
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
Voice Server → WebSocket → SDK Dispatcher → agent.on("event", handler)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
All handlers receive event-specific data as the first argument and the `Call` object as the last:
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
agent.on("event.name", (event, call) => {
|
|
24
|
+
// event — payload (varies per event)
|
|
25
|
+
// call — the Call object for this session
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Event catalog
|
|
32
|
+
|
|
33
|
+
### At a glance
|
|
34
|
+
|
|
35
|
+
| Category | Events | Transport |
|
|
36
|
+
|----------|--------|-----------|
|
|
37
|
+
| [Lifecycle](#lifecycle) | `call.started`, `call.ended`, `call.preparing`, `call.ringing`, `call.forwarded`, `call.recording` | All |
|
|
38
|
+
| [Transport start](#transport-specific-start-events) | `chat.started`, `whatsapp.started` | Chat, WA |
|
|
39
|
+
| [User speech](#user-speech) | `speech.started`, `speech.ended`, `user.speaking`, `user.message` | Voice, WebRTC |
|
|
40
|
+
| [Turn detection](#turn-detection) | `eager.turn`, `turn.end`, `turn.continued` | Voice, WebRTC |
|
|
41
|
+
| [Bot speech](#bot-speech) | `bot.speaking`, `bot.word`, `bot.finished`, `bot.interrupted` | Voice, WebRTC |
|
|
42
|
+
| [Bot preview](#bot-preview-pattern) | `bot.word` + `call.currentBotText` | Voice, WebRTC |
|
|
43
|
+
| [Messages](#message-lifecycle) | `message.confirmed`, `message.aborted`, `reply.rejected` | Voice, WebRTC |
|
|
44
|
+
| [Tools](#tools) | `llm.toolCall` | All |
|
|
45
|
+
| [Session](#session) | `session.idleWarning`, `session.timeout`, `session.paused`, `session.resumed` | Voice, WebRTC |
|
|
46
|
+
| [Hold & mute](#hold--mute) | `call.held`, `call.unheld`, `call.muted`, `call.unmuted` | Voice, WebRTC |
|
|
47
|
+
| [DTMF](#dtmf) | `call.dtmf_sent` | Voice |
|
|
48
|
+
| [WhatsApp](#whatsapp) | `whatsapp.message`, `whatsapp.response`, `whatsapp.status`, `whatsapp.sessionEnded` | WhatsApp |
|
|
49
|
+
| [Billing](#billing) | `credits.rejected`, `credits.exhausted` | All |
|
|
50
|
+
| [Audio](#audio-metrics) | `audio.metrics` | Voice, WebRTC |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Lifecycle
|
|
55
|
+
|
|
56
|
+
### `call.started`
|
|
57
|
+
|
|
58
|
+
A new **voice** call connected (phone or WebRTC).
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
agent.on("call.started", (call) => {
|
|
62
|
+
console.log(`📞 ${call.direction} call from ${call.from}`);
|
|
63
|
+
call.setPromptVars({ customer_name: "John" });
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
| Field | Type | Description |
|
|
68
|
+
|-------|------|-------------|
|
|
69
|
+
| `call.id` | `string` | Unique call ID |
|
|
70
|
+
| `call.from` | `string` | Caller number or `"webrtc"` |
|
|
71
|
+
| `call.to` | `string` | Agent phone or agent ID |
|
|
72
|
+
| `call.direction` | `"inbound" \| "outbound"` | Call direction |
|
|
73
|
+
| `call.transport` | `"phone" \| "webrtc"` | Transport type |
|
|
74
|
+
| `call.metadata` | `object` | Optional metadata from dial or alarm |
|
|
75
|
+
|
|
76
|
+
> **Note:** `call.started` fires **only for voice** transports. For chat → `chat.started`. For WhatsApp → `whatsapp.started`.
|
|
77
|
+
|
|
78
|
+
### `call.preparing`
|
|
79
|
+
|
|
80
|
+
Fires before **every** LLM generation — voice, chat, and WhatsApp. Use it to refresh prompt variables that need to be current on every turn (dates, format rules, etc.).
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
agent.on("call.preparing", (call) => {
|
|
84
|
+
call.setPromptVars({
|
|
85
|
+
date_block: buildFreshDate(),
|
|
86
|
+
format_rules: call.transport === "phone" ? VOICE_FORMAT : CHAT_FORMAT,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The server waits briefly (~150ms) for your handler to finish before proceeding with the LLM call.
|
|
92
|
+
|
|
93
|
+
### `call.ended`
|
|
94
|
+
|
|
95
|
+
The call ended. The `Call` is now fully populated with `duration`, `endedAt`, `messages`, and `transcript`.
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
agent.on("call.ended", (call, reason) => {
|
|
99
|
+
console.log(`Call ended: ${reason}, lasted ${call.duration}s`);
|
|
100
|
+
console.log(`Transcript:`, call.transcript);
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
| Field | Type | Description |
|
|
105
|
+
|-------|------|-------------|
|
|
106
|
+
| `reason` | `string` | Why it ended |
|
|
107
|
+
| `call.duration` | `number` | Duration in seconds |
|
|
108
|
+
| `call.endedAt` | `number` | Unix timestamp |
|
|
109
|
+
| `call.messages` | `array` | Full LLM message history |
|
|
110
|
+
| `call.transcript` | `array` | `[{ role, content }]` pairs |
|
|
111
|
+
|
|
112
|
+
**Reason values:** `hangup`, `timeout`, `idle_timeout`, `max_duration`, `no_answer`, `busy`, `failed`, `client_hangup`, `chat_completed`, `chat_error`.
|
|
113
|
+
|
|
114
|
+
### `call.ringing`
|
|
115
|
+
|
|
116
|
+
An inbound call is ringing — the caller hasn't been answered yet. Use with `call.screen()` to decide whether to accept or reject.
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
agent.on("call.ringing", (ringingCall) => {
|
|
120
|
+
if (isBlacklisted(ringingCall.from)) {
|
|
121
|
+
ringingCall.reject();
|
|
122
|
+
} else {
|
|
123
|
+
ringingCall.accept();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
See [Call Screening guide](/guides/call-ringing) for details.
|
|
129
|
+
|
|
130
|
+
### `call.forwarded`
|
|
131
|
+
|
|
132
|
+
The call was forwarded to another number via `call.forward()`.
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
agent.on("call.forwarded", (event, call) => {
|
|
136
|
+
console.log(`Call forwarded to ${event.to}`);
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### `call.recording`
|
|
141
|
+
|
|
142
|
+
A recording is available after the call ended. Contains the complete audio as base64-encoded WAV.
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
agent.on("call.recording", (event, call) => {
|
|
146
|
+
// event.audio — base64 WAV data
|
|
147
|
+
// event.duration_ms — recording duration
|
|
148
|
+
// event.format — "wav"
|
|
149
|
+
// event.sample_rate — typically 8000
|
|
150
|
+
fs.writeFileSync(`recording-${call.id}.wav`, Buffer.from(event.audio, "base64"));
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
> Only emitted when recording is enabled in the session config (`analysis.recording: true`).
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Transport-specific start events
|
|
159
|
+
|
|
160
|
+
### `chat.started`
|
|
161
|
+
|
|
162
|
+
A new chat session started (text-only, no voice).
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
agent.on("chat.started", (call) => {
|
|
166
|
+
// call.transport === "chat"
|
|
167
|
+
call.setPromptVars({ format: "markdown" });
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### `whatsapp.started`
|
|
172
|
+
|
|
173
|
+
A new WhatsApp session started (first message from a contact).
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
agent.on("whatsapp.started", (call, session) => {
|
|
177
|
+
// call — universal Call object
|
|
178
|
+
// session — WhatsAppSession with contactPhone, contactName
|
|
179
|
+
call.setPromptVars({ customer_name: session.contactName });
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
See [WhatsApp guide](/guides/whatsapp) for the full session lifecycle.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## User speech
|
|
188
|
+
|
|
189
|
+
### `speech.started`
|
|
190
|
+
|
|
191
|
+
VAD detected the user started speaking (audio energy crossed the speech threshold).
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
agent.on("speech.started", (event, call) => {
|
|
195
|
+
// event.turn_id, event.confidence
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### `speech.ended`
|
|
200
|
+
|
|
201
|
+
VAD detected the user stopped speaking.
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
agent.on("speech.ended", (event, call) => {
|
|
205
|
+
// event.turn_id, event.duration_ms
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### `user.speaking`
|
|
210
|
+
|
|
211
|
+
Interim STT transcript — fires multiple times as the STT engine refines its guess.
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
agent.on("user.speaking", (event, call) => {
|
|
215
|
+
console.log(`Hearing: "${event.text}"`);
|
|
216
|
+
// Updates rapidly: "hel" → "hello" → "hello how" → "hello how are you"
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### `user.message`
|
|
221
|
+
|
|
222
|
+
Final confirmed user text. After this fires, `eager.turn` or `turn.end` follows shortly.
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
agent.on("user.message", (event, call) => {
|
|
226
|
+
console.log(`User said: "${event.text}"`);
|
|
227
|
+
// event.messageId — use for reply correlation
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Turn detection
|
|
234
|
+
|
|
235
|
+
Turn detection determines when the user finished their thought and the bot should respond. See [Turn Detection concept](/concepts/turn-detection) for how modes work.
|
|
236
|
+
|
|
237
|
+
### `eager.turn`
|
|
238
|
+
|
|
239
|
+
Early signal that the user *probably* finished a turn. Use for low-latency responses — start the LLM, but be ready to abort if `turn.continued` fires.
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
agent.on("eager.turn", (turn, call) => {
|
|
243
|
+
// turn.text — accumulated transcript
|
|
244
|
+
// turn.probability — confidence (0–1)
|
|
245
|
+
// turn.messageId — for in_reply_to validation
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### `turn.end`
|
|
250
|
+
|
|
251
|
+
Final turn signal — higher confidence than `eager.turn`. This is where most apps trigger the LLM.
|
|
252
|
+
|
|
253
|
+
```javascript
|
|
254
|
+
agent.on("turn.end", (turn, call) => {
|
|
255
|
+
call.reply(turn.text);
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### `turn.continued`
|
|
260
|
+
|
|
261
|
+
The user kept talking after a turn signal. Any active `ReplyStream` auto-aborts. Your handler doesn't need to do anything — just don't be surprised when the stream stops.
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
agent.on("turn.continued", (event, call) => {
|
|
265
|
+
console.log("User continued — aborting previous response");
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Bot speech
|
|
272
|
+
|
|
273
|
+
Bot speech follows this lifecycle:
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
bot.speaking → bot.word × N → bot.finished (completed normally)
|
|
277
|
+
bot.interrupted (user barged in)
|
|
278
|
+
message.confirmed (full text saved)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### `bot.speaking`
|
|
282
|
+
|
|
283
|
+
The bot started speaking a message.
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
agent.on("bot.speaking", (event, call) => {
|
|
287
|
+
// event.messageId — tracks this specific utterance
|
|
288
|
+
// event.text — full text for non-streaming replies (empty for replyStream)
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
For `call.say()` and `call.reply()`, `event.text` contains the full response. For `call.replyStream()`, text is empty — use `bot.word` events instead.
|
|
293
|
+
|
|
294
|
+
### `bot.word`
|
|
295
|
+
|
|
296
|
+
A single word was just played by TTS — synchronized with audio playback. Use for live captions, subtitles, or transcript UIs.
|
|
297
|
+
|
|
298
|
+
```javascript
|
|
299
|
+
agent.on("bot.word", (event, call) => {
|
|
300
|
+
// event.messageId — which message this word belongs to
|
|
301
|
+
// event.word — the word just spoken
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
> **Timing:** Words arrive spread across the audio duration, not all at once. A 5-second sentence = words arriving over 5 seconds.
|
|
306
|
+
|
|
307
|
+
### `bot.finished`
|
|
308
|
+
|
|
309
|
+
The bot finished speaking — TTS audio fully played.
|
|
310
|
+
|
|
311
|
+
```javascript
|
|
312
|
+
agent.on("bot.finished", (event, call) => {
|
|
313
|
+
// event.messageId
|
|
314
|
+
// event.durationMs — how long the bot spoke
|
|
315
|
+
console.log(`Done (${event.durationMs}ms): "${call.currentBotText}"`);
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
`call.currentBotText` is still available during this handler — it clears immediately after.
|
|
320
|
+
|
|
321
|
+
### `bot.interrupted`
|
|
322
|
+
|
|
323
|
+
The user cut off the bot mid-speech (barge-in).
|
|
324
|
+
|
|
325
|
+
```javascript
|
|
326
|
+
agent.on("bot.interrupted", (event, call) => {
|
|
327
|
+
// event.messageId
|
|
328
|
+
// event.playedMs — how long the bot spoke before interruption
|
|
329
|
+
// event.reason — "user_spoke" (after 2s) or "early" (before 2s)
|
|
330
|
+
console.log(`Interrupted after ${event.playedMs}ms, said: "${call.currentBotText}"`);
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Bot preview pattern
|
|
337
|
+
|
|
338
|
+
The **bot preview** pattern combines `bot.word` events with `call.currentBotText` to show a live, word-by-word preview of what the bot is saying — like real-time subtitles.
|
|
339
|
+
|
|
340
|
+
`call.currentBotText` accumulates each `bot.word` automatically:
|
|
341
|
+
- **Resets** on each new `bot.speaking`
|
|
342
|
+
- **Available** during `bot.finished` and `bot.interrupted` handlers
|
|
343
|
+
- **Clears** immediately after those handlers return
|
|
344
|
+
|
|
345
|
+
```javascript
|
|
346
|
+
// Live subtitles — grows word-by-word as the bot speaks
|
|
347
|
+
agent.on("bot.word", (event, call) => {
|
|
348
|
+
updateSubtitle(call.currentBotText);
|
|
349
|
+
// "¡Hola!"
|
|
350
|
+
// "¡Hola! Estoy"
|
|
351
|
+
// "¡Hola! Estoy bien,"
|
|
352
|
+
// "¡Hola! Estoy bien, gracias."
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Capture full text when bot finishes
|
|
356
|
+
agent.on("bot.finished", (event, call) => {
|
|
357
|
+
saveToTranscript("bot", call.currentBotText);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Capture partial text when user interrupts
|
|
361
|
+
agent.on("bot.interrupted", (event, call) => {
|
|
362
|
+
saveToTranscript("bot (interrupted)", call.currentBotText);
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Message lifecycle
|
|
369
|
+
|
|
370
|
+
### `message.confirmed`
|
|
371
|
+
|
|
372
|
+
The server acknowledged a bot message you sent (via `say`, `reply`, or `replyStream`). The message text is now saved to LLM history.
|
|
373
|
+
|
|
374
|
+
```javascript
|
|
375
|
+
agent.on("message.confirmed", (event, call) => {
|
|
376
|
+
// event.messageId
|
|
377
|
+
// event.text — the confirmed message text
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### `message.aborted`
|
|
382
|
+
|
|
383
|
+
A bot message was aborted before it could be confirmed — typically because the user barged in or a new turn started.
|
|
384
|
+
|
|
385
|
+
```javascript
|
|
386
|
+
agent.on("message.aborted", (event, call) => {
|
|
387
|
+
// event.messageId
|
|
388
|
+
// event.reason
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### `reply.rejected`
|
|
393
|
+
|
|
394
|
+
A bot reply was rejected because the `in_reply_to` message ID no longer matches the current user message. This happens when the user continued speaking after the bot started preparing a response.
|
|
395
|
+
|
|
396
|
+
```javascript
|
|
397
|
+
agent.on("reply.rejected", (event, call) => {
|
|
398
|
+
// event.messageId — the rejected bot message
|
|
399
|
+
// event.in_reply_to — what the reply referenced
|
|
400
|
+
// event.expected_reply_to — what the server expected
|
|
401
|
+
// event.reason — "message_obsolete" etc.
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
> This is a protocol-level event. You typically don't need to handle it — the SDK manages reply validation automatically.
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Tools
|
|
410
|
+
|
|
411
|
+
### `llm.toolCall`
|
|
412
|
+
|
|
413
|
+
The server-side LLM is requesting one or more tool calls. If you registered tools with `tool()`, the SDK auto-executes them and sends results back. This event still fires — use it for logging, metrics, or UI updates.
|
|
414
|
+
|
|
415
|
+
```javascript
|
|
416
|
+
agent.on("llm.toolCall", (data, call) => {
|
|
417
|
+
for (const tc of data.toolCalls) {
|
|
418
|
+
console.log(`🔧 ${tc.name}(${tc.arguments})`);
|
|
419
|
+
}
|
|
420
|
+
// data.msgId — correlation ID
|
|
421
|
+
// data.toolCalls — [{ id, name, arguments }]
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
See [Tools and Functions guide](/guides/tools-and-functions) for how to define tools.
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## Session
|
|
430
|
+
|
|
431
|
+
### `session.idleWarning`
|
|
432
|
+
|
|
433
|
+
Fires before idle timeout — the user hasn't spoken in a while. Use it to prompt them.
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
agent.on("session.idleWarning", (event, call) => {
|
|
437
|
+
// event.remainingSeconds — time left before timeout
|
|
438
|
+
// event.idleTimeoutSeconds — total idle timeout configured
|
|
439
|
+
call.say("Are you still there?");
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### `session.timeout`
|
|
444
|
+
|
|
445
|
+
A session limit was hit. The call is about to end.
|
|
446
|
+
|
|
447
|
+
```javascript
|
|
448
|
+
agent.on("session.timeout", (event, call) => {
|
|
449
|
+
// event.reason — "max_duration" | "idle_timeout"
|
|
450
|
+
call.say("We've reached the time limit. Goodbye!");
|
|
451
|
+
});
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### `session.paused`
|
|
455
|
+
|
|
456
|
+
Confirmation that the agent was paused (human-in-the-loop). Fires after `agent.pause()`.
|
|
457
|
+
|
|
458
|
+
```javascript
|
|
459
|
+
agent.on("session.paused", (event) => {
|
|
460
|
+
// event.sessionId — set for session-level pause
|
|
461
|
+
// event.contact — set for contact-level pause
|
|
462
|
+
// both undefined = global pause
|
|
463
|
+
});
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### `session.resumed`
|
|
467
|
+
|
|
468
|
+
Confirmation that the agent was resumed. Fires after `agent.resume()`.
|
|
469
|
+
|
|
470
|
+
```javascript
|
|
471
|
+
agent.on("session.resumed", (event) => {
|
|
472
|
+
// event.sessionId
|
|
473
|
+
// event.contact
|
|
474
|
+
});
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## Hold & mute
|
|
480
|
+
|
|
481
|
+
These events fire when you use the `call.hold()` / `call.unhold()` / `call.mute()` / `call.unmute()` methods.
|
|
482
|
+
|
|
483
|
+
### `call.held`
|
|
484
|
+
|
|
485
|
+
The call was placed on hold. Hold music starts playing.
|
|
486
|
+
|
|
487
|
+
```javascript
|
|
488
|
+
agent.on("call.held", (event, call) => {
|
|
489
|
+
console.log("📞 Call on hold");
|
|
490
|
+
});
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### `call.unheld`
|
|
494
|
+
|
|
495
|
+
The call was taken off hold. Normal conversation resumes.
|
|
496
|
+
|
|
497
|
+
```javascript
|
|
498
|
+
agent.on("call.unheld", (event, call) => {
|
|
499
|
+
console.log("📞 Call resumed");
|
|
500
|
+
});
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### `call.muted`
|
|
504
|
+
|
|
505
|
+
The mic was muted. Transcripts are buffered while muted.
|
|
506
|
+
|
|
507
|
+
```javascript
|
|
508
|
+
agent.on("call.muted", (event, call) => {
|
|
509
|
+
console.log("🔇 Mic muted");
|
|
510
|
+
});
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### `call.unmuted`
|
|
514
|
+
|
|
515
|
+
The mic was unmuted. Any speech captured while muted is available as buffered text.
|
|
516
|
+
|
|
517
|
+
```javascript
|
|
518
|
+
agent.on("call.unmuted", (event, call) => {
|
|
519
|
+
if (event.muted_transcript) {
|
|
520
|
+
console.log(`While muted, user said: "${event.muted_transcript}"`);
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## DTMF
|
|
528
|
+
|
|
529
|
+
### `call.dtmf_sent`
|
|
530
|
+
|
|
531
|
+
DTMF tones were sent on the call (via `call.sendDTMF()`).
|
|
532
|
+
|
|
533
|
+
```javascript
|
|
534
|
+
agent.on("call.dtmf_sent", (event, call) => {
|
|
535
|
+
// event.digits — the digits sent
|
|
536
|
+
});
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## WhatsApp
|
|
542
|
+
|
|
543
|
+
### `whatsapp.message`
|
|
544
|
+
|
|
545
|
+
Incoming WhatsApp message from the user.
|
|
546
|
+
|
|
547
|
+
```javascript
|
|
548
|
+
agent.on("whatsapp.message", (event) => {
|
|
549
|
+
// event.sessionId
|
|
550
|
+
// event.from — contact phone number
|
|
551
|
+
// event.name — contact name
|
|
552
|
+
// event.type — "text" | "audio" | "image" | "video" | "document"
|
|
553
|
+
// event.text — message text (for audio, this is the transcript)
|
|
554
|
+
// event.messageId
|
|
555
|
+
// event.paused — true when agent is paused (human-in-the-loop)
|
|
556
|
+
});
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
When `paused` is `true`, the AI did **not** respond — a human should handle this message via `agent.sendMessage()`.
|
|
560
|
+
|
|
561
|
+
### `whatsapp.response`
|
|
562
|
+
|
|
563
|
+
The agent sent a WhatsApp response.
|
|
564
|
+
|
|
565
|
+
```javascript
|
|
566
|
+
agent.on("whatsapp.response", (event) => {
|
|
567
|
+
// event.sessionId
|
|
568
|
+
// event.to — recipient phone
|
|
569
|
+
// event.text — message text
|
|
570
|
+
// event.source — "human" when sent by operator via agent.sendMessage()
|
|
571
|
+
});
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### `whatsapp.status`
|
|
575
|
+
|
|
576
|
+
Delivery status update from Meta.
|
|
577
|
+
|
|
578
|
+
```javascript
|
|
579
|
+
agent.on("whatsapp.status", (event) => {
|
|
580
|
+
// event.status — "sent" | "delivered" | "read"
|
|
581
|
+
// event.recipient
|
|
582
|
+
// event.messageId
|
|
583
|
+
});
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### `whatsapp.sessionEnded`
|
|
587
|
+
|
|
588
|
+
A WhatsApp session ended (inactivity timeout or manual close).
|
|
589
|
+
|
|
590
|
+
```javascript
|
|
591
|
+
agent.on("whatsapp.sessionEnded", (event) => {
|
|
592
|
+
// event.session_id
|
|
593
|
+
// event.contact_phone
|
|
594
|
+
// event.duration
|
|
595
|
+
// event.message_count
|
|
596
|
+
});
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## Billing
|
|
602
|
+
|
|
603
|
+
### `credits.rejected`
|
|
604
|
+
|
|
605
|
+
The call was rejected at connection time because the org has no credits remaining.
|
|
606
|
+
|
|
607
|
+
```javascript
|
|
608
|
+
agent.on("credits.rejected", (event) => {
|
|
609
|
+
console.log("⛔ No credits — call rejected");
|
|
610
|
+
});
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### `credits.exhausted`
|
|
614
|
+
|
|
615
|
+
Credits ran out during an active call. The server will end the call shortly.
|
|
616
|
+
|
|
617
|
+
```javascript
|
|
618
|
+
agent.on("credits.exhausted", (event, call) => {
|
|
619
|
+
call.say("We've run out of credits. The call will end shortly.");
|
|
620
|
+
});
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
## Audio metrics
|
|
626
|
+
|
|
627
|
+
When you enable `analysis.send_audio_metrics`:
|
|
628
|
+
|
|
629
|
+
```javascript
|
|
630
|
+
agent.on("audio.metrics", (event, call) => {
|
|
631
|
+
// event.source — "user" | "bot"
|
|
632
|
+
// event.energyDb — -60 to 0
|
|
633
|
+
// event.rms — 0–1
|
|
634
|
+
// event.peak — 0–1
|
|
635
|
+
// event.isSpeech — VAD detection
|
|
636
|
+
// event.vadProb — 0–1
|
|
637
|
+
});
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
Use for live waveform UIs, energy meters, or VAD visualization. Fires every ~100ms.
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
## Real-time flow
|
|
645
|
+
|
|
646
|
+
Here's the complete sequence of events during a typical voice exchange:
|
|
647
|
+
|
|
648
|
+

|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
## SSE events
|
|
653
|
+
|
|
654
|
+
When streamed over SSE (via `pc.stream()` or `agent.stream()`), each event has an `event:` field and a JSON `data:` body:
|
|
655
|
+
|
|
656
|
+
```
|
|
657
|
+
event: user.message
|
|
658
|
+
data: {"callId":"CA123","text":"Hello","messageId":"msg_abc","agent":"mara"}
|
|
659
|
+
|
|
660
|
+
event: bot.word
|
|
661
|
+
data: {"callId":"CA123","word":"Hi","messageId":"msg_def","agent":"mara"}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
A `:ping` comment is sent every 30s as keepalive.
|
|
665
|
+
|
|
666
|
+
SSE streams include: `call.started`, `bot.speaking`, `bot.word`, `message.confirmed`, `user.speaking`, `user.message`, `call.ended`.
|
|
667
|
+
|
|
668
|
+
---
|
|
669
|
+
|
|
670
|
+
## What's next
|
|
671
|
+
|
|
672
|
+
- [Events Reference](/reference/events) — compact type signatures for all events
|
|
673
|
+
- [Call API](/api/call) — methods to call in response to events
|
|
674
|
+
- [Turn Detection](/concepts/turn-detection) — how turn modes affect event timing
|
|
675
|
+
- [Tools and Functions](/guides/tools-and-functions) — handling `llm.toolCall`
|
|
676
|
+
- [WhatsApp](/guides/whatsapp) — WhatsApp session lifecycle
|
|
677
|
+
- [Live Listening](/guides/live-listening) — `audio.metrics` for visualization
|