@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,184 @@
1
+ ---
2
+ title: "Example: Chat Bot"
3
+ description: "Text chat agent using @pinecall/web/chat — same agent, text instead of voice."
4
+ ---
5
+
6
+ # Example: Chat Bot
7
+
8
+ A text-based chat agent using `@pinecall/web/chat`. Same Pinecall agent, same prompt, same tools — but text over WebSocket instead of audio over WebRTC.
9
+
10
+ ## What it does
11
+
12
+ A booking assistant for a spa. Users chat via a React widget, the agent responds with streamed text, and calls a tool to check availability.
13
+
14
+ ## Backend — `server.js`
15
+
16
+ ```typescript
17
+ import { Pinecall, tool } from "@pinecall/sdk";
18
+ import { z } from "zod";
19
+ import express from "express";
20
+
21
+ const pc = new Pinecall();
22
+
23
+ const getAvailability = tool({
24
+ name: "getAvailability",
25
+ description: "Check available time slots for a service and date.",
26
+ schema: z.object({
27
+ service: z.string(),
28
+ date: z.string().describe("YYYY-MM-DD"),
29
+ }),
30
+ execute: async ({ service, date }) => ({
31
+ slots: ["10:00", "11:30", "14:00", "16:00"],
32
+ }),
33
+ });
34
+
35
+ const agent = pc.agent("florencia", {
36
+ prompt: `You are Florencia, the booking assistant for Blossom Beauty Spa.
37
+ Help customers book appointments. Be warm and concise.
38
+ Available services: Haircut ($30), Color ($80), Facial ($60), Massage ($90).`,
39
+ llm: "openai/gpt-5-chat-latest",
40
+ language: "es",
41
+ allowedOrigins: ["http://localhost:*"],
42
+ tools: [getAvailability],
43
+ });
44
+
45
+ const app = express();
46
+ app.use(express.static("public"));
47
+ app.get("/events", (req, res) => agent.stream(res));
48
+ app.listen(3000, () => console.log("http://localhost:3000"));
49
+ ```
50
+
51
+ ## Frontend — React chat widget
52
+
53
+ ```tsx
54
+ import { usePinecallChat } from "@pinecall/web/chat/react";
55
+
56
+ function Chat() {
57
+ const { messages, send, connected, typing } = usePinecallChat({
58
+ agent: "florencia",
59
+ });
60
+
61
+ if (!connected) return <p>Connecting...</p>;
62
+
63
+ return (
64
+ <div className="chat">
65
+ <div className="messages">
66
+ {messages.map((m) => (
67
+ <div key={m.id} className={`msg ${m.role}`}>
68
+ <strong>{m.role === "user" ? "You" : "Florencia"}:</strong>{" "}
69
+ {m.text}
70
+ {m.isStreaming && "▊"}
71
+ </div>
72
+ ))}
73
+ {typing && <div className="msg bot typing">Florencia is typing…</div>}
74
+ </div>
75
+
76
+ <input
77
+ placeholder="Type a message..."
78
+ onKeyDown={(e) => {
79
+ if (e.key === "Enter" && e.currentTarget.value.trim()) {
80
+ send(e.currentTarget.value);
81
+ e.currentTarget.value = "";
82
+ }
83
+ }}
84
+ />
85
+ </div>
86
+ );
87
+ }
88
+ ```
89
+
90
+ The `usePinecallChat` hook handles token fetching (via `allowedOrigins`), WebSocket lifecycle, streamed messages (token-by-token), typing indicator, and auto-reconnect.
91
+
92
+ ## Rendering tool results in the UI
93
+
94
+ Tools execute on the backend — the agent calls `getAvailability`, gets slots, and **responds with text** describing them. But you can also show rich UI alongside the chat.
95
+
96
+ Use `setContext` to sync frontend state into the agent's prompt, so it knows what the user is seeing:
97
+
98
+ ```tsx
99
+ import { usePinecallChat } from "@pinecall/web/chat/react";
100
+ import { useState, useEffect } from "react";
101
+
102
+ function BookingChat() {
103
+ const { messages, send, connected, typing, setContext } = usePinecallChat({
104
+ agent: "florencia",
105
+ });
106
+ const [selectedSlot, setSelectedSlot] = useState<string | null>(null);
107
+
108
+ // Sync selection to the agent's prompt
109
+ useEffect(() => {
110
+ if (selectedSlot) {
111
+ setContext("user_selection", `User selected time slot: ${selectedSlot}`);
112
+ }
113
+ return () => setContext("user_selection", null);
114
+ }, [selectedSlot, setContext]);
115
+
116
+ if (!connected) return <p>Connecting...</p>;
117
+
118
+ return (
119
+ <div className="chat">
120
+ <div className="messages">
121
+ {messages.map((m) => (
122
+ <div key={m.id} className={`msg ${m.role}`}>
123
+ <strong>{m.role === "user" ? "You" : "Florencia"}:</strong>{" "}
124
+ {m.text}
125
+ {m.isStreaming && "▊"}
126
+ </div>
127
+ ))}
128
+ {typing && <div className="msg bot typing">Florencia is typing…</div>}
129
+ </div>
130
+
131
+ {/* Quick-select buttons — inject user choice as text */}
132
+ <div className="quick-actions">
133
+ {["Haircut", "Facial", "Massage"].map((service) => (
134
+ <button
135
+ key={service}
136
+ onClick={() => send(`I'd like to book a ${service}`)}
137
+ >
138
+ {service}
139
+ </button>
140
+ ))}
141
+ </div>
142
+
143
+ <input
144
+ placeholder="Type a message..."
145
+ onKeyDown={(e) => {
146
+ if (e.key === "Enter" && e.currentTarget.value.trim()) {
147
+ send(e.currentTarget.value);
148
+ e.currentTarget.value = "";
149
+ }
150
+ }}
151
+ />
152
+ </div>
153
+ );
154
+ }
155
+ ```
156
+
157
+ ### Chat API reference
158
+
159
+ | API | What it does |
160
+ |-----|-------------|
161
+ | `messages` | Array of `{ id, role, text, isStreaming }` — full conversation |
162
+ | `send(text)` | Send a user message |
163
+ | `typing` | True while the bot is streaming |
164
+ | `setContext(key, value)` | Inject context into the LLM prompt (e.g. form state) |
165
+ | `connected` | True when WebSocket is connected |
166
+
167
+ ### How `setContext` works in chat
168
+
169
+ Same as voice — the server appends your context as a `## UI Context` section in the system prompt. The agent can reference it naturally:
170
+
171
+ ```
172
+ "The user selected the 10:00 AM slot, now ask for their name."
173
+ ```
174
+
175
+ ## Same agent, voice + chat
176
+
177
+ The same agent handles **both** text (chat) and voice (WebRTC) automatically. Same prompt, same tools, same conversation context — no extra configuration needed.
178
+
179
+ ## What's next
180
+
181
+ - [`@pinecall/web/chat` reference](/web/chat/overview) — full ChatSession API
182
+ - [Browser Widget example](/examples/browser-widget) — the voice equivalent with interactive tool UI
183
+ - [SSE Event Streaming](/guides/sse-streaming) — build a live dashboard
184
+
@@ -0,0 +1,121 @@
1
+ ---
2
+ title: "Example: Headless Agent"
3
+ description: "Complete runnable example — a phone support agent with zero web server."
4
+ ---
5
+
6
+ # Example: Headless Agent
7
+
8
+ A complete, production-ready voice agent in a single file. No web server, no frontend, no infrastructure beyond a Node.js process. This pattern is ideal for intercoms, IoT devices, single-purpose phone bots, and WhatsApp-only deployments.
9
+
10
+ Run it with `pinecall run agent/index.js`.
11
+
12
+ ## What it does
13
+
14
+ `agent/index.js` is a phone support agent for an e-commerce store. It answers calls, looks up orders by phone number, and handles returns.
15
+
16
+ ## The complete file
17
+
18
+ ```typescript
19
+ // agent/index.js
20
+ import { Pinecall, tool } from "@pinecall/sdk";
21
+ import { z } from "zod";
22
+ import { promises as fs } from "node:fs";
23
+
24
+ const pc = new Pinecall();
25
+
26
+ const lookupOrder = tool({
27
+ name: "lookupOrder",
28
+ description: "Look up the customer's most recent order.",
29
+ schema: z.object({
30
+ phone: z.string().describe("Customer phone number"),
31
+ }),
32
+ execute: async ({ phone }) => {
33
+ // your logic — query your database, etc.
34
+ return { orderId: "ORD-1234", status: "shipped", eta: "tomorrow" };
35
+ },
36
+ });
37
+
38
+ export const agent = pc.agent("support", {
39
+ voice: "elevenlabs/sarah",
40
+ language: "en",
41
+ llm: "openai/gpt-5-chat-latest",
42
+ stt: "deepgram/flux",
43
+ prompt: `You are a support agent for an online store.
44
+ Help customers check order status and process returns.
45
+ Be friendly, brief, and professional.`,
46
+ phoneNumber: "+13186330963",
47
+ greeting: "Hi! Thanks for calling. How can I help you today?",
48
+ tools: [lookupOrder],
49
+ });
50
+
51
+ // Log every call to disk
52
+ agent.on("call.ended", async (call, reason) => {
53
+ await fs.appendFile("./calls.jsonl", JSON.stringify({
54
+ id: call.id, from: call.from, duration: call.duration,
55
+ reason, endedAt: new Date().toISOString(),
56
+ }) + "\n");
57
+ });
58
+ ```
59
+
60
+ ## Run it
61
+
62
+ ```bash
63
+ pinecall run agent/index.js
64
+ ```
65
+
66
+ You'll see the boot banner with agent name, model, and phone number. When calls come in, the runner displays a live transcript with tool calls.
67
+
68
+ That's it. No web server, no token endpoint, no frontend. The agent answers calls to `+13186330963` and logs every call to `calls.jsonl`. When the LLM calls `lookupOrder`, the SDK validates the args with Zod and runs the execute function automatically.
69
+
70
+ ## Adding more tools
71
+
72
+ Just define more `tool()` objects and include them in the array:
73
+
74
+ ```typescript
75
+ const processReturn = tool({
76
+ name: "processReturn",
77
+ description: "Start a return process for an order.",
78
+ schema: z.object({
79
+ orderId: z.string().describe("The order ID to return"),
80
+ reason: z.string().describe("Reason for the return"),
81
+ }),
82
+ execute: async ({ orderId, reason }) => {
83
+ // your logic — create a return ticket, etc.
84
+ return { returnId: "RET-001", status: "initiated" };
85
+ },
86
+ });
87
+
88
+ const agent = pc.agent("support", {
89
+ // ...same config
90
+ tools: [lookupOrder, processReturn],
91
+ });
92
+ ```
93
+
94
+ ## Adding WhatsApp
95
+
96
+ Same headless pattern — add a channel:
97
+
98
+ ```typescript
99
+ agent.addWhatsapp({
100
+ phoneNumberId: process.env.WA_PHONE_NUMBER_ID,
101
+ accessToken: process.env.WA_TOKEN,
102
+ appSecret: process.env.WA_APP_SECRET,
103
+ });
104
+ ```
105
+
106
+ Now the agent answers both phone calls **and** WhatsApp messages. Same prompt, same tools, no extra code.
107
+
108
+ ## Deploy options
109
+
110
+ - **PM2 / systemd** — long-running daemon on a server
111
+ - **Docker container** — one image, multiple instances
112
+ - **Fly.io / Railway / Render** — managed processes
113
+
114
+ The agent only needs outbound network access to `voice.pinecall.io`. No inbound ports, no public IPs.
115
+
116
+ ## What's next
117
+
118
+ - [Multi-channel bot example](/examples/multi-channel-bot)
119
+ - [Chat bot example](/examples/chat-bot)
120
+ - [Browser widget example](/examples/browser-widget)
121
+ - [Deployment topologies](/concepts/deployment-topologies)
@@ -0,0 +1,183 @@
1
+ ---
2
+ title: "Examples"
3
+ description: "Runnable examples showing Pinecall SDK features in action."
4
+ ---
5
+
6
+ # Examples
7
+
8
+ Each example is a self-contained project in `examples/` following the `agent/index.js` convention. Clone, configure, and run.
9
+
10
+ ## Quick Start
11
+
12
+ ```bash
13
+ cd examples/<example>
14
+ cp .env.example .env # edit with your values
15
+ npm install
16
+ pinecall run agent/index.js # or agent/index.ts
17
+ ```
18
+
19
+ ---
20
+
21
+ ## Runnable Examples
22
+
23
+ ### Simple Agent
24
+
25
+ **`examples/simple/`** — The minimal Pinecall setup. A voice agent with `JsonFileHistory` for automatic call persistence.
26
+
27
+ **What it shows:**
28
+ - `agent/index.js` convention — auto-connect, export agent
29
+ - `greeting` in config — no `call.started` handler needed
30
+ - `JsonFileHistory` for automatic call persistence
31
+ - Auto-restored history for returning callers
32
+
33
+ ```typescript
34
+ // agent/index.js
35
+ const pc = new Pinecall();
36
+
37
+ export const agent = pc.agent("simple-agent", {
38
+ voice: "elevenlabs/sarah",
39
+ stt: "deepgram/flux",
40
+ llm: "openai/gpt-5-chat-latest",
41
+ phoneNumber: process.env.PHONE,
42
+ greeting: "Hello! How can I help you today?",
43
+ history: new JsonFileHistory("./data/calls.json"),
44
+ });
45
+ ```
46
+
47
+ 📖 Related: [Inbound Voice](/guides/inbound-voice) · [Conversation History](/guides/conversation-history)
48
+
49
+ ---
50
+
51
+ ### Reservations Agent
52
+
53
+ **`examples/reservations/`** — A restaurant reservation agent with tools. Demonstrates the `tool()` + Zod pattern with `agent/index.ts` and `agent/tools.ts`.
54
+
55
+ **What it shows:**
56
+ - `tool()` with Zod schemas for type-safe tool definitions
57
+ - Tools in a separate `agent/tools.ts` module
58
+ - Agent exports pattern — `export const agent = pc.agent(...)`
59
+ - `pinecall run agent/index.ts` for live development
60
+
61
+ ```typescript
62
+ // agent/tools.ts
63
+ export const checkAvailability = tool({
64
+ name: "checkAvailability",
65
+ description: "Check table availability for a date and party size.",
66
+ schema: z.object({
67
+ date: z.string(),
68
+ time: z.string(),
69
+ partySize: z.number(),
70
+ }),
71
+ execute: async ({ date, time, partySize }) => ({ available: true, table: "garden terrace" }),
72
+ });
73
+ ```
74
+
75
+ 📖 Related: [Tool definitions](/api/agent#creation) · [CLI reference](/reference/cli#pinecall-run-file)
76
+
77
+ ---
78
+
79
+ ### Turn Detection
80
+
81
+ **`examples/turn-detection/`** — Debug turn detection events in real-time. Switch between Flux (native turns) and Nova-3 (SmartTurn + Silero) by changing `MODEL` in `.env`.
82
+
83
+ **What it shows:**
84
+ - `speech.started`, `user.speaking`, `user.message` event flow
85
+ - `turn.pause` (SmartTurn analyzing) vs `turn.end` (confirmed)
86
+ - `bot.speaking` / `bot.finished` / `bot.interrupted` (barge-in)
87
+ - Colored console output with timestamps
88
+
89
+ **Config via `.env`:**
90
+ ```env
91
+ PHONE=+13049709763
92
+ MODEL=nova # "flux" or "nova"
93
+ STT_LANG=es # "en", "es", "ar", etc.
94
+ ```
95
+
96
+ **Key difference to observe:**
97
+ | | Flux | Nova-3 |
98
+ |---|---|---|
99
+ | `turn.pause` | ❌ Never | ✅ SmartTurn emits while analyzing |
100
+ | Turn speed | ~200ms (native) | ~400-600ms (Silero → SmartTurn) |
101
+
102
+ 📖 Related: [Turn Detection Guide](/concepts/turn-detection) · [STT Providers](/reference/stt-providers)
103
+
104
+ ---
105
+
106
+ ### Call Ringing
107
+
108
+ **`examples/ringing/`** — Accept or reject incoming calls programmatically. When `ringing: true`, calls emit `call.ringing` instead of auto-answering.
109
+
110
+ **What it shows:**
111
+ - `call.ringing` event with caller info
112
+ - `call.accept()` and `call.reject(reason)` flow
113
+ - Blacklist filtering (reject specific numbers)
114
+ - Timeout auto-accept (if SDK doesn't respond in time)
115
+
116
+ ```typescript
117
+ agent.on("call.ringing", (call) => {
118
+ if (BLACKLIST.includes(call.from)) {
119
+ call.reject("busy");
120
+ } else {
121
+ call.accept();
122
+ }
123
+ });
124
+ ```
125
+
126
+ 📖 Related: [Call Ringing Guide](/guides/call-ringing)
127
+
128
+ ---
129
+
130
+ ### Conversation History
131
+
132
+ **`examples/history/`** — Conversation persistence across calls. When the same contact calls again, the agent restores the previous conversation context.
133
+
134
+ **What it shows:**
135
+ - `JsonFileHistory` — save/load to a JSON file
136
+ - `history.findByContact()` — find prior conversations
137
+ - `call.setHistory()` — inject previous messages
138
+ - Custom `HistoryStore` interface for your own backend
139
+
140
+ 📖 Related: [Conversation History Guide](/guides/conversation-history)
141
+
142
+ ---
143
+
144
+ ### WhatsApp Dashboard
145
+
146
+ **`examples/whatsapp-dashboard/`** — A WhatsApp agent with a human takeover dashboard. Express backend + React frontend with SSE live updates.
147
+
148
+ **What it shows:**
149
+ - `agent.addWhatsapp()` channel registration
150
+ - `whatsapp.sessionStarted` / `whatsapp.message` / `whatsapp.response` events
151
+ - `agent.pause()` / `agent.resume()` for human takeover
152
+ - `agent.sendMessage()` — human operator sends messages
153
+ - `agent.stream(res)` — SSE event stream for React dashboard
154
+ - Conversation history via `JsonFileHistory`
155
+
156
+ ```typescript
157
+ const agent = pc.agent("support", {
158
+ llm: "openai/gpt-5-chat-latest",
159
+ prompt: "You are a helpful customer support agent on WhatsApp.",
160
+ history: new JsonFileHistory("./data/conversations.json"),
161
+ });
162
+
163
+ agent.addWhatsapp({
164
+ phoneNumberId: process.env.WA_PHONE_NUMBER_ID,
165
+ accessToken: process.env.WA_ACCESS_TOKEN,
166
+ });
167
+ ```
168
+
169
+ 📖 Related: [WhatsApp Guide](/guides/whatsapp) · [Human Takeover](/guides/human-takeover) · [SSE Streaming](/guides/sse-streaming)
170
+
171
+ ---
172
+
173
+ ## Conceptual Examples (docs only)
174
+
175
+ These examples live in the docs as full code walkthroughs — no separate `examples/` folder.
176
+
177
+ | Example | What it covers |
178
+ |---|---|
179
+ | [Browser Widget](/examples/browser-widget) | WebRTC `@pinecall/web` integration |
180
+ | [Chat Bot](/examples/chat-bot) | Text-only LLM chat (no audio) |
181
+ | [Headless Agent](/examples/headless-agent) | Server-side agent without UI |
182
+ | [Multi-Channel Bot](/examples/multi-channel-bot) | Voice + WhatsApp + chat on one agent |
183
+ | [Turn Detection](/examples/turn-detection) | Flux vs SmartTurn event comparison |
@@ -0,0 +1,173 @@
1
+ ---
2
+ title: "Example: Multi-Channel Bot"
3
+ description: "One agent serving phone, WhatsApp, and browser WebRTC simultaneously."
4
+ ---
5
+
6
+ # Example: Multi-Channel Bot
7
+
8
+ A support bot serving phone calls, WhatsApp messages, and a browser widget — from the **same agent**, with the **same prompt and tools**. The agent code doesn't care which transport the conversation arrived on.
9
+
10
+ ## What it does
11
+
12
+ `support.js` is the support bot for Acme Corp:
13
+
14
+ - Customers call `+13186330963` (Twilio) → phone conversation
15
+ - Customers message Acme's WhatsApp Business number → WhatsApp conversation
16
+ - Customers in the app click "Talk to support" → WebRTC browser conversation
17
+
18
+ All three converge on the same agent. Same prompt. Same tools. Same database. Same logging.
19
+
20
+ ## The complete file
21
+
22
+ ```typescript
23
+ // support.js
24
+ import { Pinecall } from "@pinecall/sdk";
25
+ import express from "express";
26
+
27
+ // ---- Mock database (replace with yours) ----
28
+ const orders = {
29
+ "ORD-001": { status: "shipped", tracking: "1Z999AA10123456784" },
30
+ "ORD-002": { status: "processing" },
31
+ "ORD-003": { status: "delivered", deliveredAt: "2026-05-20" },
32
+ };
33
+
34
+ // ---- Pinecall client ----
35
+ const pc = new Pinecall();
36
+
37
+ // ---- Tools ----
38
+ import { tool } from "@pinecall/sdk";
39
+ import { z } from "zod";
40
+
41
+ const lookupOrder = tool({
42
+ name: "lookupOrder",
43
+ description: "Look up an order by its ID (format: ORD-XXX)",
44
+ schema: z.object({ orderId: z.string() }),
45
+ execute: async ({ orderId }) => orders[orderId] ?? { error: "not_found" },
46
+ });
47
+
48
+ const transferToHuman = tool({
49
+ name: "transferToHuman",
50
+ description: "Transfer voice call to a human agent. Only works on phone/WebRTC.",
51
+ schema: z.object({}),
52
+ execute: async (_, call) => {
53
+ if (call.transport === "phone" || call.transport === "webrtc") {
54
+ call.say("Sure, let me get a human on the line.");
55
+ call.forward("+15558675309");
56
+ return { transferred: true };
57
+ }
58
+ return { transferred: false, note: "A human will respond within an hour." };
59
+ },
60
+ });
61
+
62
+ const endConversation = tool({
63
+ name: "endConversation",
64
+ description: "End the conversation when the customer says goodbye.",
65
+ schema: z.object({}),
66
+ execute: async (_, call) => {
67
+ if (call.transport === "phone" || call.transport === "webrtc") {
68
+ call.say("Thanks for calling. Have a great day!");
69
+ call.once("bot.finished", () => call.hangup());
70
+ }
71
+ return { ended: true };
72
+ },
73
+ });
74
+
75
+ // ---- The agent ----
76
+ const support = pc.agent("acme-support", {
77
+ voice: "elevenlabs/sarah",
78
+ language: "en",
79
+ llm: "openai/gpt-5-chat-latest",
80
+ stt: "deepgram/flux",
81
+ phoneNumber: "+13186330963",
82
+ prompt: `You are Nova, a support agent at Acme Corp.
83
+
84
+ You can:
85
+ - Look up order status with lookupOrder
86
+ - Transfer to a human with transferToHuman (use sparingly)
87
+ - End the call/conversation when the customer is done
88
+
89
+ Be concise. On voice, keep responses to 1-2 sentences.
90
+ On WhatsApp, you can be slightly longer but still brief.`,
91
+ tools: [lookupOrder, transferToHuman, endConversation],
92
+ greeting: "Hi, this is Nova at Acme. How can I help?",
93
+ });
94
+
95
+ // ---- Register WhatsApp ----
96
+ support.addWhatsapp({
97
+ phoneNumberId: process.env.WA_PHONE_NUMBER_ID,
98
+ accessToken: process.env.WA_TOKEN,
99
+ appSecret: process.env.WA_APP_SECRET,
100
+ });
101
+
102
+
103
+
104
+ // ---- Logging (universal) ----
105
+ support.on("call.ended", async (call, reason) => {
106
+ console.log(
107
+ `[${call.transport}] ${call.id} ended (${reason}) — ${call.duration}s, ${call.transcript.length} msgs`,
108
+ );
109
+ // Save to your DB...
110
+ });
111
+
112
+ // ---- WhatsApp-specific observability ----
113
+ support.on("whatsapp.message", (event) => {
114
+ console.log(`💬 ${event.name}: ${event.text}`);
115
+ });
116
+
117
+ // ---- Express server for token endpoint (WebRTC) ----
118
+ const app = express();
119
+
120
+ app.get("/api/token", async (req, res) => {
121
+ // In production: add your auth check here
122
+ const token = await support.createToken("webrtc");
123
+ res.json(token);
124
+ });
125
+
126
+ // ---- Live event stream for dashboard ----
127
+ app.get("/api/events", (req, res) => {
128
+ support.stream(res);
129
+ });
130
+
131
+ app.listen(3000, () => {
132
+ console.log("Support bot live on phone, WhatsApp, and WebRTC");
133
+ console.log("Token endpoint: http://localhost:3000/api/token");
134
+ console.log("Event stream: http://localhost:3000/api/events");
135
+ });
136
+ ```
137
+
138
+ ## Why this works
139
+
140
+ The agent code never branches on transport. The LLM gets the same prompt and tools regardless of whether the user is calling, messaging, or in the browser. The only places the code checks `call.transport` are:
141
+
142
+ 1. **Greeting** — voice channels need a greeting, WhatsApp doesn't (they message first)
143
+ 2. **Tool effects** — `transferToHuman` and `endConversation` only make sense on voice
144
+
145
+ Everything else — tool definitions, the LLM, the response generation — is unified.
146
+
147
+ ## Adding a fourth channel
148
+
149
+ Need to add SIP for a call center integration? One line:
150
+
151
+ ```typescript
152
+ support.addPhoneNumber("sip:bot@trunk.acmetel.com");
153
+ ```
154
+
155
+ WebRTC and Chat work automatically via tokens — the agent handles all transports.
156
+
157
+ ## Deploy
158
+
159
+ ```bash
160
+ PINECALL_API_KEY=pk_... \
161
+ WA_PHONE_NUMBER_ID=123... \
162
+ WA_TOKEN=EAA... \
163
+ WA_APP_SECRET=abc... \
164
+ node support.js
165
+ ```
166
+
167
+ Configure the WhatsApp webhook (in Meta's dashboard) to point to `https://voice.pinecall.io/whatsapp/webhook`. Done.
168
+
169
+ ## What's next
170
+
171
+ - [Browser widget example](/examples/browser-widget)
172
+ - [Multi-tenant guide](/guides/multi-tenant) — host many bots like this
173
+ - [WhatsApp guide](/guides/whatsapp) — full WhatsApp setup