getpatter 0.4.4 → 0.5.1
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 +156 -158
- package/dist/carrier-config-CPG5CROM.mjs +84 -0
- package/dist/{chunk-JO5C35FM.mjs → chunk-AKQFOFLG.mjs} +1 -1
- package/dist/{chunk-O3RQG3NL.mjs → chunk-B6C3KIBG.mjs} +177 -567
- package/dist/index.d.mts +1163 -377
- package/dist/index.d.ts +1163 -377
- package/dist/index.js +2028 -1835
- package/dist/index.mjs +1644 -329
- package/dist/{test-mode-ASSLSQU2.mjs → test-mode-JZMYE5HY.mjs} +1 -1
- package/dist/{tunnel-BL7A7GXW.mjs → tunnel-O7ICMSTP.mjs} +1 -1
- package/package.json +1 -1
- package/dist/lib-4WCAS54J.mjs +0 -830
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<h1 align="center">Patter TypeScript SDK</h1>
|
|
3
|
-
<p align="center">Connect AI agents to phone numbers
|
|
3
|
+
<p align="center">Connect AI agents to phone numbers in four lines of code</p>
|
|
4
4
|
</p>
|
|
5
5
|
|
|
6
6
|
<p align="center">
|
|
@@ -28,52 +28,63 @@ Patter is the open-source SDK that gives your AI agent a phone number. Point it
|
|
|
28
28
|
npm install getpatter
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
import { Patter } from "getpatter";
|
|
31
|
+
Set the env vars your carrier and engine need:
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
```bash
|
|
34
|
+
export TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
35
|
+
export TWILIO_AUTH_TOKEN=your_auth_token
|
|
36
|
+
export OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
|
|
37
|
+
```
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
systemPrompt: "You are a friendly customer service agent for Acme Corp.",
|
|
42
|
-
voice: "alloy",
|
|
43
|
-
firstMessage: "Hello! Thanks for calling. How can I help?",
|
|
44
|
-
});
|
|
39
|
+
Four lines of TypeScript:
|
|
45
40
|
|
|
46
|
-
|
|
41
|
+
```typescript
|
|
42
|
+
import { Patter, Twilio, OpenAIRealtime } from "getpatter";
|
|
43
|
+
|
|
44
|
+
const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });
|
|
45
|
+
const agent = phone.agent({ engine: new OpenAIRealtime(), systemPrompt: "You are a friendly receptionist for Acme Corp.", firstMessage: "Hello! How can I help?" });
|
|
46
|
+
await phone.serve({ agent, tunnel: true });
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
`tunnel: true` spawns a Cloudflare tunnel and points your Twilio number at it. In production, pass `webhookUrl: "api.prod.example.com"` to the constructor instead.
|
|
50
|
+
|
|
49
51
|
## Features
|
|
50
52
|
|
|
51
53
|
| Feature | Method | Example |
|
|
52
54
|
|---|---|---|
|
|
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
|
|
56
|
-
| Custom STT + TTS | `agent(
|
|
57
|
-
| Dynamic variables | `agent(variables
|
|
58
|
-
|
|
|
59
|
-
|
|
|
55
|
+
| Inbound calls | `phone.serve({ agent })` | Answer calls as an AI |
|
|
56
|
+
| Outbound calls + AMD | `phone.call({ to, machineDetection: true })` | Place calls with voicemail detection |
|
|
57
|
+
| Tool calling | `agent({ tools: [tool(...)] })` | Agent calls external APIs mid-conversation |
|
|
58
|
+
| Custom STT + TTS | `agent({ stt: new DeepgramSTT(), tts: new ElevenLabsTTS() })` | Bring your own voice providers |
|
|
59
|
+
| Dynamic variables | `agent({ variables: {...} })` | Personalize prompts per caller |
|
|
60
|
+
| Pluggable LLM | `agent({ llm: new AnthropicLLM() })` | 5 built-in providers: OpenAI, Anthropic, Groq, Cerebras, Google |
|
|
61
|
+
| Custom LLM (any model) | `serve({ onMessage })` | Route to anything — local inference, internal gateways, etc. |
|
|
62
|
+
| Call recording | `serve({ recording: true })` | Record all calls |
|
|
60
63
|
| Call transfer | `transfer_call` (auto-injected) | Transfer to a human |
|
|
61
|
-
| Voicemail drop | `call(voicemailMessage
|
|
64
|
+
| Voicemail drop | `call({ voicemailMessage: "..." })` | Play message on voicemail |
|
|
62
65
|
|
|
63
66
|
## Configuration
|
|
64
67
|
|
|
65
|
-
### Environment
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
|
70
|
-
|
|
71
|
-
| `TWILIO_AUTH_TOKEN` |
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `ELEVENLABS_API_KEY` |
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
68
|
+
### Environment variables
|
|
69
|
+
|
|
70
|
+
Every provider reads its credentials from the environment by default. Pass `apiKey: "..."` to any constructor to override.
|
|
71
|
+
|
|
72
|
+
| Variable | Used by |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN` | `new Twilio()` carrier |
|
|
75
|
+
| `TELNYX_API_KEY`, `TELNYX_CONNECTION_ID`, `TELNYX_PUBLIC_KEY` (optional) | `new Telnyx()` carrier |
|
|
76
|
+
| `OPENAI_API_KEY` | `OpenAIRealtime`, `WhisperSTT`, `OpenAITTS` |
|
|
77
|
+
| `ELEVENLABS_API_KEY`, `ELEVENLABS_AGENT_ID` | `ElevenLabsConvAI`, `ElevenLabsTTS` |
|
|
78
|
+
| `DEEPGRAM_API_KEY` | `DeepgramSTT` |
|
|
79
|
+
| `CARTESIA_API_KEY` | `CartesiaSTT`, `CartesiaTTS` |
|
|
80
|
+
| `RIME_API_KEY` | `RimeTTS` |
|
|
81
|
+
| `LMNT_API_KEY` | `LMNTTTS` |
|
|
82
|
+
| `SONIOX_API_KEY` | `SonioxSTT` |
|
|
83
|
+
| `ASSEMBLYAI_API_KEY` | `AssemblyAISTT` |
|
|
84
|
+
| `ANTHROPIC_API_KEY` | `AnthropicLLM` |
|
|
85
|
+
| `GROQ_API_KEY` | `GroqLLM` |
|
|
86
|
+
| `CEREBRAS_API_KEY` | `CerebrasLLM` |
|
|
87
|
+
| `GEMINI_API_KEY` (or `GOOGLE_API_KEY`) | `GoogleLLM` |
|
|
77
88
|
|
|
78
89
|
```bash
|
|
79
90
|
cp .env.example .env
|
|
@@ -87,219 +98,206 @@ cp .env.example .env
|
|
|
87
98
|
| Mode | Latency | Quality | Best For |
|
|
88
99
|
|---|---|---|---|
|
|
89
100
|
| **OpenAI Realtime** | Lowest | High | Fluid, low-latency conversations |
|
|
90
|
-
| **
|
|
101
|
+
| **Pipeline** (STT + LLM + TTS) | Low | High | Independent control over STT and TTS |
|
|
91
102
|
| **ElevenLabs ConvAI** | Low | High | ElevenLabs-managed conversation flow |
|
|
92
103
|
|
|
93
104
|
## API Reference
|
|
94
105
|
|
|
95
|
-
### `Patter`
|
|
106
|
+
### `Patter` constructor
|
|
96
107
|
|
|
97
108
|
```typescript
|
|
98
109
|
new Patter({
|
|
99
|
-
|
|
100
|
-
twilioToken: string;
|
|
101
|
-
openaiKey: string;
|
|
110
|
+
carrier: Twilio | Telnyx;
|
|
102
111
|
phoneNumber: string;
|
|
103
|
-
webhookUrl?: string;
|
|
112
|
+
webhookUrl?: string; // Public hostname. Mutually exclusive with tunnel.
|
|
113
|
+
tunnel?: CloudflareTunnel | StaticTunnel; // Or pass tunnel: true on serve() for dev.
|
|
104
114
|
})
|
|
105
115
|
```
|
|
106
116
|
|
|
107
117
|
| Parameter | Type | Description |
|
|
108
118
|
|---|---|---|
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `webhookUrl` | `string` | Public URL for Twilio webhooks (optional) |
|
|
119
|
+
| `carrier` | `Twilio` / `Telnyx` | Carrier instance. Reads env vars by default. |
|
|
120
|
+
| `phoneNumber` | `string` | Your phone number in E.164 format. |
|
|
121
|
+
| `webhookUrl` | `string` | Public hostname your local server is reachable on. |
|
|
122
|
+
| `tunnel` | instance | `new CloudflareTunnel()` or `new StaticTunnel({ hostname: ... })`. |
|
|
114
123
|
|
|
115
|
-
### `phone.agent()`
|
|
124
|
+
### `phone.agent()`
|
|
116
125
|
|
|
117
126
|
```typescript
|
|
118
127
|
phone.agent({
|
|
119
128
|
systemPrompt: string;
|
|
129
|
+
engine?: OpenAIRealtime | ElevenLabsConvAI; // default: new OpenAIRealtime()
|
|
130
|
+
stt?: STTProvider; // e.g. new DeepgramSTT()
|
|
131
|
+
tts?: TTSProvider; // e.g. new ElevenLabsTTS()
|
|
120
132
|
voice?: string;
|
|
133
|
+
model?: string;
|
|
134
|
+
language?: string;
|
|
121
135
|
firstMessage?: string;
|
|
136
|
+
tools?: Tool[];
|
|
137
|
+
guardrails?: Guardrail[];
|
|
122
138
|
variables?: Record<string, string>;
|
|
123
|
-
tools?: Array<{name, description, parameters, webhookUrl}>;
|
|
124
139
|
})
|
|
125
140
|
```
|
|
126
141
|
|
|
127
|
-
|
|
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}` |
|
|
142
|
+
Pass `engine` for end-to-end mode, `stt` + `tts` for pipeline mode. Both arguments may take plain adapter instances (e.g. `new DeepgramSTT()`) that read their API key from the environment.
|
|
134
143
|
|
|
135
|
-
### `phone.serve()`
|
|
144
|
+
### `phone.serve()`
|
|
136
145
|
|
|
137
146
|
```typescript
|
|
138
147
|
await phone.serve({
|
|
139
148
|
agent: Agent;
|
|
140
149
|
port?: number;
|
|
150
|
+
tunnel?: boolean; // shortcut for Patter({ tunnel: new CloudflareTunnel() })
|
|
141
151
|
dashboard?: boolean;
|
|
142
152
|
recording?: boolean;
|
|
143
|
-
onCallStart?: (data
|
|
144
|
-
onCallEnd?: (data
|
|
145
|
-
onTranscript?: (data
|
|
146
|
-
|
|
153
|
+
onCallStart?: (data) => Promise<void>;
|
|
154
|
+
onCallEnd?: (data) => Promise<void>;
|
|
155
|
+
onTranscript?: (data) => Promise<void>;
|
|
156
|
+
onMessage?: (data) => Promise<string> | string;
|
|
157
|
+
voicemailMessage?: string;
|
|
158
|
+
dashboardToken?: string;
|
|
159
|
+
});
|
|
147
160
|
```
|
|
148
161
|
|
|
149
|
-
|
|
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` |
|
|
158
|
-
|
|
159
|
-
### `phone.call()` Method
|
|
162
|
+
### `phone.call()`
|
|
160
163
|
|
|
161
164
|
```typescript
|
|
162
165
|
await phone.call({
|
|
163
166
|
to: string;
|
|
167
|
+
agent?: Agent;
|
|
168
|
+
from?: string;
|
|
164
169
|
firstMessage?: string;
|
|
165
170
|
machineDetection?: boolean;
|
|
166
171
|
voicemailMessage?: string;
|
|
167
|
-
|
|
172
|
+
ringTimeout?: number;
|
|
173
|
+
});
|
|
168
174
|
```
|
|
169
175
|
|
|
170
|
-
|
|
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 |
|
|
176
|
-
|
|
177
|
-
### Static Provider Helpers
|
|
176
|
+
### STT / TTS catalog
|
|
178
177
|
|
|
179
178
|
```typescript
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
179
|
+
import {
|
|
180
|
+
// Carriers
|
|
181
|
+
Twilio, Telnyx,
|
|
182
|
+
// Engines
|
|
183
|
+
OpenAIRealtime, ElevenLabsConvAI,
|
|
184
|
+
// STT
|
|
185
|
+
DeepgramSTT, WhisperSTT, CartesiaSTT, SonioxSTT, AssemblyAISTT,
|
|
186
|
+
// TTS
|
|
187
|
+
ElevenLabsTTS, OpenAITTS, CartesiaTTS, RimeTTS, LMNTTTS,
|
|
188
|
+
// LLM
|
|
189
|
+
OpenAILLM, AnthropicLLM, GroqLLM, CerebrasLLM, GoogleLLM,
|
|
190
|
+
// Tunnels
|
|
191
|
+
CloudflareTunnel, StaticTunnel,
|
|
192
|
+
// Primitives
|
|
193
|
+
Tool, Guardrail, tool, guardrail,
|
|
194
|
+
} from "getpatter";
|
|
184
195
|
```
|
|
185
196
|
|
|
197
|
+
Every class reads its API key from the environment by default, so `new DeepgramSTT()` / `new ElevenLabsTTS()` work out of the box when the corresponding env var is set.
|
|
198
|
+
|
|
186
199
|
## Examples
|
|
187
200
|
|
|
188
|
-
### Inbound
|
|
201
|
+
### Inbound calls — default engine
|
|
189
202
|
|
|
190
203
|
```typescript
|
|
191
|
-
import { Patter,
|
|
204
|
+
import { Patter, Twilio, OpenAIRealtime } from "getpatter";
|
|
192
205
|
|
|
193
|
-
const phone = new Patter({
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
206
|
+
const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });
|
|
207
|
+
const agent = phone.agent({
|
|
208
|
+
engine: new OpenAIRealtime(),
|
|
209
|
+
systemPrompt: "You are a helpful customer service agent.",
|
|
210
|
+
firstMessage: "Hello! How can I help?",
|
|
197
211
|
});
|
|
198
212
|
|
|
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
|
-
}
|
|
205
|
-
|
|
206
213
|
await phone.serve({
|
|
207
|
-
agent
|
|
208
|
-
|
|
209
|
-
firstMessage: "Hello! How can I help?",
|
|
210
|
-
}),
|
|
211
|
-
port: 8000,
|
|
214
|
+
agent,
|
|
215
|
+
tunnel: true,
|
|
212
216
|
onCallStart: (data) => console.log(`Call from ${data.caller}`),
|
|
213
|
-
onCallEnd: (
|
|
217
|
+
onCallEnd: () => console.log("Call ended"),
|
|
214
218
|
});
|
|
215
219
|
```
|
|
216
220
|
|
|
217
|
-
###
|
|
221
|
+
### Custom voice — Deepgram STT + ElevenLabs TTS
|
|
218
222
|
|
|
219
223
|
```typescript
|
|
220
|
-
import { Patter } from "getpatter";
|
|
221
|
-
|
|
222
|
-
const phone = new Patter({
|
|
223
|
-
twilioSid: "AC...", twilioToken: "...",
|
|
224
|
-
openaiKey: "sk-...",
|
|
225
|
-
phoneNumber: "+1...",
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
const agentConfig = phone.agent({
|
|
229
|
-
systemPrompt: "You are making reminder calls.",
|
|
230
|
-
firstMessage: "Hi, this is an automated reminder from Acme Corp.",
|
|
231
|
-
});
|
|
224
|
+
import { Patter, Twilio, DeepgramSTT, ElevenLabsTTS } from "getpatter";
|
|
232
225
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
226
|
+
const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });
|
|
227
|
+
const agent = phone.agent({
|
|
228
|
+
stt: new DeepgramSTT(), // reads DEEPGRAM_API_KEY
|
|
229
|
+
tts: new ElevenLabsTTS({ voice: "rachel" }), // reads ELEVENLABS_API_KEY
|
|
230
|
+
systemPrompt: "You are a helpful voice assistant.",
|
|
237
231
|
});
|
|
232
|
+
await phone.serve({ agent, tunnel: true });
|
|
238
233
|
```
|
|
239
234
|
|
|
240
|
-
###
|
|
235
|
+
### Pipeline mode — pick STT, LLM, TTS independently
|
|
241
236
|
|
|
242
237
|
```typescript
|
|
238
|
+
import { Patter, Twilio, DeepgramSTT, AnthropicLLM, ElevenLabsTTS } from "getpatter";
|
|
239
|
+
|
|
240
|
+
const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });
|
|
243
241
|
const agent = phone.agent({
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
parameters: {
|
|
249
|
-
type: "object",
|
|
250
|
-
properties: {
|
|
251
|
-
date: { type: "string", description: "ISO date, e.g. 2025-06-15" },
|
|
252
|
-
},
|
|
253
|
-
required: ["date"],
|
|
254
|
-
},
|
|
255
|
-
webhookUrl: "https://api.example.com/availability",
|
|
256
|
-
}],
|
|
242
|
+
stt: new DeepgramSTT(), // reads DEEPGRAM_API_KEY
|
|
243
|
+
llm: new AnthropicLLM(), // reads ANTHROPIC_API_KEY
|
|
244
|
+
tts: new ElevenLabsTTS({ voiceId: "rachel" }), // reads ELEVENLABS_API_KEY
|
|
245
|
+
systemPrompt: "You are a helpful voice assistant.",
|
|
257
246
|
});
|
|
247
|
+
await phone.serve({ agent, tunnel: true });
|
|
258
248
|
```
|
|
259
249
|
|
|
260
|
-
|
|
250
|
+
Available LLM providers: `OpenAILLM`, `AnthropicLLM`, `GroqLLM`, `CerebrasLLM`, `GoogleLLM`. Tool calling works across all five. For fully custom logic, drop `llm` and pass an `onMessage` callback to `serve()` instead.
|
|
251
|
+
|
|
252
|
+
### Tool calling
|
|
261
253
|
|
|
262
254
|
```typescript
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
255
|
+
import { Patter, Twilio, OpenAIRealtime, tool } from "getpatter";
|
|
256
|
+
|
|
257
|
+
const checkAvailability = tool({
|
|
258
|
+
name: "check_availability",
|
|
259
|
+
description: "Check appointment availability for a given ISO date.",
|
|
260
|
+
parameters: {
|
|
261
|
+
type: "object",
|
|
262
|
+
properties: { date: { type: "string" } },
|
|
263
|
+
required: ["date"],
|
|
264
|
+
},
|
|
265
|
+
handler: async ({ date }) => ({ available: true }),
|
|
267
266
|
});
|
|
268
267
|
|
|
268
|
+
const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });
|
|
269
269
|
const agent = phone.agent({
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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" }),
|
|
270
|
+
engine: new OpenAIRealtime(),
|
|
271
|
+
systemPrompt: "You are a booking assistant.",
|
|
272
|
+
tools: [checkAvailability],
|
|
280
273
|
});
|
|
274
|
+
await phone.serve({ agent, tunnel: true });
|
|
281
275
|
```
|
|
282
276
|
|
|
283
|
-
###
|
|
277
|
+
### Outbound calls
|
|
284
278
|
|
|
285
279
|
```typescript
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
280
|
+
import { Patter, Twilio, OpenAIRealtime } from "getpatter";
|
|
281
|
+
|
|
282
|
+
const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });
|
|
283
|
+
const agent = phone.agent({
|
|
284
|
+
engine: new OpenAIRealtime(),
|
|
285
|
+
systemPrompt: "You are making reminder calls.",
|
|
286
|
+
firstMessage: "Hi, this is a reminder from Acme Corp.",
|
|
290
287
|
});
|
|
288
|
+
|
|
289
|
+
await phone.serve({ agent, tunnel: true });
|
|
290
|
+
await phone.call({ to: "+14155551234", agent });
|
|
291
291
|
```
|
|
292
292
|
|
|
293
|
-
### Dynamic
|
|
293
|
+
### Dynamic variables
|
|
294
294
|
|
|
295
295
|
```typescript
|
|
296
296
|
const agent = phone.agent({
|
|
297
|
+
engine: new OpenAIRealtime(),
|
|
297
298
|
systemPrompt: "You are helping {customer_name}, account #{account_id}.",
|
|
298
299
|
firstMessage: "Hi {customer_name}! How can I help you today?",
|
|
299
|
-
variables: {
|
|
300
|
-
customer_name: "Jane",
|
|
301
|
-
account_id: "A-789",
|
|
302
|
-
},
|
|
300
|
+
variables: { customer_name: "Jane", account_id: "A-789" },
|
|
303
301
|
});
|
|
304
302
|
```
|
|
305
303
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getLogger
|
|
3
|
+
} from "./chunk-FMNRCP5X.mjs";
|
|
4
|
+
import "./chunk-OOIUSZB4.mjs";
|
|
5
|
+
|
|
6
|
+
// src/carrier-config.ts
|
|
7
|
+
var TWILIO_API_BASE = "https://api.twilio.com/2010-04-01";
|
|
8
|
+
var TELNYX_API_BASE = "https://api.telnyx.com/v2";
|
|
9
|
+
async function configureTwilioNumber(accountSid, authToken, phoneNumber, voiceUrl) {
|
|
10
|
+
const auth = `Basic ${Buffer.from(`${accountSid}:${authToken}`).toString("base64")}`;
|
|
11
|
+
const listUrl = `${TWILIO_API_BASE}/Accounts/${accountSid}/IncomingPhoneNumbers.json?PhoneNumber=${encodeURIComponent(phoneNumber)}`;
|
|
12
|
+
const listResp = await fetch(listUrl, {
|
|
13
|
+
method: "GET",
|
|
14
|
+
headers: { Authorization: auth }
|
|
15
|
+
});
|
|
16
|
+
if (!listResp.ok) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`Twilio IncomingPhoneNumbers.list failed: ${listResp.status} ${await listResp.text()}`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
const body = await listResp.json();
|
|
22
|
+
const match = body.incoming_phone_numbers?.[0];
|
|
23
|
+
if (!match) {
|
|
24
|
+
throw new Error(`Twilio number ${phoneNumber} not found on account ${accountSid}`);
|
|
25
|
+
}
|
|
26
|
+
const updateUrl = `${TWILIO_API_BASE}/Accounts/${accountSid}/IncomingPhoneNumbers/${match.sid}.json`;
|
|
27
|
+
const form = new URLSearchParams({ VoiceUrl: voiceUrl, VoiceMethod: "POST" });
|
|
28
|
+
const updateResp = await fetch(updateUrl, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: {
|
|
31
|
+
Authorization: auth,
|
|
32
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
33
|
+
},
|
|
34
|
+
body: form.toString()
|
|
35
|
+
});
|
|
36
|
+
if (!updateResp.ok) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Twilio IncomingPhoneNumbers.update failed: ${updateResp.status} ${await updateResp.text()}`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function configureTelnyxNumber(apiKey, connectionId, phoneNumber) {
|
|
43
|
+
const resp = await fetch(`${TELNYX_API_BASE}/phone_numbers/${encodeURIComponent(phoneNumber)}`, {
|
|
44
|
+
method: "PATCH",
|
|
45
|
+
headers: {
|
|
46
|
+
Authorization: `Bearer ${apiKey}`,
|
|
47
|
+
"Content-Type": "application/json"
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify({ connection_id: connectionId })
|
|
50
|
+
});
|
|
51
|
+
if (!resp.ok) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Telnyx PATCH /phone_numbers/${phoneNumber} failed: ${resp.status} ${await resp.text()}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function autoConfigureCarrier(params) {
|
|
58
|
+
const log = getLogger();
|
|
59
|
+
const provider = params.telephonyProvider ?? (params.twilioSid ? "twilio" : "telnyx");
|
|
60
|
+
if (provider === "twilio" && params.twilioSid && params.twilioToken) {
|
|
61
|
+
const voiceUrl = `https://${params.webhookHost}/webhooks/twilio/voice`;
|
|
62
|
+
try {
|
|
63
|
+
await configureTwilioNumber(params.twilioSid, params.twilioToken, params.phoneNumber, voiceUrl);
|
|
64
|
+
log.info("Twilio webhook set to %s", voiceUrl);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
log.warn("Could not auto-configure Twilio webhook: %s", err instanceof Error ? err.message : String(err));
|
|
67
|
+
log.info("Set webhook manually to: %s", voiceUrl);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (provider === "telnyx" && params.telnyxKey && params.telnyxConnectionId) {
|
|
72
|
+
try {
|
|
73
|
+
await configureTelnyxNumber(params.telnyxKey, params.telnyxConnectionId, params.phoneNumber);
|
|
74
|
+
log.info("Telnyx number %s associated with connection %s", params.phoneNumber, params.telnyxConnectionId);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
log.warn("Could not auto-configure Telnyx number: %s", err instanceof Error ? err.message : String(err));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
autoConfigureCarrier,
|
|
82
|
+
configureTelnyxNumber,
|
|
83
|
+
configureTwilioNumber
|
|
84
|
+
};
|
|
@@ -7,7 +7,7 @@ var log = getLogger();
|
|
|
7
7
|
async function startTunnel(port, timeoutMs = 3e4) {
|
|
8
8
|
let tunnelMod;
|
|
9
9
|
try {
|
|
10
|
-
tunnelMod = await import("
|
|
10
|
+
tunnelMod = await import("cloudflared");
|
|
11
11
|
} catch {
|
|
12
12
|
throw new Error(
|
|
13
13
|
'Built-in tunnel requires the "cloudflared" package. Install it with:\n\n npm install cloudflared\n\nOr provide your own webhookUrl instead of using tunnel: true.'
|