kugelaudio 0.7.0 → 0.9.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/CHANGELOG.md +21 -0
- package/README.md +41 -3
- package/dist/chunk-CB3B6T7F.mjs +391 -0
- package/dist/chunk-D57AKD2S.mjs +391 -0
- package/dist/index.d.mts +200 -16
- package/dist/index.d.ts +200 -16
- package/dist/index.js +287 -23
- package/dist/index.mjs +276 -347
- package/dist/livekit/index.d.mts +222 -0
- package/dist/livekit/index.d.ts +222 -0
- package/dist/livekit/index.js +994 -0
- package/dist/livekit/index.mjs +746 -0
- package/package.json +23 -3
- package/src/cfg-scale.test.ts +33 -0
- package/src/client.test.ts +466 -2
- package/src/client.ts +308 -17
- package/src/index.ts +4 -0
- package/src/livekit/index.ts +32 -0
- package/src/livekit/models.test.ts +30 -0
- package/src/livekit/models.ts +84 -0
- package/src/livekit/tts.functional.test.ts +348 -0
- package/src/livekit/tts.ts +833 -0
- package/src/livekit/wire.test.ts +112 -0
- package/src/livekit/wire.ts +126 -0
- package/src/types.ts +171 -13
- package/src/utils.ts +16 -0
package/dist/index.js
CHANGED
|
@@ -39,7 +39,8 @@ __export(index_exports, {
|
|
|
39
39
|
classifyWsHandshakeError: () => classifyWsHandshakeError,
|
|
40
40
|
createWavBlob: () => createWavBlob,
|
|
41
41
|
createWavFile: () => createWavFile,
|
|
42
|
-
decodePCM16: () => decodePCM16
|
|
42
|
+
decodePCM16: () => decodePCM16,
|
|
43
|
+
parseSessionUsage: () => parseSessionUsage
|
|
43
44
|
});
|
|
44
45
|
module.exports = __toCommonJS(index_exports);
|
|
45
46
|
|
|
@@ -429,7 +430,30 @@ function classifyWsHandshakeError(err) {
|
|
|
429
430
|
return build(status, void 0, typeof e.message === "string" ? e.message : "");
|
|
430
431
|
}
|
|
431
432
|
|
|
433
|
+
// src/types.ts
|
|
434
|
+
function parseSessionUsage(data) {
|
|
435
|
+
const raw = data.usage;
|
|
436
|
+
const source = raw && typeof raw === "object" ? raw : data;
|
|
437
|
+
const audioSeconds = typeof source.audio_seconds === "number" ? source.audio_seconds : typeof data.total_audio_seconds === "number" ? data.total_audio_seconds : void 0;
|
|
438
|
+
if (audioSeconds === void 0) return null;
|
|
439
|
+
const costCents = typeof source.cost_cents === "number" ? source.cost_cents : null;
|
|
440
|
+
return {
|
|
441
|
+
audioSeconds,
|
|
442
|
+
costCents,
|
|
443
|
+
currency: typeof source.currency === "string" ? source.currency : void 0,
|
|
444
|
+
characters: typeof source.characters === "number" ? source.characters : void 0,
|
|
445
|
+
modelId: typeof source.model_id === "string" ? source.model_id : void 0,
|
|
446
|
+
costAvailable: costCents !== null
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
432
450
|
// src/utils.ts
|
|
451
|
+
var MIN_CFG_SCALE = 1.2;
|
|
452
|
+
var MAX_CFG_SCALE = 2.5;
|
|
453
|
+
function clampCfgScale(cfgScale) {
|
|
454
|
+
if (cfgScale === void 0) return void 0;
|
|
455
|
+
return Math.min(MAX_CFG_SCALE, Math.max(MIN_CFG_SCALE, cfgScale));
|
|
456
|
+
}
|
|
433
457
|
function base64ToArrayBuffer(base64) {
|
|
434
458
|
if (typeof atob === "function") {
|
|
435
459
|
const binary = atob(base64);
|
|
@@ -515,7 +539,7 @@ function getWebSocket() {
|
|
|
515
539
|
// package.json
|
|
516
540
|
var package_default = {
|
|
517
541
|
name: "kugelaudio",
|
|
518
|
-
version: "0.
|
|
542
|
+
version: "0.9.0",
|
|
519
543
|
description: "Official JavaScript/TypeScript SDK for KugelAudio TTS API",
|
|
520
544
|
main: "dist/index.js",
|
|
521
545
|
module: "dist/index.mjs",
|
|
@@ -525,6 +549,11 @@ var package_default = {
|
|
|
525
549
|
types: "./dist/index.d.ts",
|
|
526
550
|
import: "./dist/index.mjs",
|
|
527
551
|
require: "./dist/index.js"
|
|
552
|
+
},
|
|
553
|
+
"./livekit": {
|
|
554
|
+
types: "./dist/livekit/index.d.ts",
|
|
555
|
+
import: "./dist/livekit/index.mjs",
|
|
556
|
+
require: "./dist/livekit/index.js"
|
|
528
557
|
}
|
|
529
558
|
},
|
|
530
559
|
files: [
|
|
@@ -534,8 +563,8 @@ var package_default = {
|
|
|
534
563
|
"CHANGELOG.md"
|
|
535
564
|
],
|
|
536
565
|
scripts: {
|
|
537
|
-
build: "tsup src/index.ts --format cjs,esm --dts",
|
|
538
|
-
dev: "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
566
|
+
build: "tsup src/index.ts src/livekit/index.ts --format cjs,esm --dts",
|
|
567
|
+
dev: "tsup src/index.ts src/livekit/index.ts --format cjs,esm --dts --watch",
|
|
539
568
|
lint: "eslint src/",
|
|
540
569
|
test: "vitest run",
|
|
541
570
|
"test:watch": "vitest",
|
|
@@ -561,11 +590,26 @@ var package_default = {
|
|
|
561
590
|
url: "https://github.com/Kugelaudio/KugelAudio/issues"
|
|
562
591
|
},
|
|
563
592
|
devDependencies: {
|
|
593
|
+
"@livekit/agents": "^1.4.11",
|
|
594
|
+
"@livekit/rtc-node": "^0.13.30",
|
|
564
595
|
"@types/node": "^25.3.2",
|
|
596
|
+
"@types/ws": "^8.18.1",
|
|
565
597
|
tsup: "^8.0.0",
|
|
566
598
|
typescript: "^6.0.2",
|
|
567
599
|
vitest: "^4.0.18"
|
|
568
600
|
},
|
|
601
|
+
peerDependencies: {
|
|
602
|
+
"@livekit/agents": ">=1.0.0",
|
|
603
|
+
"@livekit/rtc-node": ">=0.13.0"
|
|
604
|
+
},
|
|
605
|
+
peerDependenciesMeta: {
|
|
606
|
+
"@livekit/agents": {
|
|
607
|
+
optional: true
|
|
608
|
+
},
|
|
609
|
+
"@livekit/rtc-node": {
|
|
610
|
+
optional: true
|
|
611
|
+
}
|
|
612
|
+
},
|
|
569
613
|
engines: {
|
|
570
614
|
node: ">=18.0.0"
|
|
571
615
|
},
|
|
@@ -605,6 +649,105 @@ function createWs(url) {
|
|
|
605
649
|
return new WS(url);
|
|
606
650
|
}
|
|
607
651
|
var WS_OPEN = 1;
|
|
652
|
+
var UPDATABLE_SETTINGS = {
|
|
653
|
+
cfgScale: "cfg_scale",
|
|
654
|
+
temperature: "temperature",
|
|
655
|
+
speed: "speed",
|
|
656
|
+
maxNewTokens: "max_new_tokens",
|
|
657
|
+
language: "language",
|
|
658
|
+
normalize: "normalize"
|
|
659
|
+
};
|
|
660
|
+
function buildSettingsUpdateBody(settings) {
|
|
661
|
+
const body = {};
|
|
662
|
+
for (const camel of Object.keys(UPDATABLE_SETTINGS)) {
|
|
663
|
+
const value = settings[camel];
|
|
664
|
+
if (value !== void 0) body[UPDATABLE_SETTINGS[camel]] = value;
|
|
665
|
+
}
|
|
666
|
+
if (Object.keys(body).length === 0) {
|
|
667
|
+
throw new KugelAudioError(
|
|
668
|
+
`updateSettings requires at least one parameter to change (one of ${Object.keys(UPDATABLE_SETTINGS).join(", ")})`
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
return body;
|
|
672
|
+
}
|
|
673
|
+
function parseEffectiveSettings(raw) {
|
|
674
|
+
const s = raw && typeof raw === "object" ? raw : {};
|
|
675
|
+
return {
|
|
676
|
+
cfgScale: s.cfg_scale,
|
|
677
|
+
temperature: s.temperature,
|
|
678
|
+
speed: s.speed,
|
|
679
|
+
maxNewTokens: s.max_new_tokens,
|
|
680
|
+
language: s.language,
|
|
681
|
+
normalize: s.normalize
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
function applyAcceptedSettingsUpdate(config, settings) {
|
|
685
|
+
for (const camel of Object.keys(UPDATABLE_SETTINGS)) {
|
|
686
|
+
if (settings[camel] !== void 0) {
|
|
687
|
+
config[camel] = settings[camel];
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
function sendUpdateSettings(ws, body) {
|
|
692
|
+
const QUIET_TIMEOUT_MS = 15e3;
|
|
693
|
+
return new Promise((resolve, reject) => {
|
|
694
|
+
let settled = false;
|
|
695
|
+
let timer;
|
|
696
|
+
const prevMessage = ws.onmessage;
|
|
697
|
+
const prevClose = ws.onclose;
|
|
698
|
+
const finish = (action) => {
|
|
699
|
+
if (settled) return;
|
|
700
|
+
settled = true;
|
|
701
|
+
clearTimeout(timer);
|
|
702
|
+
ws.onmessage = prevMessage;
|
|
703
|
+
ws.onclose = prevClose;
|
|
704
|
+
action();
|
|
705
|
+
};
|
|
706
|
+
const armQuietTimer = () => {
|
|
707
|
+
clearTimeout(timer);
|
|
708
|
+
timer = setTimeout(
|
|
709
|
+
() => finish(
|
|
710
|
+
() => reject(
|
|
711
|
+
new KugelAudioError(
|
|
712
|
+
"Timed out waiting for settings_updated acknowledgement"
|
|
713
|
+
)
|
|
714
|
+
)
|
|
715
|
+
),
|
|
716
|
+
QUIET_TIMEOUT_MS
|
|
717
|
+
);
|
|
718
|
+
};
|
|
719
|
+
armQuietTimer();
|
|
720
|
+
ws.onmessage = (event) => {
|
|
721
|
+
armQuietTimer();
|
|
722
|
+
let data = null;
|
|
723
|
+
try {
|
|
724
|
+
const raw = typeof event.data === "string" ? event.data : event.data instanceof Buffer ? event.data.toString() : String(event.data);
|
|
725
|
+
data = JSON.parse(raw);
|
|
726
|
+
} catch {
|
|
727
|
+
}
|
|
728
|
+
if (data?.settings_updated) {
|
|
729
|
+
finish(() => resolve(parseEffectiveSettings(data.settings)));
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (data?.error) {
|
|
733
|
+
finish(() => reject(new KugelAudioError(String(data.error))));
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (prevMessage) prevMessage.call(ws, event);
|
|
737
|
+
};
|
|
738
|
+
ws.onclose = (event) => {
|
|
739
|
+
if (prevClose) prevClose.call(ws, event);
|
|
740
|
+
finish(
|
|
741
|
+
() => reject(
|
|
742
|
+
new ConnectionError(
|
|
743
|
+
"WebSocket closed before settings_updated acknowledgement"
|
|
744
|
+
)
|
|
745
|
+
)
|
|
746
|
+
);
|
|
747
|
+
};
|
|
748
|
+
ws.send(JSON.stringify({ update_settings: body }));
|
|
749
|
+
});
|
|
750
|
+
}
|
|
608
751
|
var _languageWarningLogged = false;
|
|
609
752
|
function warnIfNoLanguage(language, normalize) {
|
|
610
753
|
const normEnabled = normalize === void 0 || normalize;
|
|
@@ -660,6 +803,7 @@ var VoicesResource = class {
|
|
|
660
803
|
category: v.category,
|
|
661
804
|
sex: v.sex,
|
|
662
805
|
age: v.age,
|
|
806
|
+
quality: v.quality,
|
|
663
807
|
supportedLanguages: v.supported_languages || [],
|
|
664
808
|
sampleText: v.sample_text,
|
|
665
809
|
avatarUrl: v.avatar_url,
|
|
@@ -1031,7 +1175,8 @@ var TTSResource = class {
|
|
|
1031
1175
|
durationMs: data.dur_ms,
|
|
1032
1176
|
generationMs: data.gen_ms,
|
|
1033
1177
|
rtf: data.rtf,
|
|
1034
|
-
error: data.error
|
|
1178
|
+
error: data.error,
|
|
1179
|
+
usage: parseSessionUsage(data) ?? void 0
|
|
1035
1180
|
};
|
|
1036
1181
|
pending.callbacks.onFinal?.(stats);
|
|
1037
1182
|
this.pendingRequests.delete(requestId);
|
|
@@ -1118,15 +1263,19 @@ var TTSResource = class {
|
|
|
1118
1263
|
text: options.text,
|
|
1119
1264
|
model_id: options.modelId || "kugel-3",
|
|
1120
1265
|
voice_id: options.voiceId,
|
|
1121
|
-
cfg_scale: options.cfgScale ?? 2,
|
|
1266
|
+
cfg_scale: clampCfgScale(options.cfgScale) ?? 2,
|
|
1122
1267
|
...options.temperature !== void 0 && { temperature: options.temperature },
|
|
1123
1268
|
max_new_tokens: options.maxNewTokens ?? 2048,
|
|
1124
1269
|
sample_rate: options.sampleRate ?? 24e3,
|
|
1270
|
+
...options.outputFormat && { output_format: options.outputFormat },
|
|
1125
1271
|
normalize: options.normalize ?? true,
|
|
1126
1272
|
...options.language && { language: options.language },
|
|
1127
1273
|
...options.wordTimestamps && { word_timestamps: true },
|
|
1128
1274
|
...options.speed !== void 0 && { speed: options.speed },
|
|
1129
|
-
...options.projectId !== void 0 && { project_id: options.projectId }
|
|
1275
|
+
...options.projectId !== void 0 && { project_id: options.projectId },
|
|
1276
|
+
// [] is meaningful (explicit opt-out) and must be sent; only
|
|
1277
|
+
// undefined (use the project default) is omitted.
|
|
1278
|
+
...options.dictionaryIds !== void 0 && { dictionary_ids: options.dictionaryIds }
|
|
1130
1279
|
}));
|
|
1131
1280
|
});
|
|
1132
1281
|
}
|
|
@@ -1144,14 +1293,18 @@ var TTSResource = class {
|
|
|
1144
1293
|
text: options.text,
|
|
1145
1294
|
model_id: options.modelId || "kugel-3",
|
|
1146
1295
|
voice_id: options.voiceId,
|
|
1147
|
-
cfg_scale: options.cfgScale ?? 2,
|
|
1296
|
+
cfg_scale: clampCfgScale(options.cfgScale) ?? 2,
|
|
1148
1297
|
max_new_tokens: options.maxNewTokens ?? 2048,
|
|
1149
1298
|
sample_rate: options.sampleRate ?? 24e3,
|
|
1299
|
+
...options.outputFormat && { output_format: options.outputFormat },
|
|
1150
1300
|
normalize: options.normalize ?? true,
|
|
1151
1301
|
...options.language && { language: options.language },
|
|
1152
1302
|
...options.wordTimestamps && { word_timestamps: true },
|
|
1153
1303
|
...options.speed !== void 0 && { speed: options.speed },
|
|
1154
|
-
...options.projectId !== void 0 && { project_id: options.projectId }
|
|
1304
|
+
...options.projectId !== void 0 && { project_id: options.projectId },
|
|
1305
|
+
// [] is meaningful (explicit opt-out) and must be sent; only
|
|
1306
|
+
// undefined (use the project default) is omitted.
|
|
1307
|
+
...options.dictionaryIds !== void 0 && { dictionary_ids: options.dictionaryIds }
|
|
1155
1308
|
}));
|
|
1156
1309
|
};
|
|
1157
1310
|
ws.onmessage = (event) => {
|
|
@@ -1173,7 +1326,8 @@ var TTSResource = class {
|
|
|
1173
1326
|
durationMs: data.dur_ms,
|
|
1174
1327
|
generationMs: data.gen_ms,
|
|
1175
1328
|
rtf: data.rtf,
|
|
1176
|
-
error: data.error
|
|
1329
|
+
error: data.error,
|
|
1330
|
+
usage: parseSessionUsage(data) ?? void 0
|
|
1177
1331
|
};
|
|
1178
1332
|
callbacks.onFinal?.(stats);
|
|
1179
1333
|
ws.close();
|
|
@@ -1343,7 +1497,11 @@ var MultiContextSession = class {
|
|
|
1343
1497
|
this.ws = null;
|
|
1344
1498
|
this.callbacks = {};
|
|
1345
1499
|
this.contexts = /* @__PURE__ */ new Set();
|
|
1500
|
+
/** Contexts a create message has been sent for (not yet necessarily
|
|
1501
|
+
* confirmed by the server via context_created). */
|
|
1502
|
+
this.requestedContexts = /* @__PURE__ */ new Set();
|
|
1346
1503
|
this._sessionId = null;
|
|
1504
|
+
this._contextUsage = /* @__PURE__ */ new Map();
|
|
1347
1505
|
this.isStarted = false;
|
|
1348
1506
|
this.config = config || {};
|
|
1349
1507
|
}
|
|
@@ -1353,6 +1511,18 @@ var MultiContextSession = class {
|
|
|
1353
1511
|
get sessionId() {
|
|
1354
1512
|
return this._sessionId;
|
|
1355
1513
|
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Per-context usage (audio time + amount charged) for a closed context, or
|
|
1516
|
+
* null if that context hasn't closed yet. Each context is its own
|
|
1517
|
+
* conversation — use this to bill per conversation. See {@link SessionUsage}.
|
|
1518
|
+
*/
|
|
1519
|
+
usageFor(contextId) {
|
|
1520
|
+
return this._contextUsage.get(contextId) ?? null;
|
|
1521
|
+
}
|
|
1522
|
+
/** Map of context_id → per-context usage for all closed contexts. */
|
|
1523
|
+
get contextUsage() {
|
|
1524
|
+
return new Map(this._contextUsage);
|
|
1525
|
+
}
|
|
1356
1526
|
/**
|
|
1357
1527
|
* Connect to the multi-context WebSocket endpoint.
|
|
1358
1528
|
*
|
|
@@ -1406,12 +1576,19 @@ var MultiContextSession = class {
|
|
|
1406
1576
|
};
|
|
1407
1577
|
this.callbacks.onChunk?.(chunk);
|
|
1408
1578
|
}
|
|
1579
|
+
if (data.final && data.context_id) {
|
|
1580
|
+
this.callbacks.onFinal?.(data.context_id);
|
|
1581
|
+
}
|
|
1409
1582
|
if (data.context_closed) {
|
|
1410
1583
|
this.contexts.delete(data.context_id);
|
|
1411
|
-
this.
|
|
1584
|
+
this.requestedContexts.delete(data.context_id);
|
|
1585
|
+
const ctxUsage = parseSessionUsage(data) ?? void 0;
|
|
1586
|
+
if (ctxUsage) this._contextUsage.set(data.context_id, ctxUsage);
|
|
1587
|
+
this.callbacks.onContextClosed?.(data.context_id, ctxUsage);
|
|
1412
1588
|
}
|
|
1413
1589
|
if (data.context_timeout) {
|
|
1414
1590
|
this.contexts.delete(data.context_id);
|
|
1591
|
+
this.requestedContexts.delete(data.context_id);
|
|
1415
1592
|
this.callbacks.onContextTimeout?.(data.context_id);
|
|
1416
1593
|
}
|
|
1417
1594
|
if (data.session_closed) {
|
|
@@ -1451,6 +1628,7 @@ var MultiContextSession = class {
|
|
|
1451
1628
|
this.ws = null;
|
|
1452
1629
|
this.isStarted = false;
|
|
1453
1630
|
this.contexts.clear();
|
|
1631
|
+
this.requestedContexts.clear();
|
|
1454
1632
|
};
|
|
1455
1633
|
});
|
|
1456
1634
|
}
|
|
@@ -1461,6 +1639,7 @@ var MultiContextSession = class {
|
|
|
1461
1639
|
if (!this.ws || this.ws.readyState !== WS_OPEN) {
|
|
1462
1640
|
throw new KugelAudioError("WebSocket not connected");
|
|
1463
1641
|
}
|
|
1642
|
+
this.requestedContexts.add(contextId);
|
|
1464
1643
|
const msg = {
|
|
1465
1644
|
text: " ",
|
|
1466
1645
|
context_id: contextId
|
|
@@ -1468,23 +1647,27 @@ var MultiContextSession = class {
|
|
|
1468
1647
|
if (!this.isStarted) {
|
|
1469
1648
|
warnIfNoLanguage(this.config.language, this.config.normalize);
|
|
1470
1649
|
if (this.config.sampleRate) msg.sample_rate = this.config.sampleRate;
|
|
1471
|
-
if (this.config.
|
|
1650
|
+
if (this.config.outputFormat) msg.output_format = this.config.outputFormat;
|
|
1651
|
+
if (this.config.cfgScale !== void 0) msg.cfg_scale = clampCfgScale(this.config.cfgScale);
|
|
1472
1652
|
if (this.config.temperature !== void 0) msg.temperature = this.config.temperature;
|
|
1473
1653
|
if (this.config.maxNewTokens) msg.max_new_tokens = this.config.maxNewTokens;
|
|
1474
1654
|
if (this.config.normalize !== void 0) msg.normalize = this.config.normalize;
|
|
1475
1655
|
if (this.config.language) msg.language = this.config.language;
|
|
1656
|
+
if (this.config.dictionaryIds !== void 0) msg.dictionary_ids = this.config.dictionaryIds;
|
|
1476
1657
|
if (this.config.inactivityTimeout) msg.inactivity_timeout = this.config.inactivityTimeout;
|
|
1477
1658
|
}
|
|
1659
|
+
const voiceSettings = {};
|
|
1478
1660
|
const voiceId = options?.voiceId || this.config.defaultVoiceId;
|
|
1479
|
-
if (voiceId)
|
|
1661
|
+
if (voiceId) voiceSettings.voice_id = voiceId;
|
|
1480
1662
|
if (options?.voiceSettings) {
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1663
|
+
voiceSettings.stability = options.voiceSettings.stability;
|
|
1664
|
+
voiceSettings.similarity_boost = options.voiceSettings.similarityBoost;
|
|
1665
|
+
voiceSettings.style = options.voiceSettings.style;
|
|
1666
|
+
voiceSettings.use_speaker_boost = options.voiceSettings.useSpeakerBoost;
|
|
1667
|
+
voiceSettings.speed = options.voiceSettings.speed;
|
|
1668
|
+
}
|
|
1669
|
+
if (Object.keys(voiceSettings).length > 0) {
|
|
1670
|
+
msg.voice_settings = voiceSettings;
|
|
1488
1671
|
}
|
|
1489
1672
|
this.ws.send(JSON.stringify(msg));
|
|
1490
1673
|
}
|
|
@@ -1495,7 +1678,7 @@ var MultiContextSession = class {
|
|
|
1495
1678
|
if (!this.ws || this.ws.readyState !== WS_OPEN) {
|
|
1496
1679
|
throw new KugelAudioError("WebSocket not connected");
|
|
1497
1680
|
}
|
|
1498
|
-
if (!this.
|
|
1681
|
+
if (!this.requestedContexts.has(contextId) && !this.contexts.has(contextId)) {
|
|
1499
1682
|
this.createContext(contextId);
|
|
1500
1683
|
}
|
|
1501
1684
|
this.ws.send(JSON.stringify({
|
|
@@ -1542,6 +1725,33 @@ var MultiContextSession = class {
|
|
|
1542
1725
|
context_id: contextId
|
|
1543
1726
|
}));
|
|
1544
1727
|
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Change the session's generation parameters mid-connection (KUG-1166).
|
|
1730
|
+
*
|
|
1731
|
+
* Session-scoped (there is no `contextId`): the update applies to contexts
|
|
1732
|
+
* started **after** it — a context already streaming keeps the settings it
|
|
1733
|
+
* began with, since generation parameters are bound when a context's engine
|
|
1734
|
+
* session opens. With the common one-context-per-turn pattern that means it
|
|
1735
|
+
* takes effect on the next turn. Per-context `cfgScale` / `maxNewTokens`
|
|
1736
|
+
* passed to {@link createContext} still win for that context.
|
|
1737
|
+
*
|
|
1738
|
+
* Resolves with the generation parameters now in effect (the server's echo).
|
|
1739
|
+
*
|
|
1740
|
+
* @throws {KugelAudioError} if no field is provided, the socket is not open,
|
|
1741
|
+
* or the server rejects the update.
|
|
1742
|
+
*/
|
|
1743
|
+
updateSettings(settings) {
|
|
1744
|
+
if (!this.ws || this.ws.readyState !== WS_OPEN) {
|
|
1745
|
+
throw new KugelAudioError(
|
|
1746
|
+
"MultiContextSession not connected. Call connect() first."
|
|
1747
|
+
);
|
|
1748
|
+
}
|
|
1749
|
+
const body = buildSettingsUpdateBody(settings);
|
|
1750
|
+
return sendUpdateSettings(this.ws, body).then((effective) => {
|
|
1751
|
+
applyAcceptedSettingsUpdate(this.config, settings);
|
|
1752
|
+
return effective;
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1545
1755
|
/**
|
|
1546
1756
|
* Close the session and all contexts.
|
|
1547
1757
|
*/
|
|
@@ -1553,6 +1763,7 @@ var MultiContextSession = class {
|
|
|
1553
1763
|
this.ws = null;
|
|
1554
1764
|
this.isStarted = false;
|
|
1555
1765
|
this.contexts.clear();
|
|
1766
|
+
this.requestedContexts.clear();
|
|
1556
1767
|
}
|
|
1557
1768
|
/**
|
|
1558
1769
|
* Get active context IDs.
|
|
@@ -1571,10 +1782,19 @@ var StreamingSession = class {
|
|
|
1571
1782
|
constructor(client, config, callbacks) {
|
|
1572
1783
|
this.ws = null;
|
|
1573
1784
|
this.configSent = false;
|
|
1785
|
+
this._lastUsage = null;
|
|
1574
1786
|
this.client = client;
|
|
1575
1787
|
this.config = config;
|
|
1576
1788
|
this.callbacks = callbacks;
|
|
1577
1789
|
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Per-session usage from the most recently closed session, or null before
|
|
1792
|
+
* the first session closes. Use this to bill your own customers per
|
|
1793
|
+
* conversation. See {@link SessionUsage}.
|
|
1794
|
+
*/
|
|
1795
|
+
get lastUsage() {
|
|
1796
|
+
return this._lastUsage;
|
|
1797
|
+
}
|
|
1578
1798
|
/**
|
|
1579
1799
|
* Open the WebSocket connection and authenticate.
|
|
1580
1800
|
*
|
|
@@ -1638,7 +1858,15 @@ var StreamingSession = class {
|
|
|
1638
1858
|
if (data.interrupted) {
|
|
1639
1859
|
this.callbacks.onInterrupted?.();
|
|
1640
1860
|
}
|
|
1861
|
+
if (data.final) {
|
|
1862
|
+
this.callbacks.onFinal?.(
|
|
1863
|
+
data.total_audio_seconds ?? 0,
|
|
1864
|
+
data.total_text_chunks ?? 0,
|
|
1865
|
+
data.total_audio_chunks ?? 0
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1641
1868
|
if (data.session_closed) {
|
|
1869
|
+
this._lastUsage = parseSessionUsage(data);
|
|
1642
1870
|
this.callbacks.onSessionClosed?.(
|
|
1643
1871
|
data.total_audio_seconds ?? 0,
|
|
1644
1872
|
data.total_text_chunks ?? 0,
|
|
@@ -1702,10 +1930,11 @@ var StreamingSession = class {
|
|
|
1702
1930
|
if (!this.configSent) {
|
|
1703
1931
|
if (this.config.voiceId !== void 0) msg.voice_id = this.config.voiceId;
|
|
1704
1932
|
if (this.config.modelId !== void 0) msg.model_id = this.config.modelId;
|
|
1705
|
-
if (this.config.cfgScale !== void 0) msg.cfg_scale = this.config.cfgScale;
|
|
1933
|
+
if (this.config.cfgScale !== void 0) msg.cfg_scale = clampCfgScale(this.config.cfgScale);
|
|
1706
1934
|
if (this.config.temperature !== void 0) msg.temperature = this.config.temperature;
|
|
1707
1935
|
if (this.config.maxNewTokens !== void 0) msg.max_new_tokens = this.config.maxNewTokens;
|
|
1708
1936
|
if (this.config.sampleRate !== void 0) msg.sample_rate = this.config.sampleRate;
|
|
1937
|
+
if (this.config.outputFormat !== void 0) msg.output_format = this.config.outputFormat;
|
|
1709
1938
|
if (this.config.flushTimeoutMs !== void 0) msg.flush_timeout_ms = this.config.flushTimeoutMs;
|
|
1710
1939
|
if (this.config.maxBufferLength !== void 0) msg.max_buffer_length = this.config.maxBufferLength;
|
|
1711
1940
|
if (this.config.normalize !== void 0) msg.normalize = this.config.normalize;
|
|
@@ -1714,6 +1943,7 @@ var StreamingSession = class {
|
|
|
1714
1943
|
if (this.config.autoMode !== void 0) msg.auto_mode = this.config.autoMode;
|
|
1715
1944
|
if (this.config.chunkLengthSchedule?.length) msg.chunk_length_schedule = this.config.chunkLengthSchedule;
|
|
1716
1945
|
if (this.config.speed !== void 0) msg.speed = this.config.speed;
|
|
1946
|
+
if (this.config.dictionaryIds !== void 0) msg.dictionary_ids = this.config.dictionaryIds;
|
|
1717
1947
|
this.configSent = true;
|
|
1718
1948
|
}
|
|
1719
1949
|
this.ws.send(JSON.stringify(msg));
|
|
@@ -1849,6 +2079,39 @@ var StreamingSession = class {
|
|
|
1849
2079
|
Object.assign(this.config, config);
|
|
1850
2080
|
this.configSent = false;
|
|
1851
2081
|
}
|
|
2082
|
+
/**
|
|
2083
|
+
* Change generation parameters mid-connection without reconnecting (KUG-1166).
|
|
2084
|
+
*
|
|
2085
|
+
* Sends an `update_settings` message and resolves with the generation
|
|
2086
|
+
* parameters now in effect (the server's echo). Only the six fields of
|
|
2087
|
+
* {@link SettingsUpdate} are updatable; identity / audio-format fields
|
|
2088
|
+
* (`voiceId`, `modelId`, `sampleRate`, `outputFormat`, `dictionaryIds`) are
|
|
2089
|
+
* fixed for the connection — change those with {@link updateConfig} after
|
|
2090
|
+
* {@link endSession} instead.
|
|
2091
|
+
*
|
|
2092
|
+
* The change applies to the **next turn**: a turn already streaming keeps the
|
|
2093
|
+
* settings it started with, so call this between turns.
|
|
2094
|
+
*
|
|
2095
|
+
* @example
|
|
2096
|
+
* ```typescript
|
|
2097
|
+
* const effective = await session.updateSettings({ cfgScale: 1.5, speed: 1.1 });
|
|
2098
|
+
* ```
|
|
2099
|
+
*
|
|
2100
|
+
* @throws {KugelAudioError} if no field is provided, the socket is not open,
|
|
2101
|
+
* or the server rejects the update (e.g. a value out of range).
|
|
2102
|
+
*/
|
|
2103
|
+
updateSettings(settings) {
|
|
2104
|
+
if (!this.ws || this.ws.readyState !== WS_OPEN) {
|
|
2105
|
+
throw new KugelAudioError(
|
|
2106
|
+
"StreamingSession not connected. Call connect() first."
|
|
2107
|
+
);
|
|
2108
|
+
}
|
|
2109
|
+
const body = buildSettingsUpdateBody(settings);
|
|
2110
|
+
return sendUpdateSettings(this.ws, body).then((effective) => {
|
|
2111
|
+
applyAcceptedSettingsUpdate(this.config, settings);
|
|
2112
|
+
return effective;
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
1852
2115
|
/**
|
|
1853
2116
|
* Close the session and the WebSocket connection.
|
|
1854
2117
|
*
|
|
@@ -2093,5 +2356,6 @@ var KugelAudio = class _KugelAudio {
|
|
|
2093
2356
|
classifyWsHandshakeError,
|
|
2094
2357
|
createWavBlob,
|
|
2095
2358
|
createWavFile,
|
|
2096
|
-
decodePCM16
|
|
2359
|
+
decodePCM16,
|
|
2360
|
+
parseSessionUsage
|
|
2097
2361
|
});
|