@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,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Hot-Reload"
|
|
3
|
+
description: "Change voice, language, prompt, tools — even during an active call."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Hot-Reload
|
|
7
|
+
|
|
8
|
+
Everything in Pinecall is hot-reloadable. Voice, language, STT provider, prompt, tools — all can change **during an active call**. The server applies changes on the next LLM turn.
|
|
9
|
+
|
|
10
|
+
This isn't a power-user feature you'll use rarely. It's the foundation of how Pinecall handles real-world conversation: switching languages when the user does, injecting CRM context when the call connects, swapping voices for different contexts.
|
|
11
|
+
|
|
12
|
+
## The three scopes
|
|
13
|
+
|
|
14
|
+
| Scope | Method | Affects |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Agent defaults | `pc.agent("id", config)` | All future calls |
|
|
17
|
+
| Agent hot-reload | `agent.update(updates)` | Updates defaults, future calls |
|
|
18
|
+
| Session (mid-call) | `call.update(opts)` | This call only |
|
|
19
|
+
| Prompt (mid-call) | `call.setPrompt(text)` | This call's system prompt |
|
|
20
|
+
| Template vars | `call.setPromptVars(vars)` | This call's `{{var}}` values |
|
|
21
|
+
| Context | `call.addContext(text)` | Appended after prompt |
|
|
22
|
+
|
|
23
|
+
## Updating the agent's defaults
|
|
24
|
+
|
|
25
|
+
`agent.update()` updates the agent's defaults at runtime. Changes take effect on **all future calls** — existing calls keep their current config.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// Switch the default voice to French
|
|
29
|
+
agent.update({ voice: "elevenlabs/claire", language: "fr" });
|
|
30
|
+
|
|
31
|
+
// Upgrade to a bigger model
|
|
32
|
+
agent.update({ llm: "openai/gpt-5-chat-latest", prompt: "..." });
|
|
33
|
+
|
|
34
|
+
// Swap STT providers
|
|
35
|
+
agent.update({ stt: "gladia" });
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
No REST call needed. `agent.update()` uses the existing WebSocket — changes propagate to the server instantly.
|
|
39
|
+
|
|
40
|
+
## Changing a live call
|
|
41
|
+
|
|
42
|
+
`call.update()` changes the active call only. Other calls on the same agent are unaffected.
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// User asks for Spanish mid-conversation
|
|
46
|
+
call.update({ voice: "elevenlabs/valentina", language: "es" });
|
|
47
|
+
call.reply("¡Claro! Ahora hablo en español.");
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The next TTS the bot produces uses the new voice. The next STT transcription uses the new language.
|
|
51
|
+
|
|
52
|
+
## Prompt template variables
|
|
53
|
+
|
|
54
|
+
Define a prompt with `{{placeholders}}`. The server resolves them before each LLM request. Built-in variables: `{{date}}`, `{{time}}`.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const agent = pc.agent("support", {
|
|
58
|
+
llm: "openai/gpt-5-chat-latest",
|
|
59
|
+
prompt: `You are {{agent_name}}, support agent at {{company}}.
|
|
60
|
+
Today is {{date}}, {{time}}.
|
|
61
|
+
Customer: {{customer_name}} ({{tier}} tier).`,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
agent.on("call.started", async (call) => {
|
|
65
|
+
const customer = await lookupCaller(call.from);
|
|
66
|
+
await call.setPromptVars({
|
|
67
|
+
agent_name: "Nova",
|
|
68
|
+
company: "Acme Corp",
|
|
69
|
+
customer_name: customer.name,
|
|
70
|
+
tier: customer.tier,
|
|
71
|
+
});
|
|
72
|
+
call.say(`Hi ${customer.name}! How can I help?`);
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This pattern lets you keep a single canonical prompt but personalize it for every caller without rewriting the whole template.
|
|
77
|
+
|
|
78
|
+
## Adding context mid-call
|
|
79
|
+
|
|
80
|
+
Append dynamic context without replacing the prompt:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
agent.on("call.started", async (call) => {
|
|
84
|
+
const orders = await getRecentOrders(call.from);
|
|
85
|
+
await call.addContext(
|
|
86
|
+
`Recent orders:\n${orders.map((o) => `- ${o.id}: ${o.status}`).join("\n")}`,
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
You can call `addContext` multiple times during a call — each call appends. Use it to inject anything that changes during the conversation: lookups, calculations, tool results you want the LLM to remember.
|
|
92
|
+
|
|
93
|
+
## Replacing the prompt mid-call
|
|
94
|
+
|
|
95
|
+
For more aggressive changes — escalation, new persona, mode switch — replace the whole prompt:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
call.setPrompt(
|
|
99
|
+
"You are now in escalation mode. Be more formal. Offer to connect to a human.",
|
|
100
|
+
);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The next LLM turn uses the new prompt. History is preserved.
|
|
104
|
+
|
|
105
|
+
## Why this matters
|
|
106
|
+
|
|
107
|
+
Most voice platforms treat the agent as a fixed config: you upload a JSON, the platform serves it, the end. Changes require redeploying or hitting a dashboard.
|
|
108
|
+
|
|
109
|
+
Pinecall treats the agent as **live state inside your process**. That changes what you can build:
|
|
110
|
+
|
|
111
|
+
- **Personalize every call** — load CRM data on `call.started`, set prompt vars, the LLM knows about the customer from word one
|
|
112
|
+
- **Multi-language by default** — detect language from the first user message, switch voice + STT accordingly
|
|
113
|
+
- **Phase transitions** — `setPrompt` when the conversation enters a new mode (qualification → demo → close)
|
|
114
|
+
- **Live A/B testing** — `agent.update` to flip the model or voice based on traffic without redeploying
|
|
115
|
+
|
|
116
|
+
## What's next
|
|
117
|
+
|
|
118
|
+
- [Tools and Functions](/guides/tools-and-functions) — combine hot-reload with tool calling
|
|
119
|
+
- [`Call` API reference](/api/call) — every method you can use mid-call
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Philosophy"
|
|
3
|
+
description: "Why Pinecall is code-first and what that means for your architecture."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Philosophy
|
|
7
|
+
|
|
8
|
+
Pinecall SDK is designed around one idea: **any existing app can add a voice agent without changing its architecture.**
|
|
9
|
+
|
|
10
|
+
## Code-first, not platform-first
|
|
11
|
+
|
|
12
|
+
Traditional voice AI platforms ask you to adapt your app to them — configure agents in a dashboard, expose webhooks, maintain JSON tool schemas separately from your code, send data to their servers.
|
|
13
|
+
|
|
14
|
+
Pinecall flips this. The agent runs **inside your process**:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { Pinecall, tool } from "@pinecall/sdk";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
import { db } from "./db.js";
|
|
20
|
+
|
|
21
|
+
const pc = new Pinecall();
|
|
22
|
+
|
|
23
|
+
const lookupOrder = tool({
|
|
24
|
+
name: "lookupOrder",
|
|
25
|
+
description: "Look up a customer's order by their phone number.",
|
|
26
|
+
schema: z.object({ phone: z.string() }),
|
|
27
|
+
execute: async ({ phone }) => await db.orders.findOne({ phone }),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const agent = pc.agent("support", {
|
|
31
|
+
prompt: "You are a support agent for Acme Corp.",
|
|
32
|
+
llm: "openai/gpt-5-chat-latest",
|
|
33
|
+
voice: "elevenlabs/sarah",
|
|
34
|
+
stt: "deepgram/flux",
|
|
35
|
+
phoneNumber: "+15551234567",
|
|
36
|
+
greeting: "Hi, how can I help?",
|
|
37
|
+
tools: [lookupOrder],
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
No webhooks. No separate tool server. No "upload your tools as JSON". Your tools are just functions with Zod schemas, auto-executed by the SDK.
|
|
42
|
+
|
|
43
|
+
## Voice as a library, not a platform
|
|
44
|
+
|
|
45
|
+
The SDK is a **dependency** — `npm install @pinecall/sdk`. It lives in your `package.json` alongside Express, Prisma, and everything else you already use.
|
|
46
|
+
|
|
47
|
+
You don't migrate to Pinecall. You add it to your existing app. Your existing Express routes, your existing database connection, your existing auth — they all stay exactly where they are.
|
|
48
|
+
|
|
49
|
+
## The server does the hard parts
|
|
50
|
+
|
|
51
|
+
Your code handles business logic. The Pinecall voice server handles the things that are genuinely hard:
|
|
52
|
+
|
|
53
|
+
| Your code | Voice server |
|
|
54
|
+
|---|---|
|
|
55
|
+
| Prompts and personality | Audio transport (WebRTC, Twilio, SIP) |
|
|
56
|
+
| Tool functions | Speech-to-text (Deepgram, Gladia, AWS) |
|
|
57
|
+
| Business logic | Text-to-speech (ElevenLabs, Cartesia) |
|
|
58
|
+
| Database queries | Voice Activity Detection (VAD) |
|
|
59
|
+
| Conversation history | Turn detection |
|
|
60
|
+
| When to start/stop calls | Audio mixing and streaming |
|
|
61
|
+
|
|
62
|
+
The split is clean: you own the **what** (what the agent says, what tools it has, what data it accesses), the server owns the **how** (how audio is captured, transcribed, synthesized, and played back).
|
|
63
|
+
|
|
64
|
+
## One connection, many agents
|
|
65
|
+
|
|
66
|
+
A single WebSocket connection multiplexes everything:
|
|
67
|
+
|
|
68
|
+

|
|
69
|
+
|
|
70
|
+
No separate infrastructure per agent. No load balancer per channel. One `new Pinecall()`, as many agents as you need.
|
|
71
|
+
|
|
72
|
+
## Config is code
|
|
73
|
+
|
|
74
|
+
There is no dashboard to configure. Agent config lives in your source code, version-controlled, reviewable:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const agent = pc.agent("mara", {
|
|
78
|
+
prompt: fs.readFileSync("./prompts/mara.md", "utf-8"),
|
|
79
|
+
llm: "openai/gpt-5-chat-latest",
|
|
80
|
+
voice: "elevenlabs/sarah",
|
|
81
|
+
language: "es",
|
|
82
|
+
stt: { provider: "deepgram-flux", keyterms: ["Acme", "checkout"] },
|
|
83
|
+
phoneNumber: "+13186330963",
|
|
84
|
+
sessionLimits: { idle_timeout_seconds: 30, idle_warning_seconds: 10 },
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Change anything — prompt, voice, model, channels — and the server picks it up on the next connection. No redeployment of a separate config layer. See [Hot Reload](/concepts/hot-reload).
|
|
89
|
+
|
|
90
|
+
## Your data never leaves your process
|
|
91
|
+
|
|
92
|
+
When the LLM calls a tool, Pinecall routes that call to your SDK handler. Your handler runs in your process — it can query your database, call your internal APIs, read files from disk. The tool result goes back to the LLM through the same WebSocket.
|
|
93
|
+
|
|
94
|
+
No data is stored on Pinecall servers. No conversation history is persisted unless you persist it. No tool results are logged unless you log them.
|
|
95
|
+
|
|
96
|
+
## What's next
|
|
97
|
+
|
|
98
|
+
- [Quickstart](/quickstart) — see the philosophy in action
|
|
99
|
+
- [Agents and Channels](/concepts/agents-and-channels) — the core abstraction
|
|
100
|
+
- [Deployment Topologies](/concepts/deployment-topologies) — how to run in production
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Server-side vs Client-side LLM"
|
|
3
|
+
description: "The single most important architectural decision when building a Pinecall agent."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Server-side vs Client-side LLM
|
|
7
|
+
|
|
8
|
+
When you build a Pinecall agent, you choose where the LLM runs. This is the single most important architectural decision in the SDK.
|
|
9
|
+
|
|
10
|
+
## The two modes
|
|
11
|
+
|
|
12
|
+
### Server-side LLM (recommended)
|
|
13
|
+
|
|
14
|
+
The Pinecall server runs the LLM. You give it a prompt, a model, and (optionally) tool definitions. The server handles STT, runs the LLM, generates TTS — you only handle tool calls.
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { tool } from "@pinecall/sdk";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
|
|
20
|
+
const lookupCustomer = tool({
|
|
21
|
+
name: "lookupCustomer",
|
|
22
|
+
description: "Look up a customer by phone",
|
|
23
|
+
schema: z.object({ phone: z.string() }),
|
|
24
|
+
execute: async ({ phone }) => await db.customers.findOne({ phone }),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const agent = pc.agent("receptionist", {
|
|
28
|
+
prompt: "You are a helpful receptionist. Be concise.",
|
|
29
|
+
llm: "openai/gpt-5-chat-latest",
|
|
30
|
+
voice: "elevenlabs/sarah",
|
|
31
|
+
stt: "deepgram/flux",
|
|
32
|
+
language: "en",
|
|
33
|
+
tools: [lookupCustomer],
|
|
34
|
+
greeting: "Hello, how can I help?",
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Client-side LLM (bring your own)
|
|
39
|
+
|
|
40
|
+
You run the LLM yourself. The server handles STT → text and text → TTS. You receive the user's text on `turn.end`, generate a response with whatever LLM you want, and stream it back.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import OpenAI from "openai";
|
|
44
|
+
const openai = new OpenAI();
|
|
45
|
+
|
|
46
|
+
const agent = pc.agent("my-bot", { voice: "cartesia/yumiko", language: "en" });
|
|
47
|
+
|
|
48
|
+
agent.on("turn.end", async (turn, call) => {
|
|
49
|
+
const stream = call.replyStream(turn);
|
|
50
|
+
const completion = await openai.chat.completions.create({
|
|
51
|
+
llm: "openai/gpt-5-chat-latest",
|
|
52
|
+
messages: [
|
|
53
|
+
{ role: "system", content: "You are helpful. Be concise." },
|
|
54
|
+
{ role: "user", content: turn.text },
|
|
55
|
+
],
|
|
56
|
+
stream: true,
|
|
57
|
+
});
|
|
58
|
+
for await (const chunk of completion) {
|
|
59
|
+
if (stream.aborted) break;
|
|
60
|
+
const token = chunk.choices[0]?.delta?.content;
|
|
61
|
+
if (token) stream.write(token);
|
|
62
|
+
}
|
|
63
|
+
stream.end();
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Which one to choose
|
|
68
|
+
|
|
69
|
+
| | Server-side | Client-side |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| LLM choice | OpenAI, Mistral, Google, Anthropic | Any provider, any model, local |
|
|
72
|
+
| You handle conversation history | ❌ Server does it | ✅ You do it |
|
|
73
|
+
| You see tool calls | ✅ Via `llm.toolCall` | ✅ You define them |
|
|
74
|
+
| Easier to ship | ✅ Yes | Slightly more code |
|
|
75
|
+
| Required for WhatsApp | ✅ Yes | ❌ No (server-side only) |
|
|
76
|
+
| Latency | Slightly lower (LLM runs near the audio pipeline) | Depends on your provider |
|
|
77
|
+
| Cost | Pinecall passes through provider cost | You pay your provider directly |
|
|
78
|
+
|
|
79
|
+
**Pick server-side if**: you're using OpenAI, Mistral, Google, or Anthropic, you want the simplest possible code, or you need WhatsApp.
|
|
80
|
+
|
|
81
|
+
**Pick client-side if**: you need a specific LLM Pinecall doesn't host (local Ollama, a fine-tuned model), you have an existing LangChain/LlamaIndex pipeline, or you need full control over the prompt-building logic.
|
|
82
|
+
|
|
83
|
+
## You can mix them
|
|
84
|
+
|
|
85
|
+
A single `Pinecall` instance can host multiple agents, each with a different LLM strategy:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Server-side agent for WhatsApp + phone
|
|
89
|
+
const support = pc.agent("support", {
|
|
90
|
+
llm: "openai/gpt-5-chat-latest",
|
|
91
|
+
stt: "deepgram/flux",
|
|
92
|
+
prompt: "...",
|
|
93
|
+
phoneNumber: "+13186330963",
|
|
94
|
+
whatsapp: [{ phoneNumberId: "123", accessToken: "EAA..." }],
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Client-side agent using a local Ollama model for a specialized use case
|
|
98
|
+
const research = pc.agent("research", { voice: "elevenlabs/george", language: "en" });
|
|
99
|
+
research.on("turn.end", async (turn, call) => {
|
|
100
|
+
/* call your own LLM (Ollama, fine-tuned model, ...), stream back */
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## What about hybrid?
|
|
105
|
+
|
|
106
|
+
What if you want to use the server-side LLM but inject context or modify history mid-call? You can:
|
|
107
|
+
|
|
108
|
+
- **Inject context dynamically** — `call.addContext("Recent order: #12345 shipped today")`
|
|
109
|
+
- **Replace the prompt mid-call** — `call.setPrompt("Now you're in escalation mode.")`
|
|
110
|
+
- **Set template variables** — define `{{customer_name}}` in the prompt, fill it per-call
|
|
111
|
+
- **Modify history** — `call.addHistory([...])`, `call.setHistory([...])`, `call.clearHistory()`
|
|
112
|
+
|
|
113
|
+
See [Hot-Reload](/concepts/hot-reload) for the full set of mid-call controls.
|
|
114
|
+
|
|
115
|
+
## What's next
|
|
116
|
+
|
|
117
|
+
- [Hot-reload everything](/concepts/hot-reload)
|
|
118
|
+
- [Tool calling guide](/guides/tools-and-functions)
|
|
119
|
+
- [Events reference](/reference/events) — see all the events you can hook into
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pinecall-examples
|
|
3
|
+
description: >-
|
|
4
|
+
Copy-paste recipes — full working agents for common scenarios. Use when the user is building, configuring, or debugging with @pinecall/sdk. Keywords: example, recipe, sample, outbound dispatch, chat bot, browser widget, multi-channel, headless.
|
|
5
|
+
license: MIT
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Examples
|
|
9
|
+
|
|
10
|
+
Copy-paste recipes — full working agents for common scenarios.
|
|
11
|
+
|
|
12
|
+
This skill bundles the official Pinecall documentation for **Examples**. 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
|
+
| **Examples** | Runnable examples showing Pinecall SDK features in action. | [`references/examples/index.md`](references/examples/index.md) · [docs](https://docs.pinecall.io/examples/index) |
|
|
19
|
+
| **Outbound Dispatch** | CSV-driven outbound campaign with rate limiting, dedup, and result writeback. | [`references/examples/outbound-dispatch.md`](references/examples/outbound-dispatch.md) · [docs](https://docs.pinecall.io/examples/outbound-dispatch) |
|
|
20
|
+
| **Example: Turn Detection** | Debug turn events in real-time — per-turn containers showing the full state machine lifecycle. | [`references/examples/turn-detection.md`](references/examples/turn-detection.md) · [docs](https://docs.pinecall.io/examples/turn-detection) |
|
|
21
|
+
| **Example: Headless Agent** | Complete runnable example — a phone support agent with zero web server. | [`references/examples/headless-agent.md`](references/examples/headless-agent.md) · [docs](https://docs.pinecall.io/examples/headless-agent) |
|
|
22
|
+
| **Example: Multi-Channel Bot** | One agent serving phone, WhatsApp, and browser WebRTC simultaneously. | [`references/examples/multi-channel-bot.md`](references/examples/multi-channel-bot.md) · [docs](https://docs.pinecall.io/examples/multi-channel-bot) |
|
|
23
|
+
| **Example: Chat Bot** | Text chat agent using @pinecall/web/chat — same agent, text instead of voice. | [`references/examples/chat-bot.md`](references/examples/chat-bot.md) · [docs](https://docs.pinecall.io/examples/chat-bot) |
|
|
24
|
+
| **Example: Browser Widget** | Express backend + React frontend with VoiceWidget. Click the orb, talk. | [`references/examples/browser-widget.md`](references/examples/browser-widget.md) · [docs](https://docs.pinecall.io/examples/browser-widget) |
|
|
25
|
+
|
|
26
|
+
## Canonical agent
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { Pinecall } from "@pinecall/sdk";
|
|
30
|
+
|
|
31
|
+
const pc = new Pinecall(); // reads PINECALL_API_KEY, auto-connects
|
|
32
|
+
|
|
33
|
+
const agent = pc.agent("mara", {
|
|
34
|
+
prompt: "You are Mara, a friendly voice assistant. Be concise.",
|
|
35
|
+
llm: "openai/gpt-5-chat-latest",
|
|
36
|
+
voice: "elevenlabs/sarah",
|
|
37
|
+
stt: "deepgram/flux",
|
|
38
|
+
language: "en",
|
|
39
|
+
greeting: "Hello! How can I help?",
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## House rules — always apply
|
|
44
|
+
|
|
45
|
+
- **Example defaults** (use these exact strings unless the user asks otherwise):
|
|
46
|
+
`stt: "deepgram/flux"`, `llm: "openai/gpt-5-chat-latest"`, `voice: "elevenlabs/sarah"`.
|
|
47
|
+
**NEVER use `deepgram/nova-2`** — it is not supported. Use `deepgram/nova-3`
|
|
48
|
+
only for languages Flux doesn't support (e.g. Arabic).
|
|
49
|
+
- **Turn detection & VAD are auto-derived from the STT provider — never set
|
|
50
|
+
`turnDetection` or `vad` manually.** Flux → native turns + native VAD;
|
|
51
|
+
every other STT → `smart_turn` + `silero`.
|
|
52
|
+
- **Greeting**: inbound → `greeting` field in `pc.agent()`; outbound → `greeting`
|
|
53
|
+
field in `agent.dial()`. It is sugar for `call.say()` in `call.started`.
|
|
54
|
+
- **Auth**: `new Pinecall()` reads `PINECALL_API_KEY` from env and auto-connects.
|
|
55
|
+
- Full documentation: <https://docs.pinecall.io>
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
*Generated from `sdk/docs/` by `@pinecall/skills` — do not edit by hand; edit the
|
|
59
|
+
docs and re-run `node build.mjs`.*
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Example: Browser Widget"
|
|
3
|
+
description: "Express backend + React frontend with VoiceWidget. Click the orb, talk."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Example: Browser Widget
|
|
7
|
+
|
|
8
|
+
An Express backend + React frontend. Users click the orb, talk to your agent — no phone number needed.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @pinecall/sdk @pinecall/web express
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Backend — `server.js`
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import express from "express";
|
|
20
|
+
import { Pinecall } from "@pinecall/sdk";
|
|
21
|
+
|
|
22
|
+
const app = express();
|
|
23
|
+
const pc = new Pinecall();
|
|
24
|
+
|
|
25
|
+
const mara = pc.agent("mara", {
|
|
26
|
+
prompt: `You are Mara, a friendly voice assistant.
|
|
27
|
+
Be brief — 1-2 sentences per response.`,
|
|
28
|
+
llm: "openai/gpt-5-chat-latest",
|
|
29
|
+
voice: "elevenlabs/sarah",
|
|
30
|
+
stt: "deepgram/flux",
|
|
31
|
+
language: "en",
|
|
32
|
+
allowedOrigins: ["http://localhost:*"],
|
|
33
|
+
greeting: "Hi! I'm Mara. How can I help?",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
mara.on("call.ended", (call, reason) => {
|
|
37
|
+
console.log(`Call ended: ${call.id} — ${reason} (${call.duration}s)`);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Token endpoint — add your own auth in production
|
|
41
|
+
app.get("/api/token", async (req, res) => {
|
|
42
|
+
const token = await mara.createToken("webrtc");
|
|
43
|
+
res.json(token);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// SSE event stream
|
|
47
|
+
app.get("/events", (req, res) => mara.stream(res));
|
|
48
|
+
|
|
49
|
+
app.listen(3000, () => console.log("http://localhost:3000"));
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Frontend — React
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { VoiceWidget } from "@pinecall/web";
|
|
56
|
+
|
|
57
|
+
function App() {
|
|
58
|
+
return (
|
|
59
|
+
<div>
|
|
60
|
+
<h1>Talk to Mara</h1>
|
|
61
|
+
<VoiceWidget
|
|
62
|
+
agent="mara"
|
|
63
|
+
tokenProvider={async () => {
|
|
64
|
+
const res = await fetch("/api/token");
|
|
65
|
+
return res.json();
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
That's it. The `VoiceWidget` renders the orb, handles mic permissions, WebRTC connection, and audio streaming.
|
|
74
|
+
|
|
75
|
+
## With `allowedOrigins` (simpler)
|
|
76
|
+
|
|
77
|
+
For demos, skip the token endpoint entirely. The `allowedOrigins` config lets the widget auto-fetch tokens:
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// No tokenProvider needed — widget auto-fetches via allowedOrigins
|
|
81
|
+
<VoiceWidget agent="mara" />
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
This works because `allowedOrigins: ["http://localhost:*"]` in the backend allows token requests from matching browser origins. For production, use the `tokenProvider` pattern with real auth.
|
|
85
|
+
|
|
86
|
+
## Rendering tools in the UI
|
|
87
|
+
|
|
88
|
+
The `VoiceWidget` supports **interactive tool UI** — the agent calls tools on the backend, and the results appear as clickable components in the browser.
|
|
89
|
+
|
|
90
|
+
### Backend — add a tool
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { tool } from "@pinecall/sdk";
|
|
94
|
+
import { z } from "zod";
|
|
95
|
+
|
|
96
|
+
const getSlots = tool({
|
|
97
|
+
name: "getSlots",
|
|
98
|
+
description: "Get available time slots for a date.",
|
|
99
|
+
schema: z.object({ date: z.string() }),
|
|
100
|
+
execute: async ({ date }) => ({
|
|
101
|
+
slots: ["10:00", "11:30", "14:00", "16:00"],
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const mara = pc.agent("mara", {
|
|
106
|
+
// ...config from above...
|
|
107
|
+
tools: [getSlots],
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Frontend — render the tool result
|
|
112
|
+
|
|
113
|
+
Pass `trackedTools` to tell the widget which results to capture. Use `useVoice()` inside a child component to render them:
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { VoiceWidget, useVoice } from "@pinecall/web";
|
|
117
|
+
|
|
118
|
+
function SlotPicker() {
|
|
119
|
+
const { toolCalls, sendText, dismissTool } = useVoice();
|
|
120
|
+
|
|
121
|
+
const slots = toolCalls.find(tc => tc.name === "getSlots" && tc.result);
|
|
122
|
+
if (!slots) return null;
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div className="slot-picker">
|
|
126
|
+
<h3>Pick a time</h3>
|
|
127
|
+
{slots.result.slots.map((slot) => (
|
|
128
|
+
<button
|
|
129
|
+
key={slot}
|
|
130
|
+
onClick={() => {
|
|
131
|
+
sendText(`I'll take the ${slot} slot`);
|
|
132
|
+
dismissTool(slots.toolCallId);
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
{slot}
|
|
136
|
+
</button>
|
|
137
|
+
))}
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function App() {
|
|
143
|
+
return (
|
|
144
|
+
<VoiceWidget
|
|
145
|
+
agent="mara"
|
|
146
|
+
trackedTools={["getSlots"]}
|
|
147
|
+
tokenProvider={async () => {
|
|
148
|
+
const res = await fetch("/api/token");
|
|
149
|
+
return res.json();
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
<SlotPicker />
|
|
153
|
+
</VoiceWidget>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### API reference
|
|
159
|
+
|
|
160
|
+
| API | What it does |
|
|
161
|
+
|-----|-------------|
|
|
162
|
+
| `trackedTools={["getSlots"]}` | Captures results for these tool names |
|
|
163
|
+
| `useVoice()` | Hook — returns `toolCalls`, `sendText`, `dismissTool`, `setContext` |
|
|
164
|
+
| `toolCalls` | Array of `{ name, toolCallId, result }` — live tool state |
|
|
165
|
+
| `sendText(text)` | Injects text as if the user spoke it (click → voice) |
|
|
166
|
+
| `dismissTool(id)` | Removes a tool from state after interaction |
|
|
167
|
+
| `setContext(key, value)` | Injects context into the LLM prompt in real time |
|
|
168
|
+
|
|
169
|
+
### Context injection
|
|
170
|
+
|
|
171
|
+
Sync UI state back to the agent's prompt so it knows what the user sees:
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
const { setContext } = useVoice();
|
|
175
|
+
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
setContext("form_state", `Name: ${name}, Email: ${email}`);
|
|
178
|
+
return () => setContext("form_state", null);
|
|
179
|
+
}, [name, email]);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The server appends this as a `## UI Context` section in the system prompt.
|
|
183
|
+
|
|
184
|
+
> For a full working example with slot picker, contact form with auto-fill, and confirmation card, see the [`booking-tools` example](https://github.com/pinecall/sdk/tree/master/examples/booking-tools).
|
|
185
|
+
|
|
186
|
+
## Run it
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
PINECALL_API_KEY=pk_... node server.js
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Open `http://localhost:3000`. Click the orb. Talk.
|
|
193
|
+
|
|
194
|
+
## Production checklist
|
|
195
|
+
|
|
196
|
+
- [ ] **Auth on `/api/token`** — add session/JWT check, never expose without auth
|
|
197
|
+
- [ ] **Rate limit** — cap tokens per user per hour
|
|
198
|
+
- [ ] **Remove `allowedOrigins`** — use `tokenProvider` with your auth instead
|
|
199
|
+
- [ ] **Mic permission UX** — explain why you need mic access before the click
|
|
200
|
+
|
|
201
|
+
## What's next
|
|
202
|
+
|
|
203
|
+
- [Security](/security) — production token auth
|
|
204
|
+
- [Tools API](/web/widget/tools-api) — full interactive tool UI reference
|
|
205
|
+
- [Headless agent example](/examples/headless-agent) — backend-only agents
|
|
206
|
+
|