getpatter 0.5.0 → 0.5.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.
- package/README.md +25 -1
- package/dist/banner-FLR2HE5Z.mjs +19 -0
- package/dist/{chunk-757NVN4L.mjs → chunk-7SDDK2AO.mjs} +61 -46
- package/dist/{chunk-JO5C35FM.mjs → chunk-AKQFOFLG.mjs} +1 -1
- package/dist/cli.js +7 -2
- package/dist/index.d.mts +363 -56
- package/dist/index.d.ts +363 -56
- package/dist/index.js +728 -870
- package/dist/index.mjs +640 -7
- package/dist/{test-mode-YFOL2HYH.mjs → test-mode-K2TTPRGE.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
|
@@ -57,7 +57,8 @@ await phone.serve({ agent, tunnel: true });
|
|
|
57
57
|
| Tool calling | `agent({ tools: [tool(...)] })` | Agent calls external APIs mid-conversation |
|
|
58
58
|
| Custom STT + TTS | `agent({ stt: new DeepgramSTT(), tts: new ElevenLabsTTS() })` | Bring your own voice providers |
|
|
59
59
|
| Dynamic variables | `agent({ variables: {...} })` | Personalize prompts per caller |
|
|
60
|
-
|
|
|
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. |
|
|
61
62
|
| Call recording | `serve({ recording: true })` | Record all calls |
|
|
62
63
|
| Call transfer | `transfer_call` (auto-injected) | Transfer to a human |
|
|
63
64
|
| Voicemail drop | `call({ voicemailMessage: "..." })` | Play message on voicemail |
|
|
@@ -80,6 +81,10 @@ Every provider reads its credentials from the environment by default. Pass `apiK
|
|
|
80
81
|
| `LMNT_API_KEY` | `LMNTTTS` |
|
|
81
82
|
| `SONIOX_API_KEY` | `SonioxSTT` |
|
|
82
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` |
|
|
83
88
|
|
|
84
89
|
```bash
|
|
85
90
|
cp .env.example .env
|
|
@@ -180,6 +185,8 @@ import {
|
|
|
180
185
|
DeepgramSTT, WhisperSTT, CartesiaSTT, SonioxSTT, AssemblyAISTT,
|
|
181
186
|
// TTS
|
|
182
187
|
ElevenLabsTTS, OpenAITTS, CartesiaTTS, RimeTTS, LMNTTTS,
|
|
188
|
+
// LLM
|
|
189
|
+
OpenAILLM, AnthropicLLM, GroqLLM, CerebrasLLM, GoogleLLM,
|
|
183
190
|
// Tunnels
|
|
184
191
|
CloudflareTunnel, StaticTunnel,
|
|
185
192
|
// Primitives
|
|
@@ -225,6 +232,23 @@ const agent = phone.agent({
|
|
|
225
232
|
await phone.serve({ agent, tunnel: true });
|
|
226
233
|
```
|
|
227
234
|
|
|
235
|
+
### Pipeline mode — pick STT, LLM, TTS independently
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
import { Patter, Twilio, DeepgramSTT, AnthropicLLM, ElevenLabsTTS } from "getpatter";
|
|
239
|
+
|
|
240
|
+
const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });
|
|
241
|
+
const agent = phone.agent({
|
|
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.",
|
|
246
|
+
});
|
|
247
|
+
await phone.serve({ agent, tunnel: true });
|
|
248
|
+
```
|
|
249
|
+
|
|
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
|
+
|
|
228
252
|
### Tool calling
|
|
229
253
|
|
|
230
254
|
```typescript
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import "./chunk-OOIUSZB4.mjs";
|
|
2
|
+
|
|
3
|
+
// src/banner.ts
|
|
4
|
+
var BANNER = `
|
|
5
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
6
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
7
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
8
|
+
\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
9
|
+
\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
10
|
+
\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
|
|
11
|
+
|
|
12
|
+
Connect AI agents to phone numbers in 4 lines of code
|
|
13
|
+
`;
|
|
14
|
+
function showBanner() {
|
|
15
|
+
console.log("\n" + BANNER);
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
showBanner
|
|
19
|
+
};
|
|
@@ -157,7 +157,7 @@ var OpenAIRealtimeAdapter = class {
|
|
|
157
157
|
import WebSocket2 from "ws";
|
|
158
158
|
var ELEVENLABS_CONVAI_URL = "wss://api.elevenlabs.io/v1/convai/conversation";
|
|
159
159
|
var ElevenLabsConvAIAdapter = class {
|
|
160
|
-
constructor(apiKey, agentId = "", voiceId = "
|
|
160
|
+
constructor(apiKey, agentId = "", voiceId = "EXAVITQu4vr4xnSDxMaL", _modelId = "eleven_turbo_v2_5", _language = "en", firstMessage = "") {
|
|
161
161
|
this.apiKey = apiKey;
|
|
162
162
|
this.agentId = agentId;
|
|
163
163
|
this.voiceId = voiceId;
|
|
@@ -2351,7 +2351,7 @@ var StreamHandler = class {
|
|
|
2351
2351
|
ttsProvider: ttsProviderName,
|
|
2352
2352
|
pricing: deps.pricing
|
|
2353
2353
|
});
|
|
2354
|
-
getLogger().
|
|
2354
|
+
getLogger().debug(`WebSocket connection opened (${deps.bridge.label})`);
|
|
2355
2355
|
}
|
|
2356
2356
|
// ---------------------------------------------------------------------------
|
|
2357
2357
|
// Public: called by the provider-specific parsers in server.ts
|
|
@@ -2367,9 +2367,12 @@ var StreamHandler = class {
|
|
|
2367
2367
|
this.metricsAcc.callId = callId;
|
|
2368
2368
|
if (customParams.caller && !this.caller) this.caller = customParams.caller;
|
|
2369
2369
|
if (customParams.callee && !this.callee) this.callee = customParams.callee;
|
|
2370
|
-
|
|
2370
|
+
const mode = this.deps.agent.engine ? `engine=${this.deps.agent.engine.kind ?? "unknown"}` : "pipeline";
|
|
2371
|
+
getLogger().info(
|
|
2372
|
+
`Call started: ${callId} (${this.deps.bridge.label}, ${mode}, ${sanitizeLogValue(this.caller || "?")} \u2192 ${sanitizeLogValue(this.callee || "?")})`
|
|
2373
|
+
);
|
|
2371
2374
|
if (Object.keys(customParams).length > 0) {
|
|
2372
|
-
getLogger().
|
|
2375
|
+
getLogger().debug(`Custom params: ${sanitizeLogValue(JSON.stringify(customParams))}`);
|
|
2373
2376
|
}
|
|
2374
2377
|
this.deps.metricsStore.recordCallStart({
|
|
2375
2378
|
call_id: callId,
|
|
@@ -2417,7 +2420,7 @@ var StreamHandler = class {
|
|
|
2417
2420
|
}
|
|
2418
2421
|
});
|
|
2419
2422
|
if (recResp.ok) {
|
|
2420
|
-
getLogger().
|
|
2423
|
+
getLogger().debug(`Recording started for ${callId}`);
|
|
2421
2424
|
} else {
|
|
2422
2425
|
getLogger().warn(`could not start recording: ${await recResp.text()}`);
|
|
2423
2426
|
}
|
|
@@ -2472,7 +2475,7 @@ var StreamHandler = class {
|
|
|
2472
2475
|
}
|
|
2473
2476
|
/** Handle a DTMF keypress event (Twilio only). */
|
|
2474
2477
|
async handleDtmf(digit) {
|
|
2475
|
-
getLogger().
|
|
2478
|
+
getLogger().debug(`DTMF: ${digit}`);
|
|
2476
2479
|
if (this.adapter instanceof OpenAIRealtimeAdapter) {
|
|
2477
2480
|
await this.adapter.sendText(`The user pressed key ${digit} on their phone keypad.`);
|
|
2478
2481
|
}
|
|
@@ -2534,14 +2537,14 @@ var StreamHandler = class {
|
|
|
2534
2537
|
this.stt = await this.deps.bridge.createStt(this.deps.agent);
|
|
2535
2538
|
this.tts = await createTTS(this.deps.agent);
|
|
2536
2539
|
if (!this.stt) {
|
|
2537
|
-
getLogger().
|
|
2540
|
+
getLogger().debug(`Pipeline mode (${label}): no STT configured`);
|
|
2538
2541
|
}
|
|
2539
2542
|
if (!this.tts) {
|
|
2540
|
-
getLogger().
|
|
2543
|
+
getLogger().debug(`Pipeline mode (${label}): no TTS configured`);
|
|
2541
2544
|
}
|
|
2542
2545
|
try {
|
|
2543
2546
|
if (this.stt) await this.stt.connect();
|
|
2544
|
-
getLogger().
|
|
2547
|
+
getLogger().debug(`Pipeline mode (${label}): STT + TTS connected`);
|
|
2545
2548
|
} catch (e) {
|
|
2546
2549
|
getLogger().error(`Pipeline connect FAILED (${label}):`, e);
|
|
2547
2550
|
try {
|
|
@@ -2574,7 +2577,24 @@ var StreamHandler = class {
|
|
|
2574
2577
|
this.history.push({ role: "assistant", text: this.deps.agent.firstMessage, timestamp: Date.now() });
|
|
2575
2578
|
}
|
|
2576
2579
|
}
|
|
2577
|
-
if (
|
|
2580
|
+
if (this.deps.agent.llm) {
|
|
2581
|
+
if (this.deps.onMessage) {
|
|
2582
|
+
throw new Error(
|
|
2583
|
+
"Cannot pass both agent({ llm }) and serve({ onMessage }). Pick one \u2014 `llm` for built-in LLMs, `onMessage` for custom logic."
|
|
2584
|
+
);
|
|
2585
|
+
}
|
|
2586
|
+
this.llmLoop = new LLMLoop(
|
|
2587
|
+
"",
|
|
2588
|
+
// apiKey unused when llmProvider is supplied
|
|
2589
|
+
"",
|
|
2590
|
+
// model unused when llmProvider is supplied
|
|
2591
|
+
resolvedPrompt,
|
|
2592
|
+
this.deps.agent.tools,
|
|
2593
|
+
this.deps.agent.llm
|
|
2594
|
+
);
|
|
2595
|
+
const llmLabel = this.deps.agent.llm.constructor?.name ?? "custom";
|
|
2596
|
+
getLogger().debug(`Built-in LLM loop active (pipeline, ${label}, llm=${llmLabel})`);
|
|
2597
|
+
} else if (!this.deps.onMessage && this.deps.config.openaiKey) {
|
|
2578
2598
|
let llmModel = this.deps.agent.model || "gpt-4o-mini";
|
|
2579
2599
|
if (llmModel.includes("realtime")) llmModel = "gpt-4o-mini";
|
|
2580
2600
|
this.llmLoop = new LLMLoop(
|
|
@@ -2583,7 +2603,7 @@ var StreamHandler = class {
|
|
|
2583
2603
|
resolvedPrompt,
|
|
2584
2604
|
this.deps.agent.tools
|
|
2585
2605
|
);
|
|
2586
|
-
getLogger().
|
|
2606
|
+
getLogger().debug(`Built-in LLM loop active (pipeline, ${label})`);
|
|
2587
2607
|
}
|
|
2588
2608
|
if (this.stt) {
|
|
2589
2609
|
this.stt.onTranscript(async (transcript) => {
|
|
@@ -2644,7 +2664,7 @@ var StreamHandler = class {
|
|
|
2644
2664
|
}
|
|
2645
2665
|
async processTranscript(transcript) {
|
|
2646
2666
|
if (transcript.text && this.isSpeaking) {
|
|
2647
|
-
getLogger().
|
|
2667
|
+
getLogger().debug(
|
|
2648
2668
|
`Barge-in: caller spoke over agent (${sanitizeLogValue(transcript.text.slice(0, 40))})`
|
|
2649
2669
|
);
|
|
2650
2670
|
this.isSpeaking = false;
|
|
@@ -2679,17 +2699,17 @@ var StreamHandler = class {
|
|
|
2679
2699
|
"cool"
|
|
2680
2700
|
]);
|
|
2681
2701
|
if (HALLUCINATIONS.has(stripped) || stripped === "") {
|
|
2682
|
-
getLogger().
|
|
2702
|
+
getLogger().debug(`Dropped likely STT hallucination: ${sanitizeLogValue(normalised.slice(0, 40))}`);
|
|
2683
2703
|
return;
|
|
2684
2704
|
}
|
|
2685
2705
|
if (sinceLastMs < 2e3 && normalised === this.lastCommitText) {
|
|
2686
|
-
getLogger().
|
|
2706
|
+
getLogger().debug(
|
|
2687
2707
|
`Dropped duplicate final transcript (${(sinceLastMs / 1e3).toFixed(1)}s since last): ${sanitizeLogValue(normalised.slice(0, 40))}`
|
|
2688
2708
|
);
|
|
2689
2709
|
return;
|
|
2690
2710
|
}
|
|
2691
2711
|
if (sinceLastMs < 500) {
|
|
2692
|
-
getLogger().
|
|
2712
|
+
getLogger().debug(
|
|
2693
2713
|
`Dropped back-to-back final transcript (${(sinceLastMs / 1e3).toFixed(2)}s since last): ${sanitizeLogValue(normalised.slice(0, 40))}`
|
|
2694
2714
|
);
|
|
2695
2715
|
return;
|
|
@@ -2697,7 +2717,7 @@ var StreamHandler = class {
|
|
|
2697
2717
|
this.lastCommitText = normalised;
|
|
2698
2718
|
this.lastCommitAt = now;
|
|
2699
2719
|
const label = this.deps.bridge.label;
|
|
2700
|
-
getLogger().
|
|
2720
|
+
getLogger().debug(`User (${label} pipeline): ${sanitizeLogValue(transcript.text)}`);
|
|
2701
2721
|
this.metricsAcc.startTurn();
|
|
2702
2722
|
this.metricsAcc.recordSttComplete(transcript.text);
|
|
2703
2723
|
if (this.deps.onTranscript) {
|
|
@@ -2712,7 +2732,7 @@ var StreamHandler = class {
|
|
|
2712
2732
|
const hookCtx = this.buildHookContext();
|
|
2713
2733
|
const filteredTranscript = await hookExecutor.runAfterTranscribe(transcript.text, hookCtx);
|
|
2714
2734
|
if (filteredTranscript === null) {
|
|
2715
|
-
getLogger().
|
|
2735
|
+
getLogger().debug(`afterTranscribe hook vetoed turn (${label})`);
|
|
2716
2736
|
this.metricsAcc.recordTurnInterrupted();
|
|
2717
2737
|
return;
|
|
2718
2738
|
}
|
|
@@ -2800,7 +2820,7 @@ var StreamHandler = class {
|
|
|
2800
2820
|
if (!this.llmLoop) {
|
|
2801
2821
|
const guard = checkGuardrails(responseText, this.deps.agent.guardrails);
|
|
2802
2822
|
if (guard) {
|
|
2803
|
-
getLogger().
|
|
2823
|
+
getLogger().debug(`Guardrail '${guard.name}' triggered (pipeline)`);
|
|
2804
2824
|
responseText = guard.replacement ?? "I'm sorry, I can't respond to that.";
|
|
2805
2825
|
}
|
|
2806
2826
|
this.metricsAcc.recordLlmComplete();
|
|
@@ -2878,7 +2898,7 @@ var StreamHandler = class {
|
|
|
2878
2898
|
this.adapter = this.deps.buildAIAdapter(resolvedPrompt);
|
|
2879
2899
|
try {
|
|
2880
2900
|
await this.adapter.connect();
|
|
2881
|
-
getLogger().
|
|
2901
|
+
getLogger().debug(`AI adapter connected (${label})`);
|
|
2882
2902
|
} catch (e) {
|
|
2883
2903
|
getLogger().error(`AI adapter connect FAILED (${label}):`, e);
|
|
2884
2904
|
try {
|
|
@@ -2920,7 +2940,7 @@ var StreamHandler = class {
|
|
|
2920
2940
|
this.deps.bridge.sendMark(this.ws, `audio_${this.chunkCount}`, this.streamSid);
|
|
2921
2941
|
} else if (type === "transcript_input") {
|
|
2922
2942
|
const inputText = eventData;
|
|
2923
|
-
getLogger().
|
|
2943
|
+
getLogger().debug(`User (${this.deps.bridge.label}): ${sanitizeLogValue(inputText)}`);
|
|
2924
2944
|
this.history.push({ role: "user", text: inputText, timestamp: Date.now() });
|
|
2925
2945
|
this.metricsAcc.startTurn();
|
|
2926
2946
|
this.currentAgentText = "";
|
|
@@ -2938,7 +2958,7 @@ var StreamHandler = class {
|
|
|
2938
2958
|
if (outputText) {
|
|
2939
2959
|
const triggered = checkGuardrails(outputText, this.deps.agent.guardrails);
|
|
2940
2960
|
if (triggered) {
|
|
2941
|
-
getLogger().
|
|
2961
|
+
getLogger().debug(`Guardrail '${triggered.name}' triggered`);
|
|
2942
2962
|
if (this.adapter instanceof OpenAIRealtimeAdapter) {
|
|
2943
2963
|
this.adapter.cancelResponse();
|
|
2944
2964
|
await this.adapter.sendText(triggered.replacement ?? "I'm sorry, I can't respond to that.");
|
|
@@ -2997,7 +3017,7 @@ var StreamHandler = class {
|
|
|
2997
3017
|
await adapter.sendFunctionResult(fc.call_id, JSON.stringify({ error: "Invalid phone number format", status: "rejected" }));
|
|
2998
3018
|
return;
|
|
2999
3019
|
}
|
|
3000
|
-
getLogger().
|
|
3020
|
+
getLogger().debug(`Transferring call to ${transferTo}`);
|
|
3001
3021
|
await adapter.sendFunctionResult(fc.call_id, JSON.stringify({ status: "transferring", to: transferTo }));
|
|
3002
3022
|
await this.deps.bridge.transferCall(this.callId, transferTo);
|
|
3003
3023
|
if (this.deps.onTranscript) {
|
|
@@ -3013,7 +3033,7 @@ var StreamHandler = class {
|
|
|
3013
3033
|
endArgs = {};
|
|
3014
3034
|
}
|
|
3015
3035
|
const reason = endArgs.reason ?? "conversation_complete";
|
|
3016
|
-
getLogger().
|
|
3036
|
+
getLogger().debug(`Ending call (${this.deps.bridge.label}): ${reason}`);
|
|
3017
3037
|
await adapter.sendFunctionResult(fc.call_id, JSON.stringify({ status: "ending", reason }));
|
|
3018
3038
|
await this.deps.bridge.endCall(this.callId, this.ws);
|
|
3019
3039
|
if (this.deps.onTranscript) {
|
|
@@ -3065,6 +3085,11 @@ var StreamHandler = class {
|
|
|
3065
3085
|
transcript: [...this.history.entries],
|
|
3066
3086
|
metrics: finalMetrics
|
|
3067
3087
|
};
|
|
3088
|
+
const cost = finalMetrics.cost?.total ?? 0;
|
|
3089
|
+
const latencyP95 = finalMetrics.latency_p95?.total_ms ?? 0;
|
|
3090
|
+
getLogger().info(
|
|
3091
|
+
`Call ended: ${this.callId} (${finalMetrics.duration_seconds.toFixed(1)}s, ${finalMetrics.turns.length} turns, cost=$${cost.toFixed(4)}, p95=${Math.round(latencyP95)}ms)`
|
|
3092
|
+
);
|
|
3068
3093
|
this.deps.metricsStore.recordCallEnd(
|
|
3069
3094
|
callEndData,
|
|
3070
3095
|
finalMetrics
|
|
@@ -3101,7 +3126,7 @@ async function queryDeepgramCost(metricsAcc, deepgramKey, deepgramRequestId) {
|
|
|
3101
3126
|
const usd = reqData.response?.details?.usd;
|
|
3102
3127
|
if (usd != null) {
|
|
3103
3128
|
metricsAcc.setActualSttCost(usd);
|
|
3104
|
-
getLogger().
|
|
3129
|
+
getLogger().debug(`Deepgram actual cost: $${usd}`);
|
|
3105
3130
|
}
|
|
3106
3131
|
}
|
|
3107
3132
|
}
|
|
@@ -3221,7 +3246,7 @@ function buildAIAdapter(config, agent, resolvedPrompt) {
|
|
|
3221
3246
|
return new ElevenLabsConvAIAdapter(
|
|
3222
3247
|
engine.apiKey,
|
|
3223
3248
|
engine.agentId,
|
|
3224
|
-
agent.voice ?? "
|
|
3249
|
+
agent.voice ?? "EXAVITQu4vr4xnSDxMaL",
|
|
3225
3250
|
"eleven_turbo_v2_5",
|
|
3226
3251
|
agent.language ?? "en",
|
|
3227
3252
|
agent.firstMessage ?? ""
|
|
@@ -3524,14 +3549,8 @@ var EmbeddedServer = class {
|
|
|
3524
3549
|
res.json({ status: "ok", mode: "local" });
|
|
3525
3550
|
});
|
|
3526
3551
|
if (this.dashboard) {
|
|
3527
|
-
if (!this.dashboardToken) {
|
|
3528
|
-
getLogger().warn(
|
|
3529
|
-
"Dashboard is enabled without authentication. Set dashboardToken to protect call data. This is safe for local development but should not be exposed on a public network."
|
|
3530
|
-
);
|
|
3531
|
-
}
|
|
3532
3552
|
mountDashboard(app, this.metricsStore, this.dashboardToken);
|
|
3533
3553
|
mountApi(app, this.metricsStore, this.dashboardToken);
|
|
3534
|
-
getLogger().info("Dashboard: http://127.0.0.1:" + port + "/");
|
|
3535
3554
|
}
|
|
3536
3555
|
app.post("/webhooks/twilio/status", (req, res) => {
|
|
3537
3556
|
if (this.config.twilioToken) {
|
|
@@ -3748,7 +3767,6 @@ var EmbeddedServer = class {
|
|
|
3748
3767
|
socket.destroy();
|
|
3749
3768
|
return;
|
|
3750
3769
|
}
|
|
3751
|
-
getLogger().info(`Upgrade request: ${req.url}`);
|
|
3752
3770
|
this.wss.handleUpgrade(req, socket, head, (ws) => {
|
|
3753
3771
|
wsConnectionsByIp.set(remoteIp, (wsConnectionsByIp.get(remoteIp) ?? 0) + 1);
|
|
3754
3772
|
ws.once("close", () => {
|
|
@@ -3764,7 +3782,6 @@ var EmbeddedServer = class {
|
|
|
3764
3782
|
});
|
|
3765
3783
|
this.wss.on("connection", (ws, req) => {
|
|
3766
3784
|
const url = new URL(req.url ?? "", `http://localhost`);
|
|
3767
|
-
getLogger().info(`WebSocket connected: ${req.url}`);
|
|
3768
3785
|
this.activeConnections.add(ws);
|
|
3769
3786
|
ws.once("close", () => {
|
|
3770
3787
|
this.activeConnections.delete(ws);
|
|
@@ -3778,19 +3795,19 @@ var EmbeddedServer = class {
|
|
|
3778
3795
|
});
|
|
3779
3796
|
await new Promise((resolve) => {
|
|
3780
3797
|
this.server.listen(port, "127.0.0.1", () => {
|
|
3781
|
-
getLogger().info(`
|
|
3782
|
-
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
3783
|
-
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
3784
|
-
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
3785
|
-
\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
3786
|
-
\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
3787
|
-
\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
|
|
3788
|
-
|
|
3789
|
-
Connect AI agents to phone numbers in 4 lines of code
|
|
3790
|
-
`);
|
|
3791
3798
|
getLogger().info(`Server on port ${port}`);
|
|
3792
3799
|
getLogger().info(`Webhook: https://${this.config.webhookUrl}`);
|
|
3793
|
-
getLogger().info(`Phone:
|
|
3800
|
+
getLogger().info(`Phone: ${this.config.phoneNumber}`);
|
|
3801
|
+
if (this.dashboard) {
|
|
3802
|
+
console.log("\n\u2500\u2500\u2500\u2500 Dashboard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
3803
|
+
getLogger().info(`URL: http://127.0.0.1:${port}/`);
|
|
3804
|
+
if (!this.dashboardToken) {
|
|
3805
|
+
getLogger().warn(
|
|
3806
|
+
"Dashboard is enabled without authentication. Set dashboardToken to protect call data. This is safe for local development but should not be exposed on a public network."
|
|
3807
|
+
);
|
|
3808
|
+
}
|
|
3809
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
3810
|
+
}
|
|
3794
3811
|
resolve();
|
|
3795
3812
|
});
|
|
3796
3813
|
});
|
|
@@ -3836,7 +3853,6 @@ Connect AI agents to phone numbers in 4 lines of code
|
|
|
3836
3853
|
return;
|
|
3837
3854
|
}
|
|
3838
3855
|
const event = data.event;
|
|
3839
|
-
getLogger().info(`WS event: ${event}`);
|
|
3840
3856
|
if (event === "start") {
|
|
3841
3857
|
handler.setStreamSid(data.streamSid ?? "");
|
|
3842
3858
|
const callSid = data.start?.callSid ?? "";
|
|
@@ -3882,7 +3898,6 @@ Connect AI agents to phone numbers in 4 lines of code
|
|
|
3882
3898
|
}
|
|
3883
3899
|
const event = data.event ?? "";
|
|
3884
3900
|
if (event === "connected") return;
|
|
3885
|
-
getLogger().info(`Telnyx event: ${event}`);
|
|
3886
3901
|
if (event === "start" && !streamStarted) {
|
|
3887
3902
|
streamStarted = true;
|
|
3888
3903
|
const callControlId = data.start?.call_control_id ?? "";
|
|
@@ -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.'
|
package/dist/cli.js
CHANGED
|
@@ -1143,7 +1143,7 @@ function getLogger() {
|
|
|
1143
1143
|
return currentLogger;
|
|
1144
1144
|
}
|
|
1145
1145
|
|
|
1146
|
-
// src/
|
|
1146
|
+
// src/banner.ts
|
|
1147
1147
|
var BANNER = `
|
|
1148
1148
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
1149
1149
|
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
@@ -1154,6 +1154,11 @@ var BANNER = `
|
|
|
1154
1154
|
|
|
1155
1155
|
Connect AI agents to phone numbers in 4 lines of code
|
|
1156
1156
|
`;
|
|
1157
|
+
function showBanner() {
|
|
1158
|
+
console.log("\n" + BANNER);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// src/cli.ts
|
|
1157
1162
|
function parseArgs(argv) {
|
|
1158
1163
|
const args = argv.slice(2);
|
|
1159
1164
|
let port = 8e3;
|
|
@@ -1176,7 +1181,7 @@ async function main() {
|
|
|
1176
1181
|
process.exit(command ? 1 : 0);
|
|
1177
1182
|
}
|
|
1178
1183
|
const { port } = parseArgs(process.argv);
|
|
1179
|
-
|
|
1184
|
+
showBanner();
|
|
1180
1185
|
const store = new MetricsStore();
|
|
1181
1186
|
console.log(` Dashboard: http://localhost:${port}/`);
|
|
1182
1187
|
console.log(` API: http://localhost:${port}/api/v1/calls`);
|