@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,377 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Conversation History"
|
|
3
|
+
description: "Save and restore conversations across calls so your agent remembers returning contacts."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Conversation History
|
|
7
|
+
|
|
8
|
+
Every call's conversation (transcript + LLM messages) is available when the call ends. The `HistoryStore` interface lets you **persist** conversations automatically and **restore** them on subsequent calls — so your agent remembers what was discussed before.
|
|
9
|
+
|
|
10
|
+
## Quick start
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { Pinecall, JsonFileHistory } from "@pinecall/sdk";
|
|
14
|
+
|
|
15
|
+
const history = new JsonFileHistory("./data/calls.json");
|
|
16
|
+
|
|
17
|
+
const pc = new Pinecall({ apiKey: process.env.PINECALL_API_KEY! });
|
|
18
|
+
|
|
19
|
+
const agent = pc.agent("my-agent", {
|
|
20
|
+
llm: "openai/gpt-5-chat-latest",
|
|
21
|
+
voice: "elevenlabs/sarah",
|
|
22
|
+
stt: "deepgram/flux",
|
|
23
|
+
prompt: "You are a helpful assistant with memory of past conversations.",
|
|
24
|
+
phoneNumber: "+13186330963",
|
|
25
|
+
history, // ← auto-saves AND auto-restores
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
agent.on("call.started", (call) => {
|
|
29
|
+
call.say("Hello! How can I help?");
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
That's it. Every call is auto-saved when it ends, and returning callers get their prior context restored automatically — no extra code needed.
|
|
34
|
+
|
|
35
|
+
## How it works
|
|
36
|
+
|
|
37
|
+
### Auto-save on `call.ended`
|
|
38
|
+
|
|
39
|
+
When `history` is set in the agent config, the SDK automatically saves a `ConversationRecord` when each call ends:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
call.ended → HistoryStore.save({
|
|
43
|
+
callId, agentId, channel, direction,
|
|
44
|
+
from, to, startedAt, endedAt, duration,
|
|
45
|
+
reason, transcript, messages, metadata
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
You never need to write a `call.ended` handler for saving — it happens automatically.
|
|
50
|
+
|
|
51
|
+
### What gets saved
|
|
52
|
+
|
|
53
|
+
| Field | Type | Description |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `callId` | string | Unique call ID |
|
|
56
|
+
| `agentId` | string | Agent that handled the call |
|
|
57
|
+
| `channel` | string | `"phone"`, `"webrtc"`, `"chat"` |
|
|
58
|
+
| `direction` | string | `"inbound"` or `"outbound"` |
|
|
59
|
+
| `from` | string | Caller identifier (phone number, userId, etc.) |
|
|
60
|
+
| `to` | string | Callee identifier |
|
|
61
|
+
| `startedAt` | number | Epoch seconds |
|
|
62
|
+
| `endedAt` | number | Epoch seconds |
|
|
63
|
+
| `duration` | number | Call duration in seconds |
|
|
64
|
+
| `reason` | string | Why the call ended (hangup, timeout, etc.) |
|
|
65
|
+
| `status` | string | `"active"` while in progress, `"ended"` after `call.ended` |
|
|
66
|
+
| `transcript` | array | User/assistant messages (clean text) |
|
|
67
|
+
| `messages` | array | Full LLM messages (including tool calls, system prompt) |
|
|
68
|
+
| `metadata` | object | Any metadata attached to the call |
|
|
69
|
+
|
|
70
|
+
### Auto-restore
|
|
71
|
+
|
|
72
|
+
When your `HistoryStore` implements `findByContact()`, the SDK **automatically restores** prior conversations for returning contacts — for all channels (voice, WebRTC, chat, and WhatsApp).
|
|
73
|
+
|
|
74
|
+
On each new call or WhatsApp session, the SDK:
|
|
75
|
+
1. Calls `findByContact(contactId, 5)` to load the last 5 conversations
|
|
76
|
+
2. Merges all messages and keeps the most recent 20 user/assistant messages
|
|
77
|
+
3. Injects them into the server-side LLM via `setHistory()`
|
|
78
|
+
|
|
79
|
+
This happens in the background — no code needed. The `JsonFileHistory` built-in store implements `findByContact()`, so auto-restore works out of the box.
|
|
80
|
+
|
|
81
|
+
### Manual override
|
|
82
|
+
|
|
83
|
+
If you need custom restore logic (e.g., different message limits, conditional restore), handle it yourself in the event handler. The auto-restore fires in the background, but your manual `setHistory()` call will override it:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
agent.on("call.started", async (call) => {
|
|
87
|
+
// Custom: only restore if the last call was within 24 hours
|
|
88
|
+
const prior = await history.findByContact(call.from, 1);
|
|
89
|
+
if (prior.length > 0 && Date.now() / 1000 - prior[0].endedAt < 86400) {
|
|
90
|
+
await call.setHistory(prior[0].messages);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Channel support
|
|
96
|
+
|
|
97
|
+
| Channel | Auto-save? | Restore? | Contact ID | Notes |
|
|
98
|
+
|---|---|---|---|---|
|
|
99
|
+
| **Phone (Twilio)** | ✅ | ✅ `call.setHistory()` | `call.from` (E.164 number) | Saved on `call.ended` |
|
|
100
|
+
| **WebRTC** | ✅ | ✅ `call.setHistory()` | `call.metadata.userId` | Pass userId from browser |
|
|
101
|
+
| **Chat** | ✅ | ✅ `call.setHistory()` | `call.metadata.userId` | Same as WebRTC |
|
|
102
|
+
| **WhatsApp** | ✅ | ✅ `session.setHistory()` | `session.contactPhone` | Uses `WhatsAppSession` object |
|
|
103
|
+
|
|
104
|
+
### WebRTC / Chat: identifying contacts
|
|
105
|
+
|
|
106
|
+
Browser sessions don't have phone numbers, so pass a `userId` in metadata when creating the token:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// Browser-side: pass userId when getting a token
|
|
110
|
+
const token = await fetch("/api/token", {
|
|
111
|
+
body: JSON.stringify({ userId: "user-123" }),
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Then in `call.started`:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
agent.on("call.started", async (call) => {
|
|
119
|
+
const contactId = call.from !== "webrtc"
|
|
120
|
+
? call.from // Phone number
|
|
121
|
+
: call.metadata?.userId // WebRTC/Chat userId
|
|
122
|
+
? String(call.metadata.userId)
|
|
123
|
+
: null;
|
|
124
|
+
|
|
125
|
+
if (contactId) {
|
|
126
|
+
const prior = await history.findByContact(contactId, 1);
|
|
127
|
+
if (prior.length > 0) {
|
|
128
|
+
await call.setHistory(prior[0].messages);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### WhatsApp: how it works
|
|
135
|
+
|
|
136
|
+
WhatsApp sessions are different from voice calls — they're **long-lived text conversations** managed server-side. But `HistoryStore` handles them the same way:
|
|
137
|
+
|
|
138
|
+
1. Contact sends a WhatsApp message → server creates a `WhatsAppSession`
|
|
139
|
+
2. Messages flow back and forth, all tracked in the server's LLM history
|
|
140
|
+
3. When the session ends (24h window expires or 2h idle), the server emits `whatsapp.sessionEnded` with the full conversation
|
|
141
|
+
4. The SDK's `HistoryStore` auto-saves it — same `ConversationRecord` as voice
|
|
142
|
+
|
|
143
|
+
**Session end triggers:**
|
|
144
|
+
- **24h window expiry** — Meta's service window closes (no inbound message for 24h)
|
|
145
|
+
- **Idle timeout** — no messages for 2 hours
|
|
146
|
+
|
|
147
|
+
The saved record has `channel: "whatsapp"` and includes `metadata.contactName` and `metadata.messageCount`.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
agent.on("whatsapp.sessionEnded", (event) => {
|
|
151
|
+
console.log(`WhatsApp session ended: ${event.contactPhone} (${event.reason})`);
|
|
152
|
+
// Already auto-saved by HistoryStore — no manual save needed
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
> WhatsApp uses `contact_phone` (e.g. `"5491155551234"`) as the `from` field. Use `findByContact()` with this number to restore prior conversations.
|
|
157
|
+
|
|
158
|
+
### WhatsApp: restoring conversations
|
|
159
|
+
|
|
160
|
+
`whatsapp.sessionStarted` passes a `WhatsAppSession` object with the same history methods as `Call`:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
agent.on("whatsapp.sessionStarted", async (session) => {
|
|
164
|
+
const prior = await history.findByContact(session.contactPhone, 1);
|
|
165
|
+
if (prior.length > 0) {
|
|
166
|
+
await session.setHistory(prior[0].messages);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`WhatsAppSession` methods:
|
|
172
|
+
|
|
173
|
+
| Method | Description |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `session.setHistory(messages)` | Replace server-side LLM history |
|
|
176
|
+
| `session.addHistory(messages)` | Append messages to history |
|
|
177
|
+
| `session.getHistory()` | Read current LLM history |
|
|
178
|
+
| `session.clearHistory()` | Clear all history |
|
|
179
|
+
| `session.setPrompt(text)` | Replace the system prompt |
|
|
180
|
+
| `session.setPromptVars(vars)` | Set `{{variable}}` values |
|
|
181
|
+
| `session.addContext(text)` | Append context after prompt |
|
|
182
|
+
|
|
183
|
+
## Built-in: `JsonFileHistory`
|
|
184
|
+
|
|
185
|
+
The SDK ships with `JsonFileHistory` — a file-based store good for prototyping and small projects:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { JsonFileHistory } from "@pinecall/sdk";
|
|
189
|
+
|
|
190
|
+
const history = new JsonFileHistory("./data/calls.json");
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
It stores all conversations in a single JSON file, upserts by `callId`, and supports all `HistoryStore` methods:
|
|
194
|
+
|
|
195
|
+
| Method | Description |
|
|
196
|
+
|---|---|
|
|
197
|
+
| `save(record)` | Save/upsert a conversation |
|
|
198
|
+
| `findByContact(id, limit?)` | Find conversations by caller (searches `from` field) |
|
|
199
|
+
| `list(agentId, limit?)` | List conversations for an agent |
|
|
200
|
+
| `get(callId)` | Get a single conversation |
|
|
201
|
+
| `delete(callId)` | Delete a conversation |
|
|
202
|
+
|
|
203
|
+
## Custom stores
|
|
204
|
+
|
|
205
|
+
For production, implement the `HistoryStore` interface with your database of choice. Only `save()` is required — everything else is optional.
|
|
206
|
+
|
|
207
|
+
### Interface
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
interface HistoryStore {
|
|
211
|
+
// Required — called automatically on call.ended
|
|
212
|
+
save(record: ConversationRecord): Promise<void>;
|
|
213
|
+
|
|
214
|
+
// Optional — for restoring prior conversations
|
|
215
|
+
findByContact?(contactId: string, limit?: number): Promise<ConversationRecord[]>;
|
|
216
|
+
|
|
217
|
+
// Optional — for admin/dashboard features
|
|
218
|
+
list?(agentId: string, limit?: number): Promise<ConversationRecord[]>;
|
|
219
|
+
get?(callId: string): Promise<ConversationRecord | null>;
|
|
220
|
+
delete?(callId: string): Promise<boolean>;
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### MongoDB example
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import type { HistoryStore, ConversationRecord } from "@pinecall/sdk";
|
|
228
|
+
import { MongoClient } from "mongodb";
|
|
229
|
+
|
|
230
|
+
const client = new MongoClient(process.env.MONGODB_URI!);
|
|
231
|
+
const db = client.db("myapp");
|
|
232
|
+
const conversations = db.collection<ConversationRecord>("conversations");
|
|
233
|
+
|
|
234
|
+
class MongoHistory implements HistoryStore {
|
|
235
|
+
async save(record: ConversationRecord): Promise<void> {
|
|
236
|
+
await conversations.updateOne(
|
|
237
|
+
{ callId: record.callId },
|
|
238
|
+
{ $set: record },
|
|
239
|
+
{ upsert: true },
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async findByContact(contactId: string, limit = 5): Promise<ConversationRecord[]> {
|
|
244
|
+
return conversations
|
|
245
|
+
.find({ from: contactId })
|
|
246
|
+
.sort({ endedAt: -1 })
|
|
247
|
+
.limit(limit)
|
|
248
|
+
.toArray();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async list(agentId: string, limit = 50): Promise<ConversationRecord[]> {
|
|
252
|
+
return conversations
|
|
253
|
+
.find({ agentId })
|
|
254
|
+
.sort({ endedAt: -1 })
|
|
255
|
+
.limit(limit)
|
|
256
|
+
.toArray();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async get(callId: string): Promise<ConversationRecord | null> {
|
|
260
|
+
return conversations.findOne({ callId });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async delete(callId: string): Promise<boolean> {
|
|
264
|
+
const result = await conversations.deleteOne({ callId });
|
|
265
|
+
return result.deletedCount > 0;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Usage
|
|
270
|
+
const agent = pc.agent("my-agent", {
|
|
271
|
+
history: new MongoHistory(),
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### PostgreSQL example
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import type { HistoryStore, ConversationRecord } from "@pinecall/sdk";
|
|
279
|
+
import pg from "pg";
|
|
280
|
+
|
|
281
|
+
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
|
|
282
|
+
|
|
283
|
+
class PostgresHistory implements HistoryStore {
|
|
284
|
+
async save(record: ConversationRecord): Promise<void> {
|
|
285
|
+
await pool.query(
|
|
286
|
+
`INSERT INTO conversations (call_id, agent_id, channel, direction, caller, callee,
|
|
287
|
+
started_at, ended_at, duration, reason, transcript, messages, metadata)
|
|
288
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
|
289
|
+
ON CONFLICT (call_id) DO UPDATE SET
|
|
290
|
+
transcript = $11, messages = $12, ended_at = $8, duration = $9`,
|
|
291
|
+
[
|
|
292
|
+
record.callId, record.agentId, record.channel, record.direction,
|
|
293
|
+
record.from, record.to, record.startedAt, record.endedAt,
|
|
294
|
+
record.duration, record.reason,
|
|
295
|
+
JSON.stringify(record.transcript),
|
|
296
|
+
JSON.stringify(record.messages),
|
|
297
|
+
JSON.stringify(record.metadata),
|
|
298
|
+
],
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async findByContact(contactId: string, limit = 5): Promise<ConversationRecord[]> {
|
|
303
|
+
const { rows } = await pool.query(
|
|
304
|
+
`SELECT * FROM conversations WHERE caller = $1 ORDER BY ended_at DESC LIMIT $2`,
|
|
305
|
+
[contactId, limit],
|
|
306
|
+
);
|
|
307
|
+
return rows.map(this.#fromRow);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#fromRow(row: any): ConversationRecord {
|
|
311
|
+
return {
|
|
312
|
+
callId: row.call_id,
|
|
313
|
+
agentId: row.agent_id,
|
|
314
|
+
channel: row.channel,
|
|
315
|
+
direction: row.direction,
|
|
316
|
+
from: row.caller,
|
|
317
|
+
to: row.callee,
|
|
318
|
+
startedAt: row.started_at,
|
|
319
|
+
endedAt: row.ended_at,
|
|
320
|
+
duration: row.duration,
|
|
321
|
+
reason: row.reason,
|
|
322
|
+
transcript: row.transcript,
|
|
323
|
+
messages: row.messages,
|
|
324
|
+
metadata: row.metadata,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### REST API example
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import type { HistoryStore, ConversationRecord } from "@pinecall/sdk";
|
|
334
|
+
|
|
335
|
+
class APIHistory implements HistoryStore {
|
|
336
|
+
constructor(private baseUrl: string, private token: string) {}
|
|
337
|
+
|
|
338
|
+
async save(record: ConversationRecord): Promise<void> {
|
|
339
|
+
await fetch(`${this.baseUrl}/conversations`, {
|
|
340
|
+
method: "POST",
|
|
341
|
+
headers: {
|
|
342
|
+
"Content-Type": "application/json",
|
|
343
|
+
"Authorization": `Bearer ${this.token}`,
|
|
344
|
+
},
|
|
345
|
+
body: JSON.stringify(record),
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async findByContact(contactId: string, limit = 5): Promise<ConversationRecord[]> {
|
|
350
|
+
const res = await fetch(
|
|
351
|
+
`${this.baseUrl}/conversations?from=${encodeURIComponent(contactId)}&limit=${limit}`,
|
|
352
|
+
{ headers: { Authorization: `Bearer ${this.token}` } },
|
|
353
|
+
);
|
|
354
|
+
return res.json();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## History API (runtime)
|
|
360
|
+
|
|
361
|
+
Beyond auto-save/restore, you can manipulate the LLM history during an active call:
|
|
362
|
+
|
|
363
|
+
| Method | What it does |
|
|
364
|
+
|---|---|
|
|
365
|
+
| `call.setHistory(messages)` | **Replace** the entire LLM conversation history |
|
|
366
|
+
| `call.addHistory(messages)` | **Append** messages to the existing history |
|
|
367
|
+
| `call.getHistory()` | **Read** the current LLM history from the server |
|
|
368
|
+
| `call.clearHistory()` | **Clear** all LLM history |
|
|
369
|
+
|
|
370
|
+
These work during active voice calls (Twilio, WebRTC, Chat) and WhatsApp sessions (via `WhatsAppSession`). They modify the server-side LLM context in real-time.
|
|
371
|
+
|
|
372
|
+
## What's next
|
|
373
|
+
|
|
374
|
+
- [Tools and Functions](/guides/tools-and-functions) — let the agent take actions
|
|
375
|
+
- [WebRTC Browser](/guides/webrtc-browser) — build browser voice widgets
|
|
376
|
+
- [WhatsApp](/guides/whatsapp) — text-based messaging agents
|
|
377
|
+
- [`Call` API reference](/api/call) — full method reference
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Dev Mode"
|
|
3
|
+
description: "Run dev and production agents on the same phone number, with zero extra Twilio cost."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Dev Mode
|
|
7
|
+
|
|
8
|
+
A common pain in voice AI: every developer needs their own phone number, every PR review needs another, and each costs you $1/month plus per-minute usage. Worse, you can't easily test "what if a real customer called this version" without disrupting prod.
|
|
9
|
+
|
|
10
|
+
Pinecall's dev mode solves both. **One phone number, many agents in parallel**, routed by the caller's phone number. Production handles all calls except yours; your calls go to your dev agent.
|
|
11
|
+
|
|
12
|
+
## How it works
|
|
13
|
+
|
|
14
|
+
Every agent has an ID — `florencia`, `mara`, `support`. In dev mode, you give your agent a unique slug — `dev-berna-florencia`, `dev-juan-florencia` — and tell Pinecall which phone numbers should route to that slug.
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
Zero extra cost. One number serves prod and every dev simultaneously.
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
### 1. Make the agent ID environment-aware
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { Pinecall } from "@pinecall/sdk";
|
|
26
|
+
import { userInfo } from "os";
|
|
27
|
+
|
|
28
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
29
|
+
const agentId = isDev ? `dev-${userInfo().username}-florencia` : "florencia";
|
|
30
|
+
|
|
31
|
+
const pc = new Pinecall({ apiKey: process.env.PINECALL_API_KEY! });
|
|
32
|
+
|
|
33
|
+
const agent = pc.agent(agentId, {
|
|
34
|
+
prompt: "...",
|
|
35
|
+
llm: "openai/gpt-5-chat-latest",
|
|
36
|
+
voice: "elevenlabs/sarah",
|
|
37
|
+
stt: "deepgram/flux",
|
|
38
|
+
phoneNumber: "+13186330963", // shared with prod!
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`os.userInfo().username` gives you `berna` on Berna's machine, `juan` on Juan's, etc. Each dev automatically gets a unique slug.
|
|
43
|
+
|
|
44
|
+
### 2. Tell Pinecall which callers to route to dev
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
if (isDev) {
|
|
48
|
+
const callers = process.env.DEV_CALLERS;
|
|
49
|
+
if (callers) {
|
|
50
|
+
agent.routeCallers(callers.split(",").map((s) => s.trim()));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 3. Each dev gets their own `.env.local`
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# .env.local — gitignored, each dev sets their own
|
|
59
|
+
DEV_CALLERS=+34600123456
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
> **Vite users:** Vite loads `.env.local` but does **not** inject non-`VITE_` variables into `process.env` for server plugins. You must set `NODE_ENV` and `DEV_CALLERS` in the shell or your start command:
|
|
63
|
+
>
|
|
64
|
+
> ```bash
|
|
65
|
+
> NODE_ENV=development DEV_CALLERS=+34600123456 npx vite --port 5170
|
|
66
|
+
> ```
|
|
67
|
+
>
|
|
68
|
+
> Alternatively, if your agent boots inside a Vite plugin (`configureServer`), load `.env.local` manually with `dotenv`.
|
|
69
|
+
|
|
70
|
+
Now when Berna calls `+13186330963` from her phone (`+34600123456`), the call routes to `dev-berna-florencia`. When anyone else calls, it goes to `florencia` (prod).
|
|
71
|
+
|
|
72
|
+
## Multiple devs at once
|
|
73
|
+
|
|
74
|
+
| Developer | Agent ID | Phone routing |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| Berna | `dev-berna-florencia` | Calls from `+34607...` → Berna's agent |
|
|
77
|
+
| Juan | `dev-juan-florencia` | Calls from `+34612...` → Juan's agent |
|
|
78
|
+
| Production | `florencia` | All other callers |
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
+13186330963 (shared Twilio number)
|
|
82
|
+
│
|
|
83
|
+
├── Call from +34607... → dev-berna-florencia
|
|
84
|
+
├── Call from +34612... → dev-juan-florencia
|
|
85
|
+
└── Call from anyone else → florencia (production)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## WhatsApp dev routing
|
|
89
|
+
|
|
90
|
+
WhatsApp uses the same sender-based routing. `routeCallers()` configures both phone and WhatsApp routing in one call:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
if (isDev) {
|
|
94
|
+
agent.routeCallers(["+34600123456"]); // routes BOTH phone calls AND WhatsApp messages
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
When Berna sends a WhatsApp message from `+34600123456`, it lands on `dev-berna-florencia`. When anyone else sends a message, it lands on prod.
|
|
99
|
+
|
|
100
|
+
## WebRTC & chat dev routing
|
|
101
|
+
|
|
102
|
+
WebRTC and chat don't use caller-based routing — there's no "caller number" in a browser. Instead they use **slug-based isolation**: each browser requests a token for a specific agent ID.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// Dev mode → agent registers as "dev-berna-florencia"
|
|
106
|
+
// The browser requests a token for "dev-berna-florencia" specifically
|
|
107
|
+
const token = await createToken({
|
|
108
|
+
channel: "webrtc",
|
|
109
|
+
agentId: "dev-berna-florencia",
|
|
110
|
+
apiKey: process.env.PINECALL_API_KEY!,
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Each dev gets their own slug, their own tokens, their own sessions. Nothing crosses over.
|
|
115
|
+
|
|
116
|
+
## Why this is better than per-dev phone numbers
|
|
117
|
+
|
|
118
|
+
| | Per-dev numbers | Pinecall dev mode |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| Cost | $1/month/dev + minutes | $0 extra |
|
|
121
|
+
| Setup per dev | Buy number, configure routing, share creds | Set `DEV_CALLERS=+...` |
|
|
122
|
+
| Realism | Different number = different call origin behavior | Same number, real routing |
|
|
123
|
+
| Cleanup when dev leaves | Cancel number, update routing | Delete from `.env.local` |
|
|
124
|
+
| Prod isolation | Manual — easy to leak | Automatic — only your number reaches your agent |
|
|
125
|
+
|
|
126
|
+
## What's next
|
|
127
|
+
|
|
128
|
+
- [Deployment topologies](/concepts/deployment-topologies) — headless agents are common for dev
|
|
129
|
+
- [Multi-tenant](/guides/multi-tenant) — similar isolation pattern for SaaS
|
|
130
|
+
- [`Agent.routeCallers`](/api/agent) — the API reference
|