getpatter 0.6.4 → 0.6.6
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/dist/{chunk-7IIV3BY4.mjs → chunk-YJX2EKON.mjs} +658 -79
- package/dist/cli.js +492 -2
- package/dist/index.d.mts +607 -6
- package/dist/index.d.ts +607 -6
- package/dist/index.js +1839 -189
- package/dist/index.mjs +1114 -70
- package/dist/{test-mode-4QLLWYVV.mjs → test-mode-XFOADUNE.mjs} +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,10 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __glob = (map) => (
|
|
9
|
-
var fn = map[
|
|
8
|
+
var __glob = (map) => (path7) => {
|
|
9
|
+
var fn = map[path7];
|
|
10
10
|
if (fn) return fn();
|
|
11
|
-
throw new Error("Module not found in bundle: " +
|
|
11
|
+
throw new Error("Module not found in bundle: " + path7);
|
|
12
12
|
};
|
|
13
13
|
var __esm = (fn, res) => function __init() {
|
|
14
14
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
@@ -118,6 +118,110 @@ var init_errors = __esm({
|
|
|
118
118
|
}
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
+
// src/telemetry/call-metrics.ts
|
|
122
|
+
function engineFromMode(mode) {
|
|
123
|
+
if (mode === "openai_realtime" || mode === "openai_realtime_2") return "realtime";
|
|
124
|
+
if (mode === "elevenlabs_convai") return "convai";
|
|
125
|
+
if (mode === "pipeline") return "pipeline";
|
|
126
|
+
return "other";
|
|
127
|
+
}
|
|
128
|
+
function providerFromMetrics(m) {
|
|
129
|
+
const mode = m.provider_mode;
|
|
130
|
+
if (mode === "openai_realtime" || mode === "openai_realtime_2") return "openai";
|
|
131
|
+
if (mode === "elevenlabs_convai") return "elevenlabs";
|
|
132
|
+
for (const key of ["llm_provider", "stt_provider", "tts_provider"]) {
|
|
133
|
+
const v = m[key];
|
|
134
|
+
if (typeof v === "string" && v) return v.toLowerCase();
|
|
135
|
+
}
|
|
136
|
+
return "other";
|
|
137
|
+
}
|
|
138
|
+
function providerFromMode(mode) {
|
|
139
|
+
if (mode === "openai_realtime" || mode === "openai_realtime_2") return "openai";
|
|
140
|
+
if (mode === "elevenlabs_convai") return "elevenlabs";
|
|
141
|
+
return "other";
|
|
142
|
+
}
|
|
143
|
+
function carrierFamily(tp) {
|
|
144
|
+
return typeof tp === "string" && tp ? tp.toLowerCase() : "none";
|
|
145
|
+
}
|
|
146
|
+
function direction(value) {
|
|
147
|
+
const v = typeof value === "string" ? value.toLowerCase() : "";
|
|
148
|
+
return v === "inbound" || v === "outbound" ? v : void 0;
|
|
149
|
+
}
|
|
150
|
+
function turnCountBucket(n) {
|
|
151
|
+
if (n <= 0) return "0";
|
|
152
|
+
if (n === 1) return "1";
|
|
153
|
+
if (n <= 3) return "2_3";
|
|
154
|
+
if (n <= 6) return "4_6";
|
|
155
|
+
if (n <= 12) return "7_12";
|
|
156
|
+
return "13_plus";
|
|
157
|
+
}
|
|
158
|
+
function latencyMs(m) {
|
|
159
|
+
const p95 = m.latency_p95;
|
|
160
|
+
if (p95 && typeof p95 === "object") {
|
|
161
|
+
return p95.agent_response_ms;
|
|
162
|
+
}
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
function recordCallStarted(telemetry, opts) {
|
|
166
|
+
if (!telemetry) return;
|
|
167
|
+
try {
|
|
168
|
+
const dims = {
|
|
169
|
+
engine: engineFromMode(opts.providerMode),
|
|
170
|
+
provider: providerFromMode(opts.providerMode),
|
|
171
|
+
carrier: carrierFamily(opts.telephonyProvider)
|
|
172
|
+
};
|
|
173
|
+
const d = direction(opts.direction);
|
|
174
|
+
if (d !== void 0) dims.direction = d;
|
|
175
|
+
telemetry.record("call_started", dims);
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function recordCallCompleted(telemetry, opts) {
|
|
180
|
+
if (!telemetry) return;
|
|
181
|
+
try {
|
|
182
|
+
const dims = { outcome: opts.outcome };
|
|
183
|
+
const d = direction(opts.direction);
|
|
184
|
+
if (d !== void 0) dims.direction = d;
|
|
185
|
+
const metrics = opts.metrics;
|
|
186
|
+
if (metrics && typeof metrics === "object") {
|
|
187
|
+
const m = metrics;
|
|
188
|
+
dims.engine = engineFromMode(m.provider_mode);
|
|
189
|
+
dims.provider = providerFromMetrics(m);
|
|
190
|
+
dims.carrier = carrierFamily(m.telephony_provider);
|
|
191
|
+
if (typeof m.duration_seconds === "number") {
|
|
192
|
+
dims.duration_seconds = Math.max(0, Math.round(m.duration_seconds));
|
|
193
|
+
}
|
|
194
|
+
const lat = latencyMs(m);
|
|
195
|
+
if (typeof lat === "number") dims.latency_ms = Math.max(0, Math.round(lat));
|
|
196
|
+
const cost = m.cost;
|
|
197
|
+
if (cost && typeof cost === "object") {
|
|
198
|
+
const total = cost.total;
|
|
199
|
+
if (typeof total === "number" && Number.isFinite(total)) {
|
|
200
|
+
dims.cost_usd = Math.max(0, Math.round(total * 1e4) / 1e4);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (Array.isArray(m.turns)) {
|
|
204
|
+
dims.turn_count_bucket = turnCountBucket(m.turns.length);
|
|
205
|
+
}
|
|
206
|
+
const errorCode = m.error_code;
|
|
207
|
+
if (typeof errorCode === "string" && errorCode) {
|
|
208
|
+
dims.error_code = errorCode;
|
|
209
|
+
dims.outcome = "error";
|
|
210
|
+
}
|
|
211
|
+
} else if (opts.carrier !== void 0) {
|
|
212
|
+
dims.carrier = carrierFamily(opts.carrier);
|
|
213
|
+
}
|
|
214
|
+
telemetry.record("call_completed", dims);
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
var init_call_metrics = __esm({
|
|
219
|
+
"src/telemetry/call-metrics.ts"() {
|
|
220
|
+
"use strict";
|
|
221
|
+
init_cjs_shims();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
121
225
|
// src/logger.ts
|
|
122
226
|
function getLogger() {
|
|
123
227
|
return currentLogger;
|
|
@@ -2171,10 +2275,10 @@ var init_plivo_adapter = __esm({
|
|
|
2171
2275
|
this.baseUrl = `${PLIVO_API_BASE}/Account/${encodeURIComponent(authId)}`;
|
|
2172
2276
|
this.authHeader = `Basic ${Buffer.from(`${authId}:${authToken}`).toString("base64")}`;
|
|
2173
2277
|
}
|
|
2174
|
-
async request(method,
|
|
2278
|
+
async request(method, path7, jsonBody) {
|
|
2175
2279
|
const headers = { Authorization: this.authHeader };
|
|
2176
2280
|
if (jsonBody !== void 0) headers["Content-Type"] = "application/json";
|
|
2177
|
-
const response = await fetch(`${this.baseUrl}${
|
|
2281
|
+
const response = await fetch(`${this.baseUrl}${path7}`, {
|
|
2178
2282
|
method,
|
|
2179
2283
|
headers,
|
|
2180
2284
|
body: jsonBody !== void 0 ? JSON.stringify(jsonBody) : void 0,
|
|
@@ -2182,7 +2286,7 @@ var init_plivo_adapter = __esm({
|
|
|
2182
2286
|
});
|
|
2183
2287
|
const text = await response.text();
|
|
2184
2288
|
if (!response.ok && response.status !== 404) {
|
|
2185
|
-
throw new Error(`Plivo ${method} ${
|
|
2289
|
+
throw new Error(`Plivo ${method} ${path7} failed: ${response.status} ${text}`);
|
|
2186
2290
|
}
|
|
2187
2291
|
let data = {};
|
|
2188
2292
|
if (text) {
|
|
@@ -3705,9 +3809,9 @@ function loadDashboardHtml() {
|
|
|
3705
3809
|
(0, import_node_path.join)(here, "dashboard", "ui.html"),
|
|
3706
3810
|
(0, import_node_path.join)(here, "..", "dashboard", "ui.html")
|
|
3707
3811
|
];
|
|
3708
|
-
for (const
|
|
3812
|
+
for (const path7 of candidates) {
|
|
3709
3813
|
try {
|
|
3710
|
-
return (0, import_node_fs.readFileSync)(
|
|
3814
|
+
return (0, import_node_fs.readFileSync)(path7, "utf8");
|
|
3711
3815
|
} catch {
|
|
3712
3816
|
}
|
|
3713
3817
|
}
|
|
@@ -4599,6 +4703,9 @@ var init_metrics = __esm({
|
|
|
4599
4703
|
ttsModel;
|
|
4600
4704
|
realtimeModel;
|
|
4601
4705
|
_pricing;
|
|
4706
|
+
// Terminal error code (lowercased ErrorCode value or "other"); set by
|
|
4707
|
+
// recordError when the call ends abnormally. Empty for a clean call.
|
|
4708
|
+
_errorCode = "";
|
|
4602
4709
|
_callStart;
|
|
4603
4710
|
_turns = [];
|
|
4604
4711
|
// mutable internal array; immutable when exposed via TurnMetrics[] → readonly TurnMetrics[]
|
|
@@ -5169,11 +5276,35 @@ var init_metrics = __esm({
|
|
|
5169
5276
|
telephony_provider: this.telephonyProvider,
|
|
5170
5277
|
stt_model: this.sttModel,
|
|
5171
5278
|
tts_model: this.ttsModel,
|
|
5172
|
-
llm_model: this._llmModel
|
|
5279
|
+
llm_model: this._llmModel,
|
|
5280
|
+
error_code: this._errorCode
|
|
5173
5281
|
};
|
|
5174
5282
|
this._eventBus?.emit("call_ended", { callId: this.callId, metrics });
|
|
5175
5283
|
return metrics;
|
|
5176
5284
|
}
|
|
5285
|
+
/**
|
|
5286
|
+
* Record the call's terminal error as a coarse, anonymous code. Stores the
|
|
5287
|
+
* PatterError `.code` lowercased; maps common timeout/connection errors; falls
|
|
5288
|
+
* back to "other". Never stores the message. Last write wins.
|
|
5289
|
+
*/
|
|
5290
|
+
recordError(err) {
|
|
5291
|
+
const code = err?.code;
|
|
5292
|
+
const name = err?.name;
|
|
5293
|
+
const sys = typeof code === "string" ? code : "";
|
|
5294
|
+
if (sys.startsWith("ECONN") || sys === "EHOSTUNREACH" || sys === "ENETUNREACH" || sys === "EPIPE") {
|
|
5295
|
+
this._errorCode = "connection";
|
|
5296
|
+
return;
|
|
5297
|
+
}
|
|
5298
|
+
if (typeof code === "string" && code) {
|
|
5299
|
+
this._errorCode = code.toLowerCase();
|
|
5300
|
+
return;
|
|
5301
|
+
}
|
|
5302
|
+
if (name === "TimeoutError" || name === "AbortError") {
|
|
5303
|
+
this._errorCode = "timeout";
|
|
5304
|
+
} else {
|
|
5305
|
+
this._errorCode = "other";
|
|
5306
|
+
}
|
|
5307
|
+
}
|
|
5177
5308
|
/** Return the cost breakdown for the call so far without ending it. */
|
|
5178
5309
|
getCostSoFar() {
|
|
5179
5310
|
const duration3 = (hrTimeMs() - this._callStart) / 1e3;
|
|
@@ -5963,7 +6094,7 @@ var init_llm_loop = __esm({
|
|
|
5963
6094
|
});
|
|
5964
6095
|
if (!response.ok) {
|
|
5965
6096
|
const errText = await response.text();
|
|
5966
|
-
getLogger().error(`LLM API error: ${response.status} ${errText}`);
|
|
6097
|
+
getLogger().error(`LLM API error: ${response.status} ${errText.slice(0, 200)}`);
|
|
5967
6098
|
throw new PatterConnectionError(
|
|
5968
6099
|
`LLM API returned ${response.status}: ${errText.slice(0, 200)}`
|
|
5969
6100
|
);
|
|
@@ -6131,12 +6262,22 @@ ${systemPrompt}` : DEFAULT_PHONE_PREAMBLE;
|
|
|
6131
6262
|
const hasAfterLlmResponse = Boolean(hookExecutor?.hasAfterLlmResponse() && hookCtx);
|
|
6132
6263
|
const hasAfterLlmChunk = Boolean(hookExecutor?.hasAfterLlmChunk());
|
|
6133
6264
|
const allEmittedText = [];
|
|
6265
|
+
const callId = callContext.call_id;
|
|
6266
|
+
const caller = callContext.caller;
|
|
6267
|
+
const callee = callContext.callee;
|
|
6268
|
+
const hasContext = typeof callId === "string" && callId.length > 0 || typeof caller === "string" && caller.length > 0 || typeof callee === "string" && callee.length > 0;
|
|
6269
|
+
const streamOpts = hasContext ? {
|
|
6270
|
+
...opts,
|
|
6271
|
+
...typeof callId === "string" && callId.length > 0 ? { callId } : {},
|
|
6272
|
+
...typeof caller === "string" && caller.length > 0 ? { caller } : {},
|
|
6273
|
+
...typeof callee === "string" && callee.length > 0 ? { callee } : {}
|
|
6274
|
+
} : opts;
|
|
6134
6275
|
for (let iter = 0; iter < maxIterations; iter++) {
|
|
6135
6276
|
const toolCallsAccumulated = /* @__PURE__ */ new Map();
|
|
6136
6277
|
const textParts = [];
|
|
6137
6278
|
let hasToolCalls = false;
|
|
6138
6279
|
let usageChunkReceived = false;
|
|
6139
|
-
for await (const chunk of this.provider.stream(messages, this.openaiTools,
|
|
6280
|
+
for await (const chunk of this.provider.stream(messages, this.openaiTools, streamOpts)) {
|
|
6140
6281
|
if (chunk.type === "text" && chunk.content) {
|
|
6141
6282
|
const content = hasAfterLlmChunk && hookExecutor ? hookExecutor.runAfterLlmChunk(chunk.content) : chunk.content;
|
|
6142
6283
|
textParts.push(content);
|
|
@@ -6264,6 +6405,7 @@ ${systemPrompt}` : DEFAULT_PHONE_PREAMBLE;
|
|
|
6264
6405
|
{ role: "system", content: this.systemPrompt }
|
|
6265
6406
|
];
|
|
6266
6407
|
for (const entry of history) {
|
|
6408
|
+
if (entry.role === "tool") continue;
|
|
6267
6409
|
messages.push({
|
|
6268
6410
|
role: entry.role === "assistant" ? "assistant" : "user",
|
|
6269
6411
|
content: entry.text
|
|
@@ -6541,10 +6683,10 @@ function mergeDefs(...defs) {
|
|
|
6541
6683
|
function cloneDef(schema) {
|
|
6542
6684
|
return mergeDefs(schema._zod.def);
|
|
6543
6685
|
}
|
|
6544
|
-
function getElementAtPath(obj,
|
|
6545
|
-
if (!
|
|
6686
|
+
function getElementAtPath(obj, path7) {
|
|
6687
|
+
if (!path7)
|
|
6546
6688
|
return obj;
|
|
6547
|
-
return
|
|
6689
|
+
return path7.reduce((acc, key) => acc?.[key], obj);
|
|
6548
6690
|
}
|
|
6549
6691
|
function promiseAllObject(promisesObj) {
|
|
6550
6692
|
const keys = Object.keys(promisesObj);
|
|
@@ -6872,11 +7014,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
6872
7014
|
}
|
|
6873
7015
|
return false;
|
|
6874
7016
|
}
|
|
6875
|
-
function prefixIssues(
|
|
7017
|
+
function prefixIssues(path7, issues) {
|
|
6876
7018
|
return issues.map((iss) => {
|
|
6877
7019
|
var _a3;
|
|
6878
7020
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
6879
|
-
iss.path.unshift(
|
|
7021
|
+
iss.path.unshift(path7);
|
|
6880
7022
|
return iss;
|
|
6881
7023
|
});
|
|
6882
7024
|
}
|
|
@@ -7095,16 +7237,16 @@ function flattenError(error2, mapper = (issue2) => issue2.message) {
|
|
|
7095
7237
|
}
|
|
7096
7238
|
function formatError(error2, mapper = (issue2) => issue2.message) {
|
|
7097
7239
|
const fieldErrors = { _errors: [] };
|
|
7098
|
-
const processError = (error3,
|
|
7240
|
+
const processError = (error3, path7 = []) => {
|
|
7099
7241
|
for (const issue2 of error3.issues) {
|
|
7100
7242
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
7101
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
7243
|
+
issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
|
|
7102
7244
|
} else if (issue2.code === "invalid_key") {
|
|
7103
|
-
processError({ issues: issue2.issues }, [...
|
|
7245
|
+
processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
|
|
7104
7246
|
} else if (issue2.code === "invalid_element") {
|
|
7105
|
-
processError({ issues: issue2.issues }, [...
|
|
7247
|
+
processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
|
|
7106
7248
|
} else {
|
|
7107
|
-
const fullpath = [...
|
|
7249
|
+
const fullpath = [...path7, ...issue2.path];
|
|
7108
7250
|
if (fullpath.length === 0) {
|
|
7109
7251
|
fieldErrors._errors.push(mapper(issue2));
|
|
7110
7252
|
} else {
|
|
@@ -17908,20 +18050,20 @@ var require_compile = __commonJS({
|
|
|
17908
18050
|
var util_1 = require_util();
|
|
17909
18051
|
var validate_1 = require_validate();
|
|
17910
18052
|
var SchemaEnv = class {
|
|
17911
|
-
constructor(
|
|
18053
|
+
constructor(env2) {
|
|
17912
18054
|
var _a3;
|
|
17913
18055
|
this.refs = {};
|
|
17914
18056
|
this.dynamicAnchors = {};
|
|
17915
18057
|
let schema;
|
|
17916
|
-
if (typeof
|
|
17917
|
-
schema =
|
|
17918
|
-
this.schema =
|
|
17919
|
-
this.schemaId =
|
|
17920
|
-
this.root =
|
|
17921
|
-
this.baseId = (_a3 =
|
|
17922
|
-
this.schemaPath =
|
|
17923
|
-
this.localRefs =
|
|
17924
|
-
this.meta =
|
|
18058
|
+
if (typeof env2.schema == "object")
|
|
18059
|
+
schema = env2.schema;
|
|
18060
|
+
this.schema = env2.schema;
|
|
18061
|
+
this.schemaId = env2.schemaId;
|
|
18062
|
+
this.root = env2.root || this;
|
|
18063
|
+
this.baseId = (_a3 = env2.baseId) !== null && _a3 !== void 0 ? _a3 : (0, resolve_1.normalizeId)(schema === null || schema === void 0 ? void 0 : schema[env2.schemaId || "$id"]);
|
|
18064
|
+
this.schemaPath = env2.schemaPath;
|
|
18065
|
+
this.localRefs = env2.localRefs;
|
|
18066
|
+
this.meta = env2.meta;
|
|
17925
18067
|
this.$async = schema === null || schema === void 0 ? void 0 : schema.$async;
|
|
17926
18068
|
this.refs = {};
|
|
17927
18069
|
}
|
|
@@ -18105,15 +18247,15 @@ var require_compile = __commonJS({
|
|
|
18105
18247
|
baseId = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, schId);
|
|
18106
18248
|
}
|
|
18107
18249
|
}
|
|
18108
|
-
let
|
|
18250
|
+
let env2;
|
|
18109
18251
|
if (typeof schema != "boolean" && schema.$ref && !(0, util_1.schemaHasRulesButRef)(schema, this.RULES)) {
|
|
18110
18252
|
const $ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, schema.$ref);
|
|
18111
|
-
|
|
18253
|
+
env2 = resolveSchema.call(this, root, $ref);
|
|
18112
18254
|
}
|
|
18113
18255
|
const { schemaId } = this.opts;
|
|
18114
|
-
|
|
18115
|
-
if (
|
|
18116
|
-
return
|
|
18256
|
+
env2 = env2 || new SchemaEnv({ schema, schemaId, root, baseId });
|
|
18257
|
+
if (env2.schema !== env2.root.schema)
|
|
18258
|
+
return env2;
|
|
18117
18259
|
return void 0;
|
|
18118
18260
|
}
|
|
18119
18261
|
}
|
|
@@ -18265,8 +18407,8 @@ var require_utils = __commonJS({
|
|
|
18265
18407
|
}
|
|
18266
18408
|
return ind;
|
|
18267
18409
|
}
|
|
18268
|
-
function removeDotSegments(
|
|
18269
|
-
let input =
|
|
18410
|
+
function removeDotSegments(path7) {
|
|
18411
|
+
let input = path7;
|
|
18270
18412
|
const output = [];
|
|
18271
18413
|
let nextSlash = -1;
|
|
18272
18414
|
let len = 0;
|
|
@@ -18519,8 +18661,8 @@ var require_schemes = __commonJS({
|
|
|
18519
18661
|
wsComponent.secure = void 0;
|
|
18520
18662
|
}
|
|
18521
18663
|
if (wsComponent.resourceName) {
|
|
18522
|
-
const [
|
|
18523
|
-
wsComponent.path =
|
|
18664
|
+
const [path7, query] = wsComponent.resourceName.split("?");
|
|
18665
|
+
wsComponent.path = path7 && path7 !== "/" ? path7 : void 0;
|
|
18524
18666
|
wsComponent.query = query;
|
|
18525
18667
|
wsComponent.resourceName = void 0;
|
|
18526
18668
|
}
|
|
@@ -19608,8 +19750,8 @@ var require_ref = __commonJS({
|
|
|
19608
19750
|
schemaType: "string",
|
|
19609
19751
|
code(cxt) {
|
|
19610
19752
|
const { gen, schema: $ref, it } = cxt;
|
|
19611
|
-
const { baseId, schemaEnv:
|
|
19612
|
-
const { root } =
|
|
19753
|
+
const { baseId, schemaEnv: env2, validateName, opts, self } = it;
|
|
19754
|
+
const { root } = env2;
|
|
19613
19755
|
if (($ref === "#" || $ref === "#/") && baseId === root.baseId)
|
|
19614
19756
|
return callRootRef();
|
|
19615
19757
|
const schOrEnv = compile_1.resolveRef.call(self, root, baseId, $ref);
|
|
@@ -19619,8 +19761,8 @@ var require_ref = __commonJS({
|
|
|
19619
19761
|
return callValidate(schOrEnv);
|
|
19620
19762
|
return inlineRefSchema(schOrEnv);
|
|
19621
19763
|
function callRootRef() {
|
|
19622
|
-
if (
|
|
19623
|
-
return callRef(cxt, validateName,
|
|
19764
|
+
if (env2 === root)
|
|
19765
|
+
return callRef(cxt, validateName, env2, env2.$async);
|
|
19624
19766
|
const rootName = gen.scopeValue("root", { ref: root });
|
|
19625
19767
|
return callRef(cxt, (0, codegen_1._)`${rootName}.validate`, root, root.$async);
|
|
19626
19768
|
}
|
|
@@ -19650,14 +19792,14 @@ var require_ref = __commonJS({
|
|
|
19650
19792
|
exports2.getValidate = getValidate;
|
|
19651
19793
|
function callRef(cxt, v, sch, $async) {
|
|
19652
19794
|
const { gen, it } = cxt;
|
|
19653
|
-
const { allErrors, schemaEnv:
|
|
19795
|
+
const { allErrors, schemaEnv: env2, opts } = it;
|
|
19654
19796
|
const passCxt = opts.passContext ? names_1.default.this : codegen_1.nil;
|
|
19655
19797
|
if ($async)
|
|
19656
19798
|
callAsyncRef();
|
|
19657
19799
|
else
|
|
19658
19800
|
callSyncRef();
|
|
19659
19801
|
function callAsyncRef() {
|
|
19660
|
-
if (!
|
|
19802
|
+
if (!env2.$async)
|
|
19661
19803
|
throw new Error("async schema referenced by sync schema");
|
|
19662
19804
|
const valid = gen.let("valid");
|
|
19663
19805
|
gen.try(() => {
|
|
@@ -21959,12 +22101,12 @@ var require_dist = __commonJS({
|
|
|
21959
22101
|
throw new Error(`Unknown format "${name}"`);
|
|
21960
22102
|
return f;
|
|
21961
22103
|
};
|
|
21962
|
-
function addFormats(ajv, list,
|
|
22104
|
+
function addFormats(ajv, list, fs8, exportName) {
|
|
21963
22105
|
var _a3;
|
|
21964
22106
|
var _b;
|
|
21965
22107
|
(_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
21966
22108
|
for (const f of list)
|
|
21967
|
-
ajv.addFormat(f,
|
|
22109
|
+
ajv.addFormat(f, fs8[f]);
|
|
21968
22110
|
}
|
|
21969
22111
|
module2.exports = exports2 = formatsPlugin;
|
|
21970
22112
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
@@ -27781,6 +27923,26 @@ function isSttHallucination(text) {
|
|
|
27781
27923
|
const pieces = stripped.split(/[.!?…。!?]+/u).map((p) => p.trim()).filter((p) => p.length > 0);
|
|
27782
27924
|
return pieces.length > 1 && pieces.every((p) => HALLUCINATIONS.has(p));
|
|
27783
27925
|
}
|
|
27926
|
+
function normalizeForEcho(text) {
|
|
27927
|
+
return text.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/u, " ").trim().replace(/\s+/gu, " ");
|
|
27928
|
+
}
|
|
27929
|
+
function looksLikeEcho(candidate, agentText) {
|
|
27930
|
+
const a = normalizeForEcho(agentText);
|
|
27931
|
+
const c = normalizeForEcho(candidate);
|
|
27932
|
+
if (!a || !c) return false;
|
|
27933
|
+
const words = c.split(" ").filter(Boolean);
|
|
27934
|
+
if (words.length < ECHO_MIN_CANDIDATE_WORDS) return false;
|
|
27935
|
+
if (a.includes(c)) return true;
|
|
27936
|
+
const agentWords = new Set(a.split(" "));
|
|
27937
|
+
const overlap = words.filter((w) => agentWords.has(w)).length / words.length;
|
|
27938
|
+
return overlap >= ECHO_WORD_OVERLAP_THRESHOLD;
|
|
27939
|
+
}
|
|
27940
|
+
function isNearDuplicate(a, b) {
|
|
27941
|
+
if (!a || !b) return false;
|
|
27942
|
+
if (a === b) return true;
|
|
27943
|
+
const [shorter, longer] = a.length <= b.length ? [a, b] : [b, a];
|
|
27944
|
+
return longer.startsWith(shorter + " ");
|
|
27945
|
+
}
|
|
27784
27946
|
async function queryDeepgramCost(metricsAcc, deepgramKey, deepgramRequestId) {
|
|
27785
27947
|
try {
|
|
27786
27948
|
const projResp = await fetch("https://api.deepgram.com/v1/projects", {
|
|
@@ -27811,7 +27973,7 @@ async function queryDeepgramCost(metricsAcc, deepgramKey, deepgramRequestId) {
|
|
|
27811
27973
|
} catch {
|
|
27812
27974
|
}
|
|
27813
27975
|
}
|
|
27814
|
-
var DEFAULT_TOOL_CALL_PREAMBLE_BLOCK, HALLUCINATIONS, StreamHandler;
|
|
27976
|
+
var DEFAULT_TOOL_CALL_PREAMBLE_BLOCK, HALLUCINATIONS, ECHO_WORD_OVERLAP_THRESHOLD, ECHO_MIN_CANDIDATE_WORDS, StreamHandler;
|
|
27815
27977
|
var init_stream_handler = __esm({
|
|
27816
27978
|
"src/stream-handler.ts"() {
|
|
27817
27979
|
"use strict";
|
|
@@ -27924,6 +28086,8 @@ Avoid:
|
|
|
27924
28086
|
"[blank_audio]",
|
|
27925
28087
|
"(silence)"
|
|
27926
28088
|
]);
|
|
28089
|
+
ECHO_WORD_OVERLAP_THRESHOLD = 0.6;
|
|
28090
|
+
ECHO_MIN_CANDIDATE_WORDS = 4;
|
|
27927
28091
|
StreamHandler = class _StreamHandler {
|
|
27928
28092
|
deps;
|
|
27929
28093
|
ws;
|
|
@@ -27936,6 +28100,17 @@ Avoid:
|
|
|
27936
28100
|
stt = null;
|
|
27937
28101
|
tts = null;
|
|
27938
28102
|
isSpeaking = false;
|
|
28103
|
+
/**
|
|
28104
|
+
* True only while the post-TTS tail-grace window is pending: the agent has
|
|
28105
|
+
* finished its turn but ``isSpeaking`` is still held for
|
|
28106
|
+
* ``PATTER_TTS_TAIL_GRACE_MS`` to swallow the fading echo tail. A VAD
|
|
28107
|
+
* ``speech_start`` (or a transcript) during this window is the user's NEXT
|
|
28108
|
+
* turn, not a barge-in — there is nothing left to interrupt. Set by
|
|
28109
|
+
* ``endSpeakingWithGrace``; cleared by ``beginSpeaking``, the grace flip,
|
|
28110
|
+
* ``cancelSpeaking``, and ``endTailGraceForNewTurn``. Parity with Python
|
|
28111
|
+
* ``_tail_grace_active``.
|
|
28112
|
+
*/
|
|
28113
|
+
tailGraceActive = false;
|
|
27939
28114
|
/**
|
|
27940
28115
|
* Ring buffer of inbound PCM16 16 kHz frames captured while the agent
|
|
27941
28116
|
* is speaking and the self-hearing guard is dropping audio. On
|
|
@@ -28011,6 +28186,35 @@ Avoid:
|
|
|
28011
28186
|
* ``isSpeaking=false``, and silently cut the agent's first turn.
|
|
28012
28187
|
*/
|
|
28013
28188
|
firstAudioSentAt = null;
|
|
28189
|
+
/**
|
|
28190
|
+
* Estimated wall-clock (ms) when the LAST audio byte pushed to the carrier
|
|
28191
|
+
* finishes PLAYING on the phone. The pipeline pushes TTS audio as fast as
|
|
28192
|
+
* the provider synthesizes it (no pacing) and the carrier buffers + plays
|
|
28193
|
+
* at realtime, so "we finished pushing" and "the caller finished hearing"
|
|
28194
|
+
* can diverge by tens of seconds — especially with agent-runtime LLMs
|
|
28195
|
+
* (Hermes/OpenClaw) that deliver a long reply all at once after a thinking
|
|
28196
|
+
* pause. ``endSpeakingWithGrace`` holds ``isSpeaking=true`` (with
|
|
28197
|
+
* ``tailGraceActive=false``) until this cursor passes, so a barge-in during
|
|
28198
|
+
* the audible backlog still takes the cancel path (``sendClear`` drops the
|
|
28199
|
+
* carrier buffer) instead of being treated as a calm next turn. Advanced by
|
|
28200
|
+
* ``trackOutboundPlayback``; reset by ``cancelSpeaking`` (the buffer is
|
|
28201
|
+
* cleared) and ``endTailGraceForNewTurn``.
|
|
28202
|
+
*/
|
|
28203
|
+
playbackBufferedUntil = 0;
|
|
28204
|
+
/**
|
|
28205
|
+
* Per-turn playback timeline used to estimate the response prefix the
|
|
28206
|
+
* caller actually HEARD when a barge-in lands. ``turnPlaybackTotalMs``
|
|
28207
|
+
* accumulates the playout duration of every chunk pushed this turn
|
|
28208
|
+
* (including filler audio, which keeps the timeline aligned);
|
|
28209
|
+
* ``turnSpokenSegments`` records ``{text, startMs}`` for each RESPONSE
|
|
28210
|
+
* sentence at its first audible chunk (filler / error-fallback audio
|
|
28211
|
+
* advances the clock but adds no segment). ``heard = total - backlog``
|
|
28212
|
+
* then maps to a sentence-granular prefix — see ``heardResponsePrefix``.
|
|
28213
|
+
* Both reset at ``beginSpeaking``. Mirrors Python
|
|
28214
|
+
* ``_turn_playback_total_s`` / ``_turn_spoken_segments``.
|
|
28215
|
+
*/
|
|
28216
|
+
turnPlaybackTotalMs = 0;
|
|
28217
|
+
turnSpokenSegments = [];
|
|
28014
28218
|
/**
|
|
28015
28219
|
* Optional barge-in confirmation strategies. With an empty array the
|
|
28016
28220
|
* SDK falls back to the legacy "cancel on first VAD speech_start"
|
|
@@ -28128,11 +28332,15 @@ Avoid:
|
|
|
28128
28332
|
}
|
|
28129
28333
|
this.speakingGeneration++;
|
|
28130
28334
|
this.isSpeaking = true;
|
|
28335
|
+
this.tailGraceActive = false;
|
|
28131
28336
|
this.speakingStartedAt = Date.now();
|
|
28132
28337
|
this.suppressedSpeechPending = false;
|
|
28133
28338
|
void isFirstMessage;
|
|
28134
28339
|
this.firstAudioSentAt = Date.now();
|
|
28135
28340
|
this.inboundAudioRing = [];
|
|
28341
|
+
this.currentAgentSpokenText = "";
|
|
28342
|
+
this.turnPlaybackTotalMs = 0;
|
|
28343
|
+
this.turnSpokenSegments = [];
|
|
28136
28344
|
this.resetVad();
|
|
28137
28345
|
}
|
|
28138
28346
|
/**
|
|
@@ -28147,6 +28355,87 @@ Avoid:
|
|
|
28147
28355
|
this.firstAudioSentAt = Date.now();
|
|
28148
28356
|
}
|
|
28149
28357
|
}
|
|
28358
|
+
/**
|
|
28359
|
+
* Advance ``playbackBufferedUntil`` by the playout duration of an outbound
|
|
28360
|
+
* TTS chunk. ``numBytes`` is the size of the chunk BEFORE carrier encoding
|
|
28361
|
+
* (the same buffer handed to ``encodePipelineAudio``): PCM16 @ 16 kHz in
|
|
28362
|
+
* the default path (32 bytes/ms), or the carrier's native μ-law @ 8 kHz
|
|
28363
|
+
* (8 bytes/ms) when the TTS adapter emits wire format directly
|
|
28364
|
+
* (``ttsOutputFormatNativeForCarrier`` — Twilio/Plivo ``ulaw_8000``;
|
|
28365
|
+
* Telnyx native is ``pcm_16000`` so it stays at 32 bytes/ms).
|
|
28366
|
+
*/
|
|
28367
|
+
trackOutboundPlayback(numBytes) {
|
|
28368
|
+
if (numBytes <= 0) return;
|
|
28369
|
+
const bytesPerMs = this.ttsOutputFormatNativeForCarrier && this.deps.bridge.telephonyProvider !== "telnyx" ? 8 : 32;
|
|
28370
|
+
const now = Date.now();
|
|
28371
|
+
const chunkMs = numBytes / bytesPerMs;
|
|
28372
|
+
const base = this.playbackBufferedUntil > now ? this.playbackBufferedUntil : now;
|
|
28373
|
+
this.playbackBufferedUntil = base + chunkMs;
|
|
28374
|
+
this.turnPlaybackTotalMs += chunkMs;
|
|
28375
|
+
}
|
|
28376
|
+
/**
|
|
28377
|
+
* Estimate the response prefix the caller actually HEARD this turn.
|
|
28378
|
+
*
|
|
28379
|
+
* The pipeline pushes audio faster than realtime, so at barge-in time
|
|
28380
|
+
* ``heard = totalPushed - carrierBacklog`` ms of audio have actually
|
|
28381
|
+
* played. Mapped at sentence granularity against ``turnSpokenSegments``:
|
|
28382
|
+
* a sentence counts as heard once its playback has STARTED
|
|
28383
|
+
* (``startMs <= heardMs``), so the sentence playing at the moment of
|
|
28384
|
+
* interruption is included.
|
|
28385
|
+
*
|
|
28386
|
+
* Returns ``null`` when no segments were tracked this turn (nothing
|
|
28387
|
+
* synthesized through the tracked path — callers fall back to the legacy
|
|
28388
|
+
* full-text behaviour). Mirrors Python ``_heard_response_prefix``.
|
|
28389
|
+
*/
|
|
28390
|
+
heardResponsePrefix() {
|
|
28391
|
+
if (this.turnSpokenSegments.length === 0) return null;
|
|
28392
|
+
const remainingMs = Math.max(0, this.playbackBufferedUntil - Date.now());
|
|
28393
|
+
const heardMs = Math.max(0, this.turnPlaybackTotalMs - remainingMs);
|
|
28394
|
+
const heard = this.turnSpokenSegments.filter((s) => s.startMs <= heardMs);
|
|
28395
|
+
return {
|
|
28396
|
+
text: heard.map((s) => s.text).join(" "),
|
|
28397
|
+
heardEverything: heard.length === this.turnSpokenSegments.length
|
|
28398
|
+
};
|
|
28399
|
+
}
|
|
28400
|
+
/**
|
|
28401
|
+
* Replace the text of the most recent assistant entry in the conversation
|
|
28402
|
+
* history. No-op when the last entry is not an assistant turn (e.g. the
|
|
28403
|
+
* caller's next turn was already committed).
|
|
28404
|
+
*/
|
|
28405
|
+
rewriteLastAssistantEntry(text) {
|
|
28406
|
+
const entries = this.history.entries;
|
|
28407
|
+
const last = entries[entries.length - 1];
|
|
28408
|
+
if (last && last.role === "assistant") {
|
|
28409
|
+
entries[entries.length - 1] = { ...last, text };
|
|
28410
|
+
}
|
|
28411
|
+
}
|
|
28412
|
+
/**
|
|
28413
|
+
* LiveKit-style "heard prefix" semantics for a barge-in that lands AFTER
|
|
28414
|
+
* the turn completed, while the carrier is still playing the buffered
|
|
28415
|
+
* tail.
|
|
28416
|
+
*
|
|
28417
|
+
* The completed turn already recorded its FULL reply in history, but the
|
|
28418
|
+
* caller only heard part of it before interrupting — a stateful agent
|
|
28419
|
+
* runtime (Hermes / OpenClaw) would otherwise "remember saying" things
|
|
28420
|
+
* the caller never heard. Rewrites the last assistant entry to the heard
|
|
28421
|
+
* prefix + ``[interrupted by caller]``.
|
|
28422
|
+
*
|
|
28423
|
+
* MUST run BEFORE ``cancelSpeaking`` resets ``playbackBufferedUntil``
|
|
28424
|
+
* (the backlog is the heard-prefix input). No-op when a turn is still in
|
|
28425
|
+
* flight (the streaming path applies its own marker), when there is no
|
|
28426
|
+
* backlog, or when everything was already heard. Mirrors Python
|
|
28427
|
+
* ``_maybe_truncate_completed_turn_history``.
|
|
28428
|
+
*/
|
|
28429
|
+
maybeTruncateCompletedTurnHistory() {
|
|
28430
|
+
if (this.dispatchTask !== null) return;
|
|
28431
|
+
const remainingMs = this.playbackBufferedUntil - Date.now();
|
|
28432
|
+
if (remainingMs <= 0) return;
|
|
28433
|
+
const heard = this.heardResponsePrefix();
|
|
28434
|
+
if (heard === null || heard.heardEverything) return;
|
|
28435
|
+
this.rewriteLastAssistantEntry(
|
|
28436
|
+
heard.text ? `${heard.text} [interrupted by caller]` : "[interrupted by caller]"
|
|
28437
|
+
);
|
|
28438
|
+
}
|
|
28150
28439
|
/**
|
|
28151
28440
|
* Atomically end speaking AND invalidate any pending grace timer.
|
|
28152
28441
|
* Use instead of ``this.isSpeaking = false`` at barge-in sites.
|
|
@@ -28157,10 +28446,12 @@ Avoid:
|
|
|
28157
28446
|
cancelSpeaking() {
|
|
28158
28447
|
this.speakingGeneration++;
|
|
28159
28448
|
this.isSpeaking = false;
|
|
28449
|
+
this.tailGraceActive = false;
|
|
28160
28450
|
this.speakingStartedAt = null;
|
|
28161
28451
|
this.firstAudioSentAt = null;
|
|
28162
28452
|
this.lastCancelAt = Date.now();
|
|
28163
28453
|
this.suppressedSpeechPending = false;
|
|
28454
|
+
this.playbackBufferedUntil = 0;
|
|
28164
28455
|
this.drainPendingMarks();
|
|
28165
28456
|
if (this.llmAbort !== null) {
|
|
28166
28457
|
try {
|
|
@@ -28233,23 +28524,37 @@ Avoid:
|
|
|
28233
28524
|
if (grace > 0) {
|
|
28234
28525
|
const gen = this.speakingGeneration;
|
|
28235
28526
|
this.clearGraceTimer();
|
|
28236
|
-
|
|
28237
|
-
this.
|
|
28238
|
-
|
|
28239
|
-
this.
|
|
28240
|
-
this.
|
|
28241
|
-
|
|
28242
|
-
|
|
28243
|
-
|
|
28244
|
-
|
|
28245
|
-
this.
|
|
28246
|
-
this.
|
|
28527
|
+
const startTailGrace = () => {
|
|
28528
|
+
this.tailGraceActive = true;
|
|
28529
|
+
this.graceTimer = setTimeout(() => {
|
|
28530
|
+
this.graceTimer = null;
|
|
28531
|
+
if (this.speakingGeneration === gen) {
|
|
28532
|
+
this.isSpeaking = false;
|
|
28533
|
+
this.tailGraceActive = false;
|
|
28534
|
+
this.speakingStartedAt = null;
|
|
28535
|
+
this.firstAudioSentAt = null;
|
|
28536
|
+
this.clearPendingBargeIn();
|
|
28537
|
+
void this.resetBargeInStrategies();
|
|
28538
|
+
if (this.suppressedSpeechPending) {
|
|
28539
|
+
this.suppressedSpeechPending = false;
|
|
28540
|
+
this.flushInboundAudioRing();
|
|
28541
|
+
}
|
|
28542
|
+
this.resetVad();
|
|
28247
28543
|
}
|
|
28248
|
-
|
|
28249
|
-
|
|
28250
|
-
|
|
28544
|
+
}, grace);
|
|
28545
|
+
};
|
|
28546
|
+
const bufferedMs = Math.max(0, this.playbackBufferedUntil - Date.now());
|
|
28547
|
+
if (bufferedMs <= 0) {
|
|
28548
|
+
startTailGrace();
|
|
28549
|
+
} else {
|
|
28550
|
+
this.graceTimer = setTimeout(() => {
|
|
28551
|
+
this.graceTimer = null;
|
|
28552
|
+
if (this.speakingGeneration === gen) startTailGrace();
|
|
28553
|
+
}, bufferedMs);
|
|
28554
|
+
}
|
|
28251
28555
|
} else {
|
|
28252
28556
|
this.isSpeaking = false;
|
|
28557
|
+
this.tailGraceActive = false;
|
|
28253
28558
|
this.speakingStartedAt = null;
|
|
28254
28559
|
this.firstAudioSentAt = null;
|
|
28255
28560
|
this.clearPendingBargeIn();
|
|
@@ -28261,6 +28566,35 @@ Avoid:
|
|
|
28261
28566
|
this.resetVad();
|
|
28262
28567
|
}
|
|
28263
28568
|
}
|
|
28569
|
+
/**
|
|
28570
|
+
* End the post-TTS tail-grace window because the user has begun their next
|
|
28571
|
+
* turn. Unlike a barge-in, the agent's response already played out in full
|
|
28572
|
+
* — there is nothing to cancel and no turn was interrupted. We flip the
|
|
28573
|
+
* speaking flag off (bumping ``speakingGeneration`` so the scheduled grace
|
|
28574
|
+
* timer no-ops), recover any leading audio the self-hearing guard captured
|
|
28575
|
+
* into the ring (the user's first ~250 ms, which VAD needed before it could
|
|
28576
|
+
* emit ``speech_start``), and let the live STT stream take over. We do NOT
|
|
28577
|
+
* call ``sendClear``, ``recordBargeinDetected`` or ``recordTurnInterrupted``
|
|
28578
|
+
* — none apply to a turn that completed normally.
|
|
28579
|
+
*
|
|
28580
|
+
* Without this, fast next-turn speech (humans reply in 200-700 ms, well
|
|
28581
|
+
* inside the 1500 ms default grace) is withheld from STT and recorded as an
|
|
28582
|
+
* empty ``[interrupted]`` turn, after which the agent goes silent for the
|
|
28583
|
+
* rest of the call. Parity with Python ``_end_tail_grace_for_new_turn``.
|
|
28584
|
+
*/
|
|
28585
|
+
endTailGraceForNewTurn() {
|
|
28586
|
+
this.isSpeaking = false;
|
|
28587
|
+
this.tailGraceActive = false;
|
|
28588
|
+
this.speakingStartedAt = null;
|
|
28589
|
+
this.firstAudioSentAt = null;
|
|
28590
|
+
this.playbackBufferedUntil = 0;
|
|
28591
|
+
this.speakingGeneration++;
|
|
28592
|
+
this.clearGraceTimer();
|
|
28593
|
+
this.clearPendingBargeIn();
|
|
28594
|
+
void this.resetBargeInStrategies();
|
|
28595
|
+
this.suppressedSpeechPending = false;
|
|
28596
|
+
this.flushInboundAudioRing();
|
|
28597
|
+
}
|
|
28264
28598
|
async resetBargeInStrategies() {
|
|
28265
28599
|
if (this.bargeInStrategies.length === 0) return;
|
|
28266
28600
|
const { resetStrategies: resetStrategies2 } = await Promise.resolve().then(() => (init_barge_in_strategies(), barge_in_strategies_exports));
|
|
@@ -28396,9 +28730,43 @@ Avoid:
|
|
|
28396
28730
|
maxDurationTimer = null;
|
|
28397
28731
|
transcriptProcessing = false;
|
|
28398
28732
|
transcriptQueue = [];
|
|
28733
|
+
/**
|
|
28734
|
+
* The in-flight turn dispatch (LLM + TTS) runs as a SINGLE tracked promise
|
|
28735
|
+
* so the transcript drain loop keeps running ``handleBargeIn`` against the
|
|
28736
|
+
* LIVE turn during a long (30-90 s) agent-runtime response, instead of
|
|
28737
|
+
* head-of-line-blocking on it. Exactly one is in flight: the launcher awaits
|
|
28738
|
+
* the previous one to settle (fast — a barge-in already aborted it) before
|
|
28739
|
+
* starting the next, preserving history/metrics ordering. Parity with
|
|
28740
|
+
* Python ``_dispatch_task``.
|
|
28741
|
+
*/
|
|
28742
|
+
dispatchTask = null;
|
|
28743
|
+
/**
|
|
28744
|
+
* Cap (ms) on how long teardown waits for the backgrounded dispatch to
|
|
28745
|
+
* settle. JS promises are not cancellable, so a user-supplied ``onMessage``
|
|
28746
|
+
* (which receives no AbortSignal) parked on a hung external call could block
|
|
28747
|
+
* call cleanup indefinitely — `llmAbort.abort()` only unblocks the built-in
|
|
28748
|
+
* LLM/TTS paths. We bound the WAIT (Python hard-cancels the task instead).
|
|
28749
|
+
* 30 s matches the webhook ceiling.
|
|
28750
|
+
*/
|
|
28751
|
+
static DISPATCH_SETTLE_TIMEOUT_MS = 3e4;
|
|
28752
|
+
/**
|
|
28753
|
+
* Opt-in (default OFF): forward inbound audio to STT even while the agent is
|
|
28754
|
+
* speaking, so the transcript barge-in path can receive a transcript on
|
|
28755
|
+
* echo-masked PSTN links where the VAD never fires. ECHO RISK without AEC.
|
|
28756
|
+
* Parity with Python ``_forward_stt_while_speaking``.
|
|
28757
|
+
*/
|
|
28758
|
+
forwardSttWhileSpeaking = ["1", "true", "yes"].includes(
|
|
28759
|
+
(process.env.PATTER_FORWARD_STT_WHILE_SPEAKING ?? "").trim().toLowerCase()
|
|
28760
|
+
);
|
|
28399
28761
|
// Throttle state for back-to-back STT finals — see ``commitTranscript``.
|
|
28400
28762
|
lastCommitText = "";
|
|
28401
28763
|
lastCommitAt = 0;
|
|
28764
|
+
/** The agent's spoken text for the CURRENT turn, accumulated as tokens stream.
|
|
28765
|
+
* The echo guard rejects transcripts matching it (the agent's own TTS bleeding
|
|
28766
|
+
* back into STT when audio is forwarded during TTS without effective AEC).
|
|
28767
|
+
* Reset in ``beginSpeaking``; only consulted while ``forwardSttWhileSpeaking``.
|
|
28768
|
+
* Parity with Python ``_current_agent_spoken_text``. */
|
|
28769
|
+
currentAgentSpokenText = "";
|
|
28402
28770
|
// PCM16 byte-alignment carry for TTS streaming (pipeline mode).
|
|
28403
28771
|
// HTTP streams from ElevenLabs / OpenAI / Cartesia can yield chunks of any
|
|
28404
28772
|
// size, including odd byte counts. Silently dropping the trailing odd byte
|
|
@@ -28418,6 +28786,11 @@ Avoid:
|
|
|
28418
28786
|
this.ws = ws;
|
|
28419
28787
|
this.caller = caller;
|
|
28420
28788
|
this.callee = callee;
|
|
28789
|
+
if (this.forwardSttWhileSpeaking) {
|
|
28790
|
+
getLogger().warn(
|
|
28791
|
+
"PATTER_FORWARD_STT_WHILE_SPEAKING=on: inbound audio is sent to STT during TTS so transcript barge-in works on echo-masked links. Without AEC the agent's own voice may be transcribed as a phantom interruption \u2014 pair with agent.bargeInStrategies."
|
|
28792
|
+
);
|
|
28793
|
+
}
|
|
28421
28794
|
this.bargeInStrategies = (deps.agent.bargeInStrategies ?? []).slice();
|
|
28422
28795
|
const confirmMs = deps.agent.bargeInConfirmMs;
|
|
28423
28796
|
this.bargeInConfirmMs = typeof confirmMs === "number" && Number.isFinite(confirmMs) && confirmMs > 0 ? confirmMs : 1500;
|
|
@@ -28617,12 +28990,12 @@ Avoid:
|
|
|
28617
28990
|
} catch {
|
|
28618
28991
|
}
|
|
28619
28992
|
if (this.deps.onCallStart) {
|
|
28620
|
-
const
|
|
28993
|
+
const direction2 = this.deps.metricsStore.getActive(callId)?.direction ?? "inbound";
|
|
28621
28994
|
await this.deps.onCallStart({
|
|
28622
28995
|
call_id: callId,
|
|
28623
28996
|
caller: this.caller,
|
|
28624
28997
|
callee: this.callee,
|
|
28625
|
-
direction,
|
|
28998
|
+
direction: direction2,
|
|
28626
28999
|
telephony_provider: this.deps.bridge.telephonyProvider,
|
|
28627
29000
|
...Object.keys(customParams).length > 0 ? { custom_params: customParams } : {}
|
|
28628
29001
|
});
|
|
@@ -28689,6 +29062,17 @@ Avoid:
|
|
|
28689
29062
|
setStreamSid(sid) {
|
|
28690
29063
|
this.streamSid = sid;
|
|
28691
29064
|
}
|
|
29065
|
+
/**
|
|
29066
|
+
* Record a terminal/processing error as a coarse, anonymous code on the call
|
|
29067
|
+
* metrics (code only, never the message). Surfaced via `call_completed`
|
|
29068
|
+
* telemetry. Safe to call with any value; last write wins.
|
|
29069
|
+
*/
|
|
29070
|
+
recordError(err) {
|
|
29071
|
+
try {
|
|
29072
|
+
this.metricsAcc.recordError(err);
|
|
29073
|
+
} catch {
|
|
29074
|
+
}
|
|
29075
|
+
}
|
|
28692
29076
|
/** Handle an incoming audio chunk (already decoded from base64). */
|
|
28693
29077
|
/** Forward inbound audio bytes to the AI adapter and (in pipeline mode) the STT provider. */
|
|
28694
29078
|
async handleAudio(audioBuffer) {
|
|
@@ -28715,6 +29099,9 @@ Avoid:
|
|
|
28715
29099
|
);
|
|
28716
29100
|
}
|
|
28717
29101
|
if (evt?.type === "speech_start") {
|
|
29102
|
+
if (this.isSpeaking && this.tailGraceActive) {
|
|
29103
|
+
this.endTailGraceForNewTurn();
|
|
29104
|
+
}
|
|
28718
29105
|
const phantomSuppressed = this.isSpeaking && !this.canBargeIn();
|
|
28719
29106
|
if (phantomSuppressed) {
|
|
28720
29107
|
getLogger().info(
|
|
@@ -28722,7 +29109,8 @@ Avoid:
|
|
|
28722
29109
|
);
|
|
28723
29110
|
this.suppressedSpeechPending = true;
|
|
28724
29111
|
} else if (this.isSpeaking) {
|
|
28725
|
-
|
|
29112
|
+
const deferCancel = this.bargeInStrategies.length > 0 || this.forwardSttWhileSpeaking && !this.aec;
|
|
29113
|
+
if (deferCancel) {
|
|
28726
29114
|
this.startPendingBargeIn();
|
|
28727
29115
|
this.metricsAcc.anchorUserSpeechStart();
|
|
28728
29116
|
return;
|
|
@@ -28732,6 +29120,7 @@ Avoid:
|
|
|
28732
29120
|
this.metricsAcc.recordBargeinDetected();
|
|
28733
29121
|
const bargeinSpan = startSpan(SPAN_BARGEIN, { "patter.call.id": this.callId });
|
|
28734
29122
|
try {
|
|
29123
|
+
this.maybeTruncateCompletedTurnHistory();
|
|
28735
29124
|
this.cancelSpeaking();
|
|
28736
29125
|
try {
|
|
28737
29126
|
this.deps.bridge.sendClear(this.ws, this.streamSid);
|
|
@@ -28776,9 +29165,10 @@ Avoid:
|
|
|
28776
29165
|
if (this.inboundAudioRing.length > _StreamHandler.INBOUND_AUDIO_RING_FRAMES) {
|
|
28777
29166
|
this.inboundAudioRing.shift();
|
|
28778
29167
|
}
|
|
29168
|
+
if (!this.forwardSttWhileSpeaking) return;
|
|
29169
|
+
} else if ((this.deps.agent.bargeInThresholdMs ?? 300) === 0) {
|
|
28779
29170
|
return;
|
|
28780
29171
|
}
|
|
28781
|
-
if ((this.deps.agent.bargeInThresholdMs ?? 300) === 0) return;
|
|
28782
29172
|
}
|
|
28783
29173
|
const hooks = this.deps.agent.hooks;
|
|
28784
29174
|
if (hooks?.beforeSendToStt) {
|
|
@@ -28840,6 +29230,27 @@ Avoid:
|
|
|
28840
29230
|
}
|
|
28841
29231
|
}
|
|
28842
29232
|
}
|
|
29233
|
+
/**
|
|
29234
|
+
* Await the backgrounded turn dispatch during teardown, but never block
|
|
29235
|
+
* longer than ``DISPATCH_SETTLE_TIMEOUT_MS``. The earlier ``llmAbort.abort()``
|
|
29236
|
+
* settles the built-in LLM/TTS paths immediately; the cap only bites a
|
|
29237
|
+
* misbehaving user ``onMessage`` parked on a hung external call (JS promises
|
|
29238
|
+
* can't be cancelled). No-op when nothing is in flight.
|
|
29239
|
+
*/
|
|
29240
|
+
async settleDispatchForTeardown() {
|
|
29241
|
+
if (!this.dispatchTask) return;
|
|
29242
|
+
const settle = this.dispatchTask.catch(() => {
|
|
29243
|
+
});
|
|
29244
|
+
let timer;
|
|
29245
|
+
const cap = new Promise((resolve2) => {
|
|
29246
|
+
timer = setTimeout(resolve2, _StreamHandler.DISPATCH_SETTLE_TIMEOUT_MS);
|
|
29247
|
+
});
|
|
29248
|
+
try {
|
|
29249
|
+
await Promise.race([settle, cap]);
|
|
29250
|
+
} finally {
|
|
29251
|
+
if (timer) clearTimeout(timer);
|
|
29252
|
+
}
|
|
29253
|
+
}
|
|
28843
29254
|
/** Handle call stop / stream end. */
|
|
28844
29255
|
/** Handle a carrier-emitted `stop` event signalling the call has ended. */
|
|
28845
29256
|
async handleStop() {
|
|
@@ -28856,6 +29267,7 @@ Avoid:
|
|
|
28856
29267
|
} catch {
|
|
28857
29268
|
}
|
|
28858
29269
|
}
|
|
29270
|
+
await this.settleDispatchForTeardown();
|
|
28859
29271
|
this.clearPendingBargeIn();
|
|
28860
29272
|
this.drainPendingMarks();
|
|
28861
29273
|
this.clearGraceTimer();
|
|
@@ -28883,6 +29295,7 @@ Avoid:
|
|
|
28883
29295
|
} catch {
|
|
28884
29296
|
}
|
|
28885
29297
|
}
|
|
29298
|
+
await this.settleDispatchForTeardown();
|
|
28886
29299
|
this.clearPendingBargeIn();
|
|
28887
29300
|
this.drainPendingMarks();
|
|
28888
29301
|
this.clearGraceTimer();
|
|
@@ -29277,7 +29690,7 @@ Avoid:
|
|
|
29277
29690
|
};
|
|
29278
29691
|
}
|
|
29279
29692
|
/** Synthesize a single sentence through TTS with hooks, sending audio to telephony. */
|
|
29280
|
-
async synthesizeSentence(sentence, hookExecutor, hookCtx, ttsFirstByteSent) {
|
|
29693
|
+
async synthesizeSentence(sentence, hookExecutor, hookCtx, ttsFirstByteSent, recordSegment = true) {
|
|
29281
29694
|
if (!this.tts || !this.isSpeaking) return;
|
|
29282
29695
|
let transformed = sentence;
|
|
29283
29696
|
const transforms = this.deps.agent.textTransforms;
|
|
@@ -29303,8 +29716,16 @@ Avoid:
|
|
|
29303
29716
|
if (this.aec) {
|
|
29304
29717
|
this.aec.pushFarEnd(processedAudio);
|
|
29305
29718
|
}
|
|
29719
|
+
if (recordSegment) {
|
|
29720
|
+
this.turnSpokenSegments.push({
|
|
29721
|
+
text: processedText,
|
|
29722
|
+
startMs: this.turnPlaybackTotalMs
|
|
29723
|
+
});
|
|
29724
|
+
recordSegment = false;
|
|
29725
|
+
}
|
|
29306
29726
|
const encoded = this.encodePipelineAudio(processedAudio);
|
|
29307
29727
|
this.deps.bridge.sendAudio(this.ws, encoded, this.streamSid);
|
|
29728
|
+
this.trackOutboundPlayback(processedAudio.length);
|
|
29308
29729
|
this.markFirstAudioSent();
|
|
29309
29730
|
}
|
|
29310
29731
|
} catch (e) {
|
|
@@ -29379,64 +29800,101 @@ Avoid:
|
|
|
29379
29800
|
return;
|
|
29380
29801
|
}
|
|
29381
29802
|
this.history.push({ role: "user", text: filteredTranscript, timestamp: Date.now() });
|
|
29382
|
-
let responseText = "";
|
|
29383
29803
|
this.metricsAcc.recordOnUserTurnCompletedDelay(0);
|
|
29384
29804
|
this.metricsAcc.recordTurnCommitted();
|
|
29385
29805
|
closeEndpointSpan();
|
|
29386
|
-
|
|
29387
|
-
|
|
29388
|
-
|
|
29806
|
+
await this.dispatchTask?.catch(() => {
|
|
29807
|
+
});
|
|
29808
|
+
const historySnapshot = [...this.history.entries];
|
|
29809
|
+
this.dispatchTask = this.dispatchTurn(
|
|
29810
|
+
filteredTranscript,
|
|
29811
|
+
hookExecutor,
|
|
29812
|
+
hookCtx,
|
|
29813
|
+
interrupted,
|
|
29814
|
+
historySnapshot
|
|
29815
|
+
);
|
|
29816
|
+
}
|
|
29817
|
+
/**
|
|
29818
|
+
* Post-commit turn body (LLM dispatch → TTS → turn-complete) run as a
|
|
29819
|
+
* tracked background task so the transcript drain loop is not blocked for
|
|
29820
|
+
* the whole (possibly 30-90 s) agent-runtime turn. A barge-in — transcript
|
|
29821
|
+
* (now reachable mid-turn) or VAD — aborts the in-flight ``llmAbort`` and
|
|
29822
|
+
* flips ``isSpeaking``, which the LLM/TTS loops here observe and break on.
|
|
29823
|
+
* Parity with Python ``_dispatch_turn``.
|
|
29824
|
+
*/
|
|
29825
|
+
async dispatchTurn(filteredTranscript, hookExecutor, hookCtx, interrupted, historySnapshot) {
|
|
29826
|
+
const label = this.deps.bridge.label;
|
|
29827
|
+
let responseText = "";
|
|
29828
|
+
try {
|
|
29829
|
+
if (this.deps.onMessage && typeof this.deps.onMessage === "function") {
|
|
29830
|
+
try {
|
|
29831
|
+
responseText = await this.deps.onMessage({
|
|
29832
|
+
text: filteredTranscript,
|
|
29833
|
+
call_id: this.callId,
|
|
29834
|
+
caller: this.caller,
|
|
29835
|
+
callee: this.callee,
|
|
29836
|
+
history: historySnapshot
|
|
29837
|
+
});
|
|
29838
|
+
} catch (e) {
|
|
29839
|
+
getLogger().error(`onMessage error (${label}):`, e);
|
|
29840
|
+
return;
|
|
29841
|
+
}
|
|
29842
|
+
if (!responseText) {
|
|
29843
|
+
getLogger().warn(
|
|
29844
|
+
`onMessage returned empty/void (${label}) \u2014 no TTS will play. If you intended to observe transcripts, use onTranscript instead; if you meant to answer via the built-in LLM, remove onMessage and pass openaiKey.`
|
|
29845
|
+
);
|
|
29846
|
+
}
|
|
29847
|
+
} else if (this.deps.onMessage && isRemoteUrl(this.deps.onMessage)) {
|
|
29848
|
+
const msgData = {
|
|
29389
29849
|
text: filteredTranscript,
|
|
29390
29850
|
call_id: this.callId,
|
|
29391
29851
|
caller: this.caller,
|
|
29392
29852
|
callee: this.callee,
|
|
29393
|
-
history:
|
|
29394
|
-
}
|
|
29395
|
-
|
|
29396
|
-
|
|
29397
|
-
|
|
29398
|
-
|
|
29399
|
-
|
|
29853
|
+
history: historySnapshot
|
|
29854
|
+
};
|
|
29855
|
+
if (isWebSocketUrl(this.deps.onMessage)) {
|
|
29856
|
+
await this.handleWebSocketResponse(msgData);
|
|
29857
|
+
return;
|
|
29858
|
+
}
|
|
29859
|
+
try {
|
|
29860
|
+
responseText = await this.deps.remoteHandler.callWebhook(this.deps.onMessage, msgData);
|
|
29861
|
+
} catch (e) {
|
|
29862
|
+
getLogger().error(`Webhook remote error (${label}):`, e);
|
|
29863
|
+
return;
|
|
29864
|
+
}
|
|
29865
|
+
} else if (this.llmLoop) {
|
|
29866
|
+
const llmResult = await this.runPipelineLlm(
|
|
29867
|
+
filteredTranscript,
|
|
29868
|
+
hookExecutor,
|
|
29869
|
+
hookCtx,
|
|
29870
|
+
historySnapshot
|
|
29871
|
+
);
|
|
29872
|
+
responseText = llmResult.text;
|
|
29873
|
+
interrupted = interrupted || llmResult.interrupted;
|
|
29874
|
+
} else {
|
|
29400
29875
|
getLogger().warn(
|
|
29401
|
-
`
|
|
29876
|
+
`Pipeline (${label}) has no llm/onMessage handler \u2014 transcript "${sanitizeLogValue(filteredTranscript.slice(0, 60))}" dropped. Check that agent.llm or onMessage is configured.`
|
|
29402
29877
|
);
|
|
29403
|
-
}
|
|
29404
|
-
} else if (this.deps.onMessage && isRemoteUrl(this.deps.onMessage)) {
|
|
29405
|
-
const msgData = {
|
|
29406
|
-
text: filteredTranscript,
|
|
29407
|
-
call_id: this.callId,
|
|
29408
|
-
caller: this.caller,
|
|
29409
|
-
callee: this.callee,
|
|
29410
|
-
history: [...this.history.entries]
|
|
29411
|
-
};
|
|
29412
|
-
if (isWebSocketUrl(this.deps.onMessage)) {
|
|
29413
|
-
await this.handleWebSocketResponse(msgData);
|
|
29414
29878
|
return;
|
|
29415
29879
|
}
|
|
29416
|
-
|
|
29417
|
-
|
|
29418
|
-
|
|
29419
|
-
|
|
29420
|
-
|
|
29880
|
+
if (!responseText) return;
|
|
29881
|
+
if (this.llmLoop) {
|
|
29882
|
+
let spokenText = responseText;
|
|
29883
|
+
if (interrupted) {
|
|
29884
|
+
const heard = this.heardResponsePrefix();
|
|
29885
|
+
spokenText = heard === null ? `${responseText} [interrupted by caller]` : heard.text ? `${heard.text} [interrupted by caller]` : "[interrupted by caller]";
|
|
29886
|
+
}
|
|
29887
|
+
await this.emitAssistantTranscript(spokenText);
|
|
29888
|
+
if (!interrupted) this.metricsAcc.recordTtsComplete(responseText);
|
|
29889
|
+
} else {
|
|
29890
|
+
interrupted = await this.runRegularLlm(responseText, hookExecutor, hookCtx) || interrupted;
|
|
29891
|
+
responseText = this.history.entries[this.history.entries.length - 1]?.text ?? responseText;
|
|
29421
29892
|
}
|
|
29422
|
-
|
|
29423
|
-
|
|
29424
|
-
|
|
29425
|
-
|
|
29426
|
-
|
|
29427
|
-
);
|
|
29428
|
-
return;
|
|
29429
|
-
}
|
|
29430
|
-
if (!responseText) return;
|
|
29431
|
-
if (this.llmLoop) {
|
|
29432
|
-
await this.emitAssistantTranscript(responseText);
|
|
29433
|
-
this.metricsAcc.recordTtsComplete(responseText);
|
|
29434
|
-
} else {
|
|
29435
|
-
interrupted = await this.runRegularLlm(responseText, hookExecutor, hookCtx) || interrupted;
|
|
29436
|
-
responseText = this.history.entries[this.history.entries.length - 1]?.text ?? responseText;
|
|
29437
|
-
}
|
|
29438
|
-
if (!interrupted) {
|
|
29439
|
-
await this.emitTurnMetrics(this.metricsAcc.recordTurnComplete(responseText));
|
|
29893
|
+
if (!interrupted) {
|
|
29894
|
+
await this.emitTurnMetrics(this.metricsAcc.recordTurnComplete(responseText));
|
|
29895
|
+
}
|
|
29896
|
+
} finally {
|
|
29897
|
+
this.dispatchTask = null;
|
|
29440
29898
|
}
|
|
29441
29899
|
}
|
|
29442
29900
|
/**
|
|
@@ -29447,6 +29905,18 @@ Avoid:
|
|
|
29447
29905
|
*/
|
|
29448
29906
|
async handleBargeInAsync(transcript) {
|
|
29449
29907
|
if (!transcript.text || !this.isSpeaking) return false;
|
|
29908
|
+
if (this.tailGraceActive) {
|
|
29909
|
+
this.endTailGraceForNewTurn();
|
|
29910
|
+
return false;
|
|
29911
|
+
}
|
|
29912
|
+
if (this.forwardSttWhileSpeaking && looksLikeEcho(transcript.text, this.currentAgentSpokenText)) {
|
|
29913
|
+
getLogger().info(
|
|
29914
|
+
`Barge-in suppressed: transcript matches agent's own speech (echo) \u2014 ${sanitizeLogValue(
|
|
29915
|
+
transcript.text.slice(0, 40)
|
|
29916
|
+
)}`
|
|
29917
|
+
);
|
|
29918
|
+
return false;
|
|
29919
|
+
}
|
|
29450
29920
|
if (!this.canBargeIn()) {
|
|
29451
29921
|
getLogger().info(
|
|
29452
29922
|
`Barge-in transcript suppressed (agent speaking < gate, aec=${this.aec ? "on" : "off"})`
|
|
@@ -29486,6 +29956,18 @@ Avoid:
|
|
|
29486
29956
|
*/
|
|
29487
29957
|
handleBargeIn(transcript) {
|
|
29488
29958
|
if (!transcript.text || !this.isSpeaking) return false;
|
|
29959
|
+
if (this.tailGraceActive) {
|
|
29960
|
+
this.endTailGraceForNewTurn();
|
|
29961
|
+
return false;
|
|
29962
|
+
}
|
|
29963
|
+
if (this.forwardSttWhileSpeaking && looksLikeEcho(transcript.text, this.currentAgentSpokenText)) {
|
|
29964
|
+
getLogger().info(
|
|
29965
|
+
`Barge-in suppressed: transcript matches agent's own speech (echo) \u2014 ${sanitizeLogValue(
|
|
29966
|
+
transcript.text.slice(0, 40)
|
|
29967
|
+
)}`
|
|
29968
|
+
);
|
|
29969
|
+
return false;
|
|
29970
|
+
}
|
|
29489
29971
|
if (this.bargeInStrategies.length === 0) {
|
|
29490
29972
|
if (!this.canBargeIn()) {
|
|
29491
29973
|
getLogger().info(
|
|
@@ -29517,6 +29999,7 @@ Avoid:
|
|
|
29517
29999
|
this.metricsAcc.recordBargeinDetected();
|
|
29518
30000
|
const bargeinSpan = startSpan(SPAN_BARGEIN, { "patter.call.id": this.callId });
|
|
29519
30001
|
try {
|
|
30002
|
+
this.maybeTruncateCompletedTurnHistory();
|
|
29520
30003
|
this.cancelSpeaking();
|
|
29521
30004
|
try {
|
|
29522
30005
|
this.deps.bridge.sendClear(this.ws, this.streamSid);
|
|
@@ -29580,15 +30063,21 @@ Avoid:
|
|
|
29580
30063
|
getLogger().debug(`Dropped likely STT hallucination: ${sanitizeLogValue(normalised.slice(0, 40))}`);
|
|
29581
30064
|
return false;
|
|
29582
30065
|
}
|
|
30066
|
+
if (this.forwardSttWhileSpeaking && this.isSpeaking && looksLikeEcho(text, this.currentAgentSpokenText)) {
|
|
30067
|
+
getLogger().debug(
|
|
30068
|
+
`Dropped agent-echo transcript (not a user turn): ${sanitizeLogValue(normalised.slice(0, 40))}`
|
|
30069
|
+
);
|
|
30070
|
+
return false;
|
|
30071
|
+
}
|
|
29583
30072
|
if (sinceLastMs < 2e3 && normalised === this.lastCommitText) {
|
|
29584
30073
|
getLogger().debug(
|
|
29585
30074
|
`Dropped duplicate final transcript (${(sinceLastMs / 1e3).toFixed(1)}s since last): ${sanitizeLogValue(normalised.slice(0, 40))}`
|
|
29586
30075
|
);
|
|
29587
30076
|
return false;
|
|
29588
30077
|
}
|
|
29589
|
-
if (sinceLastMs < 500) {
|
|
30078
|
+
if (sinceLastMs < 500 && isNearDuplicate(normalised, this.lastCommitText)) {
|
|
29590
30079
|
getLogger().debug(
|
|
29591
|
-
`Dropped back-to-back final
|
|
30080
|
+
`Dropped back-to-back near-duplicate final (${(sinceLastMs / 1e3).toFixed(2)}s since last): ${sanitizeLogValue(normalised.slice(0, 40))}`
|
|
29592
30081
|
);
|
|
29593
30082
|
return false;
|
|
29594
30083
|
}
|
|
@@ -29596,11 +30085,63 @@ Avoid:
|
|
|
29596
30085
|
this.lastCommitAt = now;
|
|
29597
30086
|
return true;
|
|
29598
30087
|
}
|
|
30088
|
+
/**
|
|
30089
|
+
* Schedule the opt-in long-turn filler and return its async ``clear()``.
|
|
30090
|
+
*
|
|
30091
|
+
* When ``agent.longTurnMessage`` is unset / empty the returned clear is a
|
|
30092
|
+
* no-op (byte-identical to today's behaviour). Otherwise a one-shot timer
|
|
30093
|
+
* fires after ``agent.longTurnMessageAfterS`` seconds and, IFF no audio has
|
|
30094
|
+
* reached the carrier this turn (``!ttsFirstByteSent.value``) AND we still own
|
|
30095
|
+
* the floor (``this.isSpeaking``), synthesizes the filler ONCE via the same
|
|
30096
|
+
* per-sentence TTS primitive every sentence uses.
|
|
30097
|
+
*
|
|
30098
|
+
* The returned ``clear()`` is **async**: it stops the timer AND, if the filler
|
|
30099
|
+
* already started synthesizing (its ``setTimeout`` callback runs in a separate
|
|
30100
|
+
* macro-task, so it can fire just before the first real sentence), AWAITS the
|
|
30101
|
+
* in-flight synthesis so the filler audio can never interleave with the real
|
|
30102
|
+
* sentence that follows. Idempotent; self-synthesis failure degrades to
|
|
30103
|
+
* silence (never crashes the turn). The caller must clear on first real audio,
|
|
30104
|
+
* on the error branch, and in the finally.
|
|
30105
|
+
*/
|
|
30106
|
+
scheduleLongTurnFiller(ttsFirstByteSent, hookExecutor, hookCtx, label) {
|
|
30107
|
+
const message = this.deps.agent.longTurnMessage;
|
|
30108
|
+
if (!message) return async () => {
|
|
30109
|
+
};
|
|
30110
|
+
const afterS = this.deps.agent.longTurnMessageAfterS ?? 4;
|
|
30111
|
+
let cancelled = false;
|
|
30112
|
+
let inFlight = null;
|
|
30113
|
+
const timer = setTimeout(() => {
|
|
30114
|
+
if (cancelled || ttsFirstByteSent.value || !this.isSpeaking) return;
|
|
30115
|
+
inFlight = this.synthesizeSentence(
|
|
30116
|
+
message,
|
|
30117
|
+
hookExecutor,
|
|
30118
|
+
hookCtx,
|
|
30119
|
+
ttsFirstByteSent,
|
|
30120
|
+
false
|
|
30121
|
+
).catch((err) => {
|
|
30122
|
+
getLogger().error(
|
|
30123
|
+
`longTurnMessage filler synthesis failed (${label}):`,
|
|
30124
|
+
err
|
|
30125
|
+
);
|
|
30126
|
+
});
|
|
30127
|
+
}, Math.max(0, afterS * 1e3));
|
|
30128
|
+
return async () => {
|
|
30129
|
+
cancelled = true;
|
|
30130
|
+
clearTimeout(timer);
|
|
30131
|
+
if (inFlight !== null) {
|
|
30132
|
+
const pending = inFlight;
|
|
30133
|
+
inFlight = null;
|
|
30134
|
+
await pending;
|
|
30135
|
+
}
|
|
30136
|
+
};
|
|
30137
|
+
}
|
|
29599
30138
|
/**
|
|
29600
30139
|
* Streaming built-in LLM path with sentence chunking and per-sentence
|
|
29601
|
-
* guardrails/TTS. Returns the concatenated response text
|
|
30140
|
+
* guardrails/TTS. Returns the concatenated (plain) response text plus whether
|
|
30141
|
+
* the turn was cut short by a barge-in — the caller applies the interrupted
|
|
30142
|
+
* marker to history only, keeping metrics on the plain text.
|
|
29602
30143
|
*/
|
|
29603
|
-
async runPipelineLlm(filteredTranscript, hookExecutor, hookCtx) {
|
|
30144
|
+
async runPipelineLlm(filteredTranscript, hookExecutor, hookCtx, historySnapshot) {
|
|
29604
30145
|
const label = this.deps.bridge.label;
|
|
29605
30146
|
const callCtx = { call_id: this.callId, caller: this.caller, callee: this.callee };
|
|
29606
30147
|
const chunker = new SentenceChunker({
|
|
@@ -29613,6 +30154,12 @@ Avoid:
|
|
|
29613
30154
|
this.llmAbort = new AbortController();
|
|
29614
30155
|
const llmSignal = this.llmAbort.signal;
|
|
29615
30156
|
let llmError = false;
|
|
30157
|
+
const clearLongTurnFiller = this.scheduleLongTurnFiller(
|
|
30158
|
+
ttsFirstByteSent,
|
|
30159
|
+
hookExecutor,
|
|
30160
|
+
hookCtx,
|
|
30161
|
+
label
|
|
30162
|
+
);
|
|
29616
30163
|
const llmSpan = startSpan(SPAN_LLM, { "patter.call.id": this.callId });
|
|
29617
30164
|
const guardAndSpeak = async (sentence, isFirst) => {
|
|
29618
30165
|
if (isFirst) this.metricsAcc.recordLlmFirstSentenceComplete();
|
|
@@ -29623,6 +30170,7 @@ Avoid:
|
|
|
29623
30170
|
if (transformed === null) return;
|
|
29624
30171
|
sentenceText = transformed;
|
|
29625
30172
|
}
|
|
30173
|
+
await clearLongTurnFiller();
|
|
29626
30174
|
await this.synthesizeSentence(sentenceText, hookExecutor, hookCtx, ttsFirstByteSent);
|
|
29627
30175
|
};
|
|
29628
30176
|
let firstSentenceEmitted = false;
|
|
@@ -29630,7 +30178,7 @@ Avoid:
|
|
|
29630
30178
|
try {
|
|
29631
30179
|
for await (const token of this.llmLoop.run(
|
|
29632
30180
|
filteredTranscript,
|
|
29633
|
-
|
|
30181
|
+
historySnapshot,
|
|
29634
30182
|
callCtx,
|
|
29635
30183
|
this.metricsAcc,
|
|
29636
30184
|
hookExecutor,
|
|
@@ -29641,6 +30189,7 @@ Avoid:
|
|
|
29641
30189
|
this.metricsAcc.recordLlmFirstToken();
|
|
29642
30190
|
await this.emitLlmFirstToken();
|
|
29643
30191
|
allParts.push(token);
|
|
30192
|
+
this.currentAgentSpokenText = allParts.join("");
|
|
29644
30193
|
for (const sentence of chunker.push(token)) {
|
|
29645
30194
|
if (!this.isSpeaking) break;
|
|
29646
30195
|
await guardAndSpeak(sentence, !firstSentenceEmitted);
|
|
@@ -29650,11 +30199,20 @@ Avoid:
|
|
|
29650
30199
|
}
|
|
29651
30200
|
} catch (e) {
|
|
29652
30201
|
const isAbort = e?.name === "AbortError" || llmSignal.aborted;
|
|
30202
|
+
await clearLongTurnFiller();
|
|
29653
30203
|
if (!isAbort) {
|
|
29654
30204
|
llmError = true;
|
|
29655
30205
|
chunker.reset();
|
|
29656
30206
|
getLogger().error(`LLM loop error (${label}):`, e);
|
|
29657
30207
|
this.metricsAcc.recordTurnInterrupted();
|
|
30208
|
+
const fallback = this.deps.agent.llmErrorMessage;
|
|
30209
|
+
if (fallback && !ttsFirstByteSent.value && this.isSpeaking) {
|
|
30210
|
+
try {
|
|
30211
|
+
await this.synthesizeSentence(fallback, hookExecutor, hookCtx, ttsFirstByteSent, false);
|
|
30212
|
+
} catch (err) {
|
|
30213
|
+
getLogger().error(`llmErrorMessage fallback synthesis failed (${label}):`, err);
|
|
30214
|
+
}
|
|
30215
|
+
}
|
|
29658
30216
|
}
|
|
29659
30217
|
}
|
|
29660
30218
|
this.metricsAcc.recordLlmComplete();
|
|
@@ -29666,6 +30224,7 @@ Avoid:
|
|
|
29666
30224
|
}
|
|
29667
30225
|
}
|
|
29668
30226
|
} finally {
|
|
30227
|
+
await clearLongTurnFiller();
|
|
29669
30228
|
this.endSpeakingWithGrace();
|
|
29670
30229
|
this.llmAbort = null;
|
|
29671
30230
|
try {
|
|
@@ -29673,7 +30232,7 @@ Avoid:
|
|
|
29673
30232
|
} catch {
|
|
29674
30233
|
}
|
|
29675
30234
|
}
|
|
29676
|
-
return allParts.join("");
|
|
30235
|
+
return { text: allParts.join(""), interrupted: llmSignal.aborted };
|
|
29677
30236
|
}
|
|
29678
30237
|
/**
|
|
29679
30238
|
* Non-streaming path (onMessage function / webhook): apply output guardrails,
|
|
@@ -30760,7 +31319,7 @@ function validateTelnyxSignature(rawBody, signature, timestamp, publicKey, toler
|
|
|
30760
31319
|
if (!Number.isFinite(ts)) return false;
|
|
30761
31320
|
const tsMs = ts < 1e12 ? ts * 1e3 : ts;
|
|
30762
31321
|
const ageMs = Date.now() - tsMs;
|
|
30763
|
-
if (ageMs
|
|
31322
|
+
if (ageMs > toleranceSec * 1e3 || ageMs < -TELNYX_FUTURE_SKEW_MS) return false;
|
|
30764
31323
|
const payload = `${timestamp}|${rawBody}`;
|
|
30765
31324
|
const keyBuffer = Buffer.from(publicKey, "base64");
|
|
30766
31325
|
const keyObject = import_node_crypto4.default.createPublicKey({
|
|
@@ -30806,7 +31365,7 @@ function sanitizeVariables(raw) {
|
|
|
30806
31365
|
for (const key of Object.keys(raw)) {
|
|
30807
31366
|
if (BLOCKED_KEYS.has(key)) continue;
|
|
30808
31367
|
const val = raw[key];
|
|
30809
|
-
safe[key] = typeof val === "string" ? val : String(val ?? "");
|
|
31368
|
+
safe[key] = (typeof val === "string" ? val : String(val ?? "")).replace(/[\x00-\x1f\x7f]/g, "").slice(0, 500);
|
|
30810
31369
|
}
|
|
30811
31370
|
return safe;
|
|
30812
31371
|
}
|
|
@@ -30901,7 +31460,7 @@ async function sleep(ms) {
|
|
|
30901
31460
|
if (ms <= 0) return;
|
|
30902
31461
|
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
30903
31462
|
}
|
|
30904
|
-
var import_node_crypto4, import_express, import_http, import_ws5, TRANSFER_CALL_TOOL, END_CALL_TOOL, TwilioBridge, TELNYX_DTMF_ALLOWED, TELNYX_DTMF_DURATION_MS, TelnyxBridge, GRACEFUL_SHUTDOWN_TIMEOUT_MS, EmbeddedServer;
|
|
31463
|
+
var import_node_crypto4, import_express, import_http, import_ws5, TRANSFER_CALL_TOOL, END_CALL_TOOL, TELNYX_FUTURE_SKEW_MS, TwilioBridge, TELNYX_DTMF_ALLOWED, TELNYX_DTMF_DURATION_MS, TelnyxBridge, GRACEFUL_SHUTDOWN_TIMEOUT_MS, EmbeddedServer;
|
|
30905
31464
|
var init_server = __esm({
|
|
30906
31465
|
"src/server.ts"() {
|
|
30907
31466
|
"use strict";
|
|
@@ -30910,6 +31469,7 @@ var init_server = __esm({
|
|
|
30910
31469
|
import_express = __toESM(require("express"));
|
|
30911
31470
|
import_http = require("http");
|
|
30912
31471
|
import_ws5 = require("ws");
|
|
31472
|
+
init_call_metrics();
|
|
30913
31473
|
init_openai_realtime_2();
|
|
30914
31474
|
init_elevenlabs_convai();
|
|
30915
31475
|
init_plivo_adapter();
|
|
@@ -30949,6 +31509,7 @@ var init_server = __esm({
|
|
|
30949
31509
|
}
|
|
30950
31510
|
}
|
|
30951
31511
|
};
|
|
31512
|
+
TELNYX_FUTURE_SKEW_MS = 3e4;
|
|
30952
31513
|
TwilioBridge = class {
|
|
30953
31514
|
constructor(config2) {
|
|
30954
31515
|
this.config = config2;
|
|
@@ -31250,6 +31811,9 @@ var init_server = __esm({
|
|
|
31250
31811
|
twilioTokenWarningLogged = false;
|
|
31251
31812
|
telnyxSigWarningLogged = false;
|
|
31252
31813
|
metricsStore;
|
|
31814
|
+
/** Anonymous telemetry client, set by ``client.ts`` ``serve()``; emits the
|
|
31815
|
+
* per-call ``call_completed`` event from the call-end path. */
|
|
31816
|
+
telemetry;
|
|
31253
31817
|
pricing;
|
|
31254
31818
|
remoteHandler = new RemoteMessageHandler();
|
|
31255
31819
|
/**
|
|
@@ -31353,6 +31917,12 @@ var init_server = __esm({
|
|
|
31353
31917
|
* Mirrors Python's ``_resolve_completion``.
|
|
31354
31918
|
*/
|
|
31355
31919
|
resolveCompletion(callId, args) {
|
|
31920
|
+
if (args.outcome === "no_answer" || args.outcome === "busy" || args.outcome === "failed") {
|
|
31921
|
+
recordCallCompleted(this.telemetry, {
|
|
31922
|
+
outcome: args.outcome,
|
|
31923
|
+
carrier: this.config.telephonyProvider
|
|
31924
|
+
});
|
|
31925
|
+
}
|
|
31356
31926
|
const entry = this.completions.get(callId);
|
|
31357
31927
|
if (!entry || entry.done) return;
|
|
31358
31928
|
const data = args.data;
|
|
@@ -32101,7 +32671,13 @@ var init_server = __esm({
|
|
|
32101
32671
|
return Object.fromEntries(Object.entries(snap).filter(([, v]) => v !== void 0));
|
|
32102
32672
|
};
|
|
32103
32673
|
const store = this.metricsStore;
|
|
32674
|
+
const telemetry = this.telemetry;
|
|
32104
32675
|
const wrappedStart = async (data) => {
|
|
32676
|
+
recordCallStarted(telemetry, {
|
|
32677
|
+
providerMode: agent.provider ?? void 0,
|
|
32678
|
+
telephonyProvider: bridge.telephonyProvider,
|
|
32679
|
+
direction: data.direction
|
|
32680
|
+
});
|
|
32105
32681
|
if (logger2.enabled) {
|
|
32106
32682
|
const callId = typeof data.call_id === "string" ? data.call_id : "";
|
|
32107
32683
|
const dataCaller = typeof data.caller === "string" ? data.caller : "";
|
|
@@ -32132,6 +32708,11 @@ var init_server = __esm({
|
|
|
32132
32708
|
if (userMetrics) await userMetrics(data);
|
|
32133
32709
|
};
|
|
32134
32710
|
const wrappedEnd = async (data) => {
|
|
32711
|
+
recordCallCompleted(this.telemetry, {
|
|
32712
|
+
outcome: "completed",
|
|
32713
|
+
metrics: data.metrics,
|
|
32714
|
+
direction: data.direction
|
|
32715
|
+
});
|
|
32135
32716
|
if (logger2.enabled) {
|
|
32136
32717
|
const callId = typeof data.call_id === "string" ? data.call_id : "";
|
|
32137
32718
|
const metricsObj = data.metrics ?? null;
|
|
@@ -32187,7 +32768,7 @@ var init_server = __esm({
|
|
|
32187
32768
|
await handler.handleCallStart(callSid, customParameters);
|
|
32188
32769
|
} else if (event === "media") {
|
|
32189
32770
|
const payload = data.media?.payload ?? "";
|
|
32190
|
-
handler.handleAudio(Buffer.from(payload, "base64"));
|
|
32771
|
+
await handler.handleAudio(Buffer.from(payload, "base64"));
|
|
32191
32772
|
} else if (event === "mark") {
|
|
32192
32773
|
const markName = String(data.mark?.name ?? "");
|
|
32193
32774
|
if (markName) await handler.onMark(markName);
|
|
@@ -32199,6 +32780,7 @@ var init_server = __esm({
|
|
|
32199
32780
|
}
|
|
32200
32781
|
} catch (err) {
|
|
32201
32782
|
getLogger().error("Stream handler error:", err);
|
|
32783
|
+
handler.recordError(err);
|
|
32202
32784
|
}
|
|
32203
32785
|
});
|
|
32204
32786
|
ws.on("close", async () => {
|
|
@@ -32243,7 +32825,7 @@ var init_server = __esm({
|
|
|
32243
32825
|
if (track !== "inbound") return;
|
|
32244
32826
|
const audioChunk = data.media?.payload ?? "";
|
|
32245
32827
|
if (!audioChunk) return;
|
|
32246
|
-
handler.handleAudio(Buffer.from(audioChunk, "base64"));
|
|
32828
|
+
await handler.handleAudio(Buffer.from(audioChunk, "base64"));
|
|
32247
32829
|
} else if (event === "dtmf") {
|
|
32248
32830
|
const digit = String(data.dtmf?.digit ?? "").trim();
|
|
32249
32831
|
if (digit) {
|
|
@@ -32257,9 +32839,11 @@ var init_server = __esm({
|
|
|
32257
32839
|
}
|
|
32258
32840
|
} catch (err) {
|
|
32259
32841
|
getLogger().error("Stream handler error (Telnyx):", err);
|
|
32842
|
+
handler.recordError(err);
|
|
32260
32843
|
}
|
|
32261
32844
|
});
|
|
32262
32845
|
ws.on("close", async () => {
|
|
32846
|
+
this.activeCallIds.delete(ws);
|
|
32263
32847
|
await handler.handleWsClose();
|
|
32264
32848
|
});
|
|
32265
32849
|
}
|
|
@@ -32288,7 +32872,7 @@ var init_server = __esm({
|
|
|
32288
32872
|
await handler.handleCallStart(callId);
|
|
32289
32873
|
} else if (event === "media") {
|
|
32290
32874
|
const payload = data.media?.payload ?? "";
|
|
32291
|
-
if (payload) handler.handleAudio(Buffer.from(payload, "base64"));
|
|
32875
|
+
if (payload) await handler.handleAudio(Buffer.from(payload, "base64"));
|
|
32292
32876
|
} else if (event === "playedStream") {
|
|
32293
32877
|
const markName = String(data.name ?? "");
|
|
32294
32878
|
if (markName) await handler.onMark(markName);
|
|
@@ -32302,6 +32886,7 @@ var init_server = __esm({
|
|
|
32302
32886
|
}
|
|
32303
32887
|
} catch (err) {
|
|
32304
32888
|
getLogger().error("Stream handler error (Plivo):", err);
|
|
32889
|
+
handler.recordError(err);
|
|
32305
32890
|
}
|
|
32306
32891
|
});
|
|
32307
32892
|
ws.on("close", async () => {
|
|
@@ -34182,6 +34767,7 @@ __export(index_exports, {
|
|
|
34182
34767
|
CerebrasLLM: () => LLM4,
|
|
34183
34768
|
ChatContext: () => ChatContext,
|
|
34184
34769
|
CloudflareTunnel: () => CloudflareTunnel,
|
|
34770
|
+
CustomLLM: () => LLM7,
|
|
34185
34771
|
DEFAULT_MIN_SENTENCE_LEN: () => DEFAULT_MIN_SENTENCE_LEN,
|
|
34186
34772
|
DEFAULT_PRICING: () => DEFAULT_PRICING,
|
|
34187
34773
|
DTMF_EVENTS: () => DTMF_EVENTS,
|
|
@@ -34205,6 +34791,7 @@ __export(index_exports, {
|
|
|
34205
34791
|
GoogleLLM: () => LLM5,
|
|
34206
34792
|
GroqLLM: () => LLM3,
|
|
34207
34793
|
Guardrail: () => Guardrail,
|
|
34794
|
+
HermesLLM: () => LLM8,
|
|
34208
34795
|
IVRActivity: () => IVRActivity,
|
|
34209
34796
|
InworldTTS: () => TTS7,
|
|
34210
34797
|
KrispFrameDuration: () => KrispFrameDuration,
|
|
@@ -34215,6 +34802,8 @@ __export(index_exports, {
|
|
|
34215
34802
|
MetricsStore: () => MetricsStore,
|
|
34216
34803
|
MinWordsStrategy: () => MinWordsStrategy,
|
|
34217
34804
|
Ngrok: () => Ngrok,
|
|
34805
|
+
OpenAICompatibleLLM: () => LLM6,
|
|
34806
|
+
OpenAICompatibleLLMProvider: () => OpenAICompatibleLLMProvider,
|
|
34218
34807
|
OpenAILLM: () => LLM,
|
|
34219
34808
|
OpenAILLMProvider: () => OpenAILLMProvider,
|
|
34220
34809
|
OpenAIRealtime: () => Realtime,
|
|
@@ -34228,6 +34817,7 @@ __export(index_exports, {
|
|
|
34228
34817
|
OpenAITranscribeSTT: () => STT3,
|
|
34229
34818
|
OpenAITranscriptionModel: () => OpenAITranscriptionModel,
|
|
34230
34819
|
OpenAIVoice: () => OpenAIVoice,
|
|
34820
|
+
OpenClawLLM: () => LLM9,
|
|
34231
34821
|
PRICING_LAST_UPDATED: () => PRICING_LAST_UPDATED,
|
|
34232
34822
|
PRICING_VERSION: () => PRICING_VERSION,
|
|
34233
34823
|
PartialStreamError: () => PartialStreamError,
|
|
@@ -34296,6 +34886,7 @@ __export(index_exports, {
|
|
|
34296
34886
|
createResampler24kTo16k: () => createResampler24kTo16k,
|
|
34297
34887
|
createResampler24kTo8k: () => createResampler24kTo8k,
|
|
34298
34888
|
createResampler8kTo16k: () => createResampler8kTo16k,
|
|
34889
|
+
custom: () => custom2,
|
|
34299
34890
|
deepgram: () => deepgram,
|
|
34300
34891
|
defineTool: () => defineTool,
|
|
34301
34892
|
elevenlabs: () => elevenlabs,
|
|
@@ -34307,6 +34898,8 @@ __export(index_exports, {
|
|
|
34307
34898
|
geminiLive: () => geminiLive,
|
|
34308
34899
|
getLogger: () => getLogger,
|
|
34309
34900
|
guardrail: () => guardrail,
|
|
34901
|
+
hashCaller: () => hashCaller,
|
|
34902
|
+
hermes: () => hermes,
|
|
34310
34903
|
initTracing: () => initTracing,
|
|
34311
34904
|
isRemoteUrl: () => isRemoteUrl,
|
|
34312
34905
|
isTracingEnabled: () => isTracingEnabled,
|
|
@@ -34319,7 +34912,9 @@ __export(index_exports, {
|
|
|
34319
34912
|
mountDashboard: () => mountDashboard,
|
|
34320
34913
|
mulawToPcm16: () => mulawToPcm16,
|
|
34321
34914
|
notifyDashboard: () => notifyDashboard,
|
|
34915
|
+
openaiCompatible: () => openaiCompatible,
|
|
34322
34916
|
openaiTts: () => openaiTts,
|
|
34917
|
+
openclaw: () => openclaw,
|
|
34323
34918
|
openclawConsult: () => openclawConsult,
|
|
34324
34919
|
openclawPostCallNotifier: () => openclawPostCallNotifier,
|
|
34325
34920
|
pcm16ToMulaw: () => pcm16ToMulaw,
|
|
@@ -34350,6 +34945,60 @@ init_cjs_shims();
|
|
|
34350
34945
|
init_errors();
|
|
34351
34946
|
init_server();
|
|
34352
34947
|
|
|
34948
|
+
// src/telephony/twilio.ts
|
|
34949
|
+
init_cjs_shims();
|
|
34950
|
+
var Carrier2 = class {
|
|
34951
|
+
kind = "twilio";
|
|
34952
|
+
accountSid;
|
|
34953
|
+
authToken;
|
|
34954
|
+
constructor(opts = {}) {
|
|
34955
|
+
const sid = opts.accountSid ?? process.env.TWILIO_ACCOUNT_SID;
|
|
34956
|
+
const tok = opts.authToken ?? process.env.TWILIO_AUTH_TOKEN;
|
|
34957
|
+
if (!sid) {
|
|
34958
|
+
throw new Error(
|
|
34959
|
+
"Twilio carrier requires accountSid. Pass { accountSid: 'AC...' } or set TWILIO_ACCOUNT_SID in the environment."
|
|
34960
|
+
);
|
|
34961
|
+
}
|
|
34962
|
+
if (!tok) {
|
|
34963
|
+
throw new Error(
|
|
34964
|
+
"Twilio carrier requires authToken. Pass { authToken: '...' } or set TWILIO_AUTH_TOKEN in the environment."
|
|
34965
|
+
);
|
|
34966
|
+
}
|
|
34967
|
+
this.accountSid = sid;
|
|
34968
|
+
this.authToken = tok;
|
|
34969
|
+
}
|
|
34970
|
+
};
|
|
34971
|
+
|
|
34972
|
+
// src/telephony/telnyx.ts
|
|
34973
|
+
init_cjs_shims();
|
|
34974
|
+
var Carrier3 = class {
|
|
34975
|
+
kind = "telnyx";
|
|
34976
|
+
apiKey;
|
|
34977
|
+
connectionId;
|
|
34978
|
+
publicKey;
|
|
34979
|
+
constructor(opts = {}) {
|
|
34980
|
+
const key = opts.apiKey ?? process.env.TELNYX_API_KEY;
|
|
34981
|
+
const conn = opts.connectionId ?? process.env.TELNYX_CONNECTION_ID;
|
|
34982
|
+
const pub = opts.publicKey ?? process.env.TELNYX_PUBLIC_KEY;
|
|
34983
|
+
if (!key) {
|
|
34984
|
+
throw new Error(
|
|
34985
|
+
"Telnyx carrier requires apiKey. Pass { apiKey: '...' } or set TELNYX_API_KEY in the environment."
|
|
34986
|
+
);
|
|
34987
|
+
}
|
|
34988
|
+
if (!conn) {
|
|
34989
|
+
throw new Error(
|
|
34990
|
+
"Telnyx carrier requires connectionId. Pass { connectionId: '...' } or set TELNYX_CONNECTION_ID in the environment."
|
|
34991
|
+
);
|
|
34992
|
+
}
|
|
34993
|
+
this.apiKey = key;
|
|
34994
|
+
this.connectionId = conn;
|
|
34995
|
+
this.publicKey = pub;
|
|
34996
|
+
}
|
|
34997
|
+
};
|
|
34998
|
+
|
|
34999
|
+
// src/client.ts
|
|
35000
|
+
init_plivo();
|
|
35001
|
+
|
|
34353
35002
|
// src/engines/openai.ts
|
|
34354
35003
|
init_cjs_shims();
|
|
34355
35004
|
init_openai_realtime();
|
|
@@ -34576,6 +35225,570 @@ function validateAllToolSchemas(tools) {
|
|
|
34576
35225
|
// src/client.ts
|
|
34577
35226
|
init_logger();
|
|
34578
35227
|
|
|
35228
|
+
// src/telemetry/index.ts
|
|
35229
|
+
init_cjs_shims();
|
|
35230
|
+
|
|
35231
|
+
// src/telemetry/client.ts
|
|
35232
|
+
init_cjs_shims();
|
|
35233
|
+
init_logger();
|
|
35234
|
+
|
|
35235
|
+
// src/telemetry/consent.ts
|
|
35236
|
+
init_cjs_shims();
|
|
35237
|
+
|
|
35238
|
+
// src/telemetry/env.ts
|
|
35239
|
+
init_cjs_shims();
|
|
35240
|
+
var CI_ENV_VARS = [
|
|
35241
|
+
"CI",
|
|
35242
|
+
"CONTINUOUS_INTEGRATION",
|
|
35243
|
+
"GITHUB_ACTIONS",
|
|
35244
|
+
"GITLAB_CI",
|
|
35245
|
+
"TRAVIS",
|
|
35246
|
+
"CIRCLECI",
|
|
35247
|
+
"APPVEYOR",
|
|
35248
|
+
"TF_BUILD",
|
|
35249
|
+
"TEAMCITY_VERSION",
|
|
35250
|
+
"BUILDKITE",
|
|
35251
|
+
"DRONE",
|
|
35252
|
+
"JENKINS_URL",
|
|
35253
|
+
"HUDSON_URL",
|
|
35254
|
+
"BAMBOO_BUILDKEY",
|
|
35255
|
+
"CODEBUILD_BUILD_ID"
|
|
35256
|
+
];
|
|
35257
|
+
var TEST_ENV_VARS = ["VITEST", "JEST_WORKER_ID"];
|
|
35258
|
+
function isTruthy(value) {
|
|
35259
|
+
if (value === void 0) return false;
|
|
35260
|
+
const v = value.trim().toLowerCase();
|
|
35261
|
+
return v !== "" && v !== "0" && v !== "false" && v !== "no" && v !== "off";
|
|
35262
|
+
}
|
|
35263
|
+
function isCi() {
|
|
35264
|
+
return CI_ENV_VARS.some((name) => isTruthy(process.env[name]));
|
|
35265
|
+
}
|
|
35266
|
+
function isTest() {
|
|
35267
|
+
if (TEST_ENV_VARS.some((name) => process.env[name] !== void 0)) return true;
|
|
35268
|
+
const node = (process.env.NODE_ENV ?? "").trim().toLowerCase();
|
|
35269
|
+
const patter = (process.env.PATTER_ENV ?? "").trim().toLowerCase();
|
|
35270
|
+
return node === "test" || patter === "test";
|
|
35271
|
+
}
|
|
35272
|
+
|
|
35273
|
+
// src/telemetry/install-id.ts
|
|
35274
|
+
init_cjs_shims();
|
|
35275
|
+
var import_node_crypto5 = require("crypto");
|
|
35276
|
+
var fs5 = __toESM(require("fs"));
|
|
35277
|
+
var os2 = __toESM(require("os"));
|
|
35278
|
+
var path5 = __toESM(require("path"));
|
|
35279
|
+
var RUN_ID = (0, import_node_crypto5.randomUUID)().replace(/-/g, "");
|
|
35280
|
+
var HEX32 = /^[0-9a-f]{32}$/;
|
|
35281
|
+
var VERSION_RE = /^[0-9][0-9a-z.+-]{0,31}$/;
|
|
35282
|
+
var cachedInstallId = null;
|
|
35283
|
+
function runId() {
|
|
35284
|
+
return RUN_ID;
|
|
35285
|
+
}
|
|
35286
|
+
function statePath() {
|
|
35287
|
+
const base = process.env.PATTER_TELEMETRY_STATE_DIR || process.env.XDG_STATE_HOME;
|
|
35288
|
+
const root = base && base.length > 0 ? base : path5.join(os2.homedir(), ".getpatter");
|
|
35289
|
+
return path5.join(root, "install-id");
|
|
35290
|
+
}
|
|
35291
|
+
function installId() {
|
|
35292
|
+
if (cachedInstallId !== null) return cachedInstallId;
|
|
35293
|
+
const p = statePath();
|
|
35294
|
+
try {
|
|
35295
|
+
const existing = fs5.readFileSync(p, "utf8").trim();
|
|
35296
|
+
if (HEX32.test(existing)) {
|
|
35297
|
+
cachedInstallId = existing;
|
|
35298
|
+
return cachedInstallId;
|
|
35299
|
+
}
|
|
35300
|
+
} catch {
|
|
35301
|
+
}
|
|
35302
|
+
const newId = (0, import_node_crypto5.randomUUID)().replace(/-/g, "");
|
|
35303
|
+
try {
|
|
35304
|
+
fs5.mkdirSync(path5.dirname(p), { recursive: true });
|
|
35305
|
+
fs5.writeFileSync(p, newId, "utf8");
|
|
35306
|
+
cachedInstallId = newId;
|
|
35307
|
+
} catch {
|
|
35308
|
+
cachedInstallId = RUN_ID;
|
|
35309
|
+
}
|
|
35310
|
+
return cachedInstallId;
|
|
35311
|
+
}
|
|
35312
|
+
function versionPath() {
|
|
35313
|
+
return path5.join(path5.dirname(statePath()), "version");
|
|
35314
|
+
}
|
|
35315
|
+
function previousVersion(current) {
|
|
35316
|
+
const p = versionPath();
|
|
35317
|
+
let prev = "";
|
|
35318
|
+
try {
|
|
35319
|
+
prev = fs5.readFileSync(p, "utf8").trim();
|
|
35320
|
+
} catch {
|
|
35321
|
+
prev = "";
|
|
35322
|
+
}
|
|
35323
|
+
try {
|
|
35324
|
+
fs5.mkdirSync(path5.dirname(p), { recursive: true });
|
|
35325
|
+
fs5.writeFileSync(p, current, "utf8");
|
|
35326
|
+
} catch {
|
|
35327
|
+
}
|
|
35328
|
+
return VERSION_RE.test(prev) ? prev : "";
|
|
35329
|
+
}
|
|
35330
|
+
function daysSinceInstallBucket() {
|
|
35331
|
+
let mtimeMs;
|
|
35332
|
+
try {
|
|
35333
|
+
mtimeMs = fs5.statSync(statePath()).mtimeMs;
|
|
35334
|
+
} catch {
|
|
35335
|
+
return "0";
|
|
35336
|
+
}
|
|
35337
|
+
const days = Math.max(0, Math.floor((Date.now() - mtimeMs) / 864e5));
|
|
35338
|
+
if (days === 0) return "0";
|
|
35339
|
+
if (days <= 7) return "1_7";
|
|
35340
|
+
if (days <= 30) return "8_30";
|
|
35341
|
+
return "30_plus";
|
|
35342
|
+
}
|
|
35343
|
+
function firstRunPath() {
|
|
35344
|
+
return path5.join(path5.dirname(statePath()), "first-run");
|
|
35345
|
+
}
|
|
35346
|
+
function isFirstRun() {
|
|
35347
|
+
const p = firstRunPath();
|
|
35348
|
+
try {
|
|
35349
|
+
if (fs5.existsSync(p)) return false;
|
|
35350
|
+
} catch {
|
|
35351
|
+
return false;
|
|
35352
|
+
}
|
|
35353
|
+
try {
|
|
35354
|
+
fs5.mkdirSync(path5.dirname(p), { recursive: true });
|
|
35355
|
+
fs5.writeFileSync(p, "1", "utf8");
|
|
35356
|
+
return true;
|
|
35357
|
+
} catch {
|
|
35358
|
+
return false;
|
|
35359
|
+
}
|
|
35360
|
+
}
|
|
35361
|
+
function optOutPath() {
|
|
35362
|
+
return path5.join(path5.dirname(statePath()), "telemetry-disabled");
|
|
35363
|
+
}
|
|
35364
|
+
function isOptedOut() {
|
|
35365
|
+
try {
|
|
35366
|
+
return fs5.existsSync(optOutPath());
|
|
35367
|
+
} catch {
|
|
35368
|
+
return false;
|
|
35369
|
+
}
|
|
35370
|
+
}
|
|
35371
|
+
|
|
35372
|
+
// src/telemetry/consent.ts
|
|
35373
|
+
function isEnabled(flag) {
|
|
35374
|
+
if (isTruthy(process.env.DO_NOT_TRACK)) return false;
|
|
35375
|
+
if (isTruthy(process.env.PATTER_TELEMETRY_DISABLED)) return false;
|
|
35376
|
+
if (isOptedOut()) return false;
|
|
35377
|
+
if (flag === false) return false;
|
|
35378
|
+
if (isCi() || isTest()) return false;
|
|
35379
|
+
return true;
|
|
35380
|
+
}
|
|
35381
|
+
|
|
35382
|
+
// src/telemetry/events.ts
|
|
35383
|
+
init_cjs_shims();
|
|
35384
|
+
var os3 = __toESM(require("os"));
|
|
35385
|
+
|
|
35386
|
+
// src/telemetry/stack.ts
|
|
35387
|
+
init_cjs_shims();
|
|
35388
|
+
var STACK_VENDORS = /* @__PURE__ */ new Set([
|
|
35389
|
+
"openai",
|
|
35390
|
+
"anthropic",
|
|
35391
|
+
"google",
|
|
35392
|
+
"cerebras",
|
|
35393
|
+
"groq",
|
|
35394
|
+
"deepgram",
|
|
35395
|
+
"elevenlabs",
|
|
35396
|
+
"cartesia",
|
|
35397
|
+
"whisper",
|
|
35398
|
+
"soniox",
|
|
35399
|
+
"assemblyai",
|
|
35400
|
+
"speechmatics",
|
|
35401
|
+
"lmnt",
|
|
35402
|
+
"rime",
|
|
35403
|
+
"inworld",
|
|
35404
|
+
"telnyx",
|
|
35405
|
+
"other"
|
|
35406
|
+
]);
|
|
35407
|
+
var VENDOR_ALIASES = {
|
|
35408
|
+
cartesia_stt: "cartesia",
|
|
35409
|
+
cartesia_tts: "cartesia",
|
|
35410
|
+
openai_tts: "openai",
|
|
35411
|
+
openai_transcribe: "openai",
|
|
35412
|
+
elevenlabs_ws: "elevenlabs",
|
|
35413
|
+
telnyx_stt: "telnyx",
|
|
35414
|
+
telnyx_tts: "telnyx"
|
|
35415
|
+
};
|
|
35416
|
+
var RAW_UNSAFE_RE = /[^a-z0-9._-]/;
|
|
35417
|
+
var DATE_SUFFIX_RE = /-\d{8}$/;
|
|
35418
|
+
function vendorOf(providerKey) {
|
|
35419
|
+
if (!providerKey) return "other";
|
|
35420
|
+
const v = VENDOR_ALIASES[providerKey] ?? providerKey;
|
|
35421
|
+
return STACK_VENDORS.has(v) ? v : "other";
|
|
35422
|
+
}
|
|
35423
|
+
function modelToken(vendor, rawModel) {
|
|
35424
|
+
if (!rawModel) return `${vendor}-other`;
|
|
35425
|
+
const m = rawModel.trim().toLowerCase();
|
|
35426
|
+
if (m.length > 40 || RAW_UNSAFE_RE.test(m)) return `${vendor}-other`;
|
|
35427
|
+
const token = m.replace(/_/g, "-").replace(DATE_SUFFIX_RE, "").replace(/^[-.]+|[-.]+$/g, "");
|
|
35428
|
+
return token ? `${vendor}-${token}` : `${vendor}-other`;
|
|
35429
|
+
}
|
|
35430
|
+
function readProviderKey(obj) {
|
|
35431
|
+
const ctor = obj?.constructor;
|
|
35432
|
+
const key = ctor?.providerKey;
|
|
35433
|
+
return typeof key === "string" && key ? key : null;
|
|
35434
|
+
}
|
|
35435
|
+
function readModel(obj) {
|
|
35436
|
+
const rec = obj;
|
|
35437
|
+
for (const attr of ["model", "modelId", "_model"]) {
|
|
35438
|
+
const v = rec?.[attr];
|
|
35439
|
+
if (typeof v === "string" && v) return v;
|
|
35440
|
+
}
|
|
35441
|
+
return "";
|
|
35442
|
+
}
|
|
35443
|
+
function layerDims(obj, providerField, modelField) {
|
|
35444
|
+
if (obj === null || obj === void 0) return {};
|
|
35445
|
+
const vendor = vendorOf(readProviderKey(obj));
|
|
35446
|
+
return { [providerField]: vendor, [modelField]: modelToken(vendor, readModel(obj)) };
|
|
35447
|
+
}
|
|
35448
|
+
function stackDimensions(stt, tts, llm) {
|
|
35449
|
+
return {
|
|
35450
|
+
...layerDims(stt, "stt_provider", "stt_model"),
|
|
35451
|
+
...layerDims(tts, "tts_provider", "tts_model"),
|
|
35452
|
+
...layerDims(llm, "llm_provider", "llm_model")
|
|
35453
|
+
};
|
|
35454
|
+
}
|
|
35455
|
+
|
|
35456
|
+
// src/telemetry/events.ts
|
|
35457
|
+
var SCHEMA_VERSION2 = 5;
|
|
35458
|
+
var EVENT_SDK_INITIALIZED = "sdk_initialized";
|
|
35459
|
+
var EVENT_FIRST_RUN = "first_run";
|
|
35460
|
+
var EVENT_CLI_COMMAND = "cli_command";
|
|
35461
|
+
var EVENT_FEATURE_USED = "feature_used";
|
|
35462
|
+
var EVENT_AGENT_CONFIGURED = "agent_configured";
|
|
35463
|
+
var EVENT_CALL_STARTED = "call_started";
|
|
35464
|
+
var EVENT_CALL_COMPLETED = "call_completed";
|
|
35465
|
+
var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
35466
|
+
EVENT_SDK_INITIALIZED,
|
|
35467
|
+
EVENT_FIRST_RUN,
|
|
35468
|
+
EVENT_CLI_COMMAND,
|
|
35469
|
+
EVENT_FEATURE_USED,
|
|
35470
|
+
EVENT_AGENT_CONFIGURED,
|
|
35471
|
+
EVENT_CALL_STARTED,
|
|
35472
|
+
EVENT_CALL_COMPLETED
|
|
35473
|
+
]);
|
|
35474
|
+
var DIMENSION_VALUES = {
|
|
35475
|
+
carrier: /* @__PURE__ */ new Set(["twilio", "telnyx", "plivo", "none"]),
|
|
35476
|
+
tunnel: /* @__PURE__ */ new Set(["static", "configured", "none"]),
|
|
35477
|
+
engine: /* @__PURE__ */ new Set(["realtime", "convai", "pipeline"]),
|
|
35478
|
+
provider: /* @__PURE__ */ new Set([
|
|
35479
|
+
"openai",
|
|
35480
|
+
"elevenlabs",
|
|
35481
|
+
"deepgram",
|
|
35482
|
+
"cartesia",
|
|
35483
|
+
"cerebras",
|
|
35484
|
+
"anthropic",
|
|
35485
|
+
"google",
|
|
35486
|
+
"whisper",
|
|
35487
|
+
"other"
|
|
35488
|
+
]),
|
|
35489
|
+
// agent_configured dimensions
|
|
35490
|
+
custom_tool_count_bucket: /* @__PURE__ */ new Set(["0", "1", "2_3", "4_6", "7_12", "13_plus"]),
|
|
35491
|
+
integration: /* @__PURE__ */ new Set(["openclaw", "mcp", "hermes", "other", "none"]),
|
|
35492
|
+
integration_kind: /* @__PURE__ */ new Set(["consult", "mcp", "none"]),
|
|
35493
|
+
mcp_server_count_bucket: /* @__PURE__ */ new Set(["0", "1", "2_3", "4_plus"]),
|
|
35494
|
+
// call_started / call_completed: inbound vs outbound — a core usage split.
|
|
35495
|
+
direction: /* @__PURE__ */ new Set(["inbound", "outbound", "none"]),
|
|
35496
|
+
// cli_command: which CLI subcommand was invoked (never args/flags values).
|
|
35497
|
+
cli_command: /* @__PURE__ */ new Set(["dashboard", "eval", "telemetry", "none", "other"]),
|
|
35498
|
+
// call_completed: the call's terminal outcome
|
|
35499
|
+
outcome: /* @__PURE__ */ new Set(["completed", "error", "no_answer", "busy", "failed"]),
|
|
35500
|
+
// call_completed: terminal error code (mirrors ErrorCode, plus "other"). Never
|
|
35501
|
+
// the error message.
|
|
35502
|
+
error_code: /* @__PURE__ */ new Set([
|
|
35503
|
+
"config",
|
|
35504
|
+
"connection",
|
|
35505
|
+
"auth",
|
|
35506
|
+
"timeout",
|
|
35507
|
+
"rate_limit",
|
|
35508
|
+
"webhook_verification",
|
|
35509
|
+
"input_validation",
|
|
35510
|
+
"provider_error",
|
|
35511
|
+
"provision",
|
|
35512
|
+
"internal",
|
|
35513
|
+
"other"
|
|
35514
|
+
]),
|
|
35515
|
+
// feature_used (pipeline): per-layer vendor of the composed stack. A
|
|
35516
|
+
// providerKey not on the closed allowlist collapses to "other"; an absent layer
|
|
35517
|
+
// is omitted (the value set keeps "none" only as a safety token).
|
|
35518
|
+
stt_provider: /* @__PURE__ */ new Set([...STACK_VENDORS, "none"]),
|
|
35519
|
+
tts_provider: /* @__PURE__ */ new Set([...STACK_VENDORS, "none"]),
|
|
35520
|
+
llm_provider: /* @__PURE__ */ new Set([...STACK_VENDORS, "none"]),
|
|
35521
|
+
// sdk_initialized: anonymous deploy-shape (presence-only env/file probes).
|
|
35522
|
+
invoked_by_agent: /* @__PURE__ */ new Set(["claude", "cursor", "copilot", "gemini", "windsurf", "other", "none"]),
|
|
35523
|
+
serverless: /* @__PURE__ */ new Set(["lambda", "cloud_run", "vercel", "azure_functions", "none"]),
|
|
35524
|
+
cloud: /* @__PURE__ */ new Set(["aws", "gcp", "azure", "fly", "none"]),
|
|
35525
|
+
package_manager: /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun", "pip", "uv", "poetry", "pipenv", "conda", "none"]),
|
|
35526
|
+
days_since_install_bucket: /* @__PURE__ */ new Set(["0", "1_7", "8_30", "30_plus"]),
|
|
35527
|
+
// agent_configured: feature-adoption (Realtime tuning).
|
|
35528
|
+
noise_reduction: /* @__PURE__ */ new Set(["near_field", "far_field", "none"]),
|
|
35529
|
+
turn_detection: /* @__PURE__ */ new Set(["default", "custom", "none"]),
|
|
35530
|
+
// call_completed: how many conversational turns the call had.
|
|
35531
|
+
turn_count_bucket: /* @__PURE__ */ new Set(["0", "1", "2_3", "4_6", "7_12", "13_plus"])
|
|
35532
|
+
};
|
|
35533
|
+
var NUMERIC_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
35534
|
+
"builtin_tool_count",
|
|
35535
|
+
"latency_ms",
|
|
35536
|
+
"duration_seconds",
|
|
35537
|
+
"cost_usd"
|
|
35538
|
+
]);
|
|
35539
|
+
var STRING_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
35540
|
+
"stt_model",
|
|
35541
|
+
"tts_model",
|
|
35542
|
+
"llm_model",
|
|
35543
|
+
"previous_sdk_version"
|
|
35544
|
+
]);
|
|
35545
|
+
var MODEL_TOKEN_RE = /^[a-z0-9][a-z0-9.-]{0,40}$/;
|
|
35546
|
+
var BOOL_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
35547
|
+
"container",
|
|
35548
|
+
"preambles_used",
|
|
35549
|
+
"per_tool_timeouts_set",
|
|
35550
|
+
"llm_fallback_configured"
|
|
35551
|
+
]);
|
|
35552
|
+
var ALLOWED_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
35553
|
+
...Object.keys(DIMENSION_VALUES),
|
|
35554
|
+
...NUMERIC_DIMENSIONS,
|
|
35555
|
+
...STRING_DIMENSIONS,
|
|
35556
|
+
...BOOL_DIMENSIONS
|
|
35557
|
+
]);
|
|
35558
|
+
function osFamily() {
|
|
35559
|
+
const p = os3.platform();
|
|
35560
|
+
if (p === "win32") return "windows";
|
|
35561
|
+
return p || "unknown";
|
|
35562
|
+
}
|
|
35563
|
+
function arch2() {
|
|
35564
|
+
const a = os3.arch();
|
|
35565
|
+
if (a === "x64") return "x86_64";
|
|
35566
|
+
if (a === "arm64") return "arm64";
|
|
35567
|
+
return "other";
|
|
35568
|
+
}
|
|
35569
|
+
function runtimeVersion() {
|
|
35570
|
+
const parts = (process.versions.node ?? "0.0").split(".");
|
|
35571
|
+
return `${parts[0] ?? "0"}.${parts[1] ?? "0"}`;
|
|
35572
|
+
}
|
|
35573
|
+
function buildEvent(name, opts) {
|
|
35574
|
+
if (!ALLOWED_EVENTS.has(name)) {
|
|
35575
|
+
throw new Error(`unknown telemetry event: ${name}`);
|
|
35576
|
+
}
|
|
35577
|
+
const event = {
|
|
35578
|
+
event: name,
|
|
35579
|
+
schema_version: SCHEMA_VERSION2,
|
|
35580
|
+
run_id: runId(),
|
|
35581
|
+
install_id: installId(),
|
|
35582
|
+
sdk: "typescript",
|
|
35583
|
+
sdk_version: opts.sdkVersion,
|
|
35584
|
+
os: osFamily(),
|
|
35585
|
+
arch: arch2(),
|
|
35586
|
+
runtime: "node",
|
|
35587
|
+
runtime_version: runtimeVersion(),
|
|
35588
|
+
ci: isCi() || isTest()
|
|
35589
|
+
};
|
|
35590
|
+
for (const [key, raw] of Object.entries(opts.dimensions ?? {})) {
|
|
35591
|
+
if (!ALLOWED_DIMENSIONS.has(key) || raw === null || raw === void 0) {
|
|
35592
|
+
continue;
|
|
35593
|
+
}
|
|
35594
|
+
let value = raw;
|
|
35595
|
+
const allowed = DIMENSION_VALUES[key];
|
|
35596
|
+
if (allowed && !(typeof value === "string" && allowed.has(value))) {
|
|
35597
|
+
value = "other";
|
|
35598
|
+
} else if (STRING_DIMENSIONS.has(key)) {
|
|
35599
|
+
if (!(typeof value === "string" && MODEL_TOKEN_RE.test(value))) {
|
|
35600
|
+
continue;
|
|
35601
|
+
}
|
|
35602
|
+
} else if (BOOL_DIMENSIONS.has(key) && typeof value !== "boolean") {
|
|
35603
|
+
continue;
|
|
35604
|
+
}
|
|
35605
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
35606
|
+
event[key] = value;
|
|
35607
|
+
}
|
|
35608
|
+
}
|
|
35609
|
+
return event;
|
|
35610
|
+
}
|
|
35611
|
+
|
|
35612
|
+
// src/telemetry/client.ts
|
|
35613
|
+
var DEFAULT_ENDPOINT = "https://telemetry.getpatter.com/v1/ingest";
|
|
35614
|
+
var TIMEOUT_MS = 3e3;
|
|
35615
|
+
var BUFFER_MAX = 256;
|
|
35616
|
+
var noticeShown = false;
|
|
35617
|
+
var liveClients = /* @__PURE__ */ new Set();
|
|
35618
|
+
var exitHookRegistered = false;
|
|
35619
|
+
function showNoticeOnce() {
|
|
35620
|
+
if (noticeShown) return;
|
|
35621
|
+
noticeShown = true;
|
|
35622
|
+
getLogger().info(
|
|
35623
|
+
"Anonymous usage telemetry is on (no PII, no call content). Collected: a random anonymous install id, SDK version, language, OS family, runtime version, coarse feature flags, the composed stack (provider + model per layer), tool counts, integration category, and per-call duration, latency, cost, and error codes (no call content, no message text). Disable with PATTER_TELEMETRY_DISABLED=1, DO_NOT_TRACK=1, or telemetry: false. Details: https://docs.getpatter.com/telemetry"
|
|
35624
|
+
);
|
|
35625
|
+
}
|
|
35626
|
+
function registerExitHook() {
|
|
35627
|
+
if (exitHookRegistered) return;
|
|
35628
|
+
exitHookRegistered = true;
|
|
35629
|
+
process.once("beforeExit", () => {
|
|
35630
|
+
for (const ref of [...liveClients]) {
|
|
35631
|
+
const client = ref.deref();
|
|
35632
|
+
if (client) void client.close();
|
|
35633
|
+
else liveClients.delete(ref);
|
|
35634
|
+
}
|
|
35635
|
+
});
|
|
35636
|
+
}
|
|
35637
|
+
var TelemetryClient = class {
|
|
35638
|
+
sdkVersion;
|
|
35639
|
+
enabledFlag;
|
|
35640
|
+
endpoint;
|
|
35641
|
+
debug;
|
|
35642
|
+
buffer = [];
|
|
35643
|
+
flushing = false;
|
|
35644
|
+
closed = false;
|
|
35645
|
+
selfRef = new WeakRef(this);
|
|
35646
|
+
constructor(options) {
|
|
35647
|
+
this.sdkVersion = options.sdkVersion;
|
|
35648
|
+
this.enabledFlag = isEnabled(options.flag);
|
|
35649
|
+
this.endpoint = options.endpoint ?? process.env.PATTER_TELEMETRY_ENDPOINT ?? DEFAULT_ENDPOINT;
|
|
35650
|
+
this.debug = isTruthy(process.env.PATTER_TELEMETRY_DEBUG);
|
|
35651
|
+
if (this.enabledFlag && !this.debug) {
|
|
35652
|
+
showNoticeOnce();
|
|
35653
|
+
registerExitHook();
|
|
35654
|
+
liveClients.add(this.selfRef);
|
|
35655
|
+
}
|
|
35656
|
+
}
|
|
35657
|
+
get enabled() {
|
|
35658
|
+
return this.enabledFlag;
|
|
35659
|
+
}
|
|
35660
|
+
/** Enqueue an event. Fire-and-forget; never throws, never blocks. */
|
|
35661
|
+
record(name, dimensions) {
|
|
35662
|
+
if (!this.enabledFlag || this.closed) return;
|
|
35663
|
+
let event;
|
|
35664
|
+
try {
|
|
35665
|
+
event = buildEvent(name, { sdkVersion: this.sdkVersion, dimensions });
|
|
35666
|
+
} catch (err) {
|
|
35667
|
+
getLogger().debug("telemetry buildEvent failed", err);
|
|
35668
|
+
return;
|
|
35669
|
+
}
|
|
35670
|
+
if (this.debug) {
|
|
35671
|
+
try {
|
|
35672
|
+
process.stderr.write(`[patter telemetry] ${JSON.stringify(event)}
|
|
35673
|
+
`);
|
|
35674
|
+
} catch {
|
|
35675
|
+
}
|
|
35676
|
+
return;
|
|
35677
|
+
}
|
|
35678
|
+
try {
|
|
35679
|
+
if (this.buffer.length >= BUFFER_MAX) this.buffer.shift();
|
|
35680
|
+
this.buffer.push(event);
|
|
35681
|
+
this.scheduleFlush();
|
|
35682
|
+
} catch (err) {
|
|
35683
|
+
getLogger().debug("telemetry enqueue failed", err);
|
|
35684
|
+
}
|
|
35685
|
+
}
|
|
35686
|
+
/**
|
|
35687
|
+
* Schedule a flush of any buffered events. Events recorded before the server
|
|
35688
|
+
* is running (e.g. at `new Patter(...)`) sit in the buffer; call this once the
|
|
35689
|
+
* server is up so they ship promptly. Cheap when disabled or buffer is empty.
|
|
35690
|
+
*/
|
|
35691
|
+
flushPending() {
|
|
35692
|
+
if (!this.enabledFlag || this.debug) return;
|
|
35693
|
+
try {
|
|
35694
|
+
this.scheduleFlush();
|
|
35695
|
+
} catch (err) {
|
|
35696
|
+
getLogger().debug("telemetry flushPending failed", err);
|
|
35697
|
+
}
|
|
35698
|
+
}
|
|
35699
|
+
/** Flush remaining events (graceful shutdown). Never throws. */
|
|
35700
|
+
async close() {
|
|
35701
|
+
if (this.closed) return;
|
|
35702
|
+
this.closed = true;
|
|
35703
|
+
liveClients.delete(this.selfRef);
|
|
35704
|
+
if (!this.enabledFlag || this.debug) return;
|
|
35705
|
+
try {
|
|
35706
|
+
await this.flush();
|
|
35707
|
+
} catch (err) {
|
|
35708
|
+
getLogger().debug("telemetry close flush failed", err);
|
|
35709
|
+
}
|
|
35710
|
+
}
|
|
35711
|
+
scheduleFlush() {
|
|
35712
|
+
if (this.flushing) return;
|
|
35713
|
+
this.flushing = true;
|
|
35714
|
+
void this.flush().finally(() => {
|
|
35715
|
+
this.flushing = false;
|
|
35716
|
+
});
|
|
35717
|
+
}
|
|
35718
|
+
async flush() {
|
|
35719
|
+
if (this.buffer.length === 0) return;
|
|
35720
|
+
const events = this.buffer.splice(0, this.buffer.length);
|
|
35721
|
+
const controller = new AbortController();
|
|
35722
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
35723
|
+
timer.unref?.();
|
|
35724
|
+
try {
|
|
35725
|
+
await fetch(this.endpoint, {
|
|
35726
|
+
method: "POST",
|
|
35727
|
+
headers: { "content-type": "application/json" },
|
|
35728
|
+
body: JSON.stringify(events),
|
|
35729
|
+
signal: controller.signal
|
|
35730
|
+
});
|
|
35731
|
+
} catch (err) {
|
|
35732
|
+
getLogger().debug("telemetry flush failed", err);
|
|
35733
|
+
} finally {
|
|
35734
|
+
clearTimeout(timer);
|
|
35735
|
+
}
|
|
35736
|
+
}
|
|
35737
|
+
};
|
|
35738
|
+
|
|
35739
|
+
// src/telemetry/environment.ts
|
|
35740
|
+
init_cjs_shims();
|
|
35741
|
+
var fs6 = __toESM(require("fs"));
|
|
35742
|
+
var env = process.env;
|
|
35743
|
+
function invokedByAgent() {
|
|
35744
|
+
if ("CLAUDECODE" in env || "CLAUDE_CODE" in env || "CLAUDE_CODE_ENTRYPOINT" in env)
|
|
35745
|
+
return "claude";
|
|
35746
|
+
if ("CURSOR_TRACE_ID" in env || "CURSOR_AGENT" in env) return "cursor";
|
|
35747
|
+
if ("GITHUB_COPILOT_AGENT" in env || "COPILOT_AGENT_ID" in env) return "copilot";
|
|
35748
|
+
if ("GEMINI_CLI" in env || "GEMINI_AGENT" in env) return "gemini";
|
|
35749
|
+
if ("WINDSURF" in env || "WINDSURF_AGENT" in env) return "windsurf";
|
|
35750
|
+
if ("AIDER" in env || "OPENAI_AGENT" in env) return "other";
|
|
35751
|
+
return "none";
|
|
35752
|
+
}
|
|
35753
|
+
function inContainer() {
|
|
35754
|
+
try {
|
|
35755
|
+
if (fs6.existsSync("/.dockerenv")) return true;
|
|
35756
|
+
} catch {
|
|
35757
|
+
}
|
|
35758
|
+
if (env.KUBERNETES_SERVICE_HOST) return true;
|
|
35759
|
+
try {
|
|
35760
|
+
const blob = fs6.readFileSync("/proc/1/cgroup", "utf8");
|
|
35761
|
+
return blob.includes("docker") || blob.includes("containerd") || blob.includes("kubepods");
|
|
35762
|
+
} catch {
|
|
35763
|
+
return false;
|
|
35764
|
+
}
|
|
35765
|
+
}
|
|
35766
|
+
function serverless() {
|
|
35767
|
+
if (env.AWS_LAMBDA_FUNCTION_NAME) return "lambda";
|
|
35768
|
+
if (env.K_SERVICE) return "cloud_run";
|
|
35769
|
+
if (env.VERCEL) return "vercel";
|
|
35770
|
+
if (env.AZURE_FUNCTIONS_ENVIRONMENT || env.FUNCTIONS_WORKER_RUNTIME) return "azure_functions";
|
|
35771
|
+
return "none";
|
|
35772
|
+
}
|
|
35773
|
+
function cloud() {
|
|
35774
|
+
if (env.AWS_REGION || env.AWS_EXECUTION_ENV || env.AWS_LAMBDA_FUNCTION_NAME) return "aws";
|
|
35775
|
+
if (env.K_SERVICE || env.GOOGLE_CLOUD_PROJECT || env.GCP_PROJECT) return "gcp";
|
|
35776
|
+
if (env.WEBSITE_INSTANCE_ID || env.AZURE_FUNCTIONS_ENVIRONMENT) return "azure";
|
|
35777
|
+
if (env.FLY_APP_NAME) return "fly";
|
|
35778
|
+
return "none";
|
|
35779
|
+
}
|
|
35780
|
+
function packageManager() {
|
|
35781
|
+
const ua = env.npm_config_user_agent ?? "";
|
|
35782
|
+
if (ua.startsWith("pnpm")) return "pnpm";
|
|
35783
|
+
if (ua.startsWith("yarn")) return "yarn";
|
|
35784
|
+
if (ua.startsWith("bun")) return "bun";
|
|
35785
|
+
if (ua.startsWith("npm")) return "npm";
|
|
35786
|
+
return "none";
|
|
35787
|
+
}
|
|
35788
|
+
|
|
35789
|
+
// src/client.ts
|
|
35790
|
+
init_version();
|
|
35791
|
+
|
|
34579
35792
|
// src/_speech-events.ts
|
|
34580
35793
|
init_cjs_shims();
|
|
34581
35794
|
init_logger();
|
|
@@ -34881,6 +36094,79 @@ function closeParkedConnections(slot) {
|
|
|
34881
36094
|
}
|
|
34882
36095
|
}
|
|
34883
36096
|
}
|
|
36097
|
+
function carrierFamily2(carrier) {
|
|
36098
|
+
if (carrier instanceof Carrier2) return "twilio";
|
|
36099
|
+
if (carrier instanceof Carrier3) return "telnyx";
|
|
36100
|
+
if (carrier instanceof Carrier) return "plivo";
|
|
36101
|
+
return "none";
|
|
36102
|
+
}
|
|
36103
|
+
function telemetryEngineFamily(opts) {
|
|
36104
|
+
if (opts.engine) {
|
|
36105
|
+
return opts.engine.constructor.name.toLowerCase().includes("convai") ? "convai" : "realtime";
|
|
36106
|
+
}
|
|
36107
|
+
if (opts.provider === "elevenlabs_convai") return "convai";
|
|
36108
|
+
if (opts.provider === "pipeline") return "pipeline";
|
|
36109
|
+
if (opts.provider === "openai_realtime") return "realtime";
|
|
36110
|
+
if (opts.stt || opts.tts) return "pipeline";
|
|
36111
|
+
return "realtime";
|
|
36112
|
+
}
|
|
36113
|
+
function telemetryProviderFamily(family) {
|
|
36114
|
+
if (family === "realtime") return "openai";
|
|
36115
|
+
if (family === "convai") return "elevenlabs";
|
|
36116
|
+
return "other";
|
|
36117
|
+
}
|
|
36118
|
+
function telemetryBucketCustomTools(n) {
|
|
36119
|
+
if (n <= 0) return "0";
|
|
36120
|
+
if (n === 1) return "1";
|
|
36121
|
+
if (n <= 3) return "2_3";
|
|
36122
|
+
if (n <= 6) return "4_6";
|
|
36123
|
+
if (n <= 12) return "7_12";
|
|
36124
|
+
return "13_plus";
|
|
36125
|
+
}
|
|
36126
|
+
function telemetryBucketMcp(n) {
|
|
36127
|
+
if (n <= 0) return "0";
|
|
36128
|
+
if (n === 1) return "1";
|
|
36129
|
+
if (n <= 3) return "2_3";
|
|
36130
|
+
return "4_plus";
|
|
36131
|
+
}
|
|
36132
|
+
function telemetryIntegration(opts) {
|
|
36133
|
+
const nMcp = opts.mcpServers?.length ?? 0;
|
|
36134
|
+
if (nMcp > 0) {
|
|
36135
|
+
return { integration: "mcp", integrationKind: "mcp", mcpBucket: telemetryBucketMcp(nMcp) };
|
|
36136
|
+
}
|
|
36137
|
+
if (opts.consult) {
|
|
36138
|
+
let isOpenclaw = false;
|
|
36139
|
+
const oc = opts.consult.openaiCompatible;
|
|
36140
|
+
if (oc) {
|
|
36141
|
+
const model = oc.model ?? "";
|
|
36142
|
+
const baseUrl = oc.baseUrl ?? "";
|
|
36143
|
+
isOpenclaw = model.startsWith("openclaw/") || baseUrl.includes(":18789");
|
|
36144
|
+
}
|
|
36145
|
+
return {
|
|
36146
|
+
integration: isOpenclaw ? "openclaw" : "other",
|
|
36147
|
+
integrationKind: "consult",
|
|
36148
|
+
mcpBucket: "0"
|
|
36149
|
+
};
|
|
36150
|
+
}
|
|
36151
|
+
return { integration: "none", integrationKind: "none", mcpBucket: "0" };
|
|
36152
|
+
}
|
|
36153
|
+
function telemetryEnvironmentDims() {
|
|
36154
|
+
try {
|
|
36155
|
+
const dims = {
|
|
36156
|
+
invoked_by_agent: invokedByAgent(),
|
|
36157
|
+
container: inContainer(),
|
|
36158
|
+
serverless: serverless(),
|
|
36159
|
+
cloud: cloud(),
|
|
36160
|
+
package_manager: packageManager(),
|
|
36161
|
+
days_since_install_bucket: daysSinceInstallBucket()
|
|
36162
|
+
};
|
|
36163
|
+
const prev = previousVersion(VERSION);
|
|
36164
|
+
if (prev) dims.previous_sdk_version = prev;
|
|
36165
|
+
return dims;
|
|
36166
|
+
} catch {
|
|
36167
|
+
return {};
|
|
36168
|
+
}
|
|
36169
|
+
}
|
|
34884
36170
|
var Patter = class {
|
|
34885
36171
|
localConfig;
|
|
34886
36172
|
embeddedServer = null;
|
|
@@ -34901,6 +36187,14 @@ var Patter = class {
|
|
|
34901
36187
|
* ``Cannot use both tunnel: true and webhookUrl``.
|
|
34902
36188
|
*/
|
|
34903
36189
|
tunnelOwnsWebhookUrl = false;
|
|
36190
|
+
/**
|
|
36191
|
+
* Anonymous usage telemetry (opt-out, default ON). Separate from
|
|
36192
|
+
* ``./observability`` (user-facing OTel). Fire-and-forget and fail-safe — it
|
|
36193
|
+
* can never block or break a call. See ``./telemetry``.
|
|
36194
|
+
*/
|
|
36195
|
+
telemetry;
|
|
36196
|
+
telemetrySeenEngines = /* @__PURE__ */ new Set();
|
|
36197
|
+
telemetrySeenAgentShapes = /* @__PURE__ */ new Set();
|
|
34904
36198
|
/**
|
|
34905
36199
|
* Pre-rendered first-message TTS audio per outbound call_id. Populated
|
|
34906
36200
|
* by :meth:`call` when ``agent.prewarmFirstMessage`` is true; consumed
|
|
@@ -35106,6 +36400,22 @@ var Patter = class {
|
|
|
35106
36400
|
openaiKey: options.openaiKey,
|
|
35107
36401
|
persistRoot: resolvePersistRoot(options.persist)
|
|
35108
36402
|
};
|
|
36403
|
+
this.telemetry = new TelemetryClient({
|
|
36404
|
+
sdkVersion: VERSION,
|
|
36405
|
+
flag: options.telemetry
|
|
36406
|
+
});
|
|
36407
|
+
const initDims = {
|
|
36408
|
+
carrier: carrierFamily2(carrier),
|
|
36409
|
+
tunnel: tunnel instanceof Static ? "static" : options.tunnel ? "configured" : "none",
|
|
36410
|
+
...telemetryEnvironmentDims()
|
|
36411
|
+
};
|
|
36412
|
+
if (this.telemetry.enabled) {
|
|
36413
|
+
try {
|
|
36414
|
+
if (isFirstRun()) this.telemetry.record("first_run", initDims);
|
|
36415
|
+
} catch {
|
|
36416
|
+
}
|
|
36417
|
+
}
|
|
36418
|
+
this.telemetry.record("sdk_initialized", initDims);
|
|
35109
36419
|
this._tunnelReady = new Promise((resolve2, reject) => {
|
|
35110
36420
|
this._tunnelReadyResolve = resolve2;
|
|
35111
36421
|
this._tunnelReadyReject = reject;
|
|
@@ -35125,6 +36435,55 @@ var Patter = class {
|
|
|
35125
36435
|
// === Agent definition ===
|
|
35126
36436
|
/** Resolve user-supplied agent options against engine defaults and return the merged config. */
|
|
35127
36437
|
agent(opts) {
|
|
36438
|
+
const family = telemetryEngineFamily(opts);
|
|
36439
|
+
const stack = stackDimensions(opts.stt, opts.tts, opts.llm);
|
|
36440
|
+
const featureKey = family + "|" + Object.entries(stack).sort().map(([k, v]) => `${k}=${v}`).join(",");
|
|
36441
|
+
if (!this.telemetrySeenEngines.has(featureKey)) {
|
|
36442
|
+
this.telemetrySeenEngines.add(featureKey);
|
|
36443
|
+
this.telemetry.record("feature_used", {
|
|
36444
|
+
engine: family,
|
|
36445
|
+
provider: telemetryProviderFamily(family),
|
|
36446
|
+
...stack
|
|
36447
|
+
});
|
|
36448
|
+
}
|
|
36449
|
+
const builtin = opts.consult ? 1 : 0;
|
|
36450
|
+
const customBucket = telemetryBucketCustomTools(opts.tools?.length ?? 0);
|
|
36451
|
+
const { integration, integrationKind, mcpBucket } = telemetryIntegration(opts);
|
|
36452
|
+
const engineObj = opts.engine;
|
|
36453
|
+
const nr = opts.openaiRealtimeNoiseReduction ?? engineObj?.noiseReduction;
|
|
36454
|
+
const noiseReduction = nr === "near_field" || nr === "far_field" ? nr : "none";
|
|
36455
|
+
const td = opts.realtimeTurnDetection ?? engineObj?.turnDetection;
|
|
36456
|
+
const turnDetection = td != null ? "custom" : "none";
|
|
36457
|
+
const preamblesUsed = Boolean(opts.toolCallPreambles);
|
|
36458
|
+
const perToolTimeoutsSet = Array.isArray(opts.tools) && opts.tools.some((t) => t.timeoutMs !== void 0);
|
|
36459
|
+
const llmFallbackConfigured = typeof opts.llm?.getAvailability === "function";
|
|
36460
|
+
const shapeKey = [
|
|
36461
|
+
builtin,
|
|
36462
|
+
customBucket,
|
|
36463
|
+
integration,
|
|
36464
|
+
integrationKind,
|
|
36465
|
+
mcpBucket,
|
|
36466
|
+
noiseReduction,
|
|
36467
|
+
turnDetection,
|
|
36468
|
+
preamblesUsed,
|
|
36469
|
+
perToolTimeoutsSet,
|
|
36470
|
+
llmFallbackConfigured
|
|
36471
|
+
].join("|");
|
|
36472
|
+
if (!this.telemetrySeenAgentShapes.has(shapeKey)) {
|
|
36473
|
+
this.telemetrySeenAgentShapes.add(shapeKey);
|
|
36474
|
+
this.telemetry.record("agent_configured", {
|
|
36475
|
+
builtin_tool_count: builtin,
|
|
36476
|
+
custom_tool_count_bucket: customBucket,
|
|
36477
|
+
integration,
|
|
36478
|
+
integration_kind: integrationKind,
|
|
36479
|
+
mcp_server_count_bucket: mcpBucket,
|
|
36480
|
+
noise_reduction: noiseReduction,
|
|
36481
|
+
turn_detection: turnDetection,
|
|
36482
|
+
preambles_used: preamblesUsed,
|
|
36483
|
+
per_tool_timeouts_set: perToolTimeoutsSet,
|
|
36484
|
+
llm_fallback_configured: llmFallbackConfigured
|
|
36485
|
+
});
|
|
36486
|
+
}
|
|
35128
36487
|
let working = { ...opts };
|
|
35129
36488
|
if (opts.engine) {
|
|
35130
36489
|
if (opts.provider) {
|
|
@@ -35314,6 +36673,7 @@ var Patter = class {
|
|
|
35314
36673
|
opts.dashboardToken ?? "",
|
|
35315
36674
|
opts.allowInsecureDashboard ?? false
|
|
35316
36675
|
);
|
|
36676
|
+
this.embeddedServer.telemetry = this.telemetry;
|
|
35317
36677
|
this.embeddedServer.popPrewarmAudio = this.popPrewarmAudio;
|
|
35318
36678
|
this.embeddedServer.popPrewarmedConnections = this.popPrewarmedConnections;
|
|
35319
36679
|
this.embeddedServer.recordPrewarmWaste = this.recordPrewarmWaste;
|
|
@@ -35323,6 +36683,7 @@ var Patter = class {
|
|
|
35323
36683
|
await waitForTunnelPubliclyReachable(webhookUrl);
|
|
35324
36684
|
}
|
|
35325
36685
|
this._readyResolve(webhookUrl);
|
|
36686
|
+
this.telemetry.flushPending();
|
|
35326
36687
|
} catch (err) {
|
|
35327
36688
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
35328
36689
|
this._readyReject(e);
|
|
@@ -36005,6 +37366,7 @@ var Patter = class {
|
|
|
36005
37366
|
* entries leak across ``serve`` / ``disconnect`` cycles. See FIX #93.
|
|
36006
37367
|
*/
|
|
36007
37368
|
async disconnect() {
|
|
37369
|
+
this.telemetry.flushPending();
|
|
36008
37370
|
for (const handle of this.prewarmTtlTimers.values()) {
|
|
36009
37371
|
clearTimeout(handle);
|
|
36010
37372
|
}
|
|
@@ -36606,6 +37968,7 @@ var PatterTool = class {
|
|
|
36606
37968
|
maxDurationSec;
|
|
36607
37969
|
recording;
|
|
36608
37970
|
started = false;
|
|
37971
|
+
hermesTelemetryEmitted = false;
|
|
36609
37972
|
/** Cached in-progress (or completed) start promise so concurrent execute()
|
|
36610
37973
|
* callers all await the same boot sequence instead of each racing into
|
|
36611
37974
|
* phone.serve(). Reset to null on failure so callers can retry after a
|
|
@@ -36762,6 +38125,20 @@ var PatterTool = class {
|
|
|
36762
38125
|
* the same wire contract.
|
|
36763
38126
|
*/
|
|
36764
38127
|
hermesHandler() {
|
|
38128
|
+
if (!this.hermesTelemetryEmitted) {
|
|
38129
|
+
this.hermesTelemetryEmitted = true;
|
|
38130
|
+
try {
|
|
38131
|
+
const tel = this.phone.telemetry;
|
|
38132
|
+
tel?.record("agent_configured", {
|
|
38133
|
+
builtin_tool_count: 0,
|
|
38134
|
+
custom_tool_count_bucket: "0",
|
|
38135
|
+
integration: "hermes",
|
|
38136
|
+
integration_kind: "none",
|
|
38137
|
+
mcp_server_count_bucket: "0"
|
|
38138
|
+
});
|
|
38139
|
+
} catch {
|
|
38140
|
+
}
|
|
38141
|
+
}
|
|
36765
38142
|
return async (args) => {
|
|
36766
38143
|
try {
|
|
36767
38144
|
const result = await this.execute(args);
|
|
@@ -41852,6 +43229,314 @@ var LLM5 = class extends GoogleLLMProvider {
|
|
|
41852
43229
|
}
|
|
41853
43230
|
};
|
|
41854
43231
|
|
|
43232
|
+
// src/llm/openai-compatible.ts
|
|
43233
|
+
init_cjs_shims();
|
|
43234
|
+
var import_node_crypto6 = require("crypto");
|
|
43235
|
+
init_llm_loop();
|
|
43236
|
+
init_errors();
|
|
43237
|
+
init_logger();
|
|
43238
|
+
init_version();
|
|
43239
|
+
var DEFAULT_TIMEOUT_S = 60;
|
|
43240
|
+
function hashCaller(caller) {
|
|
43241
|
+
if (!caller) return void 0;
|
|
43242
|
+
return (0, import_node_crypto6.createHash)("sha256").update(caller, "utf8").digest("hex").slice(0, 16);
|
|
43243
|
+
}
|
|
43244
|
+
var OpenAICompatibleLLMProvider = class {
|
|
43245
|
+
/**
|
|
43246
|
+
* Stable pricing/dashboard key — read by stream-handler/metrics. Typed as
|
|
43247
|
+
* ``string`` (not the narrowed literal) so the Hermes / OpenClaw presets can
|
|
43248
|
+
* override it with their own key while still extending this class.
|
|
43249
|
+
*/
|
|
43250
|
+
static providerKey = "openai_compatible";
|
|
43251
|
+
/** Resolved bearer; undefined for keyless gateways. */
|
|
43252
|
+
apiKey;
|
|
43253
|
+
model;
|
|
43254
|
+
baseUrl;
|
|
43255
|
+
timeoutMs;
|
|
43256
|
+
extraHeaders;
|
|
43257
|
+
sessionUserPrefix;
|
|
43258
|
+
sessionIdHeader;
|
|
43259
|
+
sessionIdPrefix;
|
|
43260
|
+
sessionKeyHeader;
|
|
43261
|
+
sessionKey;
|
|
43262
|
+
sessionKeyFactory;
|
|
43263
|
+
temperature;
|
|
43264
|
+
maxTokens;
|
|
43265
|
+
responseFormat;
|
|
43266
|
+
parallelToolCalls;
|
|
43267
|
+
toolChoice;
|
|
43268
|
+
seed;
|
|
43269
|
+
topP;
|
|
43270
|
+
frequencyPenalty;
|
|
43271
|
+
presencePenalty;
|
|
43272
|
+
stop;
|
|
43273
|
+
constructor(options) {
|
|
43274
|
+
if (!options.baseUrl) {
|
|
43275
|
+
throw new Error(
|
|
43276
|
+
'OpenAICompatibleLLMProvider requires a baseUrl (e.g. "http://127.0.0.1:11434/v1").'
|
|
43277
|
+
);
|
|
43278
|
+
}
|
|
43279
|
+
if (!options.model) {
|
|
43280
|
+
throw new Error("OpenAICompatibleLLMProvider requires a model.");
|
|
43281
|
+
}
|
|
43282
|
+
this.apiKey = options.apiKey ?? (options.apiKeyEnv ? process.env[options.apiKeyEnv] : void 0);
|
|
43283
|
+
this.model = options.model;
|
|
43284
|
+
this.baseUrl = options.baseUrl;
|
|
43285
|
+
this.timeoutMs = (options.timeout ?? DEFAULT_TIMEOUT_S) * 1e3;
|
|
43286
|
+
this.extraHeaders = options.extraHeaders;
|
|
43287
|
+
this.sessionUserPrefix = options.sessionUserPrefix;
|
|
43288
|
+
this.sessionIdHeader = options.sessionIdHeader;
|
|
43289
|
+
this.sessionIdPrefix = options.sessionIdPrefix;
|
|
43290
|
+
this.sessionKeyHeader = options.sessionKeyHeader;
|
|
43291
|
+
this.sessionKey = options.sessionKey;
|
|
43292
|
+
let sessionKeyFactory = options.sessionKeyFactory;
|
|
43293
|
+
if (!sessionKeyFactory && options.sessionKeyFrom === "caller_hash") {
|
|
43294
|
+
sessionKeyFactory = (ctx) => ctx.callerHash ? `patter-caller-${ctx.callerHash}` : void 0;
|
|
43295
|
+
} else if (options.sessionKeyFrom !== void 0 && options.sessionKeyFrom !== "caller_hash") {
|
|
43296
|
+
throw new Error(
|
|
43297
|
+
`sessionKeyFrom must be 'caller_hash' or undefined, got ${JSON.stringify(
|
|
43298
|
+
options.sessionKeyFrom
|
|
43299
|
+
)}`
|
|
43300
|
+
);
|
|
43301
|
+
}
|
|
43302
|
+
this.sessionKeyFactory = sessionKeyFactory;
|
|
43303
|
+
this.temperature = options.temperature;
|
|
43304
|
+
this.maxTokens = options.maxTokens;
|
|
43305
|
+
this.responseFormat = options.responseFormat;
|
|
43306
|
+
this.parallelToolCalls = options.parallelToolCalls;
|
|
43307
|
+
this.toolChoice = options.toolChoice;
|
|
43308
|
+
this.seed = options.seed;
|
|
43309
|
+
this.topP = options.topP;
|
|
43310
|
+
this.frequencyPenalty = options.frequencyPenalty;
|
|
43311
|
+
this.presencePenalty = options.presencePenalty;
|
|
43312
|
+
this.stop = options.stop;
|
|
43313
|
+
}
|
|
43314
|
+
/**
|
|
43315
|
+
* Assemble the request headers. ``User-Agent`` is set first so any
|
|
43316
|
+
* ``extraHeaders`` (and the per-call session headers) layer on top without
|
|
43317
|
+
* silently dropping the SDK attribution, and the ``Authorization`` header is
|
|
43318
|
+
* only added when a key is present (keyless gateways omit it).
|
|
43319
|
+
*
|
|
43320
|
+
* The two session headers are emitted INDEPENDENTLY, each gated on its own
|
|
43321
|
+
* config (decoupled from ``sessionUserPrefix`` and from each other):
|
|
43322
|
+
* - ``sessionIdHeader`` (+ ``callId``) → ``` `${sessionIdPrefix}${callId}` ```
|
|
43323
|
+
* - ``sessionKeyHeader`` (+ ``sessionKey``) → the static ``sessionKey`` value.
|
|
43324
|
+
* ``sessionKey`` is a credential-grade memory scope and is never logged.
|
|
43325
|
+
*/
|
|
43326
|
+
buildHeaders(callId, caller, callee) {
|
|
43327
|
+
const headers = {
|
|
43328
|
+
"Content-Type": "application/json",
|
|
43329
|
+
"User-Agent": `getpatter/${VERSION}`,
|
|
43330
|
+
...this.extraHeaders ?? {}
|
|
43331
|
+
};
|
|
43332
|
+
if (this.apiKey) {
|
|
43333
|
+
headers.Authorization = `Bearer ${this.apiKey}`;
|
|
43334
|
+
}
|
|
43335
|
+
if (this.sessionIdHeader && callId) {
|
|
43336
|
+
headers[this.sessionIdHeader] = `${this.sessionIdPrefix ?? ""}${callId}`;
|
|
43337
|
+
}
|
|
43338
|
+
if (this.sessionKeyHeader) {
|
|
43339
|
+
const sessionKeyValue = this.resolveSessionKey(callId, caller, callee);
|
|
43340
|
+
if (sessionKeyValue) {
|
|
43341
|
+
headers[this.sessionKeyHeader] = sessionKeyValue;
|
|
43342
|
+
}
|
|
43343
|
+
}
|
|
43344
|
+
return headers;
|
|
43345
|
+
}
|
|
43346
|
+
/**
|
|
43347
|
+
* Resolve the ``sessionKeyHeader`` VALUE for this call. When a
|
|
43348
|
+
* ``sessionKeyFactory`` is configured it is called with a
|
|
43349
|
+
* {@link SessionContext} (the raw ``caller`` plus its non-reversible
|
|
43350
|
+
* {@link hashCaller}) and its return value wins — a falsy return omits the
|
|
43351
|
+
* header. Otherwise the static ``sessionKey`` is used. Never logged.
|
|
43352
|
+
*/
|
|
43353
|
+
resolveSessionKey(callId, caller, callee) {
|
|
43354
|
+
if (this.sessionKeyFactory) {
|
|
43355
|
+
const ctx = {
|
|
43356
|
+
callId,
|
|
43357
|
+
caller,
|
|
43358
|
+
callee,
|
|
43359
|
+
callerHash: hashCaller(caller)
|
|
43360
|
+
};
|
|
43361
|
+
return this.sessionKeyFactory(ctx);
|
|
43362
|
+
}
|
|
43363
|
+
return this.sessionKey;
|
|
43364
|
+
}
|
|
43365
|
+
/**
|
|
43366
|
+
* Pre-call DNS / TLS warmup for the configured endpoint. Best-effort:
|
|
43367
|
+
* 5 s timeout, all exceptions swallowed at debug level. The ``Authorization``
|
|
43368
|
+
* header is only sent when a key is present so the operator-grade bearer is
|
|
43369
|
+
* never echoed for keyless gateways (and the key is never logged).
|
|
43370
|
+
*/
|
|
43371
|
+
async warmup() {
|
|
43372
|
+
try {
|
|
43373
|
+
const headers = {};
|
|
43374
|
+
if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
|
|
43375
|
+
await fetch(`${this.baseUrl}/models`, {
|
|
43376
|
+
method: "GET",
|
|
43377
|
+
headers,
|
|
43378
|
+
signal: AbortSignal.timeout(5e3)
|
|
43379
|
+
});
|
|
43380
|
+
} catch (err) {
|
|
43381
|
+
getLogger().debug(
|
|
43382
|
+
`OpenAI-compatible LLM warmup failed (best-effort): ${String(err)}`
|
|
43383
|
+
);
|
|
43384
|
+
}
|
|
43385
|
+
}
|
|
43386
|
+
/**
|
|
43387
|
+
* Build the request body. Mirrors the base OpenAI provider's sampling-kwarg
|
|
43388
|
+
* assembly and additionally sets ``user`` for session continuity when
|
|
43389
|
+
* ``sessionUserPrefix`` is set AND a ``callId`` is available — so the default
|
|
43390
|
+
* (prefix unset) behaviour is byte-identical to the base provider.
|
|
43391
|
+
*/
|
|
43392
|
+
buildBody(messages, tools, callId) {
|
|
43393
|
+
const body = {
|
|
43394
|
+
model: this.model,
|
|
43395
|
+
messages,
|
|
43396
|
+
stream: true,
|
|
43397
|
+
stream_options: { include_usage: true }
|
|
43398
|
+
};
|
|
43399
|
+
if (this.temperature !== void 0) body.temperature = this.temperature;
|
|
43400
|
+
if (this.maxTokens !== void 0) body.max_completion_tokens = this.maxTokens;
|
|
43401
|
+
if (this.responseFormat !== void 0) body.response_format = this.responseFormat;
|
|
43402
|
+
if (this.parallelToolCalls !== void 0) body.parallel_tool_calls = this.parallelToolCalls;
|
|
43403
|
+
if (this.toolChoice !== void 0) body.tool_choice = this.toolChoice;
|
|
43404
|
+
if (this.seed !== void 0) body.seed = this.seed;
|
|
43405
|
+
if (this.topP !== void 0) body.top_p = this.topP;
|
|
43406
|
+
if (this.frequencyPenalty !== void 0) body.frequency_penalty = this.frequencyPenalty;
|
|
43407
|
+
if (this.presencePenalty !== void 0) body.presence_penalty = this.presencePenalty;
|
|
43408
|
+
if (this.stop !== void 0) body.stop = this.stop;
|
|
43409
|
+
if (tools) body.tools = tools;
|
|
43410
|
+
if (this.sessionUserPrefix !== void 0 && callId) {
|
|
43411
|
+
body.user = `${this.sessionUserPrefix}${callId}`;
|
|
43412
|
+
}
|
|
43413
|
+
return body;
|
|
43414
|
+
}
|
|
43415
|
+
/** Stream Patter-format LLM chunks from the configured chat completions API. */
|
|
43416
|
+
async *stream(messages, tools, opts) {
|
|
43417
|
+
const callId = opts?.callId;
|
|
43418
|
+
const caller = opts?.caller;
|
|
43419
|
+
const callee = opts?.callee;
|
|
43420
|
+
const body = this.buildBody(messages, tools, callId);
|
|
43421
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
43422
|
+
method: "POST",
|
|
43423
|
+
headers: this.buildHeaders(callId, caller, callee),
|
|
43424
|
+
body: JSON.stringify(body),
|
|
43425
|
+
signal: mergeAbortSignals(opts?.signal, AbortSignal.timeout(this.timeoutMs))
|
|
43426
|
+
});
|
|
43427
|
+
if (!response.ok) {
|
|
43428
|
+
const errText = await response.text();
|
|
43429
|
+
getLogger().error(
|
|
43430
|
+
`OpenAI-compatible API error: ${response.status} ${errText}`
|
|
43431
|
+
);
|
|
43432
|
+
throw new PatterConnectionError(
|
|
43433
|
+
`LLM API returned ${response.status}: ${errText.slice(0, 200)}`
|
|
43434
|
+
);
|
|
43435
|
+
}
|
|
43436
|
+
yield* parseOpenAISseStream(response);
|
|
43437
|
+
}
|
|
43438
|
+
};
|
|
43439
|
+
var LLM6 = class extends OpenAICompatibleLLMProvider {
|
|
43440
|
+
static providerKey = "openai_compatible";
|
|
43441
|
+
};
|
|
43442
|
+
|
|
43443
|
+
// src/llm/custom.ts
|
|
43444
|
+
init_cjs_shims();
|
|
43445
|
+
var LLM7 = class extends OpenAICompatibleLLMProvider {
|
|
43446
|
+
/** Stable pricing/dashboard key — read by stream-handler/metrics. */
|
|
43447
|
+
static providerKey = "custom";
|
|
43448
|
+
};
|
|
43449
|
+
|
|
43450
|
+
// src/llm/hermes.ts
|
|
43451
|
+
init_cjs_shims();
|
|
43452
|
+
var BASE_URL = "http://127.0.0.1:8642/v1";
|
|
43453
|
+
var DEFAULT_MODEL5 = "hermes-agent";
|
|
43454
|
+
var API_KEY_ENV = "API_SERVER_KEY";
|
|
43455
|
+
var MODEL_ENV = "API_SERVER_MODEL_NAME";
|
|
43456
|
+
var SESSION_USER_PREFIX = "patter-call-";
|
|
43457
|
+
var SESSION_ID_HEADER = "X-Hermes-Session-Id";
|
|
43458
|
+
var SESSION_ID_PREFIX = "patter-call-";
|
|
43459
|
+
var SESSION_KEY_HEADER = "X-Hermes-Session-Key";
|
|
43460
|
+
var DEFAULT_TIMEOUT_S2 = 120;
|
|
43461
|
+
var LLM8 = class extends OpenAICompatibleLLMProvider {
|
|
43462
|
+
static providerKey = "hermes";
|
|
43463
|
+
constructor(opts = {}) {
|
|
43464
|
+
const model = opts.model ?? process.env[MODEL_ENV] ?? DEFAULT_MODEL5;
|
|
43465
|
+
const options = {
|
|
43466
|
+
apiKey: opts.apiKey,
|
|
43467
|
+
apiKeyEnv: API_KEY_ENV,
|
|
43468
|
+
baseUrl: opts.baseUrl ?? BASE_URL,
|
|
43469
|
+
model,
|
|
43470
|
+
timeout: opts.timeout ?? DEFAULT_TIMEOUT_S2,
|
|
43471
|
+
sessionUserPrefix: SESSION_USER_PREFIX,
|
|
43472
|
+
sessionIdHeader: SESSION_ID_HEADER,
|
|
43473
|
+
sessionIdPrefix: SESSION_ID_PREFIX,
|
|
43474
|
+
sessionKeyHeader: SESSION_KEY_HEADER,
|
|
43475
|
+
sessionKey: opts.sessionKey,
|
|
43476
|
+
sessionKeyFrom: opts.sessionKeyFrom,
|
|
43477
|
+
sessionKeyFactory: opts.sessionKeyFactory,
|
|
43478
|
+
extraHeaders: opts.extraHeaders,
|
|
43479
|
+
temperature: opts.temperature,
|
|
43480
|
+
maxTokens: opts.maxTokens,
|
|
43481
|
+
responseFormat: opts.responseFormat,
|
|
43482
|
+
parallelToolCalls: opts.parallelToolCalls,
|
|
43483
|
+
toolChoice: opts.toolChoice,
|
|
43484
|
+
seed: opts.seed,
|
|
43485
|
+
topP: opts.topP,
|
|
43486
|
+
frequencyPenalty: opts.frequencyPenalty,
|
|
43487
|
+
presencePenalty: opts.presencePenalty,
|
|
43488
|
+
stop: opts.stop
|
|
43489
|
+
};
|
|
43490
|
+
super(options);
|
|
43491
|
+
}
|
|
43492
|
+
};
|
|
43493
|
+
|
|
43494
|
+
// src/llm/openclaw.ts
|
|
43495
|
+
init_cjs_shims();
|
|
43496
|
+
var BASE_URL2 = "http://127.0.0.1:18789/v1";
|
|
43497
|
+
var API_KEY_ENV2 = "OPENCLAW_API_KEY";
|
|
43498
|
+
var SESSION_HEADER = "x-openclaw-session-key";
|
|
43499
|
+
var SESSION_USER_PREFIX2 = "patter-call-";
|
|
43500
|
+
var DEFAULT_TIMEOUT_S3 = 120;
|
|
43501
|
+
var OPENCLAW_AGENT_RE2 = /^[A-Za-z0-9._:/-]+$/;
|
|
43502
|
+
var LLM9 = class extends OpenAICompatibleLLMProvider {
|
|
43503
|
+
static providerKey = "openclaw";
|
|
43504
|
+
constructor(opts) {
|
|
43505
|
+
const agent = opts?.agent;
|
|
43506
|
+
if (!agent || !OPENCLAW_AGENT_RE2.test(agent)) {
|
|
43507
|
+
throw new Error(
|
|
43508
|
+
`Invalid OpenClaw agent id: ${JSON.stringify(agent)}. Allowed characters: letters, digits, dot, underscore, colon, slash, dash.`
|
|
43509
|
+
);
|
|
43510
|
+
}
|
|
43511
|
+
const model = agent.includes("/") || agent.includes(":") ? agent : `openclaw/${agent}`;
|
|
43512
|
+
const options = {
|
|
43513
|
+
apiKey: opts.apiKey,
|
|
43514
|
+
apiKeyEnv: API_KEY_ENV2,
|
|
43515
|
+
baseUrl: opts.baseUrl ?? BASE_URL2,
|
|
43516
|
+
model,
|
|
43517
|
+
timeout: opts.timeout ?? DEFAULT_TIMEOUT_S3,
|
|
43518
|
+
sessionUserPrefix: SESSION_USER_PREFIX2,
|
|
43519
|
+
// Wire-identical to the prior behaviour: header value is the raw call id
|
|
43520
|
+
// (empty prefix), and OpenClaw's gateway also derives the session from
|
|
43521
|
+
// the ``user`` field above. No separate memory-scope header.
|
|
43522
|
+
sessionIdHeader: SESSION_HEADER,
|
|
43523
|
+
sessionIdPrefix: "",
|
|
43524
|
+
extraHeaders: opts.extraHeaders,
|
|
43525
|
+
temperature: opts.temperature,
|
|
43526
|
+
maxTokens: opts.maxTokens,
|
|
43527
|
+
responseFormat: opts.responseFormat,
|
|
43528
|
+
parallelToolCalls: opts.parallelToolCalls,
|
|
43529
|
+
toolChoice: opts.toolChoice,
|
|
43530
|
+
seed: opts.seed,
|
|
43531
|
+
topP: opts.topP,
|
|
43532
|
+
frequencyPenalty: opts.frequencyPenalty,
|
|
43533
|
+
presencePenalty: opts.presencePenalty,
|
|
43534
|
+
stop: opts.stop
|
|
43535
|
+
};
|
|
43536
|
+
super(options);
|
|
43537
|
+
}
|
|
43538
|
+
};
|
|
43539
|
+
|
|
41855
43540
|
// src/index.ts
|
|
41856
43541
|
init_silero_vad();
|
|
41857
43542
|
|
|
@@ -42088,57 +43773,6 @@ var KrispVivaFilter = class {
|
|
|
42088
43773
|
}
|
|
42089
43774
|
};
|
|
42090
43775
|
|
|
42091
|
-
// src/telephony/twilio.ts
|
|
42092
|
-
init_cjs_shims();
|
|
42093
|
-
var Carrier2 = class {
|
|
42094
|
-
kind = "twilio";
|
|
42095
|
-
accountSid;
|
|
42096
|
-
authToken;
|
|
42097
|
-
constructor(opts = {}) {
|
|
42098
|
-
const sid = opts.accountSid ?? process.env.TWILIO_ACCOUNT_SID;
|
|
42099
|
-
const tok = opts.authToken ?? process.env.TWILIO_AUTH_TOKEN;
|
|
42100
|
-
if (!sid) {
|
|
42101
|
-
throw new Error(
|
|
42102
|
-
"Twilio carrier requires accountSid. Pass { accountSid: 'AC...' } or set TWILIO_ACCOUNT_SID in the environment."
|
|
42103
|
-
);
|
|
42104
|
-
}
|
|
42105
|
-
if (!tok) {
|
|
42106
|
-
throw new Error(
|
|
42107
|
-
"Twilio carrier requires authToken. Pass { authToken: '...' } or set TWILIO_AUTH_TOKEN in the environment."
|
|
42108
|
-
);
|
|
42109
|
-
}
|
|
42110
|
-
this.accountSid = sid;
|
|
42111
|
-
this.authToken = tok;
|
|
42112
|
-
}
|
|
42113
|
-
};
|
|
42114
|
-
|
|
42115
|
-
// src/telephony/telnyx.ts
|
|
42116
|
-
init_cjs_shims();
|
|
42117
|
-
var Carrier3 = class {
|
|
42118
|
-
kind = "telnyx";
|
|
42119
|
-
apiKey;
|
|
42120
|
-
connectionId;
|
|
42121
|
-
publicKey;
|
|
42122
|
-
constructor(opts = {}) {
|
|
42123
|
-
const key = opts.apiKey ?? process.env.TELNYX_API_KEY;
|
|
42124
|
-
const conn = opts.connectionId ?? process.env.TELNYX_CONNECTION_ID;
|
|
42125
|
-
const pub = opts.publicKey ?? process.env.TELNYX_PUBLIC_KEY;
|
|
42126
|
-
if (!key) {
|
|
42127
|
-
throw new Error(
|
|
42128
|
-
"Telnyx carrier requires apiKey. Pass { apiKey: '...' } or set TELNYX_API_KEY in the environment."
|
|
42129
|
-
);
|
|
42130
|
-
}
|
|
42131
|
-
if (!conn) {
|
|
42132
|
-
throw new Error(
|
|
42133
|
-
"Telnyx carrier requires connectionId. Pass { connectionId: '...' } or set TELNYX_CONNECTION_ID in the environment."
|
|
42134
|
-
);
|
|
42135
|
-
}
|
|
42136
|
-
this.apiKey = key;
|
|
42137
|
-
this.connectionId = conn;
|
|
42138
|
-
this.publicKey = pub;
|
|
42139
|
-
}
|
|
42140
|
-
};
|
|
42141
|
-
|
|
42142
43776
|
// src/index.ts
|
|
42143
43777
|
init_plivo();
|
|
42144
43778
|
init_openai_realtime_2();
|
|
@@ -42213,9 +43847,9 @@ init_tunnel();
|
|
|
42213
43847
|
|
|
42214
43848
|
// src/chat-context.ts
|
|
42215
43849
|
init_cjs_shims();
|
|
42216
|
-
var
|
|
43850
|
+
var import_node_crypto7 = require("crypto");
|
|
42217
43851
|
function generateId() {
|
|
42218
|
-
return (0,
|
|
43852
|
+
return (0, import_node_crypto7.randomUUID)().replace(/-/g, "").slice(0, 12);
|
|
42219
43853
|
}
|
|
42220
43854
|
function createMessage(role, content, options) {
|
|
42221
43855
|
return Object.freeze({
|
|
@@ -42864,8 +44498,8 @@ var TwilioAdapter = class _TwilioAdapter {
|
|
|
42864
44498
|
this.baseUrl = opts.region ? `https://api.${opts.region}.twilio.com/2010-04-01` : TWILIO_API_BASE2;
|
|
42865
44499
|
this.authHeader = `Basic ${Buffer.from(`${accountSid}:${authToken}`).toString("base64")}`;
|
|
42866
44500
|
}
|
|
42867
|
-
async request(method,
|
|
42868
|
-
const url2 = `${this.baseUrl}/Accounts/${encodeURIComponent(this.accountSid)}${
|
|
44501
|
+
async request(method, path7, body) {
|
|
44502
|
+
const url2 = `${this.baseUrl}/Accounts/${encodeURIComponent(this.accountSid)}${path7}`;
|
|
42869
44503
|
const headers = { Authorization: this.authHeader };
|
|
42870
44504
|
if (body) headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
42871
44505
|
const response = await fetch(url2, {
|
|
@@ -42876,7 +44510,7 @@ var TwilioAdapter = class _TwilioAdapter {
|
|
|
42876
44510
|
});
|
|
42877
44511
|
const text = await response.text();
|
|
42878
44512
|
if (!response.ok) {
|
|
42879
|
-
throw new Error(`Twilio ${method} ${
|
|
44513
|
+
throw new Error(`Twilio ${method} ${path7} failed: ${response.status} ${text}`);
|
|
42880
44514
|
}
|
|
42881
44515
|
if (!text) return {};
|
|
42882
44516
|
try {
|
|
@@ -42894,8 +44528,8 @@ var TwilioAdapter = class _TwilioAdapter {
|
|
|
42894
44528
|
const country = encodeURIComponent(opts.countryCode);
|
|
42895
44529
|
const queryParts = ["PageSize=1"];
|
|
42896
44530
|
if (opts.areaCode) queryParts.push(`AreaCode=${encodeURIComponent(opts.areaCode)}`);
|
|
42897
|
-
const
|
|
42898
|
-
const available = await this.request("GET",
|
|
44531
|
+
const path7 = `/AvailablePhoneNumbers/${country}/Local.json?${queryParts.join("&")}`;
|
|
44532
|
+
const available = await this.request("GET", path7);
|
|
42899
44533
|
const first = available.available_phone_numbers?.[0]?.phone_number;
|
|
42900
44534
|
if (!first) {
|
|
42901
44535
|
throw new Error(`TwilioAdapter: no numbers available for country ${opts.countryCode}`);
|
|
@@ -42995,7 +44629,7 @@ var TwilioAdapter = class _TwilioAdapter {
|
|
|
42995
44629
|
|
|
42996
44630
|
// src/providers/telnyx-adapter.ts
|
|
42997
44631
|
init_cjs_shims();
|
|
42998
|
-
var
|
|
44632
|
+
var import_node_crypto8 = require("crypto");
|
|
42999
44633
|
init_logger();
|
|
43000
44634
|
var TELNYX_API_BASE2 = "https://api.telnyx.com/v2";
|
|
43001
44635
|
var TelnyxAdapter = class {
|
|
@@ -43007,8 +44641,8 @@ var TelnyxAdapter = class {
|
|
|
43007
44641
|
this.apiKey = apiKey;
|
|
43008
44642
|
this.connectionId = connectionId;
|
|
43009
44643
|
}
|
|
43010
|
-
async request(method,
|
|
43011
|
-
const url2 = `${this.baseUrl}${
|
|
44644
|
+
async request(method, path7, body) {
|
|
44645
|
+
const url2 = `${this.baseUrl}${path7}`;
|
|
43012
44646
|
const headers = {
|
|
43013
44647
|
Authorization: `Bearer ${this.apiKey}`
|
|
43014
44648
|
};
|
|
@@ -43021,7 +44655,7 @@ var TelnyxAdapter = class {
|
|
|
43021
44655
|
});
|
|
43022
44656
|
const text = await response.text();
|
|
43023
44657
|
if (!response.ok) {
|
|
43024
|
-
throw new Error(`Telnyx ${method} ${
|
|
44658
|
+
throw new Error(`Telnyx ${method} ${path7} failed: ${response.status} ${text}`);
|
|
43025
44659
|
}
|
|
43026
44660
|
if (!text) return {};
|
|
43027
44661
|
try {
|
|
@@ -43105,7 +44739,7 @@ var TelnyxAdapter = class {
|
|
|
43105
44739
|
if (!callControlId) throw new Error("TelnyxAdapter: callControlId is required");
|
|
43106
44740
|
const encoded = encodeURIComponent(callControlId);
|
|
43107
44741
|
const body = {
|
|
43108
|
-
command_id: opts.commandId ?? (0,
|
|
44742
|
+
command_id: opts.commandId ?? (0, import_node_crypto8.randomUUID)()
|
|
43109
44743
|
};
|
|
43110
44744
|
try {
|
|
43111
44745
|
await this.request(
|
|
@@ -43386,6 +45020,12 @@ var TelnyxTTS = class {
|
|
|
43386
45020
|
init_cjs_shims();
|
|
43387
45021
|
init_tracing();
|
|
43388
45022
|
init_event_bus();
|
|
45023
|
+
|
|
45024
|
+
// src/index.ts
|
|
45025
|
+
var hermes = Object.freeze({ LLM: LLM8 });
|
|
45026
|
+
var openclaw = Object.freeze({ LLM: LLM9 });
|
|
45027
|
+
var openaiCompatible = Object.freeze({ LLM: LLM6 });
|
|
45028
|
+
var custom2 = Object.freeze({ LLM: LLM7 });
|
|
43389
45029
|
// Annotate the CommonJS export names for ESM import in node:
|
|
43390
45030
|
0 && (module.exports = {
|
|
43391
45031
|
AllProvidersFailedError,
|
|
@@ -43402,6 +45042,7 @@ init_event_bus();
|
|
|
43402
45042
|
CerebrasLLM,
|
|
43403
45043
|
ChatContext,
|
|
43404
45044
|
CloudflareTunnel,
|
|
45045
|
+
CustomLLM,
|
|
43405
45046
|
DEFAULT_MIN_SENTENCE_LEN,
|
|
43406
45047
|
DEFAULT_PRICING,
|
|
43407
45048
|
DTMF_EVENTS,
|
|
@@ -43425,6 +45066,7 @@ init_event_bus();
|
|
|
43425
45066
|
GoogleLLM,
|
|
43426
45067
|
GroqLLM,
|
|
43427
45068
|
Guardrail,
|
|
45069
|
+
HermesLLM,
|
|
43428
45070
|
IVRActivity,
|
|
43429
45071
|
InworldTTS,
|
|
43430
45072
|
KrispFrameDuration,
|
|
@@ -43435,6 +45077,8 @@ init_event_bus();
|
|
|
43435
45077
|
MetricsStore,
|
|
43436
45078
|
MinWordsStrategy,
|
|
43437
45079
|
Ngrok,
|
|
45080
|
+
OpenAICompatibleLLM,
|
|
45081
|
+
OpenAICompatibleLLMProvider,
|
|
43438
45082
|
OpenAILLM,
|
|
43439
45083
|
OpenAILLMProvider,
|
|
43440
45084
|
OpenAIRealtime,
|
|
@@ -43448,6 +45092,7 @@ init_event_bus();
|
|
|
43448
45092
|
OpenAITranscribeSTT,
|
|
43449
45093
|
OpenAITranscriptionModel,
|
|
43450
45094
|
OpenAIVoice,
|
|
45095
|
+
OpenClawLLM,
|
|
43451
45096
|
PRICING_LAST_UPDATED,
|
|
43452
45097
|
PRICING_VERSION,
|
|
43453
45098
|
PartialStreamError,
|
|
@@ -43516,6 +45161,7 @@ init_event_bus();
|
|
|
43516
45161
|
createResampler24kTo16k,
|
|
43517
45162
|
createResampler24kTo8k,
|
|
43518
45163
|
createResampler8kTo16k,
|
|
45164
|
+
custom,
|
|
43519
45165
|
deepgram,
|
|
43520
45166
|
defineTool,
|
|
43521
45167
|
elevenlabs,
|
|
@@ -43527,6 +45173,8 @@ init_event_bus();
|
|
|
43527
45173
|
geminiLive,
|
|
43528
45174
|
getLogger,
|
|
43529
45175
|
guardrail,
|
|
45176
|
+
hashCaller,
|
|
45177
|
+
hermes,
|
|
43530
45178
|
initTracing,
|
|
43531
45179
|
isRemoteUrl,
|
|
43532
45180
|
isTracingEnabled,
|
|
@@ -43539,7 +45187,9 @@ init_event_bus();
|
|
|
43539
45187
|
mountDashboard,
|
|
43540
45188
|
mulawToPcm16,
|
|
43541
45189
|
notifyDashboard,
|
|
45190
|
+
openaiCompatible,
|
|
43542
45191
|
openaiTts,
|
|
45192
|
+
openclaw,
|
|
43543
45193
|
openclawConsult,
|
|
43544
45194
|
openclawPostCallNotifier,
|
|
43545
45195
|
pcm16ToMulaw,
|