@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.
Files changed (68) hide show
  1. package/README.md +65 -0
  2. package/build.mjs +204 -0
  3. package/package.json +29 -0
  4. package/skills/pinecall-concepts/SKILL.md +41 -0
  5. package/skills/pinecall-concepts/references/concepts/agents-and-channels.md +155 -0
  6. package/skills/pinecall-concepts/references/concepts/deployment-topologies.md +120 -0
  7. package/skills/pinecall-concepts/references/concepts/hot-reload.md +119 -0
  8. package/skills/pinecall-concepts/references/concepts/philosophy.md +100 -0
  9. package/skills/pinecall-concepts/references/concepts/server-vs-client-llm.md +119 -0
  10. package/skills/pinecall-examples/SKILL.md +59 -0
  11. package/skills/pinecall-examples/references/examples/browser-widget.md +206 -0
  12. package/skills/pinecall-examples/references/examples/chat-bot.md +184 -0
  13. package/skills/pinecall-examples/references/examples/headless-agent.md +121 -0
  14. package/skills/pinecall-examples/references/examples/index.md +183 -0
  15. package/skills/pinecall-examples/references/examples/multi-channel-bot.md +173 -0
  16. package/skills/pinecall-examples/references/examples/outbound-dispatch.md +109 -0
  17. package/skills/pinecall-examples/references/examples/turn-detection.md +150 -0
  18. package/skills/pinecall-guides/SKILL.md +68 -0
  19. package/skills/pinecall-guides/references/guides/call-ringing.md +149 -0
  20. package/skills/pinecall-guides/references/guides/conversation-history.md +377 -0
  21. package/skills/pinecall-guides/references/guides/dev-mode.md +130 -0
  22. package/skills/pinecall-guides/references/guides/events.md +677 -0
  23. package/skills/pinecall-guides/references/guides/human-takeover.md +184 -0
  24. package/skills/pinecall-guides/references/guides/inbound-voice.md +201 -0
  25. package/skills/pinecall-guides/references/guides/knowledge-bases.md +166 -0
  26. package/skills/pinecall-guides/references/guides/live-listening.md +199 -0
  27. package/skills/pinecall-guides/references/guides/multi-tenant.md +158 -0
  28. package/skills/pinecall-guides/references/guides/outbound-calls.md +279 -0
  29. package/skills/pinecall-guides/references/guides/sse-streaming.md +207 -0
  30. package/skills/pinecall-guides/references/guides/testing-agents.md +272 -0
  31. package/skills/pinecall-guides/references/guides/tools-and-functions.md +254 -0
  32. package/skills/pinecall-guides/references/guides/webrtc-browser.md +200 -0
  33. package/skills/pinecall-guides/references/guides/whatsapp.md +370 -0
  34. package/skills/pinecall-guides/references/guides/ws-streaming.md +235 -0
  35. package/skills/pinecall-quickstart/SKILL.md +54 -0
  36. package/skills/pinecall-quickstart/references/index.md +123 -0
  37. package/skills/pinecall-quickstart/references/quickstart.md +185 -0
  38. package/skills/pinecall-reference/SKILL.md +43 -0
  39. package/skills/pinecall-reference/references/reference/cli.md +578 -0
  40. package/skills/pinecall-reference/references/reference/events.md +366 -0
  41. package/skills/pinecall-reference/references/reference/llm-providers.md +263 -0
  42. package/skills/pinecall-reference/references/reference/rest-api.md +122 -0
  43. package/skills/pinecall-reference/references/reference/session-limits.md +119 -0
  44. package/skills/pinecall-reference/references/reference/stt-providers.md +174 -0
  45. package/skills/pinecall-reference/references/reference/tts-providers.md +149 -0
  46. package/skills/pinecall-sdk-api/SKILL.md +56 -0
  47. package/skills/pinecall-sdk-api/references/api/agent.md +328 -0
  48. package/skills/pinecall-sdk-api/references/api/call.md +324 -0
  49. package/skills/pinecall-sdk-api/references/api/pinecall.md +186 -0
  50. package/skills/pinecall-sdk-api/references/api/reply-stream.md +148 -0
  51. package/skills/pinecall-security/SKILL.md +37 -0
  52. package/skills/pinecall-security/references/security.md +138 -0
  53. package/skills/pinecall-web-chat/SKILL.md +38 -0
  54. package/skills/pinecall-web-chat/references/web/chat/chat-session.md +178 -0
  55. package/skills/pinecall-web-chat/references/web/chat/overview.md +98 -0
  56. package/skills/pinecall-web-components/SKILL.md +37 -0
  57. package/skills/pinecall-web-components/references/web/components/overview.md +128 -0
  58. package/skills/pinecall-web-voice/SKILL.md +40 -0
  59. package/skills/pinecall-web-voice/references/web/core/datachannel-protocol.md +149 -0
  60. package/skills/pinecall-web-voice/references/web/core/overview.md +70 -0
  61. package/skills/pinecall-web-voice/references/web/core/state-and-phases.md +153 -0
  62. package/skills/pinecall-web-voice/references/web/core/voice-session.md +279 -0
  63. package/skills/pinecall-web-widget/SKILL.md +41 -0
  64. package/skills/pinecall-web-widget/references/web/widget/overview.md +67 -0
  65. package/skills/pinecall-web-widget/references/web/widget/props.md +291 -0
  66. package/skills/pinecall-web-widget/references/web/widget/theming.md +131 -0
  67. package/skills/pinecall-web-widget/references/web/widget/tools-api.md +381 -0
  68. 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
+ ![Real-time event flow lifecycle](/assets/diagrams/event-flow-lifecycle.png)
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