@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,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
+ ![Agent tree — one connection, many agents](/assets/diagrams/agent-tree.png)
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
+