@stackbone/cli 0.9.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/main.js +326 -76
- package/migrations/0122_model_routing_object.sql +16 -0
- package/package.json +1 -1
- package/stackbone-cli-0.9.2.tgz +0 -0
- package/voice-worker/main.js +171 -49
- package/voice-worker/main.js.map +4 -4
- package/stackbone-cli-0.9.1.tgz +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- 0122_model_routing_object.sql — a provider routing is OpenRouter's whole
|
|
2
|
+
-- `provider` object (`{ "only": ["deepinfra/fp8"], "allow_fallbacks": false }`),
|
|
3
|
+
-- not only its `sort`.
|
|
4
|
+
--
|
|
5
|
+
-- `routing` holds the object. `sort` stays, now nullable, and carries the routing
|
|
6
|
+
-- only when it is nothing but a plain sort: an SDK older than the object reads
|
|
7
|
+
-- `sort` alone, and it must see a routing it cannot apply as no routing, never
|
|
8
|
+
-- apply half of it. Rows written before 0122 keep `routing` NULL; every reader
|
|
9
|
+
-- falls back to `{ "sort": sort }`.
|
|
10
|
+
--
|
|
11
|
+
-- No CHECK on `routing`, for the reason 0121 has none on `sort`.
|
|
12
|
+
ALTER TABLE stackbone_platform.model_routing
|
|
13
|
+
ADD COLUMN IF NOT EXISTS routing jsonb;
|
|
14
|
+
|
|
15
|
+
ALTER TABLE stackbone_platform.model_routing
|
|
16
|
+
ALTER COLUMN sort DROP NOT NULL;
|
package/package.json
CHANGED
|
Binary file
|
package/voice-worker/main.js
CHANGED
|
@@ -149,7 +149,7 @@ var loadVad = async () => {
|
|
|
149
149
|
};
|
|
150
150
|
|
|
151
151
|
// libs/runtime/voice-worker/src/worker.ts
|
|
152
|
-
import { log as
|
|
152
|
+
import { log as log5, voice as voice4 } from "@livekit/agents";
|
|
153
153
|
import {
|
|
154
154
|
BackgroundVoiceCancellation,
|
|
155
155
|
TelephonyBackgroundVoiceCancellation
|
|
@@ -975,8 +975,12 @@ var turnSchema = z8.strictObject({
|
|
|
975
975
|
mode: z8.enum(["stt", "livekit"]),
|
|
976
976
|
minEndpointingDelayMs: millisecondsSchema,
|
|
977
977
|
maxEndpointingDelayMs: millisecondsSchema,
|
|
978
|
-
detectorThreshold: thresholdSchema
|
|
978
|
+
detectorThreshold: thresholdSchema,
|
|
979
|
+
// Flux refuses under 0.3 and above its own end-of-turn threshold (0.7 by default,
|
|
980
|
+
// not a setting), so the bound is the range Flux takes with that default.
|
|
981
|
+
eagerThreshold: z8.number().min(0.3).max(0.7)
|
|
979
982
|
});
|
|
983
|
+
var VOICE_EAGER_THRESHOLD_DEFAULT = 0.3;
|
|
980
984
|
var interruptionsSchema = z8.strictObject({
|
|
981
985
|
enabled: z8.boolean(),
|
|
982
986
|
minDurationMs: millisecondsSchema,
|
|
@@ -1017,7 +1021,12 @@ var effectiveVoiceSettingsObjectSchema = z8.strictObject({
|
|
|
1017
1021
|
sttModel: z8.enum(VOICE_STT_MODELS),
|
|
1018
1022
|
keyterms: keytermsSchema.default(() => []),
|
|
1019
1023
|
vad: vadSchema,
|
|
1020
|
-
|
|
1024
|
+
// Defaulted here, on the read side only: a box older than STA3-94 answers, and
|
|
1025
|
+
// dispatches, a `turn` without it. Not on `turnSchema`, whose `.partial()` layers
|
|
1026
|
+
// would then all declare it.
|
|
1027
|
+
turn: turnSchema.extend({
|
|
1028
|
+
eagerThreshold: turnSchema.shape.eagerThreshold.default(VOICE_EAGER_THRESHOLD_DEFAULT)
|
|
1029
|
+
}),
|
|
1021
1030
|
interruptions: interruptionsSchema,
|
|
1022
1031
|
ttsModel: z8.enum(VOICE_TTS_MODELS),
|
|
1023
1032
|
/**
|
|
@@ -1105,6 +1114,7 @@ var VOICE_SETTINGS_LEAF_PATHS = [
|
|
|
1105
1114
|
"turn.minEndpointingDelayMs",
|
|
1106
1115
|
"turn.maxEndpointingDelayMs",
|
|
1107
1116
|
"turn.detectorThreshold",
|
|
1117
|
+
"turn.eagerThreshold",
|
|
1108
1118
|
"interruptions.enabled",
|
|
1109
1119
|
"interruptions.minDurationMs",
|
|
1110
1120
|
"interruptions.minWords",
|
|
@@ -1129,11 +1139,19 @@ var VOICE_SETTINGS_LEAF_PATHS = [
|
|
|
1129
1139
|
"refusalPhrase"
|
|
1130
1140
|
];
|
|
1131
1141
|
var voiceSettingOriginSchema = z8.enum(["studio", "code", "default"]);
|
|
1142
|
+
var LEAVES_NEWER_THAN_SOME_BOXES = /* @__PURE__ */ new Set([
|
|
1143
|
+
"turn.eagerThreshold"
|
|
1144
|
+
]);
|
|
1132
1145
|
var effectiveVoiceSettingsViewSchema = z8.strictObject({
|
|
1133
1146
|
value: effectiveVoiceSettingsObjectSchema,
|
|
1134
1147
|
origins: z8.strictObject(
|
|
1135
1148
|
Object.fromEntries(
|
|
1136
|
-
|
|
1149
|
+
// A leaf newer than the box answering does not come back: that box runs its default.
|
|
1150
|
+
// Every other leaf stays required, so a box that drops one is still caught.
|
|
1151
|
+
VOICE_SETTINGS_LEAF_PATHS.map((path) => [
|
|
1152
|
+
path,
|
|
1153
|
+
LEAVES_NEWER_THAN_SOME_BOXES.has(path) ? voiceSettingOriginSchema.default("default") : voiceSettingOriginSchema
|
|
1154
|
+
])
|
|
1137
1155
|
)
|
|
1138
1156
|
)
|
|
1139
1157
|
});
|
|
@@ -1247,6 +1265,9 @@ var VOICE_CALLS_PATH = "/voice/v1/calls";
|
|
|
1247
1265
|
var voiceCallBootstrapSchema = z8.strictObject({
|
|
1248
1266
|
identities: integrationIdentitiesSchema.optional()
|
|
1249
1267
|
});
|
|
1268
|
+
var voiceCallKnowledgeSchema = z8.strictObject({
|
|
1269
|
+
text: z8.string().min(1).max(HEARD_TEXT_MAX)
|
|
1270
|
+
});
|
|
1250
1271
|
var voiceCallTranscriptSchema = z8.array(
|
|
1251
1272
|
z8.strictObject({
|
|
1252
1273
|
role: z8.enum(["user", "assistant"]),
|
|
@@ -1266,7 +1287,9 @@ var voiceCallTurnMetricsSchema = z8.strictObject({
|
|
|
1266
1287
|
transcriptionMs: turnLatencyMs,
|
|
1267
1288
|
llmTtftMs: turnLatencyMs,
|
|
1268
1289
|
ttsTtfbMs: turnLatencyMs,
|
|
1269
|
-
e2eMs: turnLatencyMs
|
|
1290
|
+
e2eMs: turnLatencyMs,
|
|
1291
|
+
/** How long before the final transcript the turn's last eager end of turn left for the box (STA3-94). */
|
|
1292
|
+
preflightLeadMs: turnLatencyMs
|
|
1270
1293
|
});
|
|
1271
1294
|
var voiceCallTurnsSchema = z8.array(voiceCallTurnMetricsSchema).max(VOICE_CALL_TURNS_MAX);
|
|
1272
1295
|
var voiceCallCompleteSchema = z8.strictObject({
|
|
@@ -1316,6 +1339,7 @@ async function* readSseData(body) {
|
|
|
1316
1339
|
// libs/runtime/voice-worker/src/box-wire.ts
|
|
1317
1340
|
var BODY_EXCERPT_CHARS = 300;
|
|
1318
1341
|
var TURN_IDLE_TIMEOUT_MS = 3e4;
|
|
1342
|
+
var KNOWLEDGE_TIMEOUT_MS = 2e3;
|
|
1319
1343
|
var NON_ASCII = /[\u007f-\uffff]/g;
|
|
1320
1344
|
var runErrorOf = ({ message, code }) => new Error(`box run failed${code ? ` (${code})` : ""}: ${message}`);
|
|
1321
1345
|
var BoxTurnError = class extends Error {
|
|
@@ -1424,23 +1448,39 @@ var toWireEvent = (ev) => {
|
|
|
1424
1448
|
return { kind: "other", type };
|
|
1425
1449
|
}
|
|
1426
1450
|
};
|
|
1451
|
+
var postCallOperation = (wire, operation, payload, timeoutMs) => fetch(
|
|
1452
|
+
boxEndpoint(
|
|
1453
|
+
wire.boxUrl,
|
|
1454
|
+
`${VOICE_CALLS_PATH}/${encodeURIComponent(wire.sessionKey)}/${operation}`
|
|
1455
|
+
),
|
|
1456
|
+
{
|
|
1457
|
+
method: "POST",
|
|
1458
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${wire.token}` },
|
|
1459
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
1460
|
+
body: JSON.stringify(payload)
|
|
1461
|
+
}
|
|
1462
|
+
);
|
|
1427
1463
|
var voiceCallRequest = async (wire, operation, payload) => {
|
|
1428
|
-
const response = await
|
|
1429
|
-
boxEndpoint(
|
|
1430
|
-
wire.boxUrl,
|
|
1431
|
-
`${VOICE_CALLS_PATH}/${encodeURIComponent(wire.sessionKey)}/${operation}`
|
|
1432
|
-
),
|
|
1433
|
-
{
|
|
1434
|
-
method: "POST",
|
|
1435
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${wire.token}` },
|
|
1436
|
-
signal: AbortSignal.timeout(TURN_IDLE_TIMEOUT_MS),
|
|
1437
|
-
body: JSON.stringify(payload)
|
|
1438
|
-
}
|
|
1439
|
-
);
|
|
1464
|
+
const response = await postCallOperation(wire, operation, payload, TURN_IDLE_TIMEOUT_MS);
|
|
1440
1465
|
if (!response.ok)
|
|
1441
1466
|
throw new BoxTurnError(wire.agent, response.status, await readExcerpt(response));
|
|
1442
1467
|
voiceCallLifecycleResponseSchema.parse(await response.json());
|
|
1443
1468
|
};
|
|
1469
|
+
var voiceCallKnowledge = async (wire, text) => {
|
|
1470
|
+
try {
|
|
1471
|
+
const response = await postCallOperation(
|
|
1472
|
+
wire,
|
|
1473
|
+
"knowledge",
|
|
1474
|
+
{ text },
|
|
1475
|
+
KNOWLEDGE_TIMEOUT_MS
|
|
1476
|
+
);
|
|
1477
|
+
await response.body?.cancel().catch(() => void 0);
|
|
1478
|
+
if (response.ok) return "sent";
|
|
1479
|
+
return response.status === 404 ? "gone" : "failed";
|
|
1480
|
+
} catch {
|
|
1481
|
+
return "failed";
|
|
1482
|
+
}
|
|
1483
|
+
};
|
|
1444
1484
|
|
|
1445
1485
|
// libs/runtime/voice-worker/src/heard.ts
|
|
1446
1486
|
var VOICE_RESPONSE_MARKER = "stackboneVoiceResponse";
|
|
@@ -1621,11 +1661,13 @@ var defined = (metrics) => Object.fromEntries(Object.entries(metrics).filter(([,
|
|
|
1621
1661
|
var watchTurnMetrics = (session, write, options = {}) => {
|
|
1622
1662
|
let caller;
|
|
1623
1663
|
let delivered;
|
|
1664
|
+
let lead;
|
|
1624
1665
|
session.on(voice2.AgentSessionEventTypes.ConversationItemAdded, ({ item }) => {
|
|
1625
1666
|
if (!(item instanceof llm.ChatMessage)) return;
|
|
1626
1667
|
if (item.role === "user") {
|
|
1627
1668
|
caller = item.metrics;
|
|
1628
1669
|
delivered = options.endOfTurnMs?.();
|
|
1670
|
+
lead = options.preflightLeadMs?.();
|
|
1629
1671
|
return;
|
|
1630
1672
|
}
|
|
1631
1673
|
if (item.role !== "assistant") return;
|
|
@@ -1637,10 +1679,12 @@ var watchTurnMetrics = (session, write, options = {}) => {
|
|
|
1637
1679
|
transcriptionMs: options.endOfTurnMs ? void 0 : ms(caller?.transcriptionDelay),
|
|
1638
1680
|
llmTtftMs: ms(reply.llmNodeTtft),
|
|
1639
1681
|
ttsTtfbMs: ms(reply.ttsNodeTtfb),
|
|
1640
|
-
e2eMs: e2eMs === void 0 || delivered === void 0 ? e2eMs : e2eMs + delivered
|
|
1682
|
+
e2eMs: e2eMs === void 0 || delivered === void 0 ? e2eMs : e2eMs + delivered,
|
|
1683
|
+
preflightLeadMs: lead === void 0 ? void 0 : Math.round(lead)
|
|
1641
1684
|
});
|
|
1642
1685
|
caller = void 0;
|
|
1643
1686
|
delivered = void 0;
|
|
1687
|
+
lead = void 0;
|
|
1644
1688
|
if (Object.keys(metrics).length === 0) return;
|
|
1645
1689
|
write(typeof runId === "string" ? { ...metrics, runId } : metrics);
|
|
1646
1690
|
});
|
|
@@ -1979,15 +2023,56 @@ var createHangUp = ({
|
|
|
1979
2023
|
// libs/runtime/voice-worker/src/session-options.ts
|
|
1980
2024
|
import { tts, voice as voice3 } from "@livekit/agents";
|
|
1981
2025
|
|
|
1982
|
-
// libs/runtime/voice-worker/src/
|
|
2026
|
+
// libs/runtime/voice-worker/src/knowledge-prefetch.ts
|
|
2027
|
+
import { log as log3, stt as stt2 } from "@livekit/agents";
|
|
1983
2028
|
import { TransformStream as TransformStream2 } from "node:stream/web";
|
|
2029
|
+
var knowledgeTap = (send, { onLead, now = Date.now } = {}) => {
|
|
2030
|
+
let sentThisTurn;
|
|
2031
|
+
let sentAt;
|
|
2032
|
+
let gone = false;
|
|
2033
|
+
let failureLogged = false;
|
|
2034
|
+
const tap = (event) => {
|
|
2035
|
+
if (gone || typeof event === "string") return;
|
|
2036
|
+
if (event.type === stt2.SpeechEventType.FINAL_TRANSCRIPT) {
|
|
2037
|
+
if (sentAt !== void 0) onLead?.(now() - sentAt);
|
|
2038
|
+
sentThisTurn = void 0;
|
|
2039
|
+
sentAt = void 0;
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
if (event.type !== stt2.SpeechEventType.PREFLIGHT_TRANSCRIPT) return;
|
|
2043
|
+
const text = event.alternatives?.[0]?.text.trim();
|
|
2044
|
+
if (!text || text === sentThisTurn) return;
|
|
2045
|
+
sentThisTurn = text;
|
|
2046
|
+
sentAt = now();
|
|
2047
|
+
void send(text).then((answer) => {
|
|
2048
|
+
if (answer === "gone") gone = true;
|
|
2049
|
+
if (answer === "failed" && !failureLogged) {
|
|
2050
|
+
failureLogged = true;
|
|
2051
|
+
log3().warn(
|
|
2052
|
+
"voice worker: the box refused a knowledge prefetch; turns search on their own"
|
|
2053
|
+
);
|
|
2054
|
+
}
|
|
2055
|
+
}).catch(() => void 0);
|
|
2056
|
+
};
|
|
2057
|
+
return (events) => events.pipeThrough(
|
|
2058
|
+
new TransformStream2({
|
|
2059
|
+
transform(event, controller) {
|
|
2060
|
+
tap(event);
|
|
2061
|
+
controller.enqueue(event);
|
|
2062
|
+
}
|
|
2063
|
+
})
|
|
2064
|
+
);
|
|
2065
|
+
};
|
|
2066
|
+
|
|
2067
|
+
// libs/runtime/voice-worker/src/number-text.ts
|
|
2068
|
+
import { TransformStream as TransformStream3 } from "node:stream/web";
|
|
1984
2069
|
var THOUSANDS_COMMA = /(\d),(?=\d{3}(?!\d))/g;
|
|
1985
2070
|
var TRAILING_NUMBER = /[\d.,]+$/;
|
|
1986
2071
|
var rewrite = (text) => text.replace(THOUSANDS_COMMA, "$1");
|
|
1987
2072
|
var dropThousandsComma = (input) => {
|
|
1988
2073
|
let pending = "";
|
|
1989
2074
|
return input.pipeThrough(
|
|
1990
|
-
new
|
|
2075
|
+
new TransformStream3({
|
|
1991
2076
|
transform(chunk, controller) {
|
|
1992
2077
|
const text = pending + chunk;
|
|
1993
2078
|
const tail = TRAILING_NUMBER.exec(text)?.[0] ?? "";
|
|
@@ -2004,7 +2089,7 @@ var dropThousandsComma = (input) => {
|
|
|
2004
2089
|
|
|
2005
2090
|
// libs/runtime/voice-worker/src/phone-voice.ts
|
|
2006
2091
|
import { AudioFrame as AudioFrame2 } from "@livekit/rtc-node";
|
|
2007
|
-
import { TransformStream as
|
|
2092
|
+
import { TransformStream as TransformStream4 } from "node:stream/web";
|
|
2008
2093
|
var LOW_CUT_HZ = 300;
|
|
2009
2094
|
var HIGH_CUT_HZ = 3400;
|
|
2010
2095
|
var Q = Math.SQRT1_2;
|
|
@@ -2040,7 +2125,7 @@ var phoneLine = (audio) => {
|
|
|
2040
2125
|
let chainRate = 0;
|
|
2041
2126
|
const norm = Math.tanh(DRIVE);
|
|
2042
2127
|
return audio.pipeThrough(
|
|
2043
|
-
new
|
|
2128
|
+
new TransformStream4({
|
|
2044
2129
|
transform(frame, controller) {
|
|
2045
2130
|
if (!chain || chainRate !== frame.sampleRate) {
|
|
2046
2131
|
chainRate = frame.sampleRate;
|
|
@@ -2066,11 +2151,18 @@ var phoneLine = (audio) => {
|
|
|
2066
2151
|
};
|
|
2067
2152
|
|
|
2068
2153
|
// libs/runtime/voice-worker/src/session-options.ts
|
|
2069
|
-
var sttOptions = ({ sttModel, language, keyterms }) => {
|
|
2154
|
+
var sttOptions = ({ sttModel, language, keyterms, turn }) => {
|
|
2070
2155
|
const terms = keyterms.length > 0 ? [...keyterms] : void 0;
|
|
2071
2156
|
const keyterm = terms ? { keyterm: terms } : void 0;
|
|
2072
2157
|
if (sttModel.startsWith("deepgram/flux")) {
|
|
2073
|
-
return {
|
|
2158
|
+
return {
|
|
2159
|
+
model: sttModel,
|
|
2160
|
+
modelOptions: {
|
|
2161
|
+
language_hint: language,
|
|
2162
|
+
...keyterm,
|
|
2163
|
+
eager_eot_threshold: turn.eagerThreshold
|
|
2164
|
+
}
|
|
2165
|
+
};
|
|
2074
2166
|
}
|
|
2075
2167
|
if (sttModel.startsWith("assemblyai/")) {
|
|
2076
2168
|
return {
|
|
@@ -2094,12 +2186,28 @@ var markupContext = (expressive, speech) => {
|
|
|
2094
2186
|
return guide === void 0 ? [] : [{ description: VOICE_TTS_MARKUP_CONTEXT, value: guide }];
|
|
2095
2187
|
};
|
|
2096
2188
|
var VoiceCallAgent = class extends voice3.Agent {
|
|
2097
|
-
constructor() {
|
|
2098
|
-
super(
|
|
2189
|
+
constructor({ sendKnowledge, ...options }) {
|
|
2190
|
+
super(options);
|
|
2099
2191
|
this.sttDelivery = createSttDeliveryMeter();
|
|
2192
|
+
this.#knowledge = sendKnowledge && knowledgeTap(sendKnowledge, {
|
|
2193
|
+
onLead: (ms2) => {
|
|
2194
|
+
this.#preflightLead = ms2;
|
|
2195
|
+
}
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
#knowledge;
|
|
2199
|
+
#preflightLead;
|
|
2200
|
+
/** The last turn's preflight head start in ms, once: a turn never reuses another's. */
|
|
2201
|
+
takePreflightLead() {
|
|
2202
|
+
const lead = this.#preflightLead;
|
|
2203
|
+
this.#preflightLead = void 0;
|
|
2204
|
+
return lead;
|
|
2100
2205
|
}
|
|
2101
2206
|
async sttNode(audio, modelSettings) {
|
|
2102
|
-
return this.sttDelivery.wrap(audio, (counted) =>
|
|
2207
|
+
return this.sttDelivery.wrap(audio, async (counted) => {
|
|
2208
|
+
const events = await super.sttNode(counted, modelSettings);
|
|
2209
|
+
return events && this.#knowledge ? this.#knowledge(events) : events;
|
|
2210
|
+
});
|
|
2103
2211
|
}
|
|
2104
2212
|
};
|
|
2105
2213
|
var PhoneLineAgent = class extends VoiceCallAgent {
|
|
@@ -2116,6 +2224,11 @@ var voiceSessionOptions = (settings, pieces) => {
|
|
|
2116
2224
|
minSilenceDuration: vad.minSilenceMs
|
|
2117
2225
|
});
|
|
2118
2226
|
const speech = pieces.createTts(ttsOptions(settings));
|
|
2227
|
+
const agentOptions = {
|
|
2228
|
+
// The box holds the prompt: LiveKit's own chat context stays prompt-less.
|
|
2229
|
+
instructions: "",
|
|
2230
|
+
sendKnowledge: pieces.sendKnowledge
|
|
2231
|
+
};
|
|
2119
2232
|
return {
|
|
2120
2233
|
session: {
|
|
2121
2234
|
vad: pieces.vad,
|
|
@@ -2155,14 +2268,13 @@ var voiceSessionOptions = (settings, pieces) => {
|
|
|
2155
2268
|
preemptiveGeneration: { enabled: false, preemptiveTts: false }
|
|
2156
2269
|
}
|
|
2157
2270
|
},
|
|
2158
|
-
|
|
2159
|
-
agent: settings.phoneLine ? new PhoneLineAgent({ instructions: "" }) : new VoiceCallAgent({ instructions: "" }),
|
|
2271
|
+
agent: settings.phoneLine ? new PhoneLineAgent(agentOptions) : new VoiceCallAgent(agentOptions),
|
|
2160
2272
|
boxContext: markupContext(settings.expressive, speech)
|
|
2161
2273
|
};
|
|
2162
2274
|
};
|
|
2163
2275
|
|
|
2164
2276
|
// libs/runtime/voice-worker/src/telemetry/call-traces.ts
|
|
2165
|
-
import { log as
|
|
2277
|
+
import { log as log4, telemetry } from "@livekit/agents";
|
|
2166
2278
|
import { ExportResultCode } from "@opentelemetry/core";
|
|
2167
2279
|
import { defaultResource, resourceFromAttributes } from "@opentelemetry/resources";
|
|
2168
2280
|
import {
|
|
@@ -2174,7 +2286,7 @@ var warnOnRefusedBatch = (exporter, sessionKey) => ({
|
|
|
2174
2286
|
export: (spans, done) => exporter.export(spans, (result) => {
|
|
2175
2287
|
try {
|
|
2176
2288
|
if (result.code !== ExportResultCode.SUCCESS) {
|
|
2177
|
-
|
|
2289
|
+
log4().warn(
|
|
2178
2290
|
{ sessionKey, error: result.error?.message ?? "unknown" },
|
|
2179
2291
|
"voice worker: a batch of call spans failed to export to the box"
|
|
2180
2292
|
);
|
|
@@ -2288,14 +2400,14 @@ var callerNoiseCancellation = (caller) => {
|
|
|
2288
2400
|
try {
|
|
2289
2401
|
return caller.kind === ParticipantKind.SIP ? TelephonyBackgroundVoiceCancellation() : BackgroundVoiceCancellation();
|
|
2290
2402
|
} catch (err) {
|
|
2291
|
-
|
|
2403
|
+
log5().warn({ error: messageOf(err) }, "voice worker: no noise cancellation on this call");
|
|
2292
2404
|
return void 0;
|
|
2293
2405
|
}
|
|
2294
2406
|
};
|
|
2295
2407
|
var runVoiceJob = async (ctx, deps) => {
|
|
2296
2408
|
const dispatch = readDispatch(ctx.job.metadata);
|
|
2297
2409
|
if (!dispatch) {
|
|
2298
|
-
|
|
2410
|
+
log5().warn(
|
|
2299
2411
|
{ metadataChars: ctx.job.metadata?.length ?? 0 },
|
|
2300
2412
|
"voice worker: refused a job without a valid dispatch"
|
|
2301
2413
|
);
|
|
@@ -2304,7 +2416,7 @@ var runVoiceJob = async (ctx, deps) => {
|
|
|
2304
2416
|
}
|
|
2305
2417
|
await ctx.connect();
|
|
2306
2418
|
const caller = await ctx.waitForParticipant().catch((err) => {
|
|
2307
|
-
|
|
2419
|
+
log5().info(
|
|
2308
2420
|
{ reason: messageOf(err) },
|
|
2309
2421
|
"voice worker: the room closed before the caller joined"
|
|
2310
2422
|
);
|
|
@@ -2332,22 +2444,22 @@ var runVoiceJob = async (ctx, deps) => {
|
|
|
2332
2444
|
try {
|
|
2333
2445
|
session.say(text);
|
|
2334
2446
|
} catch (err) {
|
|
2335
|
-
|
|
2447
|
+
log5().info({ error: messageOf(err) }, `voice worker: the ${what} was not spoken`);
|
|
2336
2448
|
}
|
|
2337
2449
|
};
|
|
2338
2450
|
const hangUpAfter = (played, reason) => {
|
|
2339
2451
|
ending = true;
|
|
2340
2452
|
session.input.setAudioEnabled(false);
|
|
2341
2453
|
void Promise.resolve().then(played).catch((err) => {
|
|
2342
|
-
|
|
2454
|
+
log5().info({ error: messageOf(err) }, "voice worker: the closing phrase was not spoken");
|
|
2343
2455
|
}).then(() => ctx.deleteRoom()).catch((err) => {
|
|
2344
|
-
|
|
2456
|
+
log5().warn({ error: messageOf(err) }, "voice worker: the room was not deleted");
|
|
2345
2457
|
}).then(
|
|
2346
2458
|
() => finishCall(
|
|
2347
2459
|
reason === "turns kept failing" || reason === "box refused the call token" ? "failed" : "completed"
|
|
2348
2460
|
)
|
|
2349
2461
|
).catch(() => {
|
|
2350
|
-
|
|
2462
|
+
log5().warn(
|
|
2351
2463
|
{ agent: dispatch.agent, sessionKey: dispatch.sessionKey, reason: "completion_failed" },
|
|
2352
2464
|
"voice worker: call finalization failed"
|
|
2353
2465
|
);
|
|
@@ -2367,12 +2479,12 @@ var runVoiceJob = async (ctx, deps) => {
|
|
|
2367
2479
|
});
|
|
2368
2480
|
const onTurnError = (err) => {
|
|
2369
2481
|
if (err instanceof BoxTurnError && err.status === 401) {
|
|
2370
|
-
|
|
2482
|
+
log5().error(boxFailure(err), "voice worker: the box refused the call token (401)");
|
|
2371
2483
|
if (!ending) endCall(CALL_CLOSING_PHRASE, "box refused the call token");
|
|
2372
2484
|
return;
|
|
2373
2485
|
}
|
|
2374
2486
|
failedInARow += 1;
|
|
2375
|
-
|
|
2487
|
+
log5().warn({ ...boxFailure(err), failedInARow }, "voice worker: a turn failed");
|
|
2376
2488
|
if (ending) return;
|
|
2377
2489
|
if (failedInARow >= MAX_FAILED_TURNS_IN_A_ROW) {
|
|
2378
2490
|
endCall(CALL_CLOSING_PHRASE, "turns kept failing");
|
|
@@ -2384,13 +2496,20 @@ var runVoiceJob = async (ctx, deps) => {
|
|
|
2384
2496
|
failedInARow = 0;
|
|
2385
2497
|
if (outcome === "interrupt" && !ending) endCall(PAUSED_RUN_PHRASE, "run paused for review");
|
|
2386
2498
|
};
|
|
2387
|
-
const
|
|
2388
|
-
const wire = {
|
|
2499
|
+
const call = {
|
|
2389
2500
|
boxUrl: dispatch.boxUrl,
|
|
2390
2501
|
token: dispatch.callToken,
|
|
2391
2502
|
agent: dispatch.agent,
|
|
2392
2503
|
sessionKey: dispatch.sessionKey,
|
|
2393
|
-
tags: dispatch.tags
|
|
2504
|
+
tags: dispatch.tags
|
|
2505
|
+
};
|
|
2506
|
+
const plan = voiceSessionOptions(settings, {
|
|
2507
|
+
...deps,
|
|
2508
|
+
vad: ctx.proc.userData.vad,
|
|
2509
|
+
sendKnowledge: (text) => voiceCallKnowledge(call, text)
|
|
2510
|
+
});
|
|
2511
|
+
const wire = {
|
|
2512
|
+
...call,
|
|
2394
2513
|
// With `expressive`, the TTS markup guide: LiveKit gives it only to its own chat context.
|
|
2395
2514
|
context: plan.boxContext
|
|
2396
2515
|
};
|
|
@@ -2406,7 +2525,7 @@ var runVoiceJob = async (ctx, deps) => {
|
|
|
2406
2525
|
hangUpAfter,
|
|
2407
2526
|
isEnding: () => ending,
|
|
2408
2527
|
onResumeFailed: (err) => {
|
|
2409
|
-
|
|
2528
|
+
log5().warn(boxFailure(err), "voice worker: the hang_up resume failed");
|
|
2410
2529
|
}
|
|
2411
2530
|
});
|
|
2412
2531
|
const brain = new BoxLLM({
|
|
@@ -2438,14 +2557,14 @@ var runVoiceJob = async (ctx, deps) => {
|
|
|
2438
2557
|
if (!(turns.length > 0 && error instanceof BoxTurnError && error.status === 400)) {
|
|
2439
2558
|
throw error;
|
|
2440
2559
|
}
|
|
2441
|
-
|
|
2560
|
+
log5().warn(
|
|
2442
2561
|
{ agent: dispatch.agent, sessionKey: dispatch.sessionKey, status: error.status },
|
|
2443
2562
|
"voice worker: the box refused the turn metrics, completing without them"
|
|
2444
2563
|
);
|
|
2445
2564
|
await complete({ transcript, outcome });
|
|
2446
2565
|
}
|
|
2447
2566
|
}).catch((error) => {
|
|
2448
|
-
|
|
2567
|
+
log5().warn(
|
|
2449
2568
|
{ agent: dispatch.agent, sessionKey: dispatch.sessionKey, reason: "completion_failed" },
|
|
2450
2569
|
"voice worker: call finalization failed"
|
|
2451
2570
|
);
|
|
@@ -2466,12 +2585,15 @@ var runVoiceJob = async (ctx, deps) => {
|
|
|
2466
2585
|
session,
|
|
2467
2586
|
(metrics) => {
|
|
2468
2587
|
if (turns.length < VOICE_CALL_TURNS_MAX) turns.push(metrics);
|
|
2469
|
-
|
|
2588
|
+
log5().info(
|
|
2470
2589
|
{ agent: dispatch.agent, sessionKey: dispatch.sessionKey, room: ctx.room.name, ...metrics },
|
|
2471
2590
|
VOICE_TURN_METRICS_MESSAGE
|
|
2472
2591
|
);
|
|
2473
2592
|
},
|
|
2474
|
-
|
|
2593
|
+
{
|
|
2594
|
+
...deliversItsOwnEndOfTurn(settings) ? { endOfTurnMs: () => plan.agent.sttDelivery.take() } : {},
|
|
2595
|
+
preflightLeadMs: () => plan.agent.takePreflightLead()
|
|
2596
|
+
}
|
|
2475
2597
|
);
|
|
2476
2598
|
const noiseCancellation = callerNoiseCancellation(participant);
|
|
2477
2599
|
await session.start({
|
|
@@ -2481,7 +2603,7 @@ var runVoiceJob = async (ctx, deps) => {
|
|
|
2481
2603
|
});
|
|
2482
2604
|
if (settings.greeting !== null) sayIfOpen(settings.greeting, "greeting");
|
|
2483
2605
|
await deps.createCue({ room: ctx.room, session, audio: settings.backgroundAudio }).then(cue.attach, (err) => {
|
|
2484
|
-
|
|
2606
|
+
log5().warn({ error: messageOf(err) }, "voice worker: the background audio did not start");
|
|
2485
2607
|
});
|
|
2486
2608
|
};
|
|
2487
2609
|
|