@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,109 @@
1
+ ---
2
+ title: "Outbound Dispatch"
3
+ description: "CSV-driven outbound campaign with rate limiting, dedup, and result writeback."
4
+ ---
5
+
6
+ # Outbound Dispatch
7
+
8
+ > **Source:** [`examples/outbound-dispatch/`](https://github.com/pinecall/sdk/tree/main/examples/outbound-dispatch)
9
+
10
+ This example builds a complete outbound campaign system that:
11
+
12
+ 1. Reads leads from a CSV file
13
+ 2. Dispatches personalized appointment reminder calls
14
+ 3. Uses per-call `promptVars` to inject contact details into the AI prompt
15
+ 4. Lets the AI confirm/cancel via a tool that writes results back to CSV
16
+ 5. Handles no-answer, busy, and rejection automatically
17
+
18
+ ## Architecture
19
+
20
+ ![Outbound dispatch architecture](/assets/diagrams/outbound-dispatch-arch.png)
21
+
22
+ ## Prerequisites
23
+
24
+ - A Pinecall API key with outbound calling enabled
25
+ - A phone number (Twilio) registered in your Pinecall organization
26
+ - Node.js ≥ 18
27
+
28
+ ## Setup
29
+
30
+ ```bash
31
+ cd examples/outbound-dispatch
32
+ cp .env.example .env
33
+ # Edit .env with your API key and phone
34
+ npm install
35
+ ```
36
+
37
+ ## CSV format
38
+
39
+ The CSV must have a header row. The dispatcher skips rows that already have a `status` value:
40
+
41
+ ```csv
42
+ name,phone,service,date,time
43
+ Maria,+14155551234,Eye Exam,June 12,10:00 AM
44
+ Carlos,+14155559876,Physiotherapy,June 15,5:30 PM
45
+ ```
46
+
47
+ After calls complete, the CSV is updated:
48
+
49
+ ```csv
50
+ name,phone,service,date,time,status
51
+ Maria,+14155551234,Eye Exam,June 12,10:00 AM,confirmed
52
+ Carlos,+14155559876,Physiotherapy,June 15,5:30 PM,no_answer
53
+ ```
54
+
55
+ ## Key concepts
56
+
57
+ ### Content-based dedup
58
+
59
+ Records are identified by `phone + service + date`, not by row index. Two identical CSV entries produce the same ID and are dispatched only once:
60
+
61
+ ```javascript
62
+ mapRow: (row) => ({
63
+ id: `${row.phone}-${row.service}-${row.date}`,
64
+ // ...
65
+ })
66
+ ```
67
+
68
+ ### Phone-level dedup
69
+
70
+ The hub tracks active phones. If Bernardo has two appointments (Eye Exam + Physio), only one call runs at a time. The second dispatches automatically after the first ends.
71
+
72
+ ### Prompt variables
73
+
74
+ Each call sends per-call context via `promptVars` on the record returned by `mapRow`:
75
+
76
+ ```javascript
77
+ promptVars: {
78
+ appointment_details: `Name: ${row.name}\nService: ${row.service}\nDate: ${row.date}\nTime: ${row.time}`,
79
+ },
80
+ ```
81
+
82
+ These replace `{{appointment_details}}` in the agent prompt.
83
+
84
+ ### Lifecycle callbacks
85
+
86
+ Handle calls that end without the AI calling a tool (rejected, no answer):
87
+
88
+ ```javascript
89
+ csv.onCompleted = (record, callId, reason) => {
90
+ // Don't overwrite if the tool already wrote a status
91
+ writeResultToCsv(record.phone, record.service, reason);
92
+ };
93
+
94
+ csv.onFailed = (record, error) => {
95
+ writeResultToCsv(record.phone, record.service, "no_answer");
96
+ };
97
+ ```
98
+
99
+ ### Dial rejection
100
+
101
+ `agent.dial()` rejects immediately when Twilio reports `busy`, `no-answer`, `failed`, or `canceled` — no 30-second timeout. The hub catches this in `onFailed`.
102
+
103
+ ## Run
104
+
105
+ ```bash
106
+ node server.js
107
+ ```
108
+
109
+ Add rows to `data/leads.csv` while the script is running — the dispatcher detects new rows on every poll cycle (default 5s) and places calls automatically.
@@ -0,0 +1,150 @@
1
+ ---
2
+ title: "Example: Turn Detection"
3
+ description: "Debug turn events in real-time — per-turn containers showing the full state machine lifecycle."
4
+ ---
5
+
6
+ # Example: Turn Detection
7
+
8
+ A dev-friendly debug tool that shows **every turn event in real-time**, grouped into visual containers that mirror the server-side turn state machine. Each turn container shows state transitions as they happen.
9
+
10
+ ## State machine
11
+
12
+ The server maintains a 5-state machine for every call:
13
+
14
+ ![Turn detection state machine](/assets/diagrams/turn-state-machine.png)
15
+
16
+ ## What you'll see
17
+
18
+ Each turn is rendered as a bordered container:
19
+
20
+ ```
21
+ ┌ Turn #1 · IDLE → LISTENING 08:53:08.000
22
+ │ 🎙 speech.started
23
+ │ 💬 "Hola, ¿qué tal?"
24
+ │ 📝 "Hola. ¿Qué tal?"
25
+
26
+ │ LISTENING → BOT_PENDING prob=96%
27
+
28
+ │ BOT_PENDING → BOT_SPEAKING
29
+ │ 🤖 bot.speaking "..."
30
+ │ 🗣 "¡Hola! Estoy bien, gracias. ¿Y tú?"
31
+ │ 📨 message.confirmed
32
+ │ 🔇 bot.finished 3846ms
33
+
34
+ └ 4.2s
35
+ ```
36
+
37
+ ### Interruptions (barge-in)
38
+
39
+ When the user cuts off the bot, a highlighted interruption section appears:
40
+
41
+ ```
42
+ ┌ Turn #3 · IDLE → LISTENING 08:54:01.000
43
+ │ 🎙 speech.started
44
+ │ 📝 "Cuéntame un cuento largo"
45
+
46
+ │ LISTENING → BOT_PENDING prob=95%
47
+
48
+ │ BOT_PENDING → BOT_SPEAKING
49
+ │ 🤖 bot.speaking "..."
50
+ │ 🗣 "Érase una vez, en un reino muy lejano..."
51
+
52
+ ├─── ⚡ INTERRUPTION ─────────────────────────────
53
+ │ BOT_SPEAKING → LISTENING barge-in after 2100ms
54
+ │ 🗣 said: "Érase una vez, en un reino muy lejano..."
55
+ │ ↻ continuation — user keeps talking
56
+
57
+ │ 💬 "No, algo más corto"
58
+ │ 📝 "No, algo más corto"
59
+
60
+ │ LISTENING → BOT_PENDING prob=97%
61
+
62
+ │ BOT_PENDING → BOT_SPEAKING
63
+ │ 🤖 bot.speaking "..."
64
+ │ 🗣 "¡Claro! Había una vez un gato que..."
65
+ │ 🔇 bot.finished 3200ms
66
+
67
+ └ 12.4s
68
+ ```
69
+
70
+ ## The code
71
+
72
+ The key pattern: a `turn` tracker object that maps SDK events to server states:
73
+
74
+ ```typescript
75
+ const turn = {
76
+ id: 0, state: "IDLE", startTime: null, open: false,
77
+
78
+ log(icon, detail) {
79
+ console.log(` │ ${icon} ${detail}`);
80
+ },
81
+ transition(to, extra = "") {
82
+ const arrow = `${this.state} → ${to}`;
83
+ this.state = to;
84
+ console.log(` │\n │ ${arrow} ${extra}\n │`);
85
+ },
86
+ start(turnId) {
87
+ this.id = turnId;
88
+ this.state = "LISTENING";
89
+ this.startTime = Date.now();
90
+ this.open = true;
91
+ console.log(`\n ┌ Turn #${this.id} · IDLE → LISTENING`);
92
+ },
93
+ end() {
94
+ const dur = ((Date.now() - this.startTime) / 1000).toFixed(1);
95
+ this.state = "IDLE";
96
+ this.open = false;
97
+ console.log(` └ ${dur}s`);
98
+ },
99
+ };
100
+
101
+ // Map events to state transitions
102
+ agent.on("speech.started", (e) => { turn.start(e.turnId); });
103
+ agent.on("user.message", (e) => { turn.log("📝", `"${e.text}"`); });
104
+ agent.on("turn.end", () => { turn.transition("BOT_PENDING"); });
105
+ agent.on("bot.speaking", () => { turn.transition("BOT_SPEAKING"); });
106
+ agent.on("bot.word", (e, call) => { /* live preview via call.currentBotText */ });
107
+ agent.on("bot.finished", () => { turn.end(); });
108
+ agent.on("bot.interrupted", (e, call) => {
109
+ // Render interruption divider, show what was said
110
+ turn.interruption(e.playedMs, e.reason, call.currentBotText);
111
+ });
112
+ ```
113
+
114
+ The full runnable version is in [`examples/turn-detection/server.js`](https://github.com/pinecall/sdk/tree/master/examples/turn-detection) — with ANSI colors, timestamps, and the state machine diagram in the startup banner.
115
+
116
+ ## Run it
117
+
118
+ ```bash
119
+ cd examples/turn-detection
120
+ cp .env.example .env # edit with your API key and phone number
121
+ node server.js
122
+ ```
123
+
124
+ ## Configuration
125
+
126
+ Set in `.env`:
127
+
128
+ | Variable | Default | Description |
129
+ |---|---|---|
130
+ | `PINECALL_API_KEY` | required | Your API key |
131
+ | `PHONE` | required | Phone number to register |
132
+ | `MODEL` | `nova` | `nova` → SmartTurn + Silero, `flux` → native turns |
133
+ | `STT_LANG` | `es` | Language code (`en`, `es`, `ar`, `fr`, `de`, `pt`) |
134
+
135
+ ## State transitions to observe
136
+
137
+ | SDK Event | State Before | State After | Notes |
138
+ |---|---|---|---|
139
+ | `speech.started` | IDLE | LISTENING | New turn opens |
140
+ | `turn.pause` | LISTENING | LISTENING | SmartTurn analyzing (nova only) |
141
+ | `turn.end` | LISTENING | BOT_PENDING | User finished, LLM fires |
142
+ | `bot.speaking` | BOT_PENDING | BOT_SPEAKING | TTS audio starts |
143
+ | `bot.finished` | BOT_SPEAKING | IDLE | Turn closes |
144
+ | `bot.interrupted` | BOT_SPEAKING | LISTENING | Barge-in, user keeps talking |
145
+
146
+ ## What's next
147
+
148
+ - [Turn Detection guide](/concepts/turn-detection) — full explanation of the state machine
149
+ - [STT Providers](/reference/stt-providers) — language coverage and tuning parameters
150
+ - [Events reference](/reference/events) — all events including `bot.word` and `currentBotText`
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: pinecall-guides
3
+ description: >-
4
+ Task guides for building Pinecall agent features. Use when the user is building, configuring, or debugging with @pinecall/sdk. Keywords: inbound, outbound, whatsapp, tools, function calling, events, live listening, conversation history, human takeover, webrtc, multi-tenant, dev mode, testing, agent.dial, call.say, tool().
5
+ license: MIT
6
+ ---
7
+
8
+ # Guides
9
+
10
+ Task guides for building Pinecall agent features.
11
+
12
+ This skill bundles the official Pinecall documentation for **Guides**. The
13
+ table below indexes every page; open the `references/…` file for the full text
14
+ (loaded on demand). Source of truth: <https://docs.pinecall.io>.
15
+
16
+ | Page | What it covers | Open |
17
+ |------|----------------|------|
18
+ | **Inbound Voice** | Build a voice agent that answers phone calls. | [`references/guides/inbound-voice.md`](references/guides/inbound-voice.md) · [docs](https://docs.pinecall.io/guides/inbound-voice) |
19
+ | **Outbound Calls** | Make programmatic outbound phone calls with a greeting and metadata. | [`references/guides/outbound-calls.md`](references/guides/outbound-calls.md) · [docs](https://docs.pinecall.io/guides/outbound-calls) |
20
+ | **Events Guide** | Complete guide to every event in the Pinecall SDK — lifecycle, speech, turn, bot, tools, session, WhatsApp, and more. | [`references/guides/events.md`](references/guides/events.md) · [docs](https://docs.pinecall.io/guides/events) |
21
+ | **Live Listening** | Listen to active calls in real-time from a browser or custom client. | [`references/guides/live-listening.md`](references/guides/live-listening.md) · [docs](https://docs.pinecall.io/guides/live-listening) |
22
+ | **WhatsApp** | Build a WhatsApp messaging agent using Meta's Cloud API. | [`references/guides/whatsapp.md`](references/guides/whatsapp.md) · [docs](https://docs.pinecall.io/guides/whatsapp) |
23
+ | **Conversation History** | Save and restore conversations across calls so your agent remembers returning contacts. | [`references/guides/conversation-history.md`](references/guides/conversation-history.md) · [docs](https://docs.pinecall.io/guides/conversation-history) |
24
+ | **Human Takeover** | Pause the AI agent so a human can intervene in real-time conversations. | [`references/guides/human-takeover.md`](references/guides/human-takeover.md) · [docs](https://docs.pinecall.io/guides/human-takeover) |
25
+ | **WebRTC in the Browser** | Embed a Pinecall voice agent in your web app using the React widget. | [`references/guides/webrtc-browser.md`](references/guides/webrtc-browser.md) · [docs](https://docs.pinecall.io/guides/webrtc-browser) |
26
+ | **Tools and Functions** | Let your agent take actions: look up data, transfer calls, book appointments. | [`references/guides/tools-and-functions.md`](references/guides/tools-and-functions.md) · [docs](https://docs.pinecall.io/guides/tools-and-functions) |
27
+ | **Knowledge bases (RAG)** | Tutorial — ground a voice or chat agent on your own documents with retrieval-augmented generation. | [`references/guides/knowledge-bases.md`](references/guides/knowledge-bases.md) · [docs](https://docs.pinecall.io/guides/knowledge-bases) |
28
+ | **Multi-Tenant Dashboards** | Host many tenants on one Pinecall instance with scoped event streams. | [`references/guides/multi-tenant.md`](references/guides/multi-tenant.md) · [docs](https://docs.pinecall.io/guides/multi-tenant) |
29
+ | **SSE Event Streaming** | Stream agent events to your frontend in real time with Server-Sent Events. | [`references/guides/sse-streaming.md`](references/guides/sse-streaming.md) · [docs](https://docs.pinecall.io/guides/sse-streaming) |
30
+ | **WebSocket Event Streaming** | Stream agent events over WebSocket for bidirectional, real-time communication with your frontend. | [`references/guides/ws-streaming.md`](references/guides/ws-streaming.md) · [docs](https://docs.pinecall.io/guides/ws-streaming) |
31
+ | **Dev Mode** | Run dev and production agents on the same phone number, with zero extra Twilio cost. | [`references/guides/dev-mode.md`](references/guides/dev-mode.md) · [docs](https://docs.pinecall.io/guides/dev-mode) |
32
+ | **Call Ringing & Reject** | Screen incoming calls before answering — accept, reject, or route based on caller info. | [`references/guides/call-ringing.md`](references/guides/call-ringing.md) · [docs](https://docs.pinecall.io/guides/call-ringing) |
33
+ | **Testing Agents** | Automated QA for voice agents using YAML specs and LLM judges. | [`references/guides/testing-agents.md`](references/guides/testing-agents.md) · [docs](https://docs.pinecall.io/guides/testing-agents) |
34
+
35
+ ## Canonical agent
36
+
37
+ ```typescript
38
+ import { Pinecall } from "@pinecall/sdk";
39
+
40
+ const pc = new Pinecall(); // reads PINECALL_API_KEY, auto-connects
41
+
42
+ const agent = pc.agent("mara", {
43
+ prompt: "You are Mara, a friendly voice assistant. Be concise.",
44
+ llm: "openai/gpt-5-chat-latest",
45
+ voice: "elevenlabs/sarah",
46
+ stt: "deepgram/flux",
47
+ language: "en",
48
+ greeting: "Hello! How can I help?",
49
+ });
50
+ ```
51
+
52
+ ## House rules — always apply
53
+
54
+ - **Example defaults** (use these exact strings unless the user asks otherwise):
55
+ `stt: "deepgram/flux"`, `llm: "openai/gpt-5-chat-latest"`, `voice: "elevenlabs/sarah"`.
56
+ **NEVER use `deepgram/nova-2`** — it is not supported. Use `deepgram/nova-3`
57
+ only for languages Flux doesn't support (e.g. Arabic).
58
+ - **Turn detection & VAD are auto-derived from the STT provider — never set
59
+ `turnDetection` or `vad` manually.** Flux → native turns + native VAD;
60
+ every other STT → `smart_turn` + `silero`.
61
+ - **Greeting**: inbound → `greeting` field in `pc.agent()`; outbound → `greeting`
62
+ field in `agent.dial()`. It is sugar for `call.say()` in `call.started`.
63
+ - **Auth**: `new Pinecall()` reads `PINECALL_API_KEY` from env and auto-connects.
64
+ - Full documentation: <https://docs.pinecall.io>
65
+
66
+ ---
67
+ *Generated from `sdk/docs/` by `@pinecall/skills` — do not edit by hand; edit the
68
+ docs and re-run `node build.mjs`.*
@@ -0,0 +1,149 @@
1
+ ---
2
+ title: "Call Ringing & Reject"
3
+ description: "Screen incoming calls before answering — accept, reject, or route based on caller info."
4
+ ---
5
+
6
+ # Call Ringing & Reject
7
+
8
+ By default, Pinecall auto-accepts every incoming call. The **ringing** feature gives you a window to inspect the call _before_ it's answered, so you can:
9
+
10
+ - **Reject** spam or blacklisted callers
11
+ - **Route** calls to different agents based on caller ID
12
+ - **Log** incoming calls for analytics
13
+ - **Conditionally accept** based on time of day, capacity, etc.
14
+
15
+ ## How it works
16
+
17
+ ![Call ringing flow — accept or reject](/assets/diagrams/call-ringing-flow.png)
18
+
19
+ Without ringing enabled, the flow goes directly from ring → `call.started` (auto-accept).
20
+
21
+ ## Enable ringing
22
+
23
+ Pass `ringing: true` in the phone number config:
24
+
25
+ ```typescript
26
+ const agent = pc.agent("receptionist", {
27
+ phoneNumber: { number: "+13186330963", ringing: true },
28
+ });
29
+ ```
30
+
31
+ > **Warning:** Only phone channels support ringing. WebRTC and chat channels don't have a ringing phase.
32
+
33
+ ## Handle `call.ringing`
34
+
35
+ When a call comes in, the SDK emits `call.ringing` with a `RingingCall` object. This object has caller info but no audio — the call isn't connected yet.
36
+
37
+ ```typescript
38
+ agent.on("call.ringing", (call) => {
39
+ console.log(`Incoming: ${call.from} → ${call.to}`);
40
+ console.log(`Call SID: ${call.callId}`);
41
+
42
+ // Accept the call — proceeds to call.started
43
+ call.accept();
44
+ });
45
+ ```
46
+
47
+ ### RingingCall API
48
+
49
+ | Property | Type | Description |
50
+ |----------|------|-------------|
51
+ | `call.callId` | `string` | Twilio Call SID |
52
+ | `call.from` | `string` | Caller phone number (E.164) |
53
+ | `call.to` | `string` | Called phone number (E.164) |
54
+ | `call.accept()` | `void` | Accept the call — triggers `call.started` |
55
+ | `call.reject(reason?)` | `void` | Reject the call. Reason: `"busy"` or `"rejected"` |
56
+
57
+ ## Reject calls
58
+
59
+ Reject with an optional reason that maps to a Twilio rejection:
60
+
61
+ ```typescript
62
+ agent.on("call.ringing", (call) => {
63
+ if (BLACKLIST.has(call.from)) {
64
+ call.reject("busy"); // caller hears busy signal
65
+ return;
66
+ }
67
+ call.accept();
68
+ });
69
+ ```
70
+
71
+ | Reason | Caller experience |
72
+ |--------|-------------------|
73
+ | `"busy"` | Hears busy tone |
74
+ | `"rejected"` | Call is dropped immediately |
75
+ | _(none)_ | Defaults to `"busy"` |
76
+
77
+ ## Default behavior
78
+
79
+ If you don't call `accept()` or `reject()` within the timeout (configurable on the server, default ~5s), the call is **auto-accepted**. This prevents calls from hanging indefinitely if your handler crashes.
80
+
81
+ > **Note:** If you don't register a `call.ringing` handler at all, calls are auto-accepted immediately — same as before this feature existed. Ringing is fully opt-in.
82
+
83
+ ## Full example
84
+
85
+ ```typescript
86
+ import { Pinecall } from "@pinecall/sdk";
87
+
88
+ const pc = new Pinecall({ apiKey: process.env.PINECALL_API_KEY });
89
+
90
+ const BLACKLIST = new Set(["+15551234567", "+15559876543"]);
91
+
92
+ const agent = pc.agent("receptionist", {
93
+ voice: "elevenlabs/sarah",
94
+ language: "en",
95
+ stt: "deepgram/flux",
96
+ llm: "openai/gpt-5-chat-latest",
97
+ prompt: "You are a receptionist. Be brief and helpful.",
98
+ // Enable ringing on the phone channel
99
+ phoneNumber: { number: "+13186330963", ringing: true },
100
+ });
101
+
102
+ // Screen calls before answering
103
+ agent.on("call.ringing", (call) => {
104
+ console.log(`🔔 Incoming: ${call.from}`);
105
+
106
+ if (BLACKLIST.has(call.from)) {
107
+ console.log(`❌ Rejected: ${call.from} (blacklisted)`);
108
+ call.reject("busy");
109
+ return;
110
+ }
111
+
112
+ console.log(`✅ Accepted: ${call.from}`);
113
+ call.accept();
114
+ });
115
+
116
+ // Normal call lifecycle
117
+ agent.on("call.started", (call) => {
118
+ call.say("Thanks for calling! How can I help?");
119
+ });
120
+
121
+ agent.on("call.ended", (call, reason) => {
122
+ console.log(`📴 ${call.id} ended: ${reason} (${call.duration}s)`);
123
+ });
124
+ ```
125
+
126
+ Run the example from the SDK repo:
127
+
128
+ ```bash
129
+ cd sdk/examples/ringing
130
+ PHONE=+13186330963 node server.js
131
+ ```
132
+
133
+ ## Wire protocol
134
+
135
+ The ringing handshake uses two new events and commands:
136
+
137
+ | Direction | Message | Payload |
138
+ |-----------|---------|---------|
139
+ | Server → SDK | `call.ringing` | `{ call_id, from, to }` |
140
+ | SDK → Server | `call.accept` | `{ call_id }` |
141
+ | SDK → Server | `call.reject` | `{ call_id, reason }` |
142
+
143
+ For full wire protocol details, see `sdk-server/PROTOCOL.md`.
144
+
145
+ ## What's next
146
+
147
+ - [Inbound Voice](/guides/inbound-voice) — the standard (non-ringing) flow
148
+ - [Dev Mode](/guides/dev-mode) — route dev calls to your local agent
149
+ - [Events Reference](/reference/events) — all SDK events