getpatter 0.6.2 → 0.6.3
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 +1 -1
- package/dist/{carrier-config-4ZKVYAWV.mjs → carrier-config-3WDQXP5J.mjs} +43 -1
- package/dist/{chunk-LE63CSOB.mjs → chunk-Z6W5XFWS.mjs} +701 -35
- package/dist/index.d.mts +3599 -3381
- package/dist/index.d.ts +3599 -3381
- package/dist/index.js +1033 -191
- package/dist/index.mjs +262 -145
- package/dist/{test-mode-RS57BDM6.mjs → test-mode-MDBQ4ECE.mjs} +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
AuthenticationError,
|
|
6
6
|
CallMetricsAccumulator,
|
|
7
|
+
Carrier,
|
|
7
8
|
DEFAULT_MIN_SENTENCE_LEN,
|
|
8
9
|
DEFAULT_PRICING,
|
|
9
10
|
DeepgramModel,
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
PatterConnectionError,
|
|
22
23
|
PatterError,
|
|
23
24
|
PipelineHookExecutor,
|
|
25
|
+
PlivoAdapter,
|
|
24
26
|
PricingUnit,
|
|
25
27
|
ProvisionError,
|
|
26
28
|
RateLimitError,
|
|
@@ -52,7 +54,7 @@ import {
|
|
|
52
54
|
mountDashboard,
|
|
53
55
|
resolveLogRoot,
|
|
54
56
|
startSpan
|
|
55
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-Z6W5XFWS.mjs";
|
|
56
58
|
import {
|
|
57
59
|
OpenAIRealtime2Adapter,
|
|
58
60
|
OpenAIRealtimeAdapter,
|
|
@@ -799,7 +801,7 @@ var Patter = class {
|
|
|
799
801
|
}
|
|
800
802
|
if (!options.carrier) {
|
|
801
803
|
throw new Error(
|
|
802
|
-
"Local mode requires a `carrier` instance. Pass `carrier: new Twilio({...})` or `carrier: new
|
|
804
|
+
"Local mode requires a `carrier` instance. Pass `carrier: new Twilio({...})`, `carrier: new Telnyx({...})` or `carrier: new Plivo({...})`."
|
|
803
805
|
);
|
|
804
806
|
}
|
|
805
807
|
const carrier = options.carrier;
|
|
@@ -977,16 +979,18 @@ var Patter = class {
|
|
|
977
979
|
throw err;
|
|
978
980
|
}
|
|
979
981
|
const carrier = this.localConfig.carrier;
|
|
980
|
-
const telephonyProvider = carrier.kind
|
|
982
|
+
const telephonyProvider = carrier.kind;
|
|
981
983
|
const wantsCarrierManagement = opts.manageWebhook !== false || wantsCloudflared;
|
|
982
984
|
if (wantsCarrierManagement) {
|
|
983
|
-
const { autoConfigureCarrier } = await import("./carrier-config-
|
|
985
|
+
const { autoConfigureCarrier } = await import("./carrier-config-3WDQXP5J.mjs");
|
|
984
986
|
await autoConfigureCarrier({
|
|
985
987
|
telephonyProvider,
|
|
986
988
|
twilioSid: carrier.kind === "twilio" ? carrier.accountSid : void 0,
|
|
987
989
|
twilioToken: carrier.kind === "twilio" ? carrier.authToken : void 0,
|
|
988
990
|
telnyxKey: carrier.kind === "telnyx" ? carrier.apiKey : void 0,
|
|
989
991
|
telnyxConnectionId: carrier.kind === "telnyx" ? carrier.connectionId : void 0,
|
|
992
|
+
plivoAuthId: carrier.kind === "plivo" ? carrier.authId : void 0,
|
|
993
|
+
plivoAuthToken: carrier.kind === "plivo" ? carrier.authToken : void 0,
|
|
990
994
|
phoneNumber: this.localConfig.phoneNumber,
|
|
991
995
|
webhookHost: webhookUrl
|
|
992
996
|
});
|
|
@@ -1002,6 +1006,8 @@ var Patter = class {
|
|
|
1002
1006
|
telnyxKey: carrier.kind === "telnyx" ? carrier.apiKey : void 0,
|
|
1003
1007
|
telnyxConnectionId: carrier.kind === "telnyx" ? carrier.connectionId : void 0,
|
|
1004
1008
|
telnyxPublicKey: carrier.kind === "telnyx" ? carrier.publicKey : void 0,
|
|
1009
|
+
plivoAuthId: carrier.kind === "plivo" ? carrier.authId : void 0,
|
|
1010
|
+
plivoAuthToken: carrier.kind === "plivo" ? carrier.authToken : void 0,
|
|
1005
1011
|
persistRoot: this.localConfig.persistRoot
|
|
1006
1012
|
},
|
|
1007
1013
|
opts.agent,
|
|
@@ -1033,7 +1039,7 @@ var Patter = class {
|
|
|
1033
1039
|
}
|
|
1034
1040
|
/** Run the agent in interactive terminal-test mode (no real telephony). */
|
|
1035
1041
|
async test(opts) {
|
|
1036
|
-
const { TestSession: TestSession2 } = await import("./test-mode-
|
|
1042
|
+
const { TestSession: TestSession2 } = await import("./test-mode-MDBQ4ECE.mjs");
|
|
1037
1043
|
const session = new TestSession2();
|
|
1038
1044
|
await session.run({
|
|
1039
1045
|
agent: opts.agent,
|
|
@@ -1413,7 +1419,15 @@ var Patter = class {
|
|
|
1413
1419
|
this.prewarmTtlTimers.set(callId, handle);
|
|
1414
1420
|
});
|
|
1415
1421
|
}
|
|
1416
|
-
/**
|
|
1422
|
+
/**
|
|
1423
|
+
* Place an outbound call via the configured carrier.
|
|
1424
|
+
*
|
|
1425
|
+
* With `wait: false` (default) this resolves to `void` the instant the
|
|
1426
|
+
* carrier accepts the dial (fire-and-forget). With `wait: true` it blocks
|
|
1427
|
+
* until the call reaches a terminal state and resolves to a
|
|
1428
|
+
* {@link CallResult} — see {@link LocalCallOptions.wait}. Mirrors Python's
|
|
1429
|
+
* `Patter.call(..., wait=False)`.
|
|
1430
|
+
*/
|
|
1417
1431
|
async call(options) {
|
|
1418
1432
|
if (!options.to) {
|
|
1419
1433
|
throw new Error("'to' phone number is required");
|
|
@@ -1421,7 +1435,13 @@ var Patter = class {
|
|
|
1421
1435
|
if (!options.to.startsWith("+")) {
|
|
1422
1436
|
throw new Error(`'to' must be in E.164 format (e.g., '+1234567890'). Got: '${options.to}'`);
|
|
1423
1437
|
}
|
|
1438
|
+
if (options.wait && !this.embeddedServer) {
|
|
1439
|
+
throw new PatterConnectionError(
|
|
1440
|
+
"call({ wait: true }) requires an active server to receive the carrier completion webhooks. Call `await phone.serve(...)` first, or use `await using phone = new Patter(...)` (and serve inside the block) which keeps the server up for the duration of the block."
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1424
1443
|
const { phoneNumber, webhookUrl, carrier } = this.localConfig;
|
|
1444
|
+
let callId = "";
|
|
1425
1445
|
const effectiveRingTimeout = options.ringTimeout === void 0 ? 25 : options.ringTimeout;
|
|
1426
1446
|
const wantsAmd = options.machineDetection !== false || Boolean(options.voicemailMessage);
|
|
1427
1447
|
if (this.embeddedServer) {
|
|
@@ -1479,11 +1499,74 @@ var Patter = class {
|
|
|
1479
1499
|
}
|
|
1480
1500
|
}
|
|
1481
1501
|
if (telnyxCallId) {
|
|
1502
|
+
callId = telnyxCallId;
|
|
1482
1503
|
this.spawnPrewarmFirstMessage(options.agent, telnyxCallId, effectiveRingTimeout, "telnyx");
|
|
1483
1504
|
if (options.agent.prewarm !== false) {
|
|
1484
1505
|
this.parkProviderConnections(options.agent, telnyxCallId);
|
|
1485
1506
|
}
|
|
1486
1507
|
}
|
|
1508
|
+
return this.maybeAwaitCompletion(options, callId, effectiveRingTimeout);
|
|
1509
|
+
}
|
|
1510
|
+
if (carrier.kind === "plivo") {
|
|
1511
|
+
const auth = `Basic ${Buffer.from(`${carrier.authId}:${carrier.authToken}`).toString("base64")}`;
|
|
1512
|
+
const plivoPayload = {
|
|
1513
|
+
from: phoneNumber,
|
|
1514
|
+
to: options.to,
|
|
1515
|
+
answer_url: `https://${webhookUrl}/webhooks/plivo/voice`,
|
|
1516
|
+
answer_method: "POST",
|
|
1517
|
+
// hangup_url is Plivo's StatusCallback analogue — without it the
|
|
1518
|
+
// /webhooks/plivo/status route never fires for outbound calls and
|
|
1519
|
+
// the dashboard misses no-answer / busy / failed.
|
|
1520
|
+
hangup_url: `https://${webhookUrl}/webhooks/plivo/status`,
|
|
1521
|
+
hangup_method: "POST"
|
|
1522
|
+
};
|
|
1523
|
+
if (effectiveRingTimeout !== null && effectiveRingTimeout !== void 0) {
|
|
1524
|
+
plivoPayload.ring_timeout = Math.max(1, Math.floor(effectiveRingTimeout));
|
|
1525
|
+
}
|
|
1526
|
+
if (wantsAmd) {
|
|
1527
|
+
plivoPayload.machine_detection = "true";
|
|
1528
|
+
plivoPayload.machine_detection_time = 5e3;
|
|
1529
|
+
plivoPayload.machine_detection_url = `https://${webhookUrl}/webhooks/plivo/amd`;
|
|
1530
|
+
plivoPayload.machine_detection_method = "POST";
|
|
1531
|
+
}
|
|
1532
|
+
if (options.voicemailMessage && this.embeddedServer) {
|
|
1533
|
+
this.embeddedServer.voicemailMessage = options.voicemailMessage;
|
|
1534
|
+
}
|
|
1535
|
+
const response2 = await fetch(`https://api.plivo.com/v1/Account/${carrier.authId}/Call/`, {
|
|
1536
|
+
method: "POST",
|
|
1537
|
+
headers: { "Content-Type": "application/json", Authorization: auth },
|
|
1538
|
+
body: JSON.stringify(plivoPayload)
|
|
1539
|
+
});
|
|
1540
|
+
if (!response2.ok) {
|
|
1541
|
+
throw new ProvisionError(`Failed to initiate Plivo call: ${await response2.text()}`);
|
|
1542
|
+
}
|
|
1543
|
+
let plivoCallId;
|
|
1544
|
+
try {
|
|
1545
|
+
const body = await response2.clone().json();
|
|
1546
|
+
plivoCallId = body.request_uuid;
|
|
1547
|
+
} catch {
|
|
1548
|
+
}
|
|
1549
|
+
if (plivoCallId) {
|
|
1550
|
+
const initiatedPayload = {
|
|
1551
|
+
call_id: plivoCallId,
|
|
1552
|
+
caller: phoneNumber,
|
|
1553
|
+
callee: options.to,
|
|
1554
|
+
direction: "outbound",
|
|
1555
|
+
status: "initiated"
|
|
1556
|
+
};
|
|
1557
|
+
if (this.embeddedServer) {
|
|
1558
|
+
this.embeddedServer.metricsStore.recordCallInitiated(initiatedPayload);
|
|
1559
|
+
}
|
|
1560
|
+
try {
|
|
1561
|
+
const { notifyDashboard: notifyDashboard2 } = await import("./persistence-LVIAHESK.mjs");
|
|
1562
|
+
notifyDashboard2(initiatedPayload);
|
|
1563
|
+
} catch {
|
|
1564
|
+
}
|
|
1565
|
+
this.spawnPrewarmFirstMessage(options.agent, plivoCallId, effectiveRingTimeout, "plivo");
|
|
1566
|
+
if (options.agent.prewarm !== false) {
|
|
1567
|
+
this.parkProviderConnections(options.agent, plivoCallId);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1487
1570
|
return;
|
|
1488
1571
|
}
|
|
1489
1572
|
const twilioSid = carrier.accountSid;
|
|
@@ -1555,11 +1638,53 @@ var Patter = class {
|
|
|
1555
1638
|
}
|
|
1556
1639
|
}
|
|
1557
1640
|
if (twilioCallSid) {
|
|
1641
|
+
callId = twilioCallSid;
|
|
1558
1642
|
this.spawnPrewarmFirstMessage(options.agent, twilioCallSid, effectiveRingTimeout, "twilio");
|
|
1559
1643
|
if (options.agent.prewarm !== false) {
|
|
1560
1644
|
this.parkProviderConnections(options.agent, twilioCallSid);
|
|
1561
1645
|
}
|
|
1562
1646
|
}
|
|
1647
|
+
return this.maybeAwaitCompletion(options, callId, effectiveRingTimeout);
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* When `options.wait` is set, register a completion promise keyed by the
|
|
1651
|
+
* carrier-issued `callId` and await it (bounded by a backstop timeout).
|
|
1652
|
+
* Otherwise resolve to `void` immediately (fire-and-forget).
|
|
1653
|
+
*
|
|
1654
|
+
* The registration happens here — after the carrier accepted the dial and
|
|
1655
|
+
* issued the id — so the future correlates to the right call. The race
|
|
1656
|
+
* window between `initiateCall` returning and this registration is
|
|
1657
|
+
* harmless: the callee is still ringing, so no terminal signal can fire
|
|
1658
|
+
* before we register. Mirrors the Python `call(wait=True)` tail block.
|
|
1659
|
+
*/
|
|
1660
|
+
async maybeAwaitCompletion(options, callId, ringTimeout) {
|
|
1661
|
+
if (!options.wait) return;
|
|
1662
|
+
const server = this.embeddedServer;
|
|
1663
|
+
if (!server || !callId) {
|
|
1664
|
+
throw new PatterConnectionError(
|
|
1665
|
+
"call({ wait: true }): no active server or carrier call id."
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
const completion = server.registerCompletion(callId);
|
|
1669
|
+
const backstopMs = ((ringTimeout ?? 25) + 1800) * 1e3;
|
|
1670
|
+
let timer;
|
|
1671
|
+
const backstop = new Promise((_resolve, reject) => {
|
|
1672
|
+
timer = setTimeout(() => {
|
|
1673
|
+
server.deleteCompletion(callId);
|
|
1674
|
+
reject(
|
|
1675
|
+
new PatterConnectionError(
|
|
1676
|
+
`call({ wait: true }): no terminal signal for call ${callId} within ${(backstopMs / 1e3).toFixed(0)}s`,
|
|
1677
|
+
{ code: ErrorCode.TIMEOUT }
|
|
1678
|
+
)
|
|
1679
|
+
);
|
|
1680
|
+
}, backstopMs);
|
|
1681
|
+
timer.unref?.();
|
|
1682
|
+
});
|
|
1683
|
+
try {
|
|
1684
|
+
return await Promise.race([completion, backstop]);
|
|
1685
|
+
} finally {
|
|
1686
|
+
if (timer) clearTimeout(timer);
|
|
1687
|
+
}
|
|
1563
1688
|
}
|
|
1564
1689
|
/**
|
|
1565
1690
|
* Stop the embedded server and any running tunnel. Safe to call multiple
|
|
@@ -1600,6 +1725,11 @@ var Patter = class {
|
|
|
1600
1725
|
this.tunnelHandle = null;
|
|
1601
1726
|
}
|
|
1602
1727
|
if (this.embeddedServer) {
|
|
1728
|
+
this.embeddedServer.failPendingCompletions(
|
|
1729
|
+
new PatterConnectionError(
|
|
1730
|
+
"Patter.disconnect() called while a call({ wait: true }) was still in flight."
|
|
1731
|
+
)
|
|
1732
|
+
);
|
|
1603
1733
|
await this.embeddedServer.stop();
|
|
1604
1734
|
this.embeddedServer = null;
|
|
1605
1735
|
}
|
|
@@ -1623,6 +1753,30 @@ var Patter = class {
|
|
|
1623
1753
|
this._ready.catch(() => {
|
|
1624
1754
|
});
|
|
1625
1755
|
}
|
|
1756
|
+
/**
|
|
1757
|
+
* Explicit-resource-management disposer so callers can write
|
|
1758
|
+
* ``await using phone = new Patter(...)`` and have {@link disconnect} run
|
|
1759
|
+
* automatically when the block exits — on the normal path AND when the
|
|
1760
|
+
* body throws. This guarantees the embedded server, any auto-started
|
|
1761
|
+
* tunnel, and in-flight prewarm/TTS work are torn down so a still-running
|
|
1762
|
+
* TTS WebSocket cannot keep the user billed after the block ends, and any
|
|
1763
|
+
* in-flight ``call({ wait: true })`` awaiter is failed rather than left
|
|
1764
|
+
* hanging. ``disconnect()`` is idempotent, so an explicit ``disconnect()``
|
|
1765
|
+
* inside the block is still safe. Mirrors Python's ``async with Patter(...)``.
|
|
1766
|
+
*
|
|
1767
|
+
* Note: this does NOT start the server (``serve()`` blocks until shutdown,
|
|
1768
|
+
* so it cannot run from a disposer) — call ``serve(...)`` inside the block:
|
|
1769
|
+
*
|
|
1770
|
+
* ```ts
|
|
1771
|
+
* await using phone = new Patter({ carrier: new Twilio(), phoneNumber: "+1555..." });
|
|
1772
|
+
* await phone.serve({ agent }); // inbound, or
|
|
1773
|
+
* const result = await phone.call({ to: "+1555...", agent, wait: true });
|
|
1774
|
+
* // disconnect() has run here — nothing left running.
|
|
1775
|
+
* ```
|
|
1776
|
+
*/
|
|
1777
|
+
async [Symbol.asyncDispose]() {
|
|
1778
|
+
await this.disconnect();
|
|
1779
|
+
}
|
|
1626
1780
|
/**
|
|
1627
1781
|
* Terminate an active call on the configured carrier.
|
|
1628
1782
|
*
|
|
@@ -1677,6 +1831,17 @@ var Patter = class {
|
|
|
1677
1831
|
}
|
|
1678
1832
|
return;
|
|
1679
1833
|
}
|
|
1834
|
+
if (carrier.kind === "plivo") {
|
|
1835
|
+
const auth = Buffer.from(`${carrier.authId}:${carrier.authToken}`).toString("base64");
|
|
1836
|
+
const res = await fetch(
|
|
1837
|
+
`https://api.plivo.com/v1/Account/${carrier.authId}/Call/${encodeURIComponent(callSid)}/`,
|
|
1838
|
+
{ method: "DELETE", headers: { Authorization: `Basic ${auth}` } }
|
|
1839
|
+
);
|
|
1840
|
+
if (!res.ok && res.status !== 404) {
|
|
1841
|
+
throw new Error(`Plivo hangup failed: ${res.status} ${await res.text()}`);
|
|
1842
|
+
}
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1680
1845
|
throw new Error(`endCall() requires a configured carrier; got kind=${carrier.kind}`);
|
|
1681
1846
|
}
|
|
1682
1847
|
};
|
|
@@ -2074,7 +2239,6 @@ init_esm_shims();
|
|
|
2074
2239
|
|
|
2075
2240
|
// src/integrations/patter-tool.ts
|
|
2076
2241
|
init_esm_shims();
|
|
2077
|
-
import { EventEmitter } from "events";
|
|
2078
2242
|
var PARAMETERS_SCHEMA = {
|
|
2079
2243
|
type: "object",
|
|
2080
2244
|
properties: {
|
|
@@ -2101,7 +2265,7 @@ var PARAMETERS_SCHEMA = {
|
|
|
2101
2265
|
};
|
|
2102
2266
|
var DEFAULT_NAME = "make_phone_call";
|
|
2103
2267
|
var DEFAULT_DESCRIPTION = "Place a real outbound phone call. Returns a JSON object with the full transcript, call status, duration in seconds, and cost. Use this when the user asks you to call someone, schedule appointments by phone, or otherwise reach a human via voice.";
|
|
2104
|
-
var PatterTool = class
|
|
2268
|
+
var PatterTool = class {
|
|
2105
2269
|
name;
|
|
2106
2270
|
description;
|
|
2107
2271
|
phone;
|
|
@@ -2109,24 +2273,6 @@ var PatterTool = class _PatterTool {
|
|
|
2109
2273
|
maxDurationSec;
|
|
2110
2274
|
recording;
|
|
2111
2275
|
started = false;
|
|
2112
|
-
/** Resolver for the next `call_initiated` SSE event. Only set inside the
|
|
2113
|
-
* dial mutex (`dialQueue`), so two parallel `execute()` calls never share
|
|
2114
|
-
* it and never lose a dispatch. */
|
|
2115
|
-
pendingDial = null;
|
|
2116
|
-
/** Mutex that serializes the dial → call_id capture critical section.
|
|
2117
|
-
* Each `execute()` chains a continuation onto this promise so the
|
|
2118
|
-
* `pendingDial` slot is owned by exactly one caller at a time. */
|
|
2119
|
-
dialQueue = Promise.resolve();
|
|
2120
|
-
/** Captured SSE listener so `stop()` can detach it (prevents leaks when
|
|
2121
|
-
* the underlying Patter instance outlives this tool). */
|
|
2122
|
-
sseListener = null;
|
|
2123
|
-
/** Captured Patter metrics store, for cleanup in `stop()`. */
|
|
2124
|
-
metricsStoreRef = null;
|
|
2125
|
-
/** call_id → pending promise machinery. */
|
|
2126
|
-
pending = /* @__PURE__ */ new Map();
|
|
2127
|
-
bus = new EventEmitter();
|
|
2128
|
-
/** How long to wait for the `call_initiated` SSE before failing the dial. */
|
|
2129
|
-
static DIAL_CAPTURE_TIMEOUT_MS = 1e4;
|
|
2130
2276
|
constructor(opts) {
|
|
2131
2277
|
if (!opts.phone) {
|
|
2132
2278
|
throw new Error("PatterTool: `phone` (a Patter instance) is required.");
|
|
@@ -2170,7 +2316,15 @@ var PatterTool = class _PatterTool {
|
|
|
2170
2316
|
};
|
|
2171
2317
|
}
|
|
2172
2318
|
// --- Lifecycle ----------------------------------------------------------
|
|
2173
|
-
/**
|
|
2319
|
+
/**
|
|
2320
|
+
* Start the underlying Patter server. Idempotent.
|
|
2321
|
+
*
|
|
2322
|
+
* `execute()` relies on `Patter.call({ wait: true })`, which requires an
|
|
2323
|
+
* active server to receive the carrier completion webhooks — that's what
|
|
2324
|
+
* `serve()` provides here. No `onCallEnd` callback is wired: the SDK's own
|
|
2325
|
+
* per-callId completion registry resolves the result, so the user's
|
|
2326
|
+
* `onCallEnd` slot is left free.
|
|
2327
|
+
*/
|
|
2174
2328
|
async start() {
|
|
2175
2329
|
if (this.started) return;
|
|
2176
2330
|
if (!this.agent) {
|
|
@@ -2182,52 +2336,31 @@ var PatterTool = class _PatterTool {
|
|
|
2182
2336
|
await this.phone.serve({
|
|
2183
2337
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2184
2338
|
agent: builtAgent,
|
|
2185
|
-
recording: this.recording
|
|
2186
|
-
onCallEnd: this.onCallEndHandler.bind(this)
|
|
2339
|
+
recording: this.recording
|
|
2187
2340
|
});
|
|
2188
|
-
const store = this.phone.metricsStore;
|
|
2189
|
-
if (!store) {
|
|
2190
|
-
throw new Error(
|
|
2191
|
-
"PatterTool.start: phone.metricsStore is null after serve() \u2014 is the dashboard disabled?"
|
|
2192
|
-
);
|
|
2193
|
-
}
|
|
2194
|
-
const listener = (event) => {
|
|
2195
|
-
if (event.type === "call_initiated" && this.pendingDial) {
|
|
2196
|
-
const callId = event.data.call_id || "";
|
|
2197
|
-
if (callId) {
|
|
2198
|
-
const dispatch = this.pendingDial;
|
|
2199
|
-
this.pendingDial = null;
|
|
2200
|
-
dispatch(callId);
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
};
|
|
2204
|
-
store.on("sse", listener);
|
|
2205
|
-
this.sseListener = listener;
|
|
2206
|
-
this.metricsStoreRef = store;
|
|
2207
2341
|
this.started = true;
|
|
2208
2342
|
}
|
|
2209
|
-
/**
|
|
2343
|
+
/** Best-effort shutdown — tear the Patter server down via `disconnect()`. */
|
|
2210
2344
|
async stop() {
|
|
2211
2345
|
if (!this.started) return;
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
for (const [, p] of this.pending) {
|
|
2219
|
-
clearTimeout(p.timer);
|
|
2220
|
-
p.reject(new Error("PatterTool: shutdown while call pending"));
|
|
2221
|
-
}
|
|
2222
|
-
this.pending.clear();
|
|
2223
|
-
const stoppable = this.phone;
|
|
2224
|
-
if (typeof stoppable.stop === "function") {
|
|
2225
|
-
await stoppable.stop();
|
|
2346
|
+
const disconnectable = this.phone;
|
|
2347
|
+
if (typeof disconnectable.disconnect === "function") {
|
|
2348
|
+
try {
|
|
2349
|
+
await disconnectable.disconnect();
|
|
2350
|
+
} catch {
|
|
2351
|
+
}
|
|
2226
2352
|
}
|
|
2227
2353
|
this.started = false;
|
|
2228
2354
|
}
|
|
2229
2355
|
// --- Execution ----------------------------------------------------------
|
|
2230
|
-
/**
|
|
2356
|
+
/**
|
|
2357
|
+
* Dial outbound, wait for the call to end, return a structured result.
|
|
2358
|
+
*
|
|
2359
|
+
* Thin wrapper over `Patter.call({ wait: true })`: the SDK now owns the
|
|
2360
|
+
* dial → callId → terminal-signal correlation, so this just bounds the wait
|
|
2361
|
+
* with `max_duration_sec` and maps the {@link CallResult} into the tool's
|
|
2362
|
+
* public envelope. Mirrors Python's `PatterTool.execute`.
|
|
2363
|
+
*/
|
|
2231
2364
|
async execute(args) {
|
|
2232
2365
|
if (!this.started) await this.start();
|
|
2233
2366
|
if (!args || typeof args.to !== "string" || !args.to.startsWith("+")) {
|
|
@@ -2243,55 +2376,32 @@ var PatterTool = class _PatterTool {
|
|
|
2243
2376
|
...args.goal !== void 0 ? { systemPrompt: args.goal } : {},
|
|
2244
2377
|
...args.first_message !== void 0 ? { firstMessage: args.first_message } : {}
|
|
2245
2378
|
});
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2379
|
+
let timer;
|
|
2380
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
2381
|
+
timer = setTimeout(() => {
|
|
2382
|
+
reject(
|
|
2383
|
+
new Error(
|
|
2384
|
+
`PatterTool.execute: call to ${args.to} exceeded ${timeoutSec}s timeout`
|
|
2385
|
+
)
|
|
2386
|
+
);
|
|
2251
2387
|
}, timeoutSec * 1e3);
|
|
2252
|
-
|
|
2253
|
-
resolve,
|
|
2254
|
-
reject,
|
|
2255
|
-
timer,
|
|
2256
|
-
startedAt: Date.now() / 1e3
|
|
2257
|
-
});
|
|
2258
|
-
});
|
|
2259
|
-
}
|
|
2260
|
-
/** Issue the outbound dial under the mutex and return its assigned call_id. */
|
|
2261
|
-
async acquireCallId(to, agent) {
|
|
2262
|
-
let release;
|
|
2263
|
-
const slot = new Promise((r) => {
|
|
2264
|
-
release = r;
|
|
2388
|
+
timer.unref?.();
|
|
2265
2389
|
});
|
|
2266
|
-
|
|
2267
|
-
this.dialQueue = previous.then(() => slot);
|
|
2268
|
-
await previous;
|
|
2269
|
-
let captureTimer = null;
|
|
2390
|
+
let result;
|
|
2270
2391
|
try {
|
|
2271
|
-
|
|
2272
|
-
this.
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
}, _PatterTool.DIAL_CAPTURE_TIMEOUT_MS);
|
|
2281
|
-
});
|
|
2282
|
-
await this.phone.call({
|
|
2283
|
-
to,
|
|
2284
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2285
|
-
agent
|
|
2286
|
-
});
|
|
2287
|
-
const callId = await callIdPromise;
|
|
2288
|
-
if (captureTimer) clearTimeout(captureTimer);
|
|
2289
|
-
return callId;
|
|
2392
|
+
result = await Promise.race([
|
|
2393
|
+
this.phone.call({
|
|
2394
|
+
to: args.to,
|
|
2395
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2396
|
+
agent: overrideAgent,
|
|
2397
|
+
wait: true
|
|
2398
|
+
}),
|
|
2399
|
+
timeout
|
|
2400
|
+
]);
|
|
2290
2401
|
} finally {
|
|
2291
|
-
if (
|
|
2292
|
-
this.pendingDial = null;
|
|
2293
|
-
release();
|
|
2402
|
+
if (timer) clearTimeout(timer);
|
|
2294
2403
|
}
|
|
2404
|
+
return resultFromCallResult(result);
|
|
2295
2405
|
}
|
|
2296
2406
|
/**
|
|
2297
2407
|
* Hermes-style handler: `(args, kwargs) => Promise<string>` returning a JSON
|
|
@@ -2309,32 +2419,32 @@ var PatterTool = class _PatterTool {
|
|
|
2309
2419
|
}
|
|
2310
2420
|
};
|
|
2311
2421
|
}
|
|
2312
|
-
// --- Internal: onCallEnd dispatcher -------------------------------------
|
|
2313
|
-
async onCallEndHandler(data) {
|
|
2314
|
-
const callId = data.call_id || "";
|
|
2315
|
-
if (!callId) return;
|
|
2316
|
-
const pending = this.pending.get(callId);
|
|
2317
|
-
if (!pending) {
|
|
2318
|
-
this.bus.emit("orphan_end", { call_id: callId, data });
|
|
2319
|
-
return;
|
|
2320
|
-
}
|
|
2321
|
-
clearTimeout(pending.timer);
|
|
2322
|
-
this.pending.delete(callId);
|
|
2323
|
-
const metrics = data.metrics && typeof data.metrics === "object" ? data.metrics : null;
|
|
2324
|
-
const cost = metrics && typeof metrics.cost === "object" && metrics.cost && typeof metrics.cost.total === "number" ? metrics.cost.total : void 0;
|
|
2325
|
-
const duration = typeof metrics?.duration_seconds === "number" ? metrics?.duration_seconds : Math.max(0, Date.now() / 1e3 - pending.startedAt);
|
|
2326
|
-
const transcript = Array.isArray(data.transcript) ? data.transcript : [];
|
|
2327
|
-
const status = data.status || "completed";
|
|
2328
|
-
pending.resolve({
|
|
2329
|
-
call_id: callId,
|
|
2330
|
-
status,
|
|
2331
|
-
duration_seconds: duration,
|
|
2332
|
-
cost_usd: cost,
|
|
2333
|
-
transcript,
|
|
2334
|
-
metrics
|
|
2335
|
-
});
|
|
2336
|
-
}
|
|
2337
2422
|
};
|
|
2423
|
+
function resultFromCallResult(result) {
|
|
2424
|
+
if (!result) {
|
|
2425
|
+
return {
|
|
2426
|
+
call_id: "",
|
|
2427
|
+
status: "completed",
|
|
2428
|
+
outcome: "",
|
|
2429
|
+
duration_seconds: 0,
|
|
2430
|
+
cost_usd: void 0,
|
|
2431
|
+
transcript: [],
|
|
2432
|
+
metrics: null
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
const costTotal = result.cost?.total;
|
|
2436
|
+
const costUsd = typeof costTotal === "number" ? costTotal : void 0;
|
|
2437
|
+
const metrics = result.metrics ? result.metrics : null;
|
|
2438
|
+
return {
|
|
2439
|
+
call_id: result.callId || "",
|
|
2440
|
+
status: result.status || "completed",
|
|
2441
|
+
outcome: result.outcome || "",
|
|
2442
|
+
duration_seconds: typeof result.durationSeconds === "number" ? result.durationSeconds : 0,
|
|
2443
|
+
cost_usd: costUsd,
|
|
2444
|
+
transcript: result.transcript ? [...result.transcript] : [],
|
|
2445
|
+
metrics
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2338
2448
|
|
|
2339
2449
|
// src/providers/gemini-live.ts
|
|
2340
2450
|
init_esm_shims();
|
|
@@ -2905,6 +3015,12 @@ var ELEVENLABS_VOICE_ID_BY_NAME = {
|
|
|
2905
3015
|
alloy: "EXAVITQu4vr4xnSDxMaL"
|
|
2906
3016
|
};
|
|
2907
3017
|
var VOICE_ID_PATTERN = /^[A-Za-z0-9]{20}$/;
|
|
3018
|
+
var CARRIER_NATIVE_FORMAT = {
|
|
3019
|
+
twilio: "ulaw_8000",
|
|
3020
|
+
telnyx: "pcm_16000",
|
|
3021
|
+
// Plivo streams mulaw 8 kHz (we pin contentType in the answer XML).
|
|
3022
|
+
plivo: "ulaw_8000"
|
|
3023
|
+
};
|
|
2908
3024
|
function resolveVoiceId(voice) {
|
|
2909
3025
|
if (!voice) return voice;
|
|
2910
3026
|
if (VOICE_ID_PATTERN.test(voice)) return voice;
|
|
@@ -2993,11 +3109,8 @@ var ElevenLabsTTS = class _ElevenLabsTTS {
|
|
|
2993
3109
|
*/
|
|
2994
3110
|
setTelephonyCarrier(carrier) {
|
|
2995
3111
|
if (this._outputFormatExplicit) return;
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
} else if (carrier === "telnyx") {
|
|
2999
|
-
this._outputFormat = ElevenLabsOutputFormat.PCM_16000;
|
|
3000
|
-
}
|
|
3112
|
+
const native = CARRIER_NATIVE_FORMAT[carrier];
|
|
3113
|
+
if (native !== void 0) this._outputFormat = native;
|
|
3001
3114
|
}
|
|
3002
3115
|
/**
|
|
3003
3116
|
* Construct an instance pre-configured for Twilio Media Streams.
|
|
@@ -5215,9 +5328,11 @@ var PLAN_REQUIRED_MSG = "ElevenLabs WS streaming requires a Pro plan or higher (
|
|
|
5215
5328
|
function sanitiseLogStr(value, limit = 200) {
|
|
5216
5329
|
return String(value).replace(/[\r\n\x00]/g, " ").slice(0, limit);
|
|
5217
5330
|
}
|
|
5218
|
-
var
|
|
5331
|
+
var CARRIER_NATIVE_FORMAT2 = {
|
|
5219
5332
|
twilio: "ulaw_8000",
|
|
5220
|
-
telnyx: "pcm_16000"
|
|
5333
|
+
telnyx: "pcm_16000",
|
|
5334
|
+
// Plivo streams mulaw 8 kHz (we pin contentType in the answer XML).
|
|
5335
|
+
plivo: "ulaw_8000"
|
|
5221
5336
|
};
|
|
5222
5337
|
var ElevenLabsWebSocketTTS = class _ElevenLabsWebSocketTTS {
|
|
5223
5338
|
static providerKey = "elevenlabs_ws";
|
|
@@ -5301,7 +5416,7 @@ var ElevenLabsWebSocketTTS = class _ElevenLabsWebSocketTTS {
|
|
|
5301
5416
|
*/
|
|
5302
5417
|
setTelephonyCarrier(carrier) {
|
|
5303
5418
|
if (this._outputFormatExplicit) return;
|
|
5304
|
-
const native =
|
|
5419
|
+
const native = CARRIER_NATIVE_FORMAT2[carrier];
|
|
5305
5420
|
if (!native) return;
|
|
5306
5421
|
this._outputFormat = native;
|
|
5307
5422
|
}
|
|
@@ -7445,7 +7560,7 @@ var KrispVivaFilter = class {
|
|
|
7445
7560
|
|
|
7446
7561
|
// src/telephony/twilio.ts
|
|
7447
7562
|
init_esm_shims();
|
|
7448
|
-
var
|
|
7563
|
+
var Carrier2 = class {
|
|
7449
7564
|
kind = "twilio";
|
|
7450
7565
|
accountSid;
|
|
7451
7566
|
authToken;
|
|
@@ -7469,7 +7584,7 @@ var Carrier = class {
|
|
|
7469
7584
|
|
|
7470
7585
|
// src/telephony/telnyx.ts
|
|
7471
7586
|
init_esm_shims();
|
|
7472
|
-
var
|
|
7587
|
+
var Carrier3 = class {
|
|
7473
7588
|
kind = "telnyx";
|
|
7474
7589
|
apiKey;
|
|
7475
7590
|
connectionId;
|
|
@@ -8757,6 +8872,8 @@ export {
|
|
|
8757
8872
|
PatterTool,
|
|
8758
8873
|
PcmCarry,
|
|
8759
8874
|
PipelineHookExecutor,
|
|
8875
|
+
Carrier as Plivo,
|
|
8876
|
+
PlivoAdapter,
|
|
8760
8877
|
PricingUnit,
|
|
8761
8878
|
ProvisionError,
|
|
8762
8879
|
RateLimitError,
|
|
@@ -8783,7 +8900,7 @@ export {
|
|
|
8783
8900
|
TurnDetectionMode as SpeechmaticsTurnDetectionMode,
|
|
8784
8901
|
StatefulResampler,
|
|
8785
8902
|
Static as StaticTunnel,
|
|
8786
|
-
|
|
8903
|
+
Carrier3 as Telnyx,
|
|
8787
8904
|
TelnyxAdapter,
|
|
8788
8905
|
TelnyxSTT,
|
|
8789
8906
|
TelnyxSTTInputFormat,
|
|
@@ -8794,7 +8911,7 @@ export {
|
|
|
8794
8911
|
TestSession,
|
|
8795
8912
|
TfidfLoopDetector,
|
|
8796
8913
|
Tool,
|
|
8797
|
-
|
|
8914
|
+
Carrier2 as Twilio,
|
|
8798
8915
|
TwilioAdapter,
|
|
8799
8916
|
ULTRAVOX_DEFAULT_API_BASE,
|
|
8800
8917
|
ULTRAVOX_DEFAULT_SR,
|