@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,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Pinecall"
|
|
3
|
+
description: "The WebSocket client. Manages auth, reconnection, and agent multiplexing."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Pinecall
|
|
7
|
+
|
|
8
|
+
The WebSocket client. One per process. Manages the connection to `voice.pinecall.io`, handles auth and reconnection, and multiplexes events across multiple agents.
|
|
9
|
+
|
|
10
|
+
**Auto-connects on construction.** When you create a `Pinecall` instance with an API key, it connects immediately — no need to call `connect()`.
|
|
11
|
+
|
|
12
|
+
## Constructor
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
new Pinecall(options)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
| Option | Type | Default | Description |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| `apiKey` | `string` | `PINECALL_API_KEY` env var | Your Pinecall API key. Auto-read from env if not provided. |
|
|
21
|
+
| `apiUrl` | `string` | `wss://voice.pinecall.io` | Server URL |
|
|
22
|
+
| `autoReconnect` | `boolean` | `true` | Auto-reconnect on disconnect |
|
|
23
|
+
| `promptsDir` | `string` | `"prompts"` | Prompts directory for `setPromptFile` |
|
|
24
|
+
|
|
25
|
+
### Example
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// Reads PINECALL_API_KEY from env automatically
|
|
29
|
+
const pc = new Pinecall();
|
|
30
|
+
|
|
31
|
+
// Or pass explicitly
|
|
32
|
+
const pc = new Pinecall({ apiKey: "pk_..." });
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Agents can be created immediately — they queue and register when the connection is ready:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
const pc = new Pinecall();
|
|
39
|
+
const agent = pc.agent("support", { /* ... */ }); // works before connected
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Methods
|
|
43
|
+
|
|
44
|
+
### `ready`
|
|
45
|
+
|
|
46
|
+
`Promise<void>` that resolves when the connection is established. Use it when you need to wait for the connection before proceeding (e.g. before dialing an outbound call).
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
await pc.ready;
|
|
50
|
+
const call = await agent.dial({ to: "+14155551234" });
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `connect()`
|
|
54
|
+
|
|
55
|
+
Manually open the WebSocket connection. **Rarely needed** — the constructor auto-connects when an API key is present. Idempotent (safe to call multiple times).
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
await pc.connect();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### `disconnect()`
|
|
62
|
+
|
|
63
|
+
Gracefully close the connection.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
await pc.disconnect();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `agent(id, config?)`
|
|
70
|
+
|
|
71
|
+
Create or retrieve an agent. If an agent with this ID already exists, returns it (idempotent).
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const agent = pc.agent("support", {
|
|
75
|
+
voice: "elevenlabs/sarah",
|
|
76
|
+
language: "en",
|
|
77
|
+
llm: "openai/gpt-5-chat-latest",
|
|
78
|
+
stt: "deepgram/flux",
|
|
79
|
+
prompt: "You are a support agent. Be concise.",
|
|
80
|
+
greeting: "Hi! How can I help you today?",
|
|
81
|
+
phoneNumber: "+13186330963",
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**`AgentConfig` fields:**
|
|
86
|
+
|
|
87
|
+
| Field | Type | Description |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `voice` | `string \| VoiceConfig` | TTS voice shortcut (e.g. `elevenlabs/sarah`) |
|
|
90
|
+
| `language` | `string` | BCP-47 language code |
|
|
91
|
+
| `stt` | `string \| STTConfig` | STT shortcut (e.g. `deepgram/flux`) |
|
|
92
|
+
| `llm` | `string \| LLMConfig` | LLM shortcut (e.g. `openai/gpt-5-chat-latest`) or full config |
|
|
93
|
+
| `prompt` | `string` | System prompt for the LLM |
|
|
94
|
+
| `greeting` | `string \| { text, addToHistory? } \| (call) => string` | Greeting spoken on inbound calls. Added to LLM history by default. |
|
|
95
|
+
| `tools` | `Tool[]` | Declarative tool definitions created with `tool()` |
|
|
96
|
+
| `phoneNumber` | `string \| PhoneNumberConfig` | Phone number or SIP URI to register (Twilio) |
|
|
97
|
+
| `phoneNumbers` | `Array<string \| PhoneNumberConfig>` | Multiple phone numbers with per-number config (e.g. one per language) |
|
|
98
|
+
| `whatsapp` | `WhatsAppChannelConfig[]` | WhatsApp channels (Meta Cloud API credentials) |
|
|
99
|
+
| `sessionLimits` | `object` | Session timeout config (see [Session Limits](/reference/session-limits)) |
|
|
100
|
+
| `allowedOrigins` | `string[]` | Allowed origins for public browser token access (see [Security](/security)) |
|
|
101
|
+
|
|
102
|
+
Dynamic greetings with a function:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
greeting: async (call) => {
|
|
106
|
+
const customer = await db.findByPhone(call.from);
|
|
107
|
+
return `Hi ${customer.name}! How can I help?`;
|
|
108
|
+
},
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Greeting without LLM history (e.g. a standalone announcement):
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
greeting: { text: "Welcome! Please hold.", addToHistory: false },
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
See [`Agent`](/api/agent) for full API reference.
|
|
118
|
+
|
|
119
|
+
### `getAgent(id)`
|
|
120
|
+
|
|
121
|
+
Look up an agent by ID. Returns `Agent | undefined`.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
const mara = pc.getAgent("mara");
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### `removeAgent(id)`
|
|
128
|
+
|
|
129
|
+
Unregister an agent. Returns `boolean` indicating whether the agent existed.
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
const removed = pc.removeAgent("mara");
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `createToken(channel, agentId)`
|
|
136
|
+
|
|
137
|
+
Generate a short-lived, single-use token for browser WebRTC or chat connections. Used to mint tokens for browsers.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
const token = await pc.createToken("webrtc", "mara");
|
|
141
|
+
// { token, server, expiresIn }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
See [Security](/security) for the full token model.
|
|
145
|
+
|
|
146
|
+
### `stream(res?, options?)`
|
|
147
|
+
|
|
148
|
+
Open an SSE stream of agent events. Works with any framework — returns a Web API `Response` or writes to a Node.js `ServerResponse`.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// Web API (Remix, Next.js, Hono, Bun)
|
|
152
|
+
app.get("/events", () => pc.stream());
|
|
153
|
+
|
|
154
|
+
// Express / Node.js
|
|
155
|
+
app.get("/events", (req, res) => pc.stream(res));
|
|
156
|
+
|
|
157
|
+
// Filtered to specific agents
|
|
158
|
+
app.get("/events", () => pc.stream({ agents: ["mara", "support"] }));
|
|
159
|
+
app.get("/events", (req, res) => pc.stream(res, { agents: ["mara"] }));
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
See [Multi-tenant guide](/guides/multi-tenant) for the filtering pattern.
|
|
163
|
+
|
|
164
|
+
## Events
|
|
165
|
+
|
|
166
|
+
Subscribe via `pc.on(event, handler)`.
|
|
167
|
+
|
|
168
|
+
| Event | Signature | When |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| `connected` | `()` | WebSocket auth succeeded |
|
|
171
|
+
| `disconnected` | `(reason)` | Connection closed |
|
|
172
|
+
| `reconnecting` | `(attempt, delay)` | Auto-reconnect attempt N |
|
|
173
|
+
| `error` | `(err)` | Protocol or transport error |
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
pc.on("connected", () => console.log("Live"));
|
|
177
|
+
pc.on("disconnected", (reason) => console.log("Down:", reason));
|
|
178
|
+
pc.on("reconnecting", (n) => console.log(`Retry ${n}`));
|
|
179
|
+
pc.on("error", (err) => console.error(err));
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## What's next
|
|
183
|
+
|
|
184
|
+
- [`Agent`](/api/agent) — channels, events, hot-reload, dial
|
|
185
|
+
- [`Call`](/api/call) — per-session control
|
|
186
|
+
- [Security](/security) — token model and best practices
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "ReplyStream"
|
|
3
|
+
description: "Token-by-token streaming for client-side LLM responses."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ReplyStream
|
|
7
|
+
|
|
8
|
+
A streaming interface for sending LLM tokens to the server. TTS starts as soon as a sentence boundary is detected — you don't wait for the full response.
|
|
9
|
+
|
|
10
|
+
Use it when running a client-side LLM (bring your own provider). For server-side LLMs, you don't need it — the server streams TTS automatically.
|
|
11
|
+
|
|
12
|
+
## Creating a stream
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
const stream = call.replyStream(turn);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Pass the `turn` object from `turn.end` so the stream is tied to that specific user turn. If the user keeps talking, the stream auto-aborts.
|
|
19
|
+
|
|
20
|
+
## Writing tokens
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
for await (const chunk of llm.stream(prompt)) {
|
|
24
|
+
if (stream.aborted) break;
|
|
25
|
+
const token = chunk.choices[0]?.delta?.content;
|
|
26
|
+
if (token) stream.write(token);
|
|
27
|
+
}
|
|
28
|
+
stream.end();
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| Method | Description |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `stream.write(token)` | Append a token to the stream |
|
|
34
|
+
| `stream.end()` | Mark the stream complete — server flushes remaining TTS |
|
|
35
|
+
| `stream.aborted` | `true` if the user interrupted or kept talking |
|
|
36
|
+
|
|
37
|
+
Always call `stream.end()` when done, even on error — otherwise the server keeps waiting.
|
|
38
|
+
|
|
39
|
+
## Handling interruptions
|
|
40
|
+
|
|
41
|
+
The `aborted` flag flips to `true` when:
|
|
42
|
+
|
|
43
|
+
- The user starts speaking again (`turn.continued`)
|
|
44
|
+
- The user explicitly cancels (`bot.interrupted`)
|
|
45
|
+
- The call ends (`call.ended`)
|
|
46
|
+
|
|
47
|
+
Always check `aborted` in your token loop:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
for await (const chunk of openai.chat.completions.create({ /* ... */ })) {
|
|
51
|
+
if (stream.aborted) break;
|
|
52
|
+
const token = chunk.choices[0]?.delta?.content;
|
|
53
|
+
if (token) stream.write(token);
|
|
54
|
+
}
|
|
55
|
+
stream.end();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If you don't, you'll keep computing tokens (and paying for them) after the user has moved on.
|
|
59
|
+
|
|
60
|
+
## Full client-side LLM pattern
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import OpenAI from "openai";
|
|
64
|
+
const openai = new OpenAI();
|
|
65
|
+
|
|
66
|
+
agent.on("turn.end", async (turn, call) => {
|
|
67
|
+
const stream = call.replyStream(turn);
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const history = await call.getHistory();
|
|
71
|
+
const completion = await openai.chat.completions.create({
|
|
72
|
+
llm: "openai/gpt-5-chat-latest",
|
|
73
|
+
messages: [
|
|
74
|
+
{ role: "system", content: "You are helpful. Be concise." },
|
|
75
|
+
...history,
|
|
76
|
+
{ role: "user", content: turn.text },
|
|
77
|
+
],
|
|
78
|
+
stream: true,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
for await (const chunk of completion) {
|
|
82
|
+
if (stream.aborted) break;
|
|
83
|
+
const token = chunk.choices[0]?.delta?.content;
|
|
84
|
+
if (token) stream.write(token);
|
|
85
|
+
}
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error("LLM error:", err);
|
|
88
|
+
} finally {
|
|
89
|
+
stream.end();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## With Anthropic
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
98
|
+
const anthropic = new Anthropic();
|
|
99
|
+
|
|
100
|
+
agent.on("turn.end", async (turn, call) => {
|
|
101
|
+
const stream = call.replyStream(turn);
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const response = await anthropic.messages.stream({
|
|
105
|
+
model: "claude-opus-4-7",
|
|
106
|
+
max_tokens: 1024,
|
|
107
|
+
system: "You are helpful. Be concise.",
|
|
108
|
+
messages: [{ role: "user", content: turn.text }],
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
for await (const event of response) {
|
|
112
|
+
if (stream.aborted) break;
|
|
113
|
+
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
|
|
114
|
+
stream.write(event.delta.text);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} finally {
|
|
118
|
+
stream.end();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## With LangChain
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { ChatOpenAI } from "@langchain/openai";
|
|
127
|
+
const model = new ChatOpenAI({ model: "gpt-5-chat-latest", streaming: true });
|
|
128
|
+
|
|
129
|
+
agent.on("turn.end", async (turn, call) => {
|
|
130
|
+
const stream = call.replyStream(turn);
|
|
131
|
+
|
|
132
|
+
const llmStream = await model.stream([
|
|
133
|
+
{ role: "system", content: "You are helpful." },
|
|
134
|
+
{ role: "user", content: turn.text },
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
for await (const chunk of llmStream) {
|
|
138
|
+
if (stream.aborted) break;
|
|
139
|
+
if (chunk.content) stream.write(chunk.content.toString());
|
|
140
|
+
}
|
|
141
|
+
stream.end();
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## What's next
|
|
146
|
+
|
|
147
|
+
- [Server-side vs client-side LLM](/concepts/server-vs-client-llm) — when to use each
|
|
148
|
+
- [Events reference](/reference/events) — `turn.end`, `turn.continued`, `bot.interrupted`
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pinecall-security
|
|
3
|
+
description: >-
|
|
4
|
+
Security Use when the user is building, configuring, or debugging with @pinecall/sdk. Keywords: .
|
|
5
|
+
license: MIT
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Security
|
|
9
|
+
|
|
10
|
+
Security
|
|
11
|
+
|
|
12
|
+
This skill bundles the official Pinecall documentation for **Security**. 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
|
+
| **Security** | Token security model and best practices for production deployments. | [`references/security.md`](references/security.md) · [docs](https://docs.pinecall.io/security) |
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## House rules — always apply
|
|
22
|
+
|
|
23
|
+
- **Example defaults** (use these exact strings unless the user asks otherwise):
|
|
24
|
+
`stt: "deepgram/flux"`, `llm: "openai/gpt-5-chat-latest"`, `voice: "elevenlabs/sarah"`.
|
|
25
|
+
**NEVER use `deepgram/nova-2`** — it is not supported. Use `deepgram/nova-3`
|
|
26
|
+
only for languages Flux doesn't support (e.g. Arabic).
|
|
27
|
+
- **Turn detection & VAD are auto-derived from the STT provider — never set
|
|
28
|
+
`turnDetection` or `vad` manually.** Flux → native turns + native VAD;
|
|
29
|
+
every other STT → `smart_turn` + `silero`.
|
|
30
|
+
- **Greeting**: inbound → `greeting` field in `pc.agent()`; outbound → `greeting`
|
|
31
|
+
field in `agent.dial()`. It is sugar for `call.say()` in `call.started`.
|
|
32
|
+
- **Auth**: `new Pinecall()` reads `PINECALL_API_KEY` from env and auto-connects.
|
|
33
|
+
- Full documentation: <https://docs.pinecall.io>
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
*Generated from `sdk/docs/` by `@pinecall/skills` — do not edit by hand; edit the
|
|
37
|
+
docs and re-run `node build.mjs`.*
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Security"
|
|
3
|
+
description: "Token security model and best practices for production deployments."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Security
|
|
7
|
+
|
|
8
|
+
This page covers how Pinecall handles browser security: the token model, why tokens are safe to expose, and when to use the convenience `allowedOrigins` mode vs full backend auth.
|
|
9
|
+
|
|
10
|
+
## Token security model
|
|
11
|
+
|
|
12
|
+
Browser connections (WebRTC and chat) use **short-lived tokens** generated by the voice server. The recommended model:
|
|
13
|
+
|
|
14
|
+
> **Your backend generates tokens using your API key, and distributes them to browsers through your own auth layer.**
|
|
15
|
+
|
|
16
|
+
This is the same model used by LiveKit, Twilio, Daily.co, and every major real-time platform.
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
### Backend (Express, Next.js, Hono, etc.)
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { Pinecall } from "@pinecall/sdk";
|
|
24
|
+
|
|
25
|
+
const pc = new Pinecall();
|
|
26
|
+
|
|
27
|
+
const agent = pc.agent("florencia", { /* config */ });
|
|
28
|
+
|
|
29
|
+
// Token endpoint — protected by YOUR auth
|
|
30
|
+
app.get("/api/token", authMiddleware, async (req, res) => {
|
|
31
|
+
const channel = req.query.channel as "webrtc" | "chat";
|
|
32
|
+
const token = await agent.createToken(channel);
|
|
33
|
+
res.json(token);
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
If the agent is in a separate process (you only have `pc` in the web server):
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
app.get("/api/token", authMiddleware, async (req, res) => {
|
|
41
|
+
const token = await pc.createToken("webrtc", "florencia");
|
|
42
|
+
res.json(token);
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Frontend (VoiceWidget)
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<VoiceWidget
|
|
50
|
+
agent="florencia"
|
|
51
|
+
tokenProvider={async () => {
|
|
52
|
+
const res = await fetch("/api/token?channel=webrtc", {
|
|
53
|
+
credentials: "include", // send your session cookie
|
|
54
|
+
});
|
|
55
|
+
return res.json();
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Why tokens are safe
|
|
61
|
+
|
|
62
|
+
Tokens have three security properties that make them safe to pass to browsers:
|
|
63
|
+
|
|
64
|
+
| Property | Value | Effect |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| **Single-use** | Consumed on first connection | Can't be reused by an attacker |
|
|
67
|
+
| **Short-lived** | 60 second TTL | Expires before anyone can steal it |
|
|
68
|
+
| **Scoped** | Locked to agent + org | Can't be used for a different agent |
|
|
69
|
+
|
|
70
|
+
The token is **not** the security boundary — **your backend is**. The token is a short-lived capability that proves "someone authorized gave me permission to connect." The security question is: who can call your `/api/token` endpoint?
|
|
71
|
+
|
|
72
|
+
- **Requires login** → only authenticated users get tokens
|
|
73
|
+
- **Rate limited** → can't bulk-generate tokens
|
|
74
|
+
- **Permission-checked** → only authorized users connect
|
|
75
|
+
|
|
76
|
+
Think of it like a movie ticket: the theater (your backend) verifies your identity and gives you a ticket. The ticket works once, for one screen, for a limited time. Even if someone steals the ticket, they get one session — and they'd need to break TLS to intercept it.
|
|
77
|
+
|
|
78
|
+
## `allowedOrigins` (convenience mode)
|
|
79
|
+
|
|
80
|
+
For simple deployments without a backend (demos, prototypes, CodePen), you can opt-in to public token access by configuring `allowedOrigins`:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const agent = pc.agent("demo-bot", {
|
|
84
|
+
allowedOrigins: [
|
|
85
|
+
"https://demo.mysite.com", // exact match
|
|
86
|
+
"https://*.mysite.com", // subdomain wildcard
|
|
87
|
+
"http://localhost:*", // any port (dev)
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
When `allowedOrigins` is set, the token endpoint accepts browser requests from matching origins **without** an API key. The `Origin` header is browser-enforced (real browsers can't spoof it).
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<VoiceWidget agent="demo-bot" />
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
> **Warning:** `allowedOrigins` protects against casual embedding but **not** against a determined attacker — Origin headers can be spoofed from scripts/curl. For production with real users, always use `tokenProvider` with your backend auth.
|
|
99
|
+
|
|
100
|
+
## Mode comparison
|
|
101
|
+
|
|
102
|
+
| Mode | Security level | Use case |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `tokenProvider` (backend) | ✅ Full auth control | Production apps |
|
|
105
|
+
| `allowedOrigins` (public) | ⚠️ Origin-based only | Demos, prototypes |
|
|
106
|
+
| Neither (default) | ❌ Rejected | — |
|
|
107
|
+
|
|
108
|
+
## API key handling
|
|
109
|
+
|
|
110
|
+
Your `PINECALL_API_KEY` is the master credential. Treat it like a database password:
|
|
111
|
+
|
|
112
|
+
- **Never** ship it in browser code, mobile apps, or public repos
|
|
113
|
+
- **Always** load it from environment variables on the server
|
|
114
|
+
- **Rotate** it if you suspect exposure (via the dashboard)
|
|
115
|
+
- **Scope** it per-environment (separate keys for dev, staging, prod)
|
|
116
|
+
|
|
117
|
+
The SDK never exposes the API key over the wire in browser-bound responses — `createToken` returns only the short-lived token.
|
|
118
|
+
|
|
119
|
+
## Webhook signature verification (WhatsApp)
|
|
120
|
+
|
|
121
|
+
For WhatsApp, set `appSecret` so the server verifies the HMAC signature on every incoming webhook:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
agent.addWhatsapp({
|
|
125
|
+
phoneNumberId: "...",
|
|
126
|
+
accessToken: "...",
|
|
127
|
+
verifyToken: "...",
|
|
128
|
+
appSecret: process.env.WA_APP_SECRET, // HMAC verification
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Without `appSecret`, the webhook accepts any request — anyone who finds your endpoint URL can inject fake messages. Always set it in production.
|
|
133
|
+
|
|
134
|
+
## What's next
|
|
135
|
+
|
|
136
|
+
- [WebRTC in the browser](/guides/webrtc-browser) — full setup
|
|
137
|
+
- [Multi-tenant](/guides/multi-tenant) — per-tenant token isolation
|
|
138
|
+
- [WhatsApp](/guides/whatsapp) — webhook verification
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pinecall-web-chat
|
|
3
|
+
description: >-
|
|
4
|
+
@pinecall/web/chat — browser text chat (ChatSession, ChatView). Use when the user is building, configuring, or debugging with @pinecall/sdk. Keywords: chat, chatsession, text chat, @pinecall/web/chat.
|
|
5
|
+
license: MIT
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# @pinecall/web/chat (Chat)
|
|
9
|
+
|
|
10
|
+
@pinecall/web/chat — browser text chat (ChatSession, ChatView).
|
|
11
|
+
|
|
12
|
+
This skill bundles the official Pinecall documentation for **@pinecall/web/chat (Chat)**. 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/web/chat** | Text chat client for Pinecall voice agents. Framework-agnostic core + React hook. | [`references/web/chat/overview.md`](references/web/chat/overview.md) · [docs](https://docs.pinecall.io/web/chat/overview) |
|
|
19
|
+
| **ChatSession API** | Full reference for ChatSession (vanilla) and usePinecallChat (React). | [`references/web/chat/chat-session.md`](references/web/chat/chat-session.md) · [docs](https://docs.pinecall.io/web/chat/chat-session) |
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## House rules — always apply
|
|
23
|
+
|
|
24
|
+
- **Example defaults** (use these exact strings unless the user asks otherwise):
|
|
25
|
+
`stt: "deepgram/flux"`, `llm: "openai/gpt-5-chat-latest"`, `voice: "elevenlabs/sarah"`.
|
|
26
|
+
**NEVER use `deepgram/nova-2`** — it is not supported. Use `deepgram/nova-3`
|
|
27
|
+
only for languages Flux doesn't support (e.g. Arabic).
|
|
28
|
+
- **Turn detection & VAD are auto-derived from the STT provider — never set
|
|
29
|
+
`turnDetection` or `vad` manually.** Flux → native turns + native VAD;
|
|
30
|
+
every other STT → `smart_turn` + `silero`.
|
|
31
|
+
- **Greeting**: inbound → `greeting` field in `pc.agent()`; outbound → `greeting`
|
|
32
|
+
field in `agent.dial()`. It is sugar for `call.say()` in `call.started`.
|
|
33
|
+
- **Auth**: `new Pinecall()` reads `PINECALL_API_KEY` from env and auto-connects.
|
|
34
|
+
- Full documentation: <https://docs.pinecall.io>
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
*Generated from `sdk/docs/` by `@pinecall/skills` — do not edit by hand; edit the
|
|
38
|
+
docs and re-run `node build.mjs`.*
|