getpatter 0.4.3 → 0.5.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 +136 -162
- package/dist/carrier-config-CPG5CROM.mjs +84 -0
- package/dist/{chunk-35EVXMGB.mjs → chunk-757NVN4L.mjs} +396 -458
- package/dist/cli.js +92 -5
- package/dist/index.d.mts +901 -241
- package/dist/index.d.ts +901 -241
- package/dist/index.js +1763 -921
- package/dist/index.mjs +1240 -419
- package/dist/{test-mode-RH65MMSP.mjs → test-mode-YFOL2HYH.mjs} +1 -1
- package/package.json +1 -1
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,58 @@ 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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
});
|
|
39
|
+
Four lines of TypeScript:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { Patter, Twilio, OpenAIRealtime } from "getpatter";
|
|
45
43
|
|
|
46
|
-
|
|
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
|
-
| Custom LLM (any model) | `serve(onMessage
|
|
59
|
-
| Call recording | `serve(recording
|
|
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
|
+
| Custom LLM (any model) | `serve({ onMessage })` | Claude, Mistral, LLaMA, etc. |
|
|
61
|
+
| Call recording | `serve({ recording: true })` | Record all calls |
|
|
60
62
|
| Call transfer | `transfer_call` (auto-injected) | Transfer to a human |
|
|
61
|
-
| Voicemail drop | `call(voicemailMessage
|
|
63
|
+
| Voicemail drop | `call({ voicemailMessage: "..." })` | Play message on voicemail |
|
|
62
64
|
|
|
63
65
|
## Configuration
|
|
64
66
|
|
|
65
|
-
### Environment
|
|
67
|
+
### Environment variables
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
|
70
|
-
|
|
71
|
-
| `TWILIO_AUTH_TOKEN` |
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `ELEVENLABS_API_KEY` |
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
69
|
+
Every provider reads its credentials from the environment by default. Pass `apiKey: "..."` to any constructor to override.
|
|
70
|
+
|
|
71
|
+
| Variable | Used by |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN` | `new Twilio()` carrier |
|
|
74
|
+
| `TELNYX_API_KEY`, `TELNYX_CONNECTION_ID`, `TELNYX_PUBLIC_KEY` (optional) | `new Telnyx()` carrier |
|
|
75
|
+
| `OPENAI_API_KEY` | `OpenAIRealtime`, `WhisperSTT`, `OpenAITTS` |
|
|
76
|
+
| `ELEVENLABS_API_KEY`, `ELEVENLABS_AGENT_ID` | `ElevenLabsConvAI`, `ElevenLabsTTS` |
|
|
77
|
+
| `DEEPGRAM_API_KEY` | `DeepgramSTT` |
|
|
78
|
+
| `CARTESIA_API_KEY` | `CartesiaSTT`, `CartesiaTTS` |
|
|
79
|
+
| `RIME_API_KEY` | `RimeTTS` |
|
|
80
|
+
| `LMNT_API_KEY` | `LMNTTTS` |
|
|
81
|
+
| `SONIOX_API_KEY` | `SonioxSTT` |
|
|
82
|
+
| `ASSEMBLYAI_API_KEY` | `AssemblyAISTT` |
|
|
77
83
|
|
|
78
84
|
```bash
|
|
79
85
|
cp .env.example .env
|
|
@@ -87,219 +93,187 @@ cp .env.example .env
|
|
|
87
93
|
| Mode | Latency | Quality | Best For |
|
|
88
94
|
|---|---|---|---|
|
|
89
95
|
| **OpenAI Realtime** | Lowest | High | Fluid, low-latency conversations |
|
|
90
|
-
| **
|
|
96
|
+
| **Pipeline** (STT + LLM + TTS) | Low | High | Independent control over STT and TTS |
|
|
91
97
|
| **ElevenLabs ConvAI** | Low | High | ElevenLabs-managed conversation flow |
|
|
92
98
|
|
|
93
99
|
## API Reference
|
|
94
100
|
|
|
95
|
-
### `Patter`
|
|
101
|
+
### `Patter` constructor
|
|
96
102
|
|
|
97
103
|
```typescript
|
|
98
104
|
new Patter({
|
|
99
|
-
|
|
100
|
-
twilioToken: string;
|
|
101
|
-
openaiKey: string;
|
|
105
|
+
carrier: Twilio | Telnyx;
|
|
102
106
|
phoneNumber: string;
|
|
103
|
-
webhookUrl?: string;
|
|
107
|
+
webhookUrl?: string; // Public hostname. Mutually exclusive with tunnel.
|
|
108
|
+
tunnel?: CloudflareTunnel | StaticTunnel; // Or pass tunnel: true on serve() for dev.
|
|
104
109
|
})
|
|
105
110
|
```
|
|
106
111
|
|
|
107
112
|
| Parameter | Type | Description |
|
|
108
113
|
|---|---|---|
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `webhookUrl` | `string` | Public URL for Twilio webhooks (optional) |
|
|
114
|
+
| `carrier` | `Twilio` / `Telnyx` | Carrier instance. Reads env vars by default. |
|
|
115
|
+
| `phoneNumber` | `string` | Your phone number in E.164 format. |
|
|
116
|
+
| `webhookUrl` | `string` | Public hostname your local server is reachable on. |
|
|
117
|
+
| `tunnel` | instance | `new CloudflareTunnel()` or `new StaticTunnel({ hostname: ... })`. |
|
|
114
118
|
|
|
115
|
-
### `phone.agent()`
|
|
119
|
+
### `phone.agent()`
|
|
116
120
|
|
|
117
121
|
```typescript
|
|
118
122
|
phone.agent({
|
|
119
123
|
systemPrompt: string;
|
|
124
|
+
engine?: OpenAIRealtime | ElevenLabsConvAI; // default: new OpenAIRealtime()
|
|
125
|
+
stt?: STTProvider; // e.g. new DeepgramSTT()
|
|
126
|
+
tts?: TTSProvider; // e.g. new ElevenLabsTTS()
|
|
120
127
|
voice?: string;
|
|
128
|
+
model?: string;
|
|
129
|
+
language?: string;
|
|
121
130
|
firstMessage?: string;
|
|
131
|
+
tools?: Tool[];
|
|
132
|
+
guardrails?: Guardrail[];
|
|
122
133
|
variables?: Record<string, string>;
|
|
123
|
-
tools?: Array<{name, description, parameters, webhookUrl}>;
|
|
124
134
|
})
|
|
125
135
|
```
|
|
126
136
|
|
|
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}` |
|
|
137
|
+
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
138
|
|
|
135
|
-
### `phone.serve()`
|
|
139
|
+
### `phone.serve()`
|
|
136
140
|
|
|
137
141
|
```typescript
|
|
138
142
|
await phone.serve({
|
|
139
143
|
agent: Agent;
|
|
140
144
|
port?: number;
|
|
145
|
+
tunnel?: boolean; // shortcut for Patter({ tunnel: new CloudflareTunnel() })
|
|
141
146
|
dashboard?: boolean;
|
|
142
147
|
recording?: boolean;
|
|
143
|
-
onCallStart?: (data
|
|
144
|
-
onCallEnd?: (data
|
|
145
|
-
onTranscript?: (data
|
|
146
|
-
|
|
148
|
+
onCallStart?: (data) => Promise<void>;
|
|
149
|
+
onCallEnd?: (data) => Promise<void>;
|
|
150
|
+
onTranscript?: (data) => Promise<void>;
|
|
151
|
+
onMessage?: (data) => Promise<string> | string;
|
|
152
|
+
voicemailMessage?: string;
|
|
153
|
+
dashboardToken?: string;
|
|
154
|
+
});
|
|
147
155
|
```
|
|
148
156
|
|
|
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
|
|
157
|
+
### `phone.call()`
|
|
160
158
|
|
|
161
159
|
```typescript
|
|
162
160
|
await phone.call({
|
|
163
161
|
to: string;
|
|
162
|
+
agent?: Agent;
|
|
163
|
+
from?: string;
|
|
164
164
|
firstMessage?: string;
|
|
165
165
|
machineDetection?: boolean;
|
|
166
166
|
voicemailMessage?: string;
|
|
167
|
-
|
|
167
|
+
ringTimeout?: number;
|
|
168
|
+
});
|
|
168
169
|
```
|
|
169
170
|
|
|
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
|
|
171
|
+
### STT / TTS catalog
|
|
178
172
|
|
|
179
173
|
```typescript
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
174
|
+
import {
|
|
175
|
+
// Carriers
|
|
176
|
+
Twilio, Telnyx,
|
|
177
|
+
// Engines
|
|
178
|
+
OpenAIRealtime, ElevenLabsConvAI,
|
|
179
|
+
// STT
|
|
180
|
+
DeepgramSTT, WhisperSTT, CartesiaSTT, SonioxSTT, AssemblyAISTT,
|
|
181
|
+
// TTS
|
|
182
|
+
ElevenLabsTTS, OpenAITTS, CartesiaTTS, RimeTTS, LMNTTTS,
|
|
183
|
+
// Tunnels
|
|
184
|
+
CloudflareTunnel, StaticTunnel,
|
|
185
|
+
// Primitives
|
|
186
|
+
Tool, Guardrail, tool, guardrail,
|
|
187
|
+
} from "getpatter";
|
|
184
188
|
```
|
|
185
189
|
|
|
190
|
+
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.
|
|
191
|
+
|
|
186
192
|
## Examples
|
|
187
193
|
|
|
188
|
-
### Inbound
|
|
194
|
+
### Inbound calls — default engine
|
|
189
195
|
|
|
190
196
|
```typescript
|
|
191
|
-
import { Patter,
|
|
197
|
+
import { Patter, Twilio, OpenAIRealtime } from "getpatter";
|
|
192
198
|
|
|
193
|
-
const phone = new Patter({
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
199
|
+
const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });
|
|
200
|
+
const agent = phone.agent({
|
|
201
|
+
engine: new OpenAIRealtime(),
|
|
202
|
+
systemPrompt: "You are a helpful customer service agent.",
|
|
203
|
+
firstMessage: "Hello! How can I help?",
|
|
197
204
|
});
|
|
198
205
|
|
|
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
206
|
await phone.serve({
|
|
207
|
-
agent
|
|
208
|
-
|
|
209
|
-
firstMessage: "Hello! How can I help?",
|
|
210
|
-
}),
|
|
211
|
-
port: 8000,
|
|
207
|
+
agent,
|
|
208
|
+
tunnel: true,
|
|
212
209
|
onCallStart: (data) => console.log(`Call from ${data.caller}`),
|
|
213
|
-
onCallEnd: (
|
|
210
|
+
onCallEnd: () => console.log("Call ended"),
|
|
214
211
|
});
|
|
215
212
|
```
|
|
216
213
|
|
|
217
|
-
###
|
|
214
|
+
### Custom voice — Deepgram STT + ElevenLabs TTS
|
|
218
215
|
|
|
219
216
|
```typescript
|
|
220
|
-
import { Patter } from "getpatter";
|
|
221
|
-
|
|
222
|
-
const phone = new Patter({
|
|
223
|
-
twilioSid: "AC...", twilioToken: "...",
|
|
224
|
-
openaiKey: "sk-...",
|
|
225
|
-
phoneNumber: "+1...",
|
|
226
|
-
});
|
|
217
|
+
import { Patter, Twilio, DeepgramSTT, ElevenLabsTTS } from "getpatter";
|
|
227
218
|
|
|
228
|
-
const
|
|
229
|
-
systemPrompt: "You are making reminder calls.",
|
|
230
|
-
firstMessage: "Hi, this is an automated reminder from Acme Corp.",
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
await phone.serve({ agent: agentConfig, port: 8000 });
|
|
234
|
-
await phone.call({
|
|
235
|
-
to: "+14155551234",
|
|
236
|
-
firstMessage: "Hi, just checking in.",
|
|
237
|
-
});
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
### Tool Calling (Agent calls external APIs)
|
|
241
|
-
|
|
242
|
-
```typescript
|
|
219
|
+
const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });
|
|
243
220
|
const agent = phone.agent({
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
description: "Check appointment availability for a given date",
|
|
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
|
-
}],
|
|
221
|
+
stt: new DeepgramSTT(), // reads DEEPGRAM_API_KEY
|
|
222
|
+
tts: new ElevenLabsTTS({ voice: "rachel" }), // reads ELEVENLABS_API_KEY
|
|
223
|
+
systemPrompt: "You are a helpful voice assistant.",
|
|
257
224
|
});
|
|
225
|
+
await phone.serve({ agent, tunnel: true });
|
|
258
226
|
```
|
|
259
227
|
|
|
260
|
-
###
|
|
228
|
+
### Tool calling
|
|
261
229
|
|
|
262
230
|
```typescript
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
231
|
+
import { Patter, Twilio, OpenAIRealtime, tool } from "getpatter";
|
|
232
|
+
|
|
233
|
+
const checkAvailability = tool({
|
|
234
|
+
name: "check_availability",
|
|
235
|
+
description: "Check appointment availability for a given ISO date.",
|
|
236
|
+
parameters: {
|
|
237
|
+
type: "object",
|
|
238
|
+
properties: { date: { type: "string" } },
|
|
239
|
+
required: ["date"],
|
|
240
|
+
},
|
|
241
|
+
handler: async ({ date }) => ({ available: true }),
|
|
267
242
|
});
|
|
268
243
|
|
|
244
|
+
const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });
|
|
269
245
|
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" }),
|
|
246
|
+
engine: new OpenAIRealtime(),
|
|
247
|
+
systemPrompt: "You are a booking assistant.",
|
|
248
|
+
tools: [checkAvailability],
|
|
280
249
|
});
|
|
250
|
+
await phone.serve({ agent, tunnel: true });
|
|
281
251
|
```
|
|
282
252
|
|
|
283
|
-
###
|
|
253
|
+
### Outbound calls
|
|
284
254
|
|
|
285
255
|
```typescript
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
256
|
+
import { Patter, Twilio, OpenAIRealtime } from "getpatter";
|
|
257
|
+
|
|
258
|
+
const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });
|
|
259
|
+
const agent = phone.agent({
|
|
260
|
+
engine: new OpenAIRealtime(),
|
|
261
|
+
systemPrompt: "You are making reminder calls.",
|
|
262
|
+
firstMessage: "Hi, this is a reminder from Acme Corp.",
|
|
290
263
|
});
|
|
264
|
+
|
|
265
|
+
await phone.serve({ agent, tunnel: true });
|
|
266
|
+
await phone.call({ to: "+14155551234", agent });
|
|
291
267
|
```
|
|
292
268
|
|
|
293
|
-
### Dynamic
|
|
269
|
+
### Dynamic variables
|
|
294
270
|
|
|
295
271
|
```typescript
|
|
296
272
|
const agent = phone.agent({
|
|
273
|
+
engine: new OpenAIRealtime(),
|
|
297
274
|
systemPrompt: "You are helping {customer_name}, account #{account_id}.",
|
|
298
275
|
firstMessage: "Hi {customer_name}! How can I help you today?",
|
|
299
|
-
variables: {
|
|
300
|
-
customer_name: "Jane",
|
|
301
|
-
account_id: "A-789",
|
|
302
|
-
},
|
|
276
|
+
variables: { customer_name: "Jane", account_id: "A-789" },
|
|
303
277
|
});
|
|
304
278
|
```
|
|
305
279
|
|
|
@@ -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
|
+
};
|