@lokutor/sdk 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -2
- package/dist/index.d.mts +34 -21
- package/dist/index.d.ts +34 -21
- package/dist/index.js +151 -86
- package/dist/index.mjs +151 -86
- package/package.json +1 -1
- package/src/client.ts +161 -92
- package/src/types.ts +36 -17
package/dist/index.mjs
CHANGED
|
@@ -87,7 +87,12 @@ function isRetryableCode(code) {
|
|
|
87
87
|
"validation.speed_out_of_range",
|
|
88
88
|
"validation.steps_out_of_range",
|
|
89
89
|
"validation.invalid_request_format",
|
|
90
|
-
"internal.cancelled"
|
|
90
|
+
"internal.cancelled",
|
|
91
|
+
"agent.not_found",
|
|
92
|
+
"billing.insufficient_balance",
|
|
93
|
+
"billing.topup_required",
|
|
94
|
+
"billing.limit_reached",
|
|
95
|
+
"billing.account_disputed"
|
|
91
96
|
];
|
|
92
97
|
return !fatal.includes(code);
|
|
93
98
|
}
|
|
@@ -662,6 +667,9 @@ function nowMs() {
|
|
|
662
667
|
function wsToHttp(url) {
|
|
663
668
|
return url.replace(/^wss:/, "https:").replace(/^ws:/, "http:");
|
|
664
669
|
}
|
|
670
|
+
function apiOrigin(base) {
|
|
671
|
+
return new URL(wsToHttp(base || DEFAULT_URLS.VOICE_AGENT)).origin;
|
|
672
|
+
}
|
|
665
673
|
async function fetchJson(url, timeoutMs = 1e4) {
|
|
666
674
|
const controller = new AbortController();
|
|
667
675
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -774,10 +782,16 @@ var VoiceAgentClient = class {
|
|
|
774
782
|
serverPlaybackRate = 44100;
|
|
775
783
|
serverInputRate = 16e3;
|
|
776
784
|
serverUrl;
|
|
785
|
+
authProvider;
|
|
786
|
+
voiceExplicit = false;
|
|
787
|
+
languageExplicit = false;
|
|
777
788
|
constructor(config) {
|
|
778
|
-
this.apiKey = config.apiKey;
|
|
789
|
+
this.apiKey = config.apiKey || "";
|
|
790
|
+
this.authProvider = config.auth;
|
|
779
791
|
this.agentId = config.agentId || "";
|
|
780
|
-
this.prompt = config.prompt;
|
|
792
|
+
this.prompt = config.prompt ?? "";
|
|
793
|
+
this.voiceExplicit = config.voice !== void 0;
|
|
794
|
+
this.languageExplicit = config.language !== void 0;
|
|
781
795
|
this.voice = config.voice || "F1" /* F1 */;
|
|
782
796
|
this.language = config.language || "en" /* ENGLISH */;
|
|
783
797
|
this.serverUrl = config.serverUrl || DEFAULT_URLS.VOICE_AGENT;
|
|
@@ -797,6 +811,15 @@ var VoiceAgentClient = class {
|
|
|
797
811
|
*/
|
|
798
812
|
async connect(customAudioManager) {
|
|
799
813
|
this.isUserDisconnect = false;
|
|
814
|
+
if (this.authProvider) {
|
|
815
|
+
try {
|
|
816
|
+
this.apiKey = await this.authProvider();
|
|
817
|
+
} catch (err) {
|
|
818
|
+
const error = new LokutorError("auth.missing_key", "The auth callback failed to return a credential", { original: err, retryable: true });
|
|
819
|
+
this.emit("error", error);
|
|
820
|
+
throw error;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
800
823
|
if (this.enableAudio || customAudioManager) {
|
|
801
824
|
if (customAudioManager) {
|
|
802
825
|
this.audioManager = customAudioManager;
|
|
@@ -819,11 +842,11 @@ var VoiceAgentClient = class {
|
|
|
819
842
|
let url = this.serverUrl;
|
|
820
843
|
if (this.apiKey) {
|
|
821
844
|
const separator = url.includes("?") ? "&" : "?";
|
|
822
|
-
url += `${separator}api_key=${this.apiKey}`;
|
|
845
|
+
url += `${separator}api_key=${encodeURIComponent(this.apiKey)}`;
|
|
823
846
|
}
|
|
824
847
|
if (this.agentId) {
|
|
825
848
|
const separator = url.includes("?") ? "&" : "?";
|
|
826
|
-
url += `${separator}agent_id=${this.agentId}`;
|
|
849
|
+
url += `${separator}agent_id=${encodeURIComponent(this.agentId)}`;
|
|
827
850
|
}
|
|
828
851
|
const redactedUrl = url.replace(/api_key=[^&]+/, "api_key=***");
|
|
829
852
|
sdkTrace("ws.connect", {
|
|
@@ -889,7 +912,7 @@ var VoiceAgentClient = class {
|
|
|
889
912
|
});
|
|
890
913
|
console.error("\u274C WebSocket error:", error.message);
|
|
891
914
|
sdkTrace("ws.error", { code: error.code, message: error.message });
|
|
892
|
-
|
|
915
|
+
this.emit("error", error);
|
|
893
916
|
if (!this.isConnected) {
|
|
894
917
|
settle(() => reject(error));
|
|
895
918
|
}
|
|
@@ -926,8 +949,8 @@ var VoiceAgentClient = class {
|
|
|
926
949
|
});
|
|
927
950
|
console.log(`Session ended by the server (code ${event.code})`);
|
|
928
951
|
this.emit("ended", { code: event.code, reason: event.reason || "" });
|
|
929
|
-
|
|
930
|
-
|
|
952
|
+
this.emit("error", ended);
|
|
953
|
+
this.emit("status", "disconnected");
|
|
931
954
|
return;
|
|
932
955
|
}
|
|
933
956
|
if (!this.isUserDisconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
@@ -935,7 +958,7 @@ var VoiceAgentClient = class {
|
|
|
935
958
|
this.reconnectAttempts++;
|
|
936
959
|
const backoffDelay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 1e4);
|
|
937
960
|
console.warn(`Connection lost. Reconnecting in ${backoffDelay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
|
938
|
-
|
|
961
|
+
this.emit("status", "reconnecting");
|
|
939
962
|
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
940
963
|
this.reconnectTimer = setTimeout(() => {
|
|
941
964
|
this.reconnectTimer = null;
|
|
@@ -945,15 +968,15 @@ var VoiceAgentClient = class {
|
|
|
945
968
|
}, backoffDelay);
|
|
946
969
|
} else if (this.isUserDisconnect) {
|
|
947
970
|
console.log("Disconnected");
|
|
948
|
-
|
|
971
|
+
this.emit("status", "disconnected");
|
|
949
972
|
} else {
|
|
950
973
|
console.log("Max reconnection attempts reached");
|
|
951
|
-
|
|
974
|
+
this.emit("status", "disconnected");
|
|
952
975
|
}
|
|
953
976
|
};
|
|
954
977
|
} catch (err) {
|
|
955
978
|
const error = err instanceof LokutorError ? err : new LokutorError("internal.error", "Failed to create WebSocket connection", { original: err });
|
|
956
|
-
|
|
979
|
+
this.emit("error", error);
|
|
957
980
|
settle(() => reject(error));
|
|
958
981
|
}
|
|
959
982
|
});
|
|
@@ -981,13 +1004,14 @@ var VoiceAgentClient = class {
|
|
|
981
1004
|
sendConfig() {
|
|
982
1005
|
if (!this.ws || !this.isConnected) return;
|
|
983
1006
|
this.configConfirmed = false;
|
|
984
|
-
|
|
985
|
-
this.ws.send(JSON.stringify({ type: "
|
|
1007
|
+
const agentSession = Boolean(this.agentId);
|
|
1008
|
+
if (!agentSession || this.voiceExplicit) this.ws.send(JSON.stringify({ type: "voice", data: this.voice }));
|
|
1009
|
+
if (!agentSession || this.languageExplicit) this.ws.send(JSON.stringify({ type: "language", data: this.language }));
|
|
986
1010
|
if (this.tools && this.tools.length > 0) {
|
|
987
1011
|
this.ws.send(JSON.stringify({ type: "tools", data: this.tools }));
|
|
988
1012
|
}
|
|
989
1013
|
this.ws.send(JSON.stringify({ type: "visemes", data: this.wantVisemes }));
|
|
990
|
-
this.ws.send(JSON.stringify({ type: "prompt", data: this.prompt }));
|
|
1014
|
+
if (!agentSession || this.prompt) this.ws.send(JSON.stringify({ type: "prompt", data: this.prompt }));
|
|
991
1015
|
this.ws.send(JSON.stringify({ type: "rates", playback: 44100, input: 16e3 }));
|
|
992
1016
|
sdkTrace("ws.send.config", {
|
|
993
1017
|
promptLen: this.prompt?.length || 0,
|
|
@@ -1048,6 +1072,10 @@ var VoiceAgentClient = class {
|
|
|
1048
1072
|
sdkTrace("server_rates", { playback: this.serverPlaybackRate, input: this.serverInputRate });
|
|
1049
1073
|
break;
|
|
1050
1074
|
case "transcript":
|
|
1075
|
+
if (msg.partial) {
|
|
1076
|
+
this.emit("transcription_partial", msg.data);
|
|
1077
|
+
break;
|
|
1078
|
+
}
|
|
1051
1079
|
const role = msg.role === "user" ? "user" : "agent";
|
|
1052
1080
|
this.messages.push({
|
|
1053
1081
|
role,
|
|
@@ -1055,11 +1083,9 @@ var VoiceAgentClient = class {
|
|
|
1055
1083
|
timestamp: nowMs()
|
|
1056
1084
|
});
|
|
1057
1085
|
if (msg.role === "user") {
|
|
1058
|
-
if (this.onTranscription) this.onTranscription(msg.data);
|
|
1059
1086
|
this.emit("transcription", msg.data);
|
|
1060
1087
|
console.log(`\u{1F4AC} You: ${msg.data}`);
|
|
1061
1088
|
} else {
|
|
1062
|
-
if (this.onResponse) this.onResponse(msg.data);
|
|
1063
1089
|
this.emit("response", msg.data);
|
|
1064
1090
|
console.log(`\u{1F916} Agent: ${msg.data}`);
|
|
1065
1091
|
}
|
|
@@ -1081,7 +1107,6 @@ var VoiceAgentClient = class {
|
|
|
1081
1107
|
if (msg.data === "interrupted" && this.audioManager) {
|
|
1082
1108
|
this.audioManager.stopPlayback();
|
|
1083
1109
|
}
|
|
1084
|
-
if (this.onStatus) this.onStatus(msg.data);
|
|
1085
1110
|
this.emit("status", msg.data);
|
|
1086
1111
|
const icons = {
|
|
1087
1112
|
"interrupted": "\u26A1",
|
|
@@ -1131,11 +1156,12 @@ var VoiceAgentClient = class {
|
|
|
1131
1156
|
}
|
|
1132
1157
|
break;
|
|
1133
1158
|
}
|
|
1134
|
-
case "tool_call":
|
|
1135
|
-
|
|
1159
|
+
case "tool_call": {
|
|
1160
|
+
const call = { name: msg.name, arguments: msg.arguments, call_id: msg.call_id };
|
|
1136
1161
|
console.log(`\u{1F6E0}\uFE0F Tool Call: ${msg.name}(${msg.arguments})`);
|
|
1137
|
-
this.emit("tool_call",
|
|
1162
|
+
this.emit("tool_call", call);
|
|
1138
1163
|
break;
|
|
1164
|
+
}
|
|
1139
1165
|
}
|
|
1140
1166
|
} catch (e) {
|
|
1141
1167
|
sdkTrace("ws.recv.parse_error", { preview: text?.slice(0, 120) });
|
|
@@ -1249,7 +1275,7 @@ var VoiceAgentClient = class {
|
|
|
1249
1275
|
* No authentication required.
|
|
1250
1276
|
*/
|
|
1251
1277
|
static async fetchVoices(baseUrl) {
|
|
1252
|
-
const url =
|
|
1278
|
+
const url = apiOrigin(baseUrl);
|
|
1253
1279
|
const data = await fetchJson(`${url}/voices`);
|
|
1254
1280
|
return data.voices || [];
|
|
1255
1281
|
}
|
|
@@ -1258,7 +1284,7 @@ var VoiceAgentClient = class {
|
|
|
1258
1284
|
* No authentication required.
|
|
1259
1285
|
*/
|
|
1260
1286
|
static async fetchLanguages(baseUrl) {
|
|
1261
|
-
const url =
|
|
1287
|
+
const url = apiOrigin(baseUrl);
|
|
1262
1288
|
const data = await fetchJson(`${url}/languages`);
|
|
1263
1289
|
return data.languages || [];
|
|
1264
1290
|
}
|
|
@@ -1267,7 +1293,7 @@ var VoiceAgentClient = class {
|
|
|
1267
1293
|
* No authentication required.
|
|
1268
1294
|
*/
|
|
1269
1295
|
static async fetchModels(baseUrl) {
|
|
1270
|
-
const url =
|
|
1296
|
+
const url = apiOrigin(baseUrl);
|
|
1271
1297
|
const data = await fetchJson(`${url}/models`);
|
|
1272
1298
|
return data.models || [];
|
|
1273
1299
|
}
|
|
@@ -1276,7 +1302,7 @@ var VoiceAgentClient = class {
|
|
|
1276
1302
|
* No authentication required.
|
|
1277
1303
|
*/
|
|
1278
1304
|
static async fetchConfig(baseUrl) {
|
|
1279
|
-
const url =
|
|
1305
|
+
const url = apiOrigin(baseUrl);
|
|
1280
1306
|
return fetchJson(`${url}/config`);
|
|
1281
1307
|
}
|
|
1282
1308
|
/**
|
|
@@ -1284,16 +1310,16 @@ var VoiceAgentClient = class {
|
|
|
1284
1310
|
* No authentication required.
|
|
1285
1311
|
*/
|
|
1286
1312
|
static async fetchStatus(baseUrl) {
|
|
1287
|
-
const url =
|
|
1313
|
+
const url = apiOrigin(baseUrl);
|
|
1288
1314
|
return fetchJson(`${url}/status`);
|
|
1289
1315
|
}
|
|
1290
1316
|
/**
|
|
1291
|
-
* Fetch
|
|
1292
|
-
* No authentication required.
|
|
1317
|
+
* Fetch readiness from the server (GET /ready: 200 once it can serve).
|
|
1318
|
+
* No authentication required. (/health is the operator dashboard's and needs its password.)
|
|
1293
1319
|
*/
|
|
1294
1320
|
static async fetchHealth(baseUrl) {
|
|
1295
|
-
const url =
|
|
1296
|
-
return fetchJson(`${url}/
|
|
1321
|
+
const url = apiOrigin(baseUrl);
|
|
1322
|
+
return fetchJson(`${url}/ready`);
|
|
1297
1323
|
}
|
|
1298
1324
|
/**
|
|
1299
1325
|
* Update the system prompt mid-conversation
|
|
@@ -1318,6 +1344,7 @@ var VoiceAgentClient = class {
|
|
|
1318
1344
|
*/
|
|
1319
1345
|
updateVoice(voice) {
|
|
1320
1346
|
this.voice = voice;
|
|
1347
|
+
this.voiceExplicit = true;
|
|
1321
1348
|
if (this.ws && this.ws.readyState === WebSocket.OPEN && this.isConnected) {
|
|
1322
1349
|
this.ws.send(JSON.stringify({ type: "voice", data: voice }));
|
|
1323
1350
|
console.log(`\u2699\uFE0F Updated voice: ${voice}`);
|
|
@@ -1328,6 +1355,7 @@ var VoiceAgentClient = class {
|
|
|
1328
1355
|
*/
|
|
1329
1356
|
updateLanguage(language) {
|
|
1330
1357
|
this.language = language;
|
|
1358
|
+
this.languageExplicit = true;
|
|
1331
1359
|
if (this.ws && this.ws.readyState === WebSocket.OPEN && this.isConnected) {
|
|
1332
1360
|
this.ws.send(JSON.stringify({ type: "language", data: language }));
|
|
1333
1361
|
console.log(`\u2699\uFE0F Updated language: ${language}`);
|
|
@@ -1387,98 +1415,120 @@ var TTSClient = class {
|
|
|
1387
1415
|
*/
|
|
1388
1416
|
synthesize(options) {
|
|
1389
1417
|
return new Promise((resolve, reject) => {
|
|
1390
|
-
let
|
|
1418
|
+
let timer;
|
|
1391
1419
|
let ws;
|
|
1392
1420
|
let startTime;
|
|
1393
1421
|
let firstByteReceived = false;
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1422
|
+
let finished = false;
|
|
1423
|
+
const finish = (err) => {
|
|
1424
|
+
if (finished) return;
|
|
1425
|
+
finished = true;
|
|
1426
|
+
if (timer) clearTimeout(timer);
|
|
1427
|
+
try {
|
|
1428
|
+
ws?.close();
|
|
1429
|
+
} catch {
|
|
1430
|
+
}
|
|
1431
|
+
if (err) {
|
|
1432
|
+
if (options.onError) options.onError(err);
|
|
1433
|
+
reject(err);
|
|
1434
|
+
} else {
|
|
1399
1435
|
resolve();
|
|
1400
|
-
}
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
const arm = () => {
|
|
1439
|
+
if (timer) clearTimeout(timer);
|
|
1440
|
+
timer = setTimeout(
|
|
1441
|
+
() => finish(new LokutorError(
|
|
1442
|
+
"internal.timeout",
|
|
1443
|
+
firstByteReceived ? "TTS stream stalled" : "No audio within 30 s",
|
|
1444
|
+
{ retryable: true }
|
|
1445
|
+
)),
|
|
1446
|
+
firstByteReceived ? 15e3 : 3e4
|
|
1447
|
+
);
|
|
1401
1448
|
};
|
|
1402
1449
|
try {
|
|
1403
1450
|
let url = DEFAULT_URLS.TTS;
|
|
1404
1451
|
if (this.apiKey) {
|
|
1405
1452
|
const separator = url.includes("?") ? "&" : "?";
|
|
1406
|
-
url += `${separator}api_key=${this.apiKey}`;
|
|
1453
|
+
url += `${separator}api_key=${encodeURIComponent(this.apiKey)}`;
|
|
1407
1454
|
}
|
|
1408
1455
|
ws = new WebSocket(url);
|
|
1409
1456
|
ws.binaryType = "arraybuffer";
|
|
1410
1457
|
ws.onopen = () => {
|
|
1411
|
-
|
|
1458
|
+
arm();
|
|
1412
1459
|
const req = {
|
|
1413
1460
|
text: options.text,
|
|
1414
1461
|
voice: options.voice || "F1" /* F1 */,
|
|
1415
1462
|
language: options.language || "en" /* ENGLISH */,
|
|
1416
|
-
speed: options.speed || 1.05,
|
|
1417
|
-
steps: options.steps || 24,
|
|
1418
1463
|
visemes: options.visemes || false
|
|
1419
1464
|
};
|
|
1465
|
+
if (options.speed !== void 0) req.speed = options.speed;
|
|
1466
|
+
if (options.steps !== void 0) req.steps = options.steps;
|
|
1420
1467
|
ws.send(JSON.stringify(req));
|
|
1421
1468
|
startTime = nowMs();
|
|
1422
1469
|
};
|
|
1423
1470
|
ws.onmessage = async (event) => {
|
|
1424
|
-
refreshTimeout();
|
|
1425
1471
|
if (event.data instanceof ArrayBuffer) {
|
|
1426
1472
|
if (!firstByteReceived) {
|
|
1427
1473
|
const ttfb = nowMs() - startTime;
|
|
1428
1474
|
if (options.onTTFB) options.onTTFB(ttfb);
|
|
1429
1475
|
firstByteReceived = true;
|
|
1430
1476
|
}
|
|
1477
|
+
arm();
|
|
1431
1478
|
if (options.onAudio) options.onAudio(new Uint8Array(event.data));
|
|
1432
1479
|
return;
|
|
1433
1480
|
}
|
|
1434
1481
|
const text = event.data.toString();
|
|
1435
1482
|
if (text === "EOS") {
|
|
1436
|
-
|
|
1437
|
-
ws.close();
|
|
1438
|
-
resolve();
|
|
1483
|
+
finish();
|
|
1439
1484
|
return;
|
|
1440
1485
|
}
|
|
1486
|
+
let msg;
|
|
1441
1487
|
try {
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
options.
|
|
1459
|
-
|
|
1460
|
-
}
|
|
1461
|
-
if (msg.type === "eos") {
|
|
1462
|
-
if (activityTimeout) clearTimeout(activityTimeout);
|
|
1463
|
-
ws.close();
|
|
1464
|
-
resolve();
|
|
1488
|
+
msg = JSON.parse(text);
|
|
1489
|
+
} catch {
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
if (msg.type === "error") {
|
|
1493
|
+
const data = typeof msg.data === "object" && msg.data ? msg.data : { message: msg.data };
|
|
1494
|
+
finish(new LokutorError(data.code ?? "internal.error", data.message ?? "TTS failed", {
|
|
1495
|
+
detail: data.detail,
|
|
1496
|
+
retryable: data.retryable ?? false
|
|
1497
|
+
}));
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
if (msg.type === "audio" && msg.data) {
|
|
1501
|
+
const audioBuffer = base64ToUint8Array(msg.data);
|
|
1502
|
+
if (!firstByteReceived) {
|
|
1503
|
+
const ttfb = nowMs() - startTime;
|
|
1504
|
+
if (options.onTTFB) options.onTTFB(ttfb);
|
|
1505
|
+
firstByteReceived = true;
|
|
1465
1506
|
}
|
|
1466
|
-
|
|
1507
|
+
arm();
|
|
1508
|
+
if (options.onAudio) options.onAudio(audioBuffer);
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
if (msg.type === "visemes" && Array.isArray(msg.data) && options.onVisemes) {
|
|
1512
|
+
options.onVisemes(normalizeVisemes(msg.data));
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
if (Array.isArray(msg) && options.onVisemes) {
|
|
1516
|
+
options.onVisemes(normalizeVisemes(msg));
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
if (msg.type === "eos") {
|
|
1520
|
+
finish();
|
|
1467
1521
|
}
|
|
1468
1522
|
};
|
|
1469
|
-
ws.onerror = (err) =>
|
|
1470
|
-
|
|
1471
|
-
if (
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
if (activityTimeout) clearTimeout(activityTimeout);
|
|
1476
|
-
resolve();
|
|
1523
|
+
ws.onerror = (err) => finish(err);
|
|
1524
|
+
ws.onclose = (ev) => {
|
|
1525
|
+
if (!finished) {
|
|
1526
|
+
if (firstByteReceived && ev.code === 1e3) finish();
|
|
1527
|
+
else finish(new LokutorError("internal.error", `TTS socket closed (${ev.code}${ev.reason ? `: ${ev.reason}` : ""})`, { retryable: true }));
|
|
1528
|
+
}
|
|
1477
1529
|
};
|
|
1478
1530
|
} catch (err) {
|
|
1479
|
-
|
|
1480
|
-
if (options.onError) options.onError(err);
|
|
1481
|
-
reject(err);
|
|
1531
|
+
finish(err);
|
|
1482
1532
|
}
|
|
1483
1533
|
});
|
|
1484
1534
|
}
|
|
@@ -1584,17 +1634,28 @@ var SpeechToTextClient = class {
|
|
|
1584
1634
|
try {
|
|
1585
1635
|
let url = this.serverUrl;
|
|
1586
1636
|
const separator = url.includes("?") ? "&" : "?";
|
|
1587
|
-
url += `${separator}api_key=${this.apiKey}`;
|
|
1637
|
+
url += `${separator}api_key=${encodeURIComponent(this.apiKey)}`;
|
|
1588
1638
|
this.ws = new WebSocket(url);
|
|
1589
1639
|
this.ws.onopen = async () => {
|
|
1590
1640
|
this.isConnected = true;
|
|
1591
1641
|
this.onStatusChange?.("connected");
|
|
1592
1642
|
this.ws.send(JSON.stringify({ language: this.language || "en" /* ENGLISH */, vad: this.vad }));
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
this.ws.
|
|
1643
|
+
try {
|
|
1644
|
+
await this.audioManager.startMicrophone((data) => {
|
|
1645
|
+
if (this.isConnected && this.ws?.readyState === WebSocket.OPEN) {
|
|
1646
|
+
this.ws.send(data);
|
|
1647
|
+
}
|
|
1648
|
+
});
|
|
1649
|
+
} catch (err) {
|
|
1650
|
+
const error = err instanceof LokutorError ? err : new LokutorError("audio.microphone_unavailable", "Could not start the microphone", { original: err });
|
|
1651
|
+
this.onError?.(error);
|
|
1652
|
+
try {
|
|
1653
|
+
this.ws?.close();
|
|
1654
|
+
} catch {
|
|
1596
1655
|
}
|
|
1597
|
-
|
|
1656
|
+
settle(() => reject(error));
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1598
1659
|
settle(() => resolve(true));
|
|
1599
1660
|
};
|
|
1600
1661
|
this.ws.onmessage = (event) => {
|
|
@@ -1608,7 +1669,11 @@ var SpeechToTextClient = class {
|
|
|
1608
1669
|
this.onPartialTranscript?.(msg.data ?? "");
|
|
1609
1670
|
}
|
|
1610
1671
|
} else if (msg.type === "error") {
|
|
1611
|
-
|
|
1672
|
+
const data = typeof msg.data === "object" && msg.data ? msg.data : { message: msg.data };
|
|
1673
|
+
this.onError?.(new LokutorError(data.code ?? "internal.error", data.message ?? "STT error", {
|
|
1674
|
+
detail: data.detail,
|
|
1675
|
+
retryable: data.retryable ?? false
|
|
1676
|
+
}));
|
|
1612
1677
|
}
|
|
1613
1678
|
} catch {
|
|
1614
1679
|
}
|