@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,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
+ ![WhatsApp session identification](/assets/diagrams/whatsapp-session.png)
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
+ ![WhatsApp message flow](/assets/diagrams/whatsapp-message-flow.png)
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`.*