@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,370 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "WhatsApp"
|
|
3
|
+
description: "Build a WhatsApp messaging agent using Meta's Cloud API."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# WhatsApp
|
|
7
|
+
|
|
8
|
+
WhatsApp is a text-based channel โ no STT/TTS/VAD pipeline. Messages route directly to the server-side LLM. The agent receives text, generates a response, and sends it back as a WhatsApp message.
|
|
9
|
+
|
|
10
|
+
> **Server-side LLM required.** WhatsApp channels use the same `llm` config as voice channels. Client-side LLM (bring-your-own) is not supported for WhatsApp.
|
|
11
|
+
|
|
12
|
+
## Setup
|
|
13
|
+
|
|
14
|
+
### 1. Create a Meta Business App
|
|
15
|
+
|
|
16
|
+
Go to [developers.facebook.com](https://developers.facebook.com) and create a Business app.
|
|
17
|
+
|
|
18
|
+
### 2. Add the WhatsApp product
|
|
19
|
+
|
|
20
|
+
In your app dashboard, add WhatsApp from the product catalog.
|
|
21
|
+
|
|
22
|
+
### 3. Collect your credentials
|
|
23
|
+
|
|
24
|
+
From the WhatsApp โ API Setup page, grab:
|
|
25
|
+
|
|
26
|
+
- **Phone Number ID** โ numeric string (e.g. `123456789012345`)
|
|
27
|
+
- **Permanent Access Token** โ generate a system user token with `whatsapp_business_messaging` permission
|
|
28
|
+
- **App Secret** โ from App Settings โ Basic (used for webhook signature verification)
|
|
29
|
+
|
|
30
|
+
### 4. Configure the webhook in your Meta app
|
|
31
|
+
|
|
32
|
+
Webhook URL:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
https://voice.pinecall.io/whatsapp/webhook
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Verification token: must match the `verifyToken` you pass to the SDK (default: `pinecall-wa-verify`).
|
|
39
|
+
|
|
40
|
+
Subscribe to the `messages` field.
|
|
41
|
+
|
|
42
|
+
## Minimal WhatsApp agent
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { Pinecall } from "@pinecall/sdk";
|
|
46
|
+
|
|
47
|
+
const pc = new Pinecall({ apiKey: process.env.PINECALL_API_KEY! });
|
|
48
|
+
|
|
49
|
+
const support = pc.agent("support", {
|
|
50
|
+
language: "en",
|
|
51
|
+
llm: "openai/gpt-5-chat-latest",
|
|
52
|
+
prompt: "You are a helpful support agent on WhatsApp. Be concise.",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
support.addWhatsapp({
|
|
56
|
+
phoneNumberId: "123456789012345",
|
|
57
|
+
accessToken: process.env.WA_TOKEN!,
|
|
58
|
+
verifyToken: "my-verify-token",
|
|
59
|
+
appSecret: process.env.WA_APP_SECRET!,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
support.on("whatsapp.message", (event) => {
|
|
63
|
+
console.log(`๐ฉ ${event.name}: ${event.text}`);
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
That's it. The first message a new contact sends fires `whatsapp.sessionStarted`, the LLM generates a response, and the SDK sends it back via the Cloud API.
|
|
68
|
+
|
|
69
|
+
## `WhatsAppChannelConfig`
|
|
70
|
+
|
|
71
|
+
| Field | Type | Required | Description |
|
|
72
|
+
|---|---|---|---|
|
|
73
|
+
| `phoneNumberId` | `string` | โ
| Meta Phone Number ID from API Setup |
|
|
74
|
+
| `accessToken` | `string` | โ
| Permanent Graph API access token |
|
|
75
|
+
| `verifyToken` | `string` | โ | Webhook verification token (default: `pinecall-wa-verify`) |
|
|
76
|
+
| `appSecret` | `string` | โ | App secret for HMAC signature verification (strongly recommended) |
|
|
77
|
+
|
|
78
|
+
## Adding tools
|
|
79
|
+
|
|
80
|
+
Tools work identically on WhatsApp and voice channels. Define them with `tool()` โ the SDK handles execution on all channels:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { tool } from "@pinecall/sdk";
|
|
84
|
+
import { z } from "zod";
|
|
85
|
+
|
|
86
|
+
const lookupOrder = tool({
|
|
87
|
+
name: "lookupOrder",
|
|
88
|
+
description: "Look up an order by ID",
|
|
89
|
+
schema: z.object({ orderId: z.string() }),
|
|
90
|
+
execute: async ({ orderId }) => await db.orders.findOne(orderId),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const support = pc.agent("support", {
|
|
94
|
+
llm: "openai/gpt-5-chat-latest",
|
|
95
|
+
prompt: "...",
|
|
96
|
+
tools: [lookupOrder],
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Multi-channel: voice + WhatsApp on the same agent
|
|
101
|
+
|
|
102
|
+
The same agent can serve WhatsApp **and** phone calls. The LLM config, tools, and prompt are shared โ only the transport differs.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const support = pc.agent("support", {
|
|
106
|
+
voice: "elevenlabs/sarah",
|
|
107
|
+
language: "en",
|
|
108
|
+
llm: "openai/gpt-5-chat-latest",
|
|
109
|
+
prompt: "...",
|
|
110
|
+
tools: [lookupOrder],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
support.addWhatsapp({ phoneNumberId: "123", accessToken: "EAA..." });
|
|
114
|
+
support.addPhoneNumber("+13186330963");
|
|
115
|
+
|
|
116
|
+
// Voice greeting (WhatsApp doesn't use this)
|
|
117
|
+
support.on("call.started", (call) => {
|
|
118
|
+
if (call.transport === "phone" || call.transport === "webrtc") {
|
|
119
|
+
call.say("Hello!");
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
// Tools auto-execute on all channels โ no extra handler needed.
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Voice notes
|
|
126
|
+
|
|
127
|
+
When a user sends a voice note on WhatsApp, the server automatically:
|
|
128
|
+
|
|
129
|
+
1. Downloads the audio (OGG/Opus) from the Cloud API
|
|
130
|
+
2. Transcribes it using Deepgram Nova-3
|
|
131
|
+
3. Feeds the transcript to the LLM as if it were a text message
|
|
132
|
+
|
|
133
|
+
The agent sees voice notes as regular text. No extra code.
|
|
134
|
+
|
|
135
|
+
> Requires `DEEPGRAM_API_KEY` set on the voice server.
|
|
136
|
+
|
|
137
|
+
## The 24-hour service window
|
|
138
|
+
|
|
139
|
+
Meta enforces a **24-hour service window** for free-form messaging:
|
|
140
|
+
|
|
141
|
+
- **Inside the window**: the agent can send any text. The window refreshes on every inbound message.
|
|
142
|
+
- **Outside the window**: only pre-approved **template messages** can be sent.
|
|
143
|
+
|
|
144
|
+
The SDK tracks the window automatically. If you try to send when it's closed, the server logs a warning. Template message support is on the roadmap.
|
|
145
|
+
|
|
146
|
+
## Human takeover (pause/resume)
|
|
147
|
+
|
|
148
|
+
Sometimes a human needs to step in. `agent.pause()` stops the AI from responding while keeping messages flowing to the SDK. The human sends replies via `agent.sendMessage()`, and when done, `agent.resume()` hands control back to the AI โ with full conversation context preserved.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// A dashboard UI triggers this when a human wants to take over
|
|
152
|
+
support.on("whatsapp.message", (event) => {
|
|
153
|
+
if (event.paused) {
|
|
154
|
+
// AI is paused โ show this message to the human operator
|
|
155
|
+
dashboard.showMessage(event);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Normal flow โ AI handles automatically
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Pause a specific WhatsApp session
|
|
162
|
+
support.pause("wa-abc123");
|
|
163
|
+
|
|
164
|
+
// Human sends a message through WhatsApp
|
|
165
|
+
support.sendMessage({
|
|
166
|
+
sessionId: "wa-abc123",
|
|
167
|
+
text: "Hi! A human agent here. Let me help you with that.",
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Resume AI when the human is done
|
|
171
|
+
support.resume("wa-abc123");
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Granularity options:**
|
|
175
|
+
|
|
176
|
+
| Method | What it pauses |
|
|
177
|
+
|--------|---------------|
|
|
178
|
+
| `agent.pause("wa-abc123")` | One specific session |
|
|
179
|
+
| `agent.pause({ contact: "+34612..." })` | All sessions with this contact |
|
|
180
|
+
| `agent.pause()` | Entire agent (all sessions) |
|
|
181
|
+
|
|
182
|
+
While paused:
|
|
183
|
+
- Messages are still forwarded to the SDK (with `paused: true`)
|
|
184
|
+
- Voice notes are still transcribed
|
|
185
|
+
- Human messages are added to LLM history (context preserved on resume)
|
|
186
|
+
- No typing indicator is shown (unless the human sends a message)
|
|
187
|
+
|
|
188
|
+
See the full [Human Takeover guide](/guides/human-takeover) for advanced patterns.
|
|
189
|
+
|
|
190
|
+
## Environment variables
|
|
191
|
+
|
|
192
|
+
Set these on the voice server (`sdk-server`):
|
|
193
|
+
|
|
194
|
+
| Variable | Required | Description |
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| `WHATSAPP_VERIFY_TOKEN` | โ | Hub verification token (default: `pinecall-wa-verify`) |
|
|
197
|
+
| `WHATSAPP_APP_SECRET` | โ | Meta App Secret for webhook HMAC verification |
|
|
198
|
+
| `DEEPGRAM_API_KEY` | For voice notes | Required if you want voice note transcription |
|
|
199
|
+
|
|
200
|
+
## Session lifecycle
|
|
201
|
+
|
|
202
|
+
WhatsApp conversations are grouped into **sessions**. Understanding the session lifecycle is key for history, human takeover, and any dashboard UI.
|
|
203
|
+
|
|
204
|
+
### How sessions are identified
|
|
205
|
+
|
|
206
|
+
Each session is uniquely identified by the **agent + contact phone number** pair. When a contact sends their first message, a new session is created with an ID like `wa-a3f2b1c4d5e6`. All subsequent messages from the same contact (to the same agent) belong to that session โ until it ends.
|
|
207
|
+
|
|
208
|
+

|
|
209
|
+
|
|
210
|
+
If a second contact writes to the same agent, they get a **separate** session with their own LLM history, window timer, and session ID.
|
|
211
|
+
|
|
212
|
+
### Message flow
|
|
213
|
+
|
|
214
|
+

|
|
215
|
+
|
|
216
|
+
**Key points:**
|
|
217
|
+
- Every incoming message is emitted to the SDK via `whatsapp.message` โ even when paused
|
|
218
|
+
- The `paused` field in the event tells your UI whether the AI responded or not
|
|
219
|
+
- Voice notes are automatically transcribed (Deepgram Nova-3) and treated as text
|
|
220
|
+
- Interactive replies (buttons, lists) are treated as text with the selected option
|
|
221
|
+
|
|
222
|
+
### How sessions end
|
|
223
|
+
|
|
224
|
+
Sessions end for one of two reasons:
|
|
225
|
+
|
|
226
|
+
| Reason | Trigger | What happens |
|
|
227
|
+
|--------|---------|--------------|
|
|
228
|
+
| `window_expired` | 24h since the last inbound message | Meta's service window closes |
|
|
229
|
+
| `idle_timeout` | No messages for the configured idle period | Contact stopped writing |
|
|
230
|
+
|
|
231
|
+
When a session ends:
|
|
232
|
+
|
|
233
|
+
1. `whatsapp.sessionEnded` is emitted with the full transcript and metadata
|
|
234
|
+
2. If a `HistoryStore` is configured, the conversation is automatically saved
|
|
235
|
+
3. The session is removed from memory
|
|
236
|
+
|
|
237
|
+
If the same contact writes again after a session ended, a **new session** is created (new ID, fresh LLM history โ unless you [restore history](#restoring-prior-conversations)).
|
|
238
|
+
|
|
239
|
+
### Session events timeline
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
Session created (first message arrives)
|
|
243
|
+
โโโ whatsapp.sessionStarted { sessionId, contactPhone, contactName }
|
|
244
|
+
โ
|
|
245
|
+
โโโ whatsapp.message { sessionId, text, paused: false }
|
|
246
|
+
โโโ whatsapp.response { sessionId, text }
|
|
247
|
+
โโโ whatsapp.status { status: "sent" }
|
|
248
|
+
โโโ whatsapp.status { status: "delivered" }
|
|
249
|
+
โโโ whatsapp.status { status: "read" }
|
|
250
|
+
โ
|
|
251
|
+
โโโ whatsapp.message { sessionId, text, paused: false }
|
|
252
|
+
โโโ whatsapp.response { sessionId, text }
|
|
253
|
+
โ ...
|
|
254
|
+
โ
|
|
255
|
+
โโโ (pause) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
256
|
+
โ โโโ session.paused { sessionId }
|
|
257
|
+
โ โโโ whatsapp.message { sessionId, text, paused: true } โ no AI response
|
|
258
|
+
โ โโโ whatsapp.response { sessionId, text, source: "human" } โ human operator
|
|
259
|
+
โ โโโ session.resumed { sessionId }
|
|
260
|
+
โ
|
|
261
|
+
โโโ whatsapp.sessionEnded { sessionId, transcript, messages, duration }
|
|
262
|
+
โ
|
|
263
|
+
โผ
|
|
264
|
+
HistoryStore.save() (automatic)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Conversation history
|
|
268
|
+
|
|
269
|
+
When a `HistoryStore` is configured, WhatsApp conversations are **automatically saved** on `whatsapp.sessionEnded` โ the same way voice calls are saved on `call.ended`.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
import { Pinecall, JsonFileHistory } from "@pinecall/sdk";
|
|
273
|
+
|
|
274
|
+
const pc = new Pinecall({ apiKey: process.env.PINECALL_API_KEY! });
|
|
275
|
+
|
|
276
|
+
const history = new JsonFileHistory("./data/conversations.json");
|
|
277
|
+
|
|
278
|
+
const support = pc.agent("support", {
|
|
279
|
+
llm: "openai/gpt-5-chat-latest",
|
|
280
|
+
prompt: "You are a support agent.",
|
|
281
|
+
history,
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### What gets saved
|
|
286
|
+
|
|
287
|
+
Each saved `ConversationRecord` includes:
|
|
288
|
+
|
|
289
|
+
| Field | Value | Example |
|
|
290
|
+
|-------|-------|---------|
|
|
291
|
+
| `callId` | The session ID | `"wa-a3f2b1c4d5e6"` |
|
|
292
|
+
| `agentId` | Agent name | `"support"` |
|
|
293
|
+
| `channel` | Always `"whatsapp"` | `"whatsapp"` |
|
|
294
|
+
| `from` | Contact's phone number | `"5491155551234"` |
|
|
295
|
+
| `transcript` | Clean user/assistant pairs | `[{role: "user", content: "Hi"}, ...]` |
|
|
296
|
+
| `messages` | Full LLM history (with system, tools) | Raw message array |
|
|
297
|
+
| `duration` | Session duration in seconds | `342` |
|
|
298
|
+
| `metadata.contactName` | WhatsApp profile name | `"John"` |
|
|
299
|
+
| `metadata.messageCount` | Total messages exchanged | `8` |
|
|
300
|
+
|
|
301
|
+
### Querying history
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
// Find all conversations with a specific contact
|
|
305
|
+
const conversations = await history.findByContact("5491155551234");
|
|
306
|
+
|
|
307
|
+
// List recent conversations for the agent
|
|
308
|
+
const recent = await history.list("support", 20);
|
|
309
|
+
|
|
310
|
+
// Get a specific conversation
|
|
311
|
+
const convo = await history.get("wa-a3f2b1c4d5e6");
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`findByContact` searches the `from` field โ which for WhatsApp is the contact's phone number (without `+`).
|
|
315
|
+
|
|
316
|
+
> **Note:** `JsonFileHistory` is for prototyping. For production, implement `HistoryStore` with your database (MongoDB, Postgres, etc).
|
|
317
|
+
|
|
318
|
+
### Restoring prior conversations
|
|
319
|
+
|
|
320
|
+
When your `HistoryStore` implements `findByContact()`, prior conversations are **automatically restored** when a returning contact starts a new session. No code needed โ just set `history` in the agent config and the SDK handles everything.
|
|
321
|
+
|
|
322
|
+
If you need custom restore logic, the `whatsapp.sessionStarted` event passes a `WhatsAppSession` object with history methods:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
agent.on("whatsapp.sessionStarted", async (session) => {
|
|
326
|
+
// Custom: only restore if prior conversation had >5 messages
|
|
327
|
+
const prior = await history.findByContact(session.contactPhone, 1);
|
|
328
|
+
if (prior.length > 0 && prior[0].messages.length > 5) {
|
|
329
|
+
await session.setHistory(prior[0].messages);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
`WhatsAppSession` provides the same history/prompt methods as `Call`:
|
|
335
|
+
|
|
336
|
+
| Method | Description |
|
|
337
|
+
|---|---|
|
|
338
|
+
| `session.setHistory(messages)` | Replace server-side LLM history |
|
|
339
|
+
| `session.addHistory(messages)` | Append messages to history |
|
|
340
|
+
| `session.getHistory()` | Read current LLM history from server |
|
|
341
|
+
| `session.clearHistory()` | Clear all LLM history |
|
|
342
|
+
| `session.setPrompt(text)` | Replace the system prompt |
|
|
343
|
+
| `session.setPromptVars(vars)` | Set `{{variable}}` values in prompt template |
|
|
344
|
+
| `session.addContext(text)` | Append context after the system prompt |
|
|
345
|
+
|
|
346
|
+
The session object also exposes `session.id`, `session.contactPhone`, `session.contactName`, and `session.agentId`.
|
|
347
|
+
|
|
348
|
+
## All WhatsApp events
|
|
349
|
+
|
|
350
|
+
| Event | Data fields | When |
|
|
351
|
+
|---|---|---|
|
|
352
|
+
| `whatsapp.sessionStarted` | `WhatsAppSession` object with `id`, `contactPhone`, `contactName`, `setHistory()`, etc. | First message from a new contact |
|
|
353
|
+
| `whatsapp.message` | `sessionId`, `from`, `name`, `type`, `text`, `messageId`, `paused` | Incoming message received |
|
|
354
|
+
| `whatsapp.response` | `sessionId`, `to`, `text`, `source?` | Agent or human sent a response |
|
|
355
|
+
| `whatsapp.status` | `status`, `recipient`, `messageId` | Delivery status update |
|
|
356
|
+
| `whatsapp.sessionEnded` | `sessionId`, `contactPhone`, `transcript`, `messages`, `duration` | Session expired or timed out |
|
|
357
|
+
| `session.paused` | `sessionId` | AI paused for a session |
|
|
358
|
+
| `session.resumed` | `sessionId` | AI resumed for a session |
|
|
359
|
+
|
|
360
|
+
Status values: `sent` โ `delivered` โ `read`.
|
|
361
|
+
|
|
362
|
+
The `source` field on `whatsapp.response` is `"human"` when the message was sent by a human operator via `sendMessage()`. Otherwise it's absent (AI-generated).
|
|
363
|
+
|
|
364
|
+
## What's next
|
|
365
|
+
|
|
366
|
+
- [WhatsApp Dashboard example](/examples/whatsapp-dashboard) โ runnable example with React UI and human takeover
|
|
367
|
+
- [Conversation History](/guides/conversation-history) โ persistence options and custom stores
|
|
368
|
+
- [Human Takeover](/guides/human-takeover) โ advanced pause/resume patterns
|
|
369
|
+
- [Tools and Functions](/guides/tools-and-functions) โ let your WhatsApp bot take actions
|
|
370
|
+
- [Dev mode](/guides/dev-mode) โ route specific WhatsApp senders to dev agents
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "WebSocket Event Streaming"
|
|
3
|
+
description: "Stream agent events over WebSocket for bidirectional, real-time communication with your frontend."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# WebSocket Event Streaming
|
|
7
|
+
|
|
8
|
+
The SDK can stream agent events over WebSocket โ a bidirectional alternative to [SSE streaming](/guides/sse-streaming). Like SSE, your agent and web server must run in the same process. Unlike SSE, WebSocket supports **two-way communication** and works better for complex client apps.
|
|
9
|
+
|
|
10
|
+
## When to use WebSocket vs SSE
|
|
11
|
+
|
|
12
|
+
| Feature | SSE (`agent.stream()`) | WebSocket (`agent.ws()`) |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| Direction | Server โ Client only | Bidirectional |
|
|
15
|
+
| Client sends actions | โ | โ
`ping` (more planned) |
|
|
16
|
+
| Session scoping | โ All events broadcast | โ
Filter to one call |
|
|
17
|
+
| Tool results | โ | โ
`llm.tool_result` |
|
|
18
|
+
| Browser API | `EventSource` | `WebSocket` |
|
|
19
|
+
| Auto-reconnect | Built into `EventSource` | SDK provides `createEventStream` |
|
|
20
|
+
|
|
21
|
+
**Use SSE** for simple dashboards where you just display data.
|
|
22
|
+
**Use WebSocket** when you need to send actions back, scope events to a session, or receive tool results.
|
|
23
|
+
|
|
24
|
+
## Server-side: `agent.ws()`
|
|
25
|
+
|
|
26
|
+
Pipe all events from an agent to a WebSocket connection:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import express from "express";
|
|
30
|
+
import { Pinecall } from "@pinecall/sdk";
|
|
31
|
+
import { WebSocketServer } from "ws";
|
|
32
|
+
|
|
33
|
+
const app = express();
|
|
34
|
+
const pc = new Pinecall();
|
|
35
|
+
|
|
36
|
+
const pines = pc.agent("pines", { /* ... */ });
|
|
37
|
+
|
|
38
|
+
const server = app.listen(3000);
|
|
39
|
+
const wss = new WebSocketServer({ server, path: "/ws/events" });
|
|
40
|
+
|
|
41
|
+
wss.on("connection", (ws) => {
|
|
42
|
+
pines.ws(ws);
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Each event is sent as a JSON message:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{ "event": "call.started", "callId": "CA_abc", "from": "+1234", "agent": "pines" }
|
|
50
|
+
{ "event": "bot.word", "word": "hello", "agent": "pines" }
|
|
51
|
+
{ "event": "call.ended", "callId": "CA_abc", "reason": "hangup", "agent": "pines" }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Session scoping
|
|
55
|
+
|
|
56
|
+
Filter events to a specific call:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
wss.on("connection", (ws, req) => {
|
|
60
|
+
const sessionId = new URL(req.url!, "http://x").searchParams.get("session");
|
|
61
|
+
pines.ws(ws, { sessionId: sessionId || undefined });
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Now only events from that call are forwarded โ useful for per-customer dashboards.
|
|
66
|
+
|
|
67
|
+
### Include tool results
|
|
68
|
+
|
|
69
|
+
By default, `llm.toolCall` is included but `llm.tool_result` is not. Enable it:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
pines.ws(ws, { toolResults: true });
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Client-side: `createEventStream()`
|
|
76
|
+
|
|
77
|
+
The SDK includes a browser client with auto-reconnect:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { createEventStream } from "@pinecall/sdk";
|
|
81
|
+
|
|
82
|
+
const stream = createEventStream({
|
|
83
|
+
url: "ws://localhost:3000/ws/events",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
stream.on("call.started", (data) => {
|
|
87
|
+
console.log(`Call from ${data.from}`);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
stream.on("bot.word", (data) => {
|
|
91
|
+
appendToTranscript(data.word);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
stream.on("llm.tool_result", (data) => {
|
|
95
|
+
updateToolCard(data.name, data.result);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Wildcard โ receive all events
|
|
99
|
+
stream.on("*", (data) => {
|
|
100
|
+
console.log(data.event, data);
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Sending actions
|
|
105
|
+
|
|
106
|
+
WebSocket is bidirectional โ send messages back to the server:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
stream.send({ action: "ping" }); // server replies with { event: "pong" }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
> Action support is currently limited to `ping`. Richer actions (`inject_text`, `set_context`) are planned.
|
|
113
|
+
|
|
114
|
+
### Connection status
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
stream.onStatus((status) => {
|
|
118
|
+
// "idle" | "connecting" | "connected" | "error"
|
|
119
|
+
updateConnectionBadge(status);
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Cleanup
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
stream.close(); // Disconnects and stops auto-reconnect
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Building a live call monitor (WebSocket version)
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import { createEventStream } from "@pinecall/sdk";
|
|
133
|
+
|
|
134
|
+
function CallMonitor() {
|
|
135
|
+
const [calls, setCalls] = useState(new Map());
|
|
136
|
+
const [connected, setConnected] = useState(false);
|
|
137
|
+
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
const stream = createEventStream({
|
|
140
|
+
url: "ws://localhost:3000/ws/events",
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
stream.onStatus((s) => setConnected(s === "connected"));
|
|
144
|
+
|
|
145
|
+
stream.on("call.started", (d) => {
|
|
146
|
+
setCalls((prev) => new Map(prev).set(d.callId, {
|
|
147
|
+
from: d.from, agent: d.agent, transcript: [],
|
|
148
|
+
}));
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
stream.on("bot.word", (d) => {
|
|
152
|
+
setCalls((prev) => {
|
|
153
|
+
const next = new Map(prev);
|
|
154
|
+
const call = next.get(d.callId);
|
|
155
|
+
if (call) call.transcript.push(d.word);
|
|
156
|
+
return next;
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
stream.on("call.ended", (d) => {
|
|
161
|
+
setCalls((prev) => {
|
|
162
|
+
const next = new Map(prev);
|
|
163
|
+
next.delete(d.callId);
|
|
164
|
+
return next;
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return () => stream.close();
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<div>
|
|
173
|
+
<h2>Active Calls ({calls.size}) {connected ? "๐ข" : "๐ด"}</h2>
|
|
174
|
+
{[...calls.entries()].map(([id, call]) => (
|
|
175
|
+
<div key={id}>
|
|
176
|
+
<strong>{call.agent}</strong> โ {call.from}
|
|
177
|
+
<p>{call.transcript.join(" ")}</p>
|
|
178
|
+
</div>
|
|
179
|
+
))}
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Framework examples
|
|
186
|
+
|
|
187
|
+
### Express + ws
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { WebSocketServer } from "ws";
|
|
191
|
+
|
|
192
|
+
const server = app.listen(3000);
|
|
193
|
+
const wss = new WebSocketServer({ server, path: "/ws/events" });
|
|
194
|
+
wss.on("connection", (ws) => pines.ws(ws));
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Fastify + @fastify/websocket
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
fastify.register(require("@fastify/websocket"));
|
|
201
|
+
fastify.get("/ws/events", { websocket: true }, (socket) => {
|
|
202
|
+
pines.ws(socket);
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Next.js (Pages API)
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// pages/api/ws.ts
|
|
210
|
+
export default function handler(req, res) {
|
|
211
|
+
if (!res.socket.server.wss) {
|
|
212
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
213
|
+
res.socket.server.on("upgrade", (req, socket, head) => {
|
|
214
|
+
wss.handleUpgrade(req, socket, head, (ws) => pines.ws(ws));
|
|
215
|
+
});
|
|
216
|
+
res.socket.server.wss = wss;
|
|
217
|
+
}
|
|
218
|
+
res.end();
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Topology constraints
|
|
223
|
+
|
|
224
|
+
Like SSE, WebSocket streaming requires the agent and web server in the **same process**:
|
|
225
|
+
|
|
226
|
+
| Topology | Works? | Why |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| Agent + web server in one process | โ
| `agent.ws()` has direct event access |
|
|
229
|
+
| Agent in a separate process | โ | Events don't cross process boundaries |
|
|
230
|
+
|
|
231
|
+
## What's next
|
|
232
|
+
|
|
233
|
+
- [SSE Streaming](/guides/sse-streaming) โ the simpler, one-way alternative
|
|
234
|
+
- [WebRTC Browser](/guides/webrtc-browser) โ for voice calling from the browser
|
|
235
|
+
- [Events Reference](/reference/events) โ every event with payload shapes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pinecall-quickstart
|
|
3
|
+
description: >-
|
|
4
|
+
Install @pinecall/sdk and build your first voice agent in minutes. Use when the user is building, configuring, or debugging with @pinecall/sdk. Keywords: install, quickstart, first agent, pc.agent, PINECALL_API_KEY, pinecall run.
|
|
5
|
+
license: MIT
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Get Started
|
|
9
|
+
|
|
10
|
+
Install @pinecall/sdk and build your first voice agent in minutes.
|
|
11
|
+
|
|
12
|
+
This skill bundles the official Pinecall documentation for **Get Started**. 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
|
+
| **Pinecall SDK** | Build real-time voice & messaging AI agents in TypeScript. | [`references/index.md`](references/index.md) ยท [docs](https://docs.pinecall.io/index) |
|
|
19
|
+
| **Quickstart** | From zero to a working voice agent in under 5 minutes. | [`references/quickstart.md`](references/quickstart.md) ยท [docs](https://docs.pinecall.io/quickstart) |
|
|
20
|
+
|
|
21
|
+
## Canonical agent
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { Pinecall } from "@pinecall/sdk";
|
|
25
|
+
|
|
26
|
+
const pc = new Pinecall(); // reads PINECALL_API_KEY, auto-connects
|
|
27
|
+
|
|
28
|
+
const agent = pc.agent("mara", {
|
|
29
|
+
prompt: "You are Mara, a friendly voice assistant. Be concise.",
|
|
30
|
+
llm: "openai/gpt-5-chat-latest",
|
|
31
|
+
voice: "elevenlabs/sarah",
|
|
32
|
+
stt: "deepgram/flux",
|
|
33
|
+
language: "en",
|
|
34
|
+
greeting: "Hello! How can I help?",
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## House rules โ always apply
|
|
39
|
+
|
|
40
|
+
- **Example defaults** (use these exact strings unless the user asks otherwise):
|
|
41
|
+
`stt: "deepgram/flux"`, `llm: "openai/gpt-5-chat-latest"`, `voice: "elevenlabs/sarah"`.
|
|
42
|
+
**NEVER use `deepgram/nova-2`** โ it is not supported. Use `deepgram/nova-3`
|
|
43
|
+
only for languages Flux doesn't support (e.g. Arabic).
|
|
44
|
+
- **Turn detection & VAD are auto-derived from the STT provider โ never set
|
|
45
|
+
`turnDetection` or `vad` manually.** Flux โ native turns + native VAD;
|
|
46
|
+
every other STT โ `smart_turn` + `silero`.
|
|
47
|
+
- **Greeting**: inbound โ `greeting` field in `pc.agent()`; outbound โ `greeting`
|
|
48
|
+
field in `agent.dial()`. It is sugar for `call.say()` in `call.started`.
|
|
49
|
+
- **Auth**: `new Pinecall()` reads `PINECALL_API_KEY` from env and auto-connects.
|
|
50
|
+
- Full documentation: <https://docs.pinecall.io>
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
*Generated from `sdk/docs/` by `@pinecall/skills` โ do not edit by hand; edit the
|
|
54
|
+
docs and re-run `node build.mjs`.*
|