getpatter 0.4.1 → 0.4.2

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 (35) hide show
  1. package/README.md +185 -587
  2. package/dist/chunk-35EVXMGB.mjs +4472 -0
  3. package/dist/chunk-AFUYSNDH.mjs +31 -0
  4. package/dist/chunk-JO5C35FM.mjs +65 -0
  5. package/dist/chunk-OOIUSZB4.mjs +37 -0
  6. package/dist/cli.js +1139 -0
  7. package/dist/index.d.mts +1063 -85
  8. package/dist/index.d.ts +1063 -85
  9. package/dist/index.js +8969 -3904
  10. package/dist/index.mjs +2382 -3354
  11. package/dist/lib-4WCAS54J.mjs +830 -0
  12. package/dist/node-cron-373UVDIO.mjs +935 -0
  13. package/dist/persistence-CYIGNHSU.mjs +7 -0
  14. package/dist/resources/audio/NOTICE +2 -0
  15. package/dist/resources/audio/city-ambience.ogg +0 -0
  16. package/dist/resources/audio/crowded-room.ogg +0 -0
  17. package/dist/resources/audio/forest-ambience.ogg +0 -0
  18. package/dist/resources/audio/hold_music.ogg +0 -0
  19. package/dist/resources/audio/keyboard-typing.ogg +0 -0
  20. package/dist/resources/audio/keyboard-typing2.ogg +0 -0
  21. package/dist/resources/audio/office-ambience.ogg +0 -0
  22. package/dist/resources/silero_vad.onnx +0 -0
  23. package/dist/{test-mode-JMXZSAJS.mjs → test-mode-RH65MMSP.mjs} +2 -1
  24. package/dist/{tunnel-HYSU7EF2.mjs → tunnel-BL7A7GXW.mjs} +2 -1
  25. package/package.json +25 -8
  26. package/src/resources/audio/NOTICE +2 -0
  27. package/src/resources/audio/city-ambience.ogg +0 -0
  28. package/src/resources/audio/crowded-room.ogg +0 -0
  29. package/src/resources/audio/forest-ambience.ogg +0 -0
  30. package/src/resources/audio/hold_music.ogg +0 -0
  31. package/src/resources/audio/keyboard-typing.ogg +0 -0
  32. package/src/resources/audio/keyboard-typing2.ogg +0 -0
  33. package/src/resources/audio/office-ambience.ogg +0 -0
  34. package/dist/chunk-TAATEHKF.mjs +0 -396
  35. package/dist/chunk-VNU4GNW3.mjs +0 -45
package/README.md CHANGED
@@ -1,112 +1,40 @@
1
1
  <p align="center">
2
- <h1 align="center">Patter</h1>
3
- <p align="center">Connect AI agents to phone numbers with 10 lines of code</p>
2
+ <h1 align="center">Patter TypeScript SDK</h1>
3
+ <p align="center">Connect AI agents to phone numbers with 4 lines of code</p>
4
4
  </p>
5
5
 
6
6
  <p align="center">
7
- <a href="#quickstart">Quickstart</a>
8
- <a href="#features">Features</a>
9
- <a href="#installation">Installation</a>
10
- <a href="#documentation">Documentation</a> •
11
- <a href="#self-hosting">Self-Hosting</a>
7
+ <a href="https://www.npmjs.com/package/getpatter"><img src="https://img.shields.io/npm/v/getpatter?logo=npm&logoColor=white&label=npm%20install%20getpatter" alt="npm" /></a>
8
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License" /></a>
9
+ <img src="https://img.shields.io/badge/typescript-5.0%2B-3178c6?logo=typescript&logoColor=white" alt="TypeScript 5+" />
12
10
  </p>
13
11
 
14
12
  <p align="center">
15
- <img src="https://img.shields.io/badge/python-3.11%2B-blue?logo=python&logoColor=white" alt="Python 3.11+" />
16
- <img src="https://img.shields.io/badge/typescript-5.0%2B-3178c6?logo=typescript&logoColor=white" alt="TypeScript 5+" />
17
- <img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License" />
13
+ <a href="#quickstart">Quickstart</a>
14
+ <a href="#features">Features</a>
15
+ <a href="#configuration">Configuration</a>
16
+ <a href="#voice-modes">Voice Modes</a> •
17
+ <a href="#api-reference">API Reference</a> •
18
+ <a href="#contributing">Contributing</a>
18
19
  </p>
19
20
 
20
21
  ---
21
22
 
22
- Patter is an open-source platform that gives your AI agent a voice and a phone number. Point it at any function that returns a string, and Patter handles the rest: telephony, speech-to-text, text-to-speech, and real-time audio streaming.
23
+ Patter is the open-source SDK that gives your AI agent a phone number. Point it at any function that returns a string, and Patter handles the rest: telephony, speech-to-text, text-to-speech, and real-time audio streaming. You build the agent — we connect it to the phone.
23
24
 
24
25
  ## Quickstart
25
26
 
26
- <details open>
27
- <summary><strong>Python</strong></summary>
28
-
29
- ```python
30
- import asyncio
31
- from patter import Patter, IncomingMessage
32
-
33
- async def on_message(msg: IncomingMessage) -> str:
34
- # Your agent logic here — return what the AI should say
35
- return f"You said: {msg.text}"
36
-
37
- async def main():
38
- phone = Patter(api_key="pt_xxx")
39
- await phone.connect(on_message=on_message) # starts listening for inbound calls
40
-
41
- asyncio.run(main())
42
- ```
43
-
44
- </details>
45
-
46
- <details>
47
- <summary><strong>TypeScript</strong></summary>
48
-
49
- ```typescript
50
- import { Patter } from "getpatter";
51
-
52
- const phone = new Patter({ apiKey: "pt_xxx" });
53
-
54
- await phone.connect({
55
- onMessage: async (msg) => {
56
- // Your agent logic here — return what the AI should say
57
- return `You said: ${msg.text}`;
58
- },
59
- });
60
- ```
61
-
62
- </details>
63
-
64
- ## Local Mode (No Cloud Required)
65
-
66
- Run Patter entirely in your process — no Patter account, no cloud backend.
67
-
68
- <details open>
69
- <summary><strong>Python</strong></summary>
70
-
71
- ```python
72
- import asyncio
73
- from patter import Patter
74
-
75
- async def main():
76
- phone = Patter(
77
- mode="local",
78
- twilio_sid="AC...", twilio_token="...",
79
- openai_key="sk-...",
80
- phone_number="+1...",
81
- webhook_url="xxx.ngrok-free.dev",
82
- )
83
-
84
- agent = phone.agent(
85
- system_prompt="You are a friendly customer service agent for Acme Corp.",
86
- voice="alloy",
87
- first_message="Hello! Thanks for calling. How can I help?",
88
- )
89
-
90
- print("Listening for calls...")
91
- await phone.serve(agent=agent, port=8000)
92
-
93
- asyncio.run(main())
27
+ ```bash
28
+ npm install getpatter
94
29
  ```
95
30
 
96
- </details>
97
-
98
- <details>
99
- <summary><strong>TypeScript</strong></summary>
100
-
101
31
  ```typescript
102
32
  import { Patter } from "getpatter";
103
33
 
104
34
  const phone = new Patter({
105
- mode: "local",
106
35
  twilioSid: "AC...", twilioToken: "...",
107
36
  openaiKey: "sk-...",
108
37
  phoneNumber: "+1...",
109
- webhookUrl: "xxx.ngrok-free.dev",
110
38
  });
111
39
 
112
40
  const agent = phone.agent({
@@ -115,339 +43,201 @@ const agent = phone.agent({
115
43
  firstMessage: "Hello! Thanks for calling. How can I help?",
116
44
  });
117
45
 
118
- console.log("Listening for calls...");
119
46
  await phone.serve({ agent, port: 8000 });
120
47
  ```
121
48
 
122
- </details>
123
-
124
- ## Local vs Cloud
125
-
126
- | | Cloud Mode | Local Mode |
127
- |---|---|---|
128
- | **Setup** | Patter API key only | Twilio/Telnyx + OpenAI keys |
129
- | **Infrastructure** | Managed by Patter | Runs in your process |
130
- | **Backend** | `wss://api.getpatter.com` | Built-in (FastAPI / Express) |
131
- | **Webhook** | Configured automatically | Requires public URL (e.g. ngrok) |
132
- | **Voice modes** | All three | All three |
133
- | **Best for** | Production, multi-tenant | Development, on-prem, full control |
134
-
135
49
  ## Features
136
50
 
137
- ### Voice
138
- - Three voice modes: OpenAI Realtime, ElevenLabs ConvAI, Pipeline (any STT + TTS)
139
- - Any STT: Deepgram, OpenAI Whisper
140
- - Any TTS: ElevenLabs, OpenAI TTS
141
- - Natural barge-in with mark-based audio tracking
142
- - DTMF keypad input forwarded to agent as `[DTMF: 1]`
143
-
144
- ### Agent
145
- - Bring your own agent (any LLM in pipeline mode)
146
- - System prompt with dynamic `{variable}` substitution
147
- - Conversation history tracked per call (`data.history` in all callbacks)
148
- - Tool calling via webhooks with automatic 3x retry
149
- - Built-in tools: `transfer_call`, `end_call` (auto-injected)
150
-
151
- ### Telephony
152
- - Twilio and Telnyx carriers
153
- - Inbound and outbound calls
154
- - Call transfer to humans (`transfer_call` system tool)
155
- - Call recording (`recording: true` in `serve()`)
156
- - Answering machine detection (`machineDetection: true` for outbound)
157
- - Voicemail drop (`voicemailMessage: "..."` plays on voicemail detection)
158
- - Custom parameters passthrough via TwiML
159
-
160
- ### Developer Experience
161
- - `pip install patter` / `npm install getpatter`
162
- - 10 lines of code to connect an agent to a phone
163
- - Local mode (embedded, no backend) + Cloud mode
164
- - Python + TypeScript SDKs with full parity
165
- - MCP server for Claude Desktop
166
- - Open-source (MIT)
167
-
168
- ## Complete Example
169
-
170
- ```typescript
171
- const phone = new Patter({
172
- mode: 'local',
173
- twilioSid: process.env.TWILIO_SID,
174
- twilioToken: process.env.TWILIO_TOKEN,
175
- openaiKey: process.env.OPENAI_KEY,
176
- phoneNumber: '+16592214527',
177
- webhookUrl: 'your-domain.ngrok-free.dev',
178
- });
179
-
180
- const agent = phone.agent({
181
- systemPrompt: `You are a customer service agent for Acme Corp.
182
- The customer is {customer_name} with order #{order_id}.
183
- Check inventory before answering stock questions.
184
- Transfer to a human if the customer is upset.`,
185
- voice: 'alloy',
186
- language: 'en',
187
- firstMessage: 'Hi {customer_name}! How can I help with order #{order_id}?',
188
- variables: {
189
- customer_name: 'John',
190
- order_id: '12345',
191
- },
192
- tools: [{
193
- name: 'check_inventory',
194
- description: 'Check product stock',
195
- parameters: { type: 'object', properties: { product: { type: 'string' } } },
196
- webhookUrl: 'https://api.acme.com/inventory',
197
- }],
198
- // Built-in: transfer_call, end_call (auto-injected)
199
- });
200
-
201
- await phone.serve({
202
- agent,
203
- port: 8000,
204
- recording: true,
205
- onCallStart: async (data) => console.log(`Call from ${data.caller}`),
206
- onCallEnd: async (data) => console.log(`Transcript: ${data.transcript?.length} turns`),
207
- onTranscript: async (data) => console.log(`${data.role}: ${data.text}`),
208
- });
209
-
210
- // Outbound with machine detection
211
- await phone.call({
212
- to: '+1234567890',
213
- machineDetection: true,
214
- voicemailMessage: 'Hi, please call us back at 555-0123.',
215
- });
216
- ```
217
-
218
- ## How It Works
51
+ | Feature | Method | Example |
52
+ |---|---|---|
53
+ | Inbound calls | `phone.serve(agent)` | Answer calls as an AI |
54
+ | Outbound calls + AMD | `phone.call(to, machineDetection)` | Place calls with voicemail detection |
55
+ | Tool calling (webhooks) | `agent(tools=[...])` | Agent calls external APIs mid-conversation |
56
+ | Custom STT + TTS | `agent(provider="pipeline")` | Bring your own voice providers |
57
+ | Dynamic variables | `agent(variables={...})` | Personalize prompts per caller |
58
+ | Custom LLM (any model) | `serve(onMessage=handler)` | Claude, Mistral, LLaMA, etc. |
59
+ | Call recording | `serve(recording=true)` | Record all calls |
60
+ | Call transfer | `transfer_call` (auto-injected) | Transfer to a human |
61
+ | Voicemail drop | `call(voicemailMessage="...")` | Play message on voicemail |
219
62
 
220
- ```
221
- Your Code (on_message handler)
222
-
223
-
224
- Patter SDK ──WebSocket──► Patter Backend ──────────────────────────────┐
225
- │ │
226
- ┌───────┴────────┐ │
227
- ▼ ▼ ▼
228
- STT Engine TTS Engine Telephony Provider
229
- (Deepgram / (ElevenLabs / (Twilio / Telnyx)
230
- Whisper / OpenAI TTS) │
231
- OpenAI RT) │ │
232
- │ └───────────────────────►│
233
- └────────────────────────────────────────►│
234
-
235
- Phone Call
236
- ```
63
+ ## Configuration
237
64
 
238
- The audio path: **Phone → Telephony → WebSocket → Backend → STT → your handler → TTS → Backend → WebSocket → Phone**
65
+ ### Environment Variables
239
66
 
240
- ## Installation
67
+ | Variable | Required | Description |
68
+ |---|---|---|
69
+ | `OPENAI_API_KEY` | Yes (Realtime mode) | OpenAI API key with Realtime access |
70
+ | `TWILIO_ACCOUNT_SID` | Yes | Twilio account SID |
71
+ | `TWILIO_AUTH_TOKEN` | Yes | Twilio auth token |
72
+ | `TWILIO_PHONE_NUMBER` | Yes | Your Twilio phone number (E.164) |
73
+ | `DEEPGRAM_API_KEY` | Pipeline mode | Deepgram STT key |
74
+ | `ELEVENLABS_API_KEY` | Pipeline mode | ElevenLabs TTS key |
75
+ | `ANTHROPIC_API_KEY` | Custom LLM | For bringing your own model |
76
+ | `WEBHOOK_URL` | No | Public URL (auto-tunneled via Cloudflare if omitted) |
241
77
 
242
78
  ```bash
243
- # Python
244
- pip install patter
245
-
246
- # TypeScript / Node.js
247
- npm install getpatter
79
+ cp .env.example .env
80
+ # Edit .env with your API keys
248
81
  ```
249
82
 
250
- ## Documentation
251
-
252
- ### Inbound Calls (AI answers the phone)
83
+ > **Telnyx:** Telnyx is a fully supported telephony provider alternative to Twilio. Both carriers receive equal support for DTMF, transfer, recording, and metrics.
253
84
 
254
- <details open>
255
- <summary><strong>Python</strong></summary>
85
+ ## Voice Modes
256
86
 
257
- ```python
258
- import asyncio
259
- from patter import Patter, IncomingMessage
87
+ | Mode | Latency | Quality | Best For |
88
+ |---|---|---|---|
89
+ | **OpenAI Realtime** | Lowest | High | Fluid, low-latency conversations |
90
+ | **Deepgram + ElevenLabs** | Low | High | Independent control over STT and TTS |
91
+ | **ElevenLabs ConvAI** | Low | High | ElevenLabs-managed conversation flow |
260
92
 
261
- async def agent(msg: IncomingMessage) -> str:
262
- if "hours" in msg.text.lower():
263
- return "We're open Monday through Friday, 9 to 5."
264
- return "How can I help you today?"
93
+ ## API Reference
265
94
 
266
- async def main():
267
- phone = Patter(api_key="pt_xxx")
268
- await phone.connect(
269
- on_message=agent,
270
- on_call_start=lambda data: print(f"Call from {data['caller']}"),
271
- on_call_end=lambda data: print("Call ended"),
272
- )
273
- await asyncio.Event().wait() # keep the process alive
95
+ ### `Patter` Constructor
274
96
 
275
- asyncio.run(main())
97
+ ```typescript
98
+ new Patter({
99
+ twilioSid: string;
100
+ twilioToken: string;
101
+ openaiKey: string;
102
+ phoneNumber: string;
103
+ webhookUrl?: string; // Optional; auto-tunneled via Cloudflare if omitted
104
+ })
276
105
  ```
277
106
 
278
- </details>
107
+ | Parameter | Type | Description |
108
+ |---|---|---|
109
+ | `twilioSid` | `string` | Twilio account SID |
110
+ | `twilioToken` | `string` | Twilio auth token |
111
+ | `openaiKey` | `string` | OpenAI API key |
112
+ | `phoneNumber` | `string` | Your Twilio phone number (E.164 format) |
113
+ | `webhookUrl` | `string` | Public URL for Twilio webhooks (optional) |
279
114
 
280
- <details>
281
- <summary><strong>TypeScript</strong></summary>
115
+ ### `phone.agent()` Method
282
116
 
283
117
  ```typescript
284
- import { Patter } from "getpatter";
285
-
286
- const phone = new Patter({ apiKey: "pt_xxx" });
287
-
288
- await phone.connect({
289
- onMessage: async (msg) => {
290
- if (msg.text.toLowerCase().includes("hours")) {
291
- return "We're open Monday through Friday, 9 to 5.";
292
- }
293
- return "How can I help you today?";
294
- },
295
- onCallStart: (data) => console.log(`Call from ${data.caller}`),
296
- onCallEnd: () => console.log("Call ended"),
297
- });
118
+ phone.agent({
119
+ systemPrompt: string;
120
+ voice?: string;
121
+ firstMessage?: string;
122
+ variables?: Record<string, string>;
123
+ tools?: Array<{name, description, parameters, webhookUrl}>;
124
+ })
298
125
  ```
299
126
 
300
- </details>
301
-
302
- ---
303
-
304
- ### Outbound Calls (AI calls someone)
305
-
306
- <details open>
307
- <summary><strong>Python</strong></summary>
308
-
309
- ```python
310
- import asyncio
311
- from patter import Patter, IncomingMessage
312
-
313
- async def agent(msg: IncomingMessage) -> str:
314
- return "Thanks for picking up. This is a reminder about your appointment tomorrow."
127
+ | Parameter | Type | Description |
128
+ |---|---|---|
129
+ | `systemPrompt` | `string` | Prompt with optional `{variable}` placeholders |
130
+ | `voice` | `string` | TTS voice name (e.g., "alloy", "echo", "fable") |
131
+ | `firstMessage` | `string` | Opening message (supports `{variable}` placeholders) |
132
+ | `variables` | `Record<string, string>` | Values substituted into prompts |
133
+ | `tools` | `Array` | Tool definitions: `{name, description, parameters, webhookUrl}` |
315
134
 
316
- async def main():
317
- phone = Patter(api_key="pt_xxx")
318
- await phone.connect(on_message=agent)
319
- await phone.call(
320
- to="+14155551234",
321
- first_message="Hi, this is an automated reminder from Acme Corp.",
322
- )
135
+ ### `phone.serve()` Method
323
136
 
324
- asyncio.run(main())
137
+ ```typescript
138
+ await phone.serve({
139
+ agent: Agent;
140
+ port?: number;
141
+ dashboard?: boolean;
142
+ recording?: boolean;
143
+ onCallStart?: (data: CallData) => Promise<void>;
144
+ onCallEnd?: (data: CallData) => Promise<void>;
145
+ onTranscript?: (data: TranscriptData) => Promise<void>;
146
+ })
325
147
  ```
326
148
 
327
- </details>
149
+ | Parameter | Type | Description |
150
+ |---|---|---|
151
+ | `agent` | `Agent` | Agent configuration to use for calls |
152
+ | `port` | `number` | Port to listen on (default: 8000) |
153
+ | `dashboard` | `boolean` | Enable the built-in monitoring dashboard |
154
+ | `recording` | `boolean` | Enable call recording via the telephony provider |
155
+ | `onCallStart` | `(data) => Promise<void>` | Called when a call connects; receives `data.caller`, `data.callId` |
156
+ | `onCallEnd` | `(data) => Promise<void>` | Called when a call ends; receives `data.history`, `data.transcript`, `data.duration` |
157
+ | `onTranscript` | `(data) => Promise<void>` | Called on each transcript turn; receives `data.role`, `data.text`, `data.history` |
328
158
 
329
- <details>
330
- <summary><strong>TypeScript</strong></summary>
159
+ ### `phone.call()` Method
331
160
 
332
161
  ```typescript
333
- import { Patter } from "getpatter";
334
-
335
- const phone = new Patter({ apiKey: "pt_xxx" });
336
-
337
- await phone.connect({
338
- onMessage: async () =>
339
- "Thanks for picking up. This is a reminder about your appointment tomorrow.",
340
- });
341
-
342
162
  await phone.call({
343
- to: "+14155551234",
344
- firstMessage: "Hi, this is an automated reminder from Acme Corp.",
345
- });
346
- ```
347
-
348
- </details>
349
-
350
- ---
351
-
352
- ### Outbound with Machine Detection + Voicemail Drop
353
-
354
- <details open>
355
- <summary><strong>Python</strong></summary>
356
-
357
- ```python
358
- await phone.call(
359
- to="+14155551234",
360
- first_message="Hi, this is an automated reminder from Acme Corp.",
361
- machine_detection=True,
362
- voicemail_message="Hi, we tried to reach you. Please call us back at 555-0123.",
363
- )
163
+ to: string;
164
+ firstMessage?: string;
165
+ machineDetection?: boolean;
166
+ voicemailMessage?: string;
167
+ })
364
168
  ```
365
169
 
366
- </details>
170
+ | Parameter | Type | Description |
171
+ |---|---|---|
172
+ | `to` | `string` | Destination phone number (E.164 format) |
173
+ | `firstMessage` | `string` | Opening message for the outbound call |
174
+ | `machineDetection` | `boolean` | Enable answering machine detection |
175
+ | `voicemailMessage` | `string` | Message to play when voicemail is detected |
367
176
 
368
- <details>
369
- <summary><strong>TypeScript</strong></summary>
177
+ ### Static Provider Helpers
370
178
 
371
179
  ```typescript
372
- await phone.call({
373
- to: "+14155551234",
374
- firstMessage: "Hi, this is an automated reminder from Acme Corp.",
375
- machineDetection: true,
376
- voicemailMessage: "Hi, we tried to reach you. Please call us back at 555-0123.",
377
- });
180
+ Patter.deepgram(options: { apiKey: string; language?: string }) -> STT
181
+ Patter.elevenlabs(options: { apiKey: string; voice?: string }) -> TTS
182
+ Patter.openaiTts(options: { apiKey: string; voice?: string }) -> TTS
183
+ Patter.whisper(options: { apiKey: string; language?: string }) -> STT
378
184
  ```
379
185
 
380
- </details>
186
+ ## Examples
381
187
 
382
- ---
188
+ ### Inbound Calls (AI answers the phone)
383
189
 
384
- ### Dynamic Variables in Prompts
190
+ ```typescript
191
+ import { Patter, IncomingMessage } from "getpatter";
385
192
 
386
- Inject call-specific data into system prompts and first messages using `{variable}` placeholders.
193
+ const phone = new Patter({
194
+ twilioSid: "AC...", twilioToken: "...",
195
+ openaiKey: "sk-...",
196
+ phoneNumber: "+1...",
197
+ });
387
198
 
388
- <details open>
389
- <summary><strong>Python</strong></summary>
199
+ async function agent(msg: IncomingMessage): Promise<string> {
200
+ if (msg.text.toLowerCase().includes("hours")) {
201
+ return "We're open Monday through Friday, 9 to 5.";
202
+ }
203
+ return "How can I help you today?";
204
+ }
390
205
 
391
- ```python
392
- agent = phone.agent(
393
- system_prompt="You are helping {customer_name}, account #{account_id}.",
394
- first_message="Hi {customer_name}! How can I help you today?",
395
- variables={
396
- "customer_name": "Jane",
397
- "account_id": "A-789",
398
- },
399
- )
206
+ await phone.serve({
207
+ agent: phone.agent({
208
+ systemPrompt: "You are a helpful customer service agent.",
209
+ firstMessage: "Hello! How can I help?",
210
+ }),
211
+ port: 8000,
212
+ onCallStart: (data) => console.log(`Call from ${data.caller}`),
213
+ onCallEnd: (data) => console.log("Call ended"),
214
+ });
400
215
  ```
401
216
 
402
- </details>
403
-
404
- <details>
405
- <summary><strong>TypeScript</strong></summary>
217
+ ### Outbound Calls (AI calls someone)
406
218
 
407
219
  ```typescript
408
- const agent = phone.agent({
409
- systemPrompt: "You are helping {customer_name}, account #{account_id}.",
410
- firstMessage: "Hi {customer_name}! How can I help you today?",
411
- variables: {
412
- customer_name: "Jane",
413
- account_id: "A-789",
414
- },
415
- });
416
- ```
220
+ import { Patter } from "getpatter";
417
221
 
418
- </details>
222
+ const phone = new Patter({
223
+ twilioSid: "AC...", twilioToken: "...",
224
+ openaiKey: "sk-...",
225
+ phoneNumber: "+1...",
226
+ });
419
227
 
420
- ---
228
+ const agentConfig = phone.agent({
229
+ systemPrompt: "You are making reminder calls.",
230
+ firstMessage: "Hi, this is an automated reminder from Acme Corp.",
231
+ });
421
232
 
422
- ### Tool Calling via Webhooks
423
-
424
- Agents can call external APIs mid-conversation. Patter POSTs to your webhook URL and retries up to 3 times on failure.
425
-
426
- <details open>
427
- <summary><strong>Python</strong></summary>
428
-
429
- ```python
430
- agent = phone.agent(
431
- system_prompt="You are a booking assistant. Check availability before confirming.",
432
- tools=[{
433
- "name": "check_availability",
434
- "description": "Check appointment availability for a given date",
435
- "parameters": {
436
- "type": "object",
437
- "properties": {
438
- "date": {"type": "string", "description": "ISO date, e.g. 2025-06-15"},
439
- },
440
- "required": ["date"],
441
- },
442
- "webhook_url": "https://api.example.com/availability",
443
- }],
444
- )
233
+ await phone.serve({ agent: agentConfig, port: 8000 });
234
+ await phone.call({
235
+ to: "+14155551234",
236
+ firstMessage: "Hi, just checking in.",
237
+ });
445
238
  ```
446
239
 
447
- </details>
448
-
449
- <details>
450
- <summary><strong>TypeScript</strong></summary>
240
+ ### Tool Calling (Agent calls external APIs)
451
241
 
452
242
  ```typescript
453
243
  const agent = phone.agent({
@@ -467,254 +257,62 @@ const agent = phone.agent({
467
257
  });
468
258
  ```
469
259
 
470
- </details>
471
-
472
- ---
473
-
474
- ### Built-in Tools: Transfer & End Call
475
-
476
- `transfer_call` and `end_call` are automatically injected into every agent — no configuration needed.
477
-
478
- - The agent calls `transfer_call` when it decides to route to a human (e.g. "Let me transfer you now.")
479
- - The agent calls `end_call` when the conversation is complete (e.g. after a confirmed booking.)
480
-
481
- ---
482
-
483
- ### Call Recording
484
-
485
- <details open>
486
- <summary><strong>Python</strong></summary>
487
-
488
- ```python
489
- await phone.serve(agent=agent, port=8000, recording=True)
490
- ```
491
-
492
- </details>
493
-
494
- <details>
495
- <summary><strong>TypeScript</strong></summary>
260
+ ### Custom Voice (Deepgram STT + ElevenLabs TTS)
496
261
 
497
262
  ```typescript
498
- await phone.serve({ agent, port: 8000, recording: true });
499
- ```
500
-
501
- </details>
502
-
503
- ---
504
-
505
- ### Conversation History
506
-
507
- Every callback receives `data.history` — the full conversation so far as a list of `{role, text}` turns.
263
+ const phone = new Patter({
264
+ twilioSid: "AC...", twilioToken: "...",
265
+ openaiKey: "sk-...",
266
+ phoneNumber: "+1...",
267
+ });
508
268
 
509
- <details open>
510
- <summary><strong>Python</strong></summary>
269
+ const agent = phone.agent({
270
+ systemPrompt: "You are a helpful voice assistant.",
271
+ voice: "aria",
272
+ });
511
273
 
512
- ```python
513
- await phone.serve(
514
- agent=agent,
515
- port=8000,
516
- on_transcript=lambda data: print(f"[{data['role']}] {data['text']}"),
517
- on_call_end=lambda data: print(f"Full history: {data['history']}"),
518
- )
274
+ // Use custom STT and TTS in pipeline mode
275
+ await phone.serve({
276
+ agent,
277
+ port: 8000,
278
+ stt: Patter.deepgram({ apiKey: "dg_...", language: "en" }),
279
+ tts: Patter.elevenlabs({ apiKey: "el_...", voice: "aria" }),
280
+ });
519
281
  ```
520
282
 
521
- </details>
522
-
523
- <details>
524
- <summary><strong>TypeScript</strong></summary>
283
+ ### Call Recording
525
284
 
526
285
  ```typescript
527
286
  await phone.serve({
528
287
  agent,
529
288
  port: 8000,
530
- onTranscript: (data) => console.log(`[${data.role}] ${data.text}`),
531
- onCallEnd: (data) => console.log(`Full history:`, data.history),
289
+ recording: true, // Records all inbound and outbound calls
532
290
  });
533
291
  ```
534
292
 
535
- </details>
536
-
537
- ---
538
-
539
- ### Custom Voice (choose your providers)
540
-
541
- <details open>
542
- <summary><strong>Python</strong></summary>
543
-
544
- ```python
545
- phone = Patter(api_key="pt_xxx", backend_url="ws://localhost:8000")
546
-
547
- await phone.connect(
548
- on_message=agent,
549
- provider="twilio",
550
- provider_key="ACxxxxxxxx",
551
- provider_secret="your_auth_token",
552
- number="+14155550000",
553
- stt=Patter.deepgram(api_key="dg_xxx", language="en"),
554
- tts=Patter.elevenlabs(api_key="el_xxx", voice="rachel"),
555
- )
556
- ```
557
-
558
- </details>
559
-
560
- <details>
561
- <summary><strong>TypeScript</strong></summary>
293
+ ### Dynamic Variables in Prompts
562
294
 
563
295
  ```typescript
564
- const phone = new Patter({ apiKey: "pt_xxx", backendUrl: "ws://localhost:8000" });
565
-
566
- await phone.connect({
567
- onMessage: agent,
568
- provider: "twilio",
569
- providerKey: "ACxxxxxxxx",
570
- providerSecret: "your_auth_token",
571
- number: "+14155550000",
572
- stt: Patter.deepgram({ apiKey: "dg_xxx", language: "en" }),
573
- tts: Patter.elevenlabs({ apiKey: "el_xxx", voice: "rachel" }),
296
+ const agent = phone.agent({
297
+ systemPrompt: "You are helping {customer_name}, account #{account_id}.",
298
+ firstMessage: "Hi {customer_name}! How can I help you today?",
299
+ variables: {
300
+ customer_name: "Jane",
301
+ account_id: "A-789",
302
+ },
574
303
  });
575
304
  ```
576
305
 
577
- </details>
578
-
579
- ## Voice Modes
580
-
581
- | Mode | Latency | Quality | Best For |
582
- |---|---|---|---|
583
- | **OpenAI Realtime** | Lowest | High | Fluid, low-latency conversations |
584
- | **Deepgram + ElevenLabs** | Low | High | Independent control over STT and TTS |
585
- | **ElevenLabs ConvAI** | Low | High | ElevenLabs-managed conversation flow |
586
-
587
- The voice mode is configured on the backend. Your `on_message` handler works identically regardless of mode.
588
-
589
- ## MCP Server (Claude Desktop)
590
-
591
- Patter ships an MCP server so you can control calls directly from Claude Desktop.
592
-
593
- ```json
594
- {
595
- "mcpServers": {
596
- "patter": {
597
- "command": "patter-mcp",
598
- "env": { "PATTER_API_KEY": "pt_xxx" }
599
- }
600
- }
601
- }
602
- ```
603
-
604
- ## Self-Hosting
605
-
606
- Run the full stack yourself — no Patter Cloud account needed.
607
-
608
- ```bash
609
- # 1. Clone the repo
610
- git clone https://github.com/your-org/patter
611
- cd patter
612
-
613
- # 2. Copy env and fill in your keys
614
- cp .env.example .env
615
-
616
- # 3. Start the backend
617
- cd backend
618
- pip install -e ".[dev]"
619
- alembic upgrade head
620
- uvicorn app.main:app --reload
621
- ```
622
-
623
- Then point your SDK at your local backend:
624
-
625
- ```python
626
- phone = Patter(
627
- api_key="pt_xxx",
628
- backend_url="ws://localhost:8000",
629
- rest_url="http://localhost:8000",
630
- )
631
- ```
632
-
633
- **Required environment variables:**
634
-
635
- | Variable | Description |
636
- |---|---|
637
- | `PATTER_DATABASE_URL` | PostgreSQL connection string |
638
- | `PATTER_ENCRYPTION_KEY` | Key for encrypting stored provider credentials |
639
- | `PATTER_SECRET_KEY` | JWT / HMAC signing secret |
640
-
641
- See `backend/.env.example` for the full list.
642
-
643
- ## API Reference
644
-
645
- ### `Patter` (Python & TypeScript)
646
-
647
- | Method | Description |
648
- |---|---|
649
- | `Patter(api_key, backend_url?, rest_url?)` | Create client. `backend_url` defaults to `wss://api.getpatter.com`. |
650
- | `connect(on_message, ...)` | Connect and start receiving calls. Blocks until disconnected. |
651
- | `call(to, first_message?, machine_detection?, voicemail_message?, ...)` | Place an outbound call. |
652
- | `disconnect()` | Gracefully close the connection. |
653
-
654
- **`serve()` options:**
655
-
656
- | Option | Type | Description |
657
- |---|---|---|
658
- | `agent` | `Agent` | Agent configuration to use for calls |
659
- | `port` | `int` | Port to listen on |
660
- | `recording` | `bool` | Enable call recording via the telephony provider |
661
- | `onCallStart` | `callable` | Called when a call connects; receives `data.caller`, `data.call_id` |
662
- | `onCallEnd` | `callable` | Called when a call ends; receives `data.history`, `data.transcript`, `data.duration` |
663
- | `onTranscript` | `callable` | Called on each transcript turn; receives `data.role`, `data.text`, `data.history` |
664
-
665
- **`agent()` options:**
666
-
667
- | Option | Type | Description |
668
- |---|---|---|
669
- | `system_prompt` | `str` | Prompt with optional `{variable}` placeholders |
670
- | `variables` | `dict` | Values substituted into `system_prompt` and `first_message` |
671
- | `voice` | `str` | TTS voice name |
672
- | `language` | `str` | BCP-47 language code |
673
- | `first_message` | `str` | Opening message (supports `{variable}` placeholders) |
674
- | `tools` | `list` | Tool definitions with `name`, `description`, `parameters`, `webhook_url` |
675
-
676
- **`call()` options:**
677
-
678
- | Option | Type | Description |
679
- |---|---|---|
680
- | `to` | `str` | Destination phone number |
681
- | `first_message` | `str` | Opening message for the outbound call |
682
- | `machine_detection` | `bool` | Enable answering machine detection |
683
- | `voicemail_message` | `str` | Message to play when voicemail is detected |
684
-
685
- **Static provider helpers** (for self-hosted mode):
686
-
687
- | Helper | Type | Description |
688
- |---|---|---|
689
- | `Patter.deepgram(api_key, language?)` | STT | Deepgram Nova |
690
- | `Patter.whisper(api_key, language?)` | STT | OpenAI Whisper |
691
- | `Patter.elevenlabs(api_key, voice?)` | TTS | ElevenLabs |
692
- | `Patter.openai_tts(api_key, voice?)` | TTS | OpenAI TTS |
693
-
694
- ### `IncomingMessage`
695
-
696
- | Field | Type | Description |
697
- |---|---|---|
698
- | `text` | `str` | Transcribed speech from the caller (includes `[DTMF: N]` for keypad presses) |
699
- | `call_id` | `str` | Unique identifier for the current call |
700
- | `history` | `list` | Conversation turns so far: `[{role, text}, ...]` |
701
-
702
306
  ## Contributing
703
307
 
704
308
  Pull requests are welcome.
705
309
 
706
310
  ```bash
707
- # Run tests
708
- cd backend && pytest tests/ -v
709
- cd sdk && pytest tests/ -v
710
-
711
- # Install dev dependencies
712
- cd backend && pip install -e ".[dev]"
713
- cd sdk && pip install -e ".[dev]"
311
+ cd sdk-ts && npm install && npm test
714
312
  ```
715
313
 
716
314
  Please open an issue before submitting large changes so we can discuss the approach first.
717
315
 
718
316
  ## License
719
317
 
720
- MIT — see [LICENSE](./LICENSE).
318
+ MIT — see [LICENSE](../LICENSE).