@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,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
|