kugelaudio 0.8.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 +8 -0
- package/README.md +30 -1
- package/dist/chunk-CB3B6T7F.mjs +391 -0
- package/dist/chunk-D57AKD2S.mjs +391 -0
- package/dist/index.d.mts +84 -11
- package/dist/index.d.ts +84 -11
- package/dist/index.js +193 -7
- package/dist/index.mjs +186 -334
- 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 +110 -0
- package/src/client.ts +213 -5
- package/src/index.ts +2 -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 +47 -7
- package/src/utils.ts +16 -0
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import {
|
|
2
|
+
AuthenticationError,
|
|
3
|
+
ConnectionError,
|
|
4
|
+
ErrorCodes,
|
|
5
|
+
InsufficientCreditsError,
|
|
6
|
+
KugelAudioError,
|
|
7
|
+
NotFoundError,
|
|
8
|
+
RateLimitError,
|
|
9
|
+
ValidationError,
|
|
10
|
+
WsCloseCodes,
|
|
11
|
+
__require,
|
|
12
|
+
base64ToArrayBuffer,
|
|
13
|
+
clampCfgScale,
|
|
14
|
+
classifyHttpError,
|
|
15
|
+
classifyWsClose,
|
|
16
|
+
classifyWsFrame,
|
|
17
|
+
classifyWsHandshakeError,
|
|
18
|
+
createWavBlob,
|
|
19
|
+
createWavFile,
|
|
20
|
+
decodePCM16,
|
|
21
|
+
package_default
|
|
22
|
+
} from "./chunk-CB3B6T7F.mjs";
|
|
7
23
|
|
|
8
24
|
// src/dictionaries.ts
|
|
9
25
|
function mapDictionary(raw) {
|
|
@@ -186,211 +202,6 @@ var DictionariesResource = class {
|
|
|
186
202
|
}
|
|
187
203
|
};
|
|
188
204
|
|
|
189
|
-
// src/errors.ts
|
|
190
|
-
var ErrorCodes = {
|
|
191
|
-
UNAUTHORIZED: "UNAUTHORIZED",
|
|
192
|
-
RATE_LIMITED: "RATE_LIMITED",
|
|
193
|
-
INSUFFICIENT_CREDITS: "INSUFFICIENT_CREDITS",
|
|
194
|
-
MODEL_UNAVAILABLE: "MODEL_UNAVAILABLE",
|
|
195
|
-
EMPTY_AUDIO: "EMPTY_AUDIO",
|
|
196
|
-
VALIDATION: "VALIDATION_ERROR",
|
|
197
|
-
INTERNAL: "INTERNAL_ERROR",
|
|
198
|
-
NOT_FOUND: "NOT_FOUND",
|
|
199
|
-
MISSING_VOICE_ID: "MISSING_VOICE_ID",
|
|
200
|
-
TOO_MANY_CONTEXTS: "TOO_MANY_CONTEXTS"
|
|
201
|
-
};
|
|
202
|
-
var WsCloseCodes = {
|
|
203
|
-
UNAUTHORIZED: 4001,
|
|
204
|
-
INSUFFICIENT_CREDITS: 4003,
|
|
205
|
-
RATE_LIMITED: 4029,
|
|
206
|
-
MODEL_UNAVAILABLE: 4500
|
|
207
|
-
};
|
|
208
|
-
var API_KEYS_URL = "https://app.kugelaudio.com/settings/api-keys";
|
|
209
|
-
var BILLING_URL = "https://app.kugelaudio.com/billing";
|
|
210
|
-
var KugelAudioError = class _KugelAudioError extends Error {
|
|
211
|
-
constructor(message, options = {}) {
|
|
212
|
-
super(options.requestId ? `${message} (request_id: ${options.requestId})` : message);
|
|
213
|
-
this.name = "KugelAudioError";
|
|
214
|
-
this.statusCode = options.statusCode;
|
|
215
|
-
this.errorCode = options.errorCode;
|
|
216
|
-
this.requestId = options.requestId;
|
|
217
|
-
this.retryAfter = options.retryAfter;
|
|
218
|
-
Object.setPrototypeOf(this, _KugelAudioError.prototype);
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
var AuthenticationError = class _AuthenticationError extends KugelAudioError {
|
|
222
|
-
constructor(message, options = {}) {
|
|
223
|
-
super(
|
|
224
|
-
message ?? `KugelAudio rejected the API key. Check it is current at ${API_KEYS_URL}.`,
|
|
225
|
-
{ statusCode: 401, errorCode: ErrorCodes.UNAUTHORIZED, ...options }
|
|
226
|
-
);
|
|
227
|
-
this.name = "AuthenticationError";
|
|
228
|
-
Object.setPrototypeOf(this, _AuthenticationError.prototype);
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
var RateLimitError = class _RateLimitError extends KugelAudioError {
|
|
232
|
-
constructor(message, options = {}) {
|
|
233
|
-
const msg = message ?? (options.retryAfter ? `KugelAudio rate limit hit; retry after ${options.retryAfter}s.` : "KugelAudio rate limit hit; retry shortly.");
|
|
234
|
-
super(msg, { statusCode: 429, errorCode: ErrorCodes.RATE_LIMITED, ...options });
|
|
235
|
-
this.name = "RateLimitError";
|
|
236
|
-
Object.setPrototypeOf(this, _RateLimitError.prototype);
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
var InsufficientCreditsError = class _InsufficientCreditsError extends KugelAudioError {
|
|
240
|
-
constructor(message, options = {}) {
|
|
241
|
-
super(
|
|
242
|
-
message ?? `Your KugelAudio account is out of credits. Top up at ${BILLING_URL}.`,
|
|
243
|
-
{ statusCode: 402, errorCode: ErrorCodes.INSUFFICIENT_CREDITS, ...options }
|
|
244
|
-
);
|
|
245
|
-
this.name = "InsufficientCreditsError";
|
|
246
|
-
Object.setPrototypeOf(this, _InsufficientCreditsError.prototype);
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
var ValidationError = class _ValidationError extends KugelAudioError {
|
|
250
|
-
constructor(message, options = {}) {
|
|
251
|
-
super(message, { statusCode: 400, errorCode: ErrorCodes.VALIDATION, ...options });
|
|
252
|
-
this.name = "ValidationError";
|
|
253
|
-
Object.setPrototypeOf(this, _ValidationError.prototype);
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
var ConnectionError = class _ConnectionError extends KugelAudioError {
|
|
257
|
-
constructor(message, options = {}) {
|
|
258
|
-
super(message, { statusCode: 503, ...options });
|
|
259
|
-
this.name = "ConnectionError";
|
|
260
|
-
Object.setPrototypeOf(this, _ConnectionError.prototype);
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
var NotFoundError = class _NotFoundError extends KugelAudioError {
|
|
264
|
-
constructor(message, options = {}) {
|
|
265
|
-
super(message ?? "Not found.", {
|
|
266
|
-
statusCode: 404,
|
|
267
|
-
errorCode: ErrorCodes.NOT_FOUND,
|
|
268
|
-
...options
|
|
269
|
-
});
|
|
270
|
-
this.name = "NotFoundError";
|
|
271
|
-
Object.setPrototypeOf(this, _NotFoundError.prototype);
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
function build(status, errorCode, message, opts = {}) {
|
|
275
|
-
const common = { ...opts };
|
|
276
|
-
if (status !== void 0) common.statusCode = status;
|
|
277
|
-
if (errorCode !== void 0) common.errorCode = errorCode;
|
|
278
|
-
if (errorCode === ErrorCodes.UNAUTHORIZED || status === 401) {
|
|
279
|
-
return new AuthenticationError(message || void 0, common);
|
|
280
|
-
}
|
|
281
|
-
if (errorCode === ErrorCodes.INSUFFICIENT_CREDITS || status === 402) {
|
|
282
|
-
return new InsufficientCreditsError(message || void 0, common);
|
|
283
|
-
}
|
|
284
|
-
if (errorCode === ErrorCodes.RATE_LIMITED || errorCode === ErrorCodes.TOO_MANY_CONTEXTS || status === 429) {
|
|
285
|
-
return new RateLimitError(message || void 0, common);
|
|
286
|
-
}
|
|
287
|
-
if (errorCode === ErrorCodes.VALIDATION || errorCode === ErrorCodes.MISSING_VOICE_ID || status === 400) {
|
|
288
|
-
return new ValidationError(message || "Request validation failed.", common);
|
|
289
|
-
}
|
|
290
|
-
if (errorCode === ErrorCodes.MODEL_UNAVAILABLE || status === 503) {
|
|
291
|
-
const detail = message || "service temporarily unavailable";
|
|
292
|
-
return new ConnectionError(
|
|
293
|
-
`KugelAudio is temporarily unavailable: ${detail}. Retry shortly.`,
|
|
294
|
-
common
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
|
-
if (errorCode === ErrorCodes.NOT_FOUND || status === 404) {
|
|
298
|
-
return new NotFoundError(message || void 0, common);
|
|
299
|
-
}
|
|
300
|
-
return new KugelAudioError(message || `HTTP ${status}`, common);
|
|
301
|
-
}
|
|
302
|
-
function readHeader(headers, name) {
|
|
303
|
-
if (headers && typeof headers.get === "function") {
|
|
304
|
-
return headers.get(name) ?? void 0;
|
|
305
|
-
}
|
|
306
|
-
const rec = headers;
|
|
307
|
-
return rec[name] ?? rec[name.toLowerCase()] ?? void 0;
|
|
308
|
-
}
|
|
309
|
-
function classifyHttpError(status, bodyText, headers) {
|
|
310
|
-
let errorCode;
|
|
311
|
-
let message = "";
|
|
312
|
-
let retryAfter;
|
|
313
|
-
if (bodyText) {
|
|
314
|
-
try {
|
|
315
|
-
const body = JSON.parse(bodyText);
|
|
316
|
-
if (body && typeof body === "object") {
|
|
317
|
-
errorCode = typeof body.error_code === "string" ? body.error_code : void 0;
|
|
318
|
-
const msg = body.error ?? body.detail;
|
|
319
|
-
if (Array.isArray(msg)) {
|
|
320
|
-
message = msg.map((m) => String(m)).join("; ");
|
|
321
|
-
} else if (typeof msg === "string") {
|
|
322
|
-
message = msg;
|
|
323
|
-
}
|
|
324
|
-
if (typeof body.retry_after === "number") {
|
|
325
|
-
retryAfter = body.retry_after;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
} catch {
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
if (retryAfter === void 0) {
|
|
332
|
-
const header = readHeader(headers, "Retry-After") ?? readHeader(headers, "retry-after");
|
|
333
|
-
if (header) {
|
|
334
|
-
const n = Number(header);
|
|
335
|
-
if (Number.isFinite(n)) retryAfter = n;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
const requestId = readHeader(headers, "x-request-id") ?? readHeader(headers, "X-Request-Id");
|
|
339
|
-
if (!message) {
|
|
340
|
-
message = (bodyText || "").trim();
|
|
341
|
-
}
|
|
342
|
-
return build(status, errorCode, message, { requestId, retryAfter });
|
|
343
|
-
}
|
|
344
|
-
function classifyWsFrame(data) {
|
|
345
|
-
const errorCode = data.error_code;
|
|
346
|
-
const message = data.error ?? "Server reported an error.";
|
|
347
|
-
const status = typeof data.code === "number" ? data.code : void 0;
|
|
348
|
-
const retryAfter = typeof data.retry_after === "number" ? data.retry_after : void 0;
|
|
349
|
-
return build(status, errorCode, message, { retryAfter });
|
|
350
|
-
}
|
|
351
|
-
function classifyWsClose(code, reason) {
|
|
352
|
-
const reasonTxt = (reason ?? "").trim();
|
|
353
|
-
if (code === WsCloseCodes.UNAUTHORIZED) {
|
|
354
|
-
let msg = `KugelAudio rejected the API key. Check it is current at ${API_KEYS_URL}.`;
|
|
355
|
-
if (reasonTxt) msg = `${msg} (${reasonTxt})`;
|
|
356
|
-
return new AuthenticationError(msg);
|
|
357
|
-
}
|
|
358
|
-
if (code === WsCloseCodes.INSUFFICIENT_CREDITS) {
|
|
359
|
-
return new InsufficientCreditsError();
|
|
360
|
-
}
|
|
361
|
-
if (code === WsCloseCodes.RATE_LIMITED) {
|
|
362
|
-
return new RateLimitError();
|
|
363
|
-
}
|
|
364
|
-
if (code === WsCloseCodes.MODEL_UNAVAILABLE) {
|
|
365
|
-
const suffix = reasonTxt ? ` (${reasonTxt})` : "";
|
|
366
|
-
return new ConnectionError(
|
|
367
|
-
`KugelAudio model is temporarily unavailable. Retry shortly.${suffix}`
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
const detail = reasonTxt || "no reason given";
|
|
371
|
-
const codeStr = code !== void 0 ? ` (code ${code})` : "";
|
|
372
|
-
return new ConnectionError(
|
|
373
|
-
`KugelAudio WebSocket closed by server: ${detail}${codeStr}.`
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
|
-
function classifyWsHandshakeError(err) {
|
|
377
|
-
if (!err || typeof err !== "object") return null;
|
|
378
|
-
const e = err;
|
|
379
|
-
let status;
|
|
380
|
-
if (typeof e.statusCode === "number") {
|
|
381
|
-
status = e.statusCode;
|
|
382
|
-
}
|
|
383
|
-
if (status === void 0 && typeof e.message === "string") {
|
|
384
|
-
const m = e.message.match(/Unexpected server response:\s*(\d{3})/i);
|
|
385
|
-
if (m) status = Number(m[1]);
|
|
386
|
-
}
|
|
387
|
-
if (status === void 0) return null;
|
|
388
|
-
if (status === 403) {
|
|
389
|
-
return new AuthenticationError();
|
|
390
|
-
}
|
|
391
|
-
return build(status, void 0, typeof e.message === "string" ? e.message : "");
|
|
392
|
-
}
|
|
393
|
-
|
|
394
205
|
// src/types.ts
|
|
395
206
|
function parseSessionUsage(data) {
|
|
396
207
|
const raw = data.usage;
|
|
@@ -408,62 +219,6 @@ function parseSessionUsage(data) {
|
|
|
408
219
|
};
|
|
409
220
|
}
|
|
410
221
|
|
|
411
|
-
// src/utils.ts
|
|
412
|
-
function base64ToArrayBuffer(base64) {
|
|
413
|
-
if (typeof atob === "function") {
|
|
414
|
-
const binary = atob(base64);
|
|
415
|
-
const bytes = new Uint8Array(binary.length);
|
|
416
|
-
for (let i = 0; i < binary.length; i++) {
|
|
417
|
-
bytes[i] = binary.charCodeAt(i);
|
|
418
|
-
}
|
|
419
|
-
return bytes.buffer;
|
|
420
|
-
} else {
|
|
421
|
-
const buffer = Buffer.from(base64, "base64");
|
|
422
|
-
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
function decodePCM16(base64) {
|
|
426
|
-
const buffer = base64ToArrayBuffer(base64);
|
|
427
|
-
const int16 = new Int16Array(buffer);
|
|
428
|
-
const float32 = new Float32Array(int16.length);
|
|
429
|
-
for (let i = 0; i < int16.length; i++) {
|
|
430
|
-
float32[i] = int16[i] / 32768;
|
|
431
|
-
}
|
|
432
|
-
return float32;
|
|
433
|
-
}
|
|
434
|
-
function createWavFile(audio, sampleRate) {
|
|
435
|
-
const dataSize = audio.byteLength;
|
|
436
|
-
const fileSize = 44 + dataSize;
|
|
437
|
-
const buffer = new ArrayBuffer(fileSize);
|
|
438
|
-
const view = new DataView(buffer);
|
|
439
|
-
writeString(view, 0, "RIFF");
|
|
440
|
-
view.setUint32(4, fileSize - 8, true);
|
|
441
|
-
writeString(view, 8, "WAVE");
|
|
442
|
-
writeString(view, 12, "fmt ");
|
|
443
|
-
view.setUint32(16, 16, true);
|
|
444
|
-
view.setUint16(20, 1, true);
|
|
445
|
-
view.setUint16(22, 1, true);
|
|
446
|
-
view.setUint32(24, sampleRate, true);
|
|
447
|
-
view.setUint32(28, sampleRate * 2, true);
|
|
448
|
-
view.setUint16(32, 2, true);
|
|
449
|
-
view.setUint16(34, 16, true);
|
|
450
|
-
writeString(view, 36, "data");
|
|
451
|
-
view.setUint32(40, dataSize, true);
|
|
452
|
-
const audioBytes = new Uint8Array(audio);
|
|
453
|
-
const wavBytes = new Uint8Array(buffer);
|
|
454
|
-
wavBytes.set(audioBytes, 44);
|
|
455
|
-
return buffer;
|
|
456
|
-
}
|
|
457
|
-
function writeString(view, offset, str) {
|
|
458
|
-
for (let i = 0; i < str.length; i++) {
|
|
459
|
-
view.setUint8(offset + i, str.charCodeAt(i));
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
function createWavBlob(audio, sampleRate) {
|
|
463
|
-
const wavBuffer = createWavFile(audio, sampleRate);
|
|
464
|
-
return new Blob([wavBuffer], { type: "audio/wav" });
|
|
465
|
-
}
|
|
466
|
-
|
|
467
222
|
// src/websocket.ts
|
|
468
223
|
var _cachedWs = null;
|
|
469
224
|
function isNodeJs() {
|
|
@@ -491,69 +246,6 @@ function getWebSocket() {
|
|
|
491
246
|
);
|
|
492
247
|
}
|
|
493
248
|
|
|
494
|
-
// package.json
|
|
495
|
-
var package_default = {
|
|
496
|
-
name: "kugelaudio",
|
|
497
|
-
version: "0.8.0",
|
|
498
|
-
description: "Official JavaScript/TypeScript SDK for KugelAudio TTS API",
|
|
499
|
-
main: "dist/index.js",
|
|
500
|
-
module: "dist/index.mjs",
|
|
501
|
-
types: "dist/index.d.ts",
|
|
502
|
-
exports: {
|
|
503
|
-
".": {
|
|
504
|
-
types: "./dist/index.d.ts",
|
|
505
|
-
import: "./dist/index.mjs",
|
|
506
|
-
require: "./dist/index.js"
|
|
507
|
-
}
|
|
508
|
-
},
|
|
509
|
-
files: [
|
|
510
|
-
"dist",
|
|
511
|
-
"src",
|
|
512
|
-
"LICENSE",
|
|
513
|
-
"CHANGELOG.md"
|
|
514
|
-
],
|
|
515
|
-
scripts: {
|
|
516
|
-
build: "tsup src/index.ts --format cjs,esm --dts",
|
|
517
|
-
dev: "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
518
|
-
lint: "eslint src/",
|
|
519
|
-
test: "vitest run",
|
|
520
|
-
"test:watch": "vitest",
|
|
521
|
-
prepublishOnly: "npm run build"
|
|
522
|
-
},
|
|
523
|
-
keywords: [
|
|
524
|
-
"tts",
|
|
525
|
-
"text-to-speech",
|
|
526
|
-
"audio",
|
|
527
|
-
"streaming",
|
|
528
|
-
"websocket",
|
|
529
|
-
"kugelaudio"
|
|
530
|
-
],
|
|
531
|
-
author: "KugelAudio <hello@kugelaudio.com>",
|
|
532
|
-
license: "MIT",
|
|
533
|
-
repository: {
|
|
534
|
-
type: "git",
|
|
535
|
-
url: "https://github.com/Kugelaudio/KugelAudio",
|
|
536
|
-
directory: "sdks/js"
|
|
537
|
-
},
|
|
538
|
-
homepage: "https://kugelaudio.com",
|
|
539
|
-
bugs: {
|
|
540
|
-
url: "https://github.com/Kugelaudio/KugelAudio/issues"
|
|
541
|
-
},
|
|
542
|
-
devDependencies: {
|
|
543
|
-
"@types/node": "^25.3.2",
|
|
544
|
-
tsup: "^8.0.0",
|
|
545
|
-
typescript: "^6.0.2",
|
|
546
|
-
vitest: "^4.0.18"
|
|
547
|
-
},
|
|
548
|
-
engines: {
|
|
549
|
-
node: ">=18.0.0"
|
|
550
|
-
},
|
|
551
|
-
dependencies: {
|
|
552
|
-
tsx: "^4.21.0",
|
|
553
|
-
ws: "^8.18.0"
|
|
554
|
-
}
|
|
555
|
-
};
|
|
556
|
-
|
|
557
249
|
// src/client.ts
|
|
558
250
|
var DEFAULT_API_URL = "https://api.kugelaudio.com";
|
|
559
251
|
var EU_API_URL = "https://api.eu.kugelaudio.com";
|
|
@@ -584,6 +276,105 @@ function createWs(url) {
|
|
|
584
276
|
return new WS(url);
|
|
585
277
|
}
|
|
586
278
|
var WS_OPEN = 1;
|
|
279
|
+
var UPDATABLE_SETTINGS = {
|
|
280
|
+
cfgScale: "cfg_scale",
|
|
281
|
+
temperature: "temperature",
|
|
282
|
+
speed: "speed",
|
|
283
|
+
maxNewTokens: "max_new_tokens",
|
|
284
|
+
language: "language",
|
|
285
|
+
normalize: "normalize"
|
|
286
|
+
};
|
|
287
|
+
function buildSettingsUpdateBody(settings) {
|
|
288
|
+
const body = {};
|
|
289
|
+
for (const camel of Object.keys(UPDATABLE_SETTINGS)) {
|
|
290
|
+
const value = settings[camel];
|
|
291
|
+
if (value !== void 0) body[UPDATABLE_SETTINGS[camel]] = value;
|
|
292
|
+
}
|
|
293
|
+
if (Object.keys(body).length === 0) {
|
|
294
|
+
throw new KugelAudioError(
|
|
295
|
+
`updateSettings requires at least one parameter to change (one of ${Object.keys(UPDATABLE_SETTINGS).join(", ")})`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
return body;
|
|
299
|
+
}
|
|
300
|
+
function parseEffectiveSettings(raw) {
|
|
301
|
+
const s = raw && typeof raw === "object" ? raw : {};
|
|
302
|
+
return {
|
|
303
|
+
cfgScale: s.cfg_scale,
|
|
304
|
+
temperature: s.temperature,
|
|
305
|
+
speed: s.speed,
|
|
306
|
+
maxNewTokens: s.max_new_tokens,
|
|
307
|
+
language: s.language,
|
|
308
|
+
normalize: s.normalize
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function applyAcceptedSettingsUpdate(config, settings) {
|
|
312
|
+
for (const camel of Object.keys(UPDATABLE_SETTINGS)) {
|
|
313
|
+
if (settings[camel] !== void 0) {
|
|
314
|
+
config[camel] = settings[camel];
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function sendUpdateSettings(ws, body) {
|
|
319
|
+
const QUIET_TIMEOUT_MS = 15e3;
|
|
320
|
+
return new Promise((resolve, reject) => {
|
|
321
|
+
let settled = false;
|
|
322
|
+
let timer;
|
|
323
|
+
const prevMessage = ws.onmessage;
|
|
324
|
+
const prevClose = ws.onclose;
|
|
325
|
+
const finish = (action) => {
|
|
326
|
+
if (settled) return;
|
|
327
|
+
settled = true;
|
|
328
|
+
clearTimeout(timer);
|
|
329
|
+
ws.onmessage = prevMessage;
|
|
330
|
+
ws.onclose = prevClose;
|
|
331
|
+
action();
|
|
332
|
+
};
|
|
333
|
+
const armQuietTimer = () => {
|
|
334
|
+
clearTimeout(timer);
|
|
335
|
+
timer = setTimeout(
|
|
336
|
+
() => finish(
|
|
337
|
+
() => reject(
|
|
338
|
+
new KugelAudioError(
|
|
339
|
+
"Timed out waiting for settings_updated acknowledgement"
|
|
340
|
+
)
|
|
341
|
+
)
|
|
342
|
+
),
|
|
343
|
+
QUIET_TIMEOUT_MS
|
|
344
|
+
);
|
|
345
|
+
};
|
|
346
|
+
armQuietTimer();
|
|
347
|
+
ws.onmessage = (event) => {
|
|
348
|
+
armQuietTimer();
|
|
349
|
+
let data = null;
|
|
350
|
+
try {
|
|
351
|
+
const raw = typeof event.data === "string" ? event.data : event.data instanceof Buffer ? event.data.toString() : String(event.data);
|
|
352
|
+
data = JSON.parse(raw);
|
|
353
|
+
} catch {
|
|
354
|
+
}
|
|
355
|
+
if (data?.settings_updated) {
|
|
356
|
+
finish(() => resolve(parseEffectiveSettings(data.settings)));
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (data?.error) {
|
|
360
|
+
finish(() => reject(new KugelAudioError(String(data.error))));
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (prevMessage) prevMessage.call(ws, event);
|
|
364
|
+
};
|
|
365
|
+
ws.onclose = (event) => {
|
|
366
|
+
if (prevClose) prevClose.call(ws, event);
|
|
367
|
+
finish(
|
|
368
|
+
() => reject(
|
|
369
|
+
new ConnectionError(
|
|
370
|
+
"WebSocket closed before settings_updated acknowledgement"
|
|
371
|
+
)
|
|
372
|
+
)
|
|
373
|
+
);
|
|
374
|
+
};
|
|
375
|
+
ws.send(JSON.stringify({ update_settings: body }));
|
|
376
|
+
});
|
|
377
|
+
}
|
|
587
378
|
var _languageWarningLogged = false;
|
|
588
379
|
function warnIfNoLanguage(language, normalize) {
|
|
589
380
|
const normEnabled = normalize === void 0 || normalize;
|
|
@@ -639,6 +430,7 @@ var VoicesResource = class {
|
|
|
639
430
|
category: v.category,
|
|
640
431
|
sex: v.sex,
|
|
641
432
|
age: v.age,
|
|
433
|
+
quality: v.quality,
|
|
642
434
|
supportedLanguages: v.supported_languages || [],
|
|
643
435
|
sampleText: v.sample_text,
|
|
644
436
|
avatarUrl: v.avatar_url,
|
|
@@ -1098,7 +890,7 @@ var TTSResource = class {
|
|
|
1098
890
|
text: options.text,
|
|
1099
891
|
model_id: options.modelId || "kugel-3",
|
|
1100
892
|
voice_id: options.voiceId,
|
|
1101
|
-
cfg_scale: options.cfgScale ?? 2,
|
|
893
|
+
cfg_scale: clampCfgScale(options.cfgScale) ?? 2,
|
|
1102
894
|
...options.temperature !== void 0 && { temperature: options.temperature },
|
|
1103
895
|
max_new_tokens: options.maxNewTokens ?? 2048,
|
|
1104
896
|
sample_rate: options.sampleRate ?? 24e3,
|
|
@@ -1128,7 +920,7 @@ var TTSResource = class {
|
|
|
1128
920
|
text: options.text,
|
|
1129
921
|
model_id: options.modelId || "kugel-3",
|
|
1130
922
|
voice_id: options.voiceId,
|
|
1131
|
-
cfg_scale: options.cfgScale ?? 2,
|
|
923
|
+
cfg_scale: clampCfgScale(options.cfgScale) ?? 2,
|
|
1132
924
|
max_new_tokens: options.maxNewTokens ?? 2048,
|
|
1133
925
|
sample_rate: options.sampleRate ?? 24e3,
|
|
1134
926
|
...options.outputFormat && { output_format: options.outputFormat },
|
|
@@ -1483,7 +1275,7 @@ var MultiContextSession = class {
|
|
|
1483
1275
|
warnIfNoLanguage(this.config.language, this.config.normalize);
|
|
1484
1276
|
if (this.config.sampleRate) msg.sample_rate = this.config.sampleRate;
|
|
1485
1277
|
if (this.config.outputFormat) msg.output_format = this.config.outputFormat;
|
|
1486
|
-
if (this.config.cfgScale) msg.cfg_scale = this.config.cfgScale;
|
|
1278
|
+
if (this.config.cfgScale !== void 0) msg.cfg_scale = clampCfgScale(this.config.cfgScale);
|
|
1487
1279
|
if (this.config.temperature !== void 0) msg.temperature = this.config.temperature;
|
|
1488
1280
|
if (this.config.maxNewTokens) msg.max_new_tokens = this.config.maxNewTokens;
|
|
1489
1281
|
if (this.config.normalize !== void 0) msg.normalize = this.config.normalize;
|
|
@@ -1560,6 +1352,33 @@ var MultiContextSession = class {
|
|
|
1560
1352
|
context_id: contextId
|
|
1561
1353
|
}));
|
|
1562
1354
|
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Change the session's generation parameters mid-connection (KUG-1166).
|
|
1357
|
+
*
|
|
1358
|
+
* Session-scoped (there is no `contextId`): the update applies to contexts
|
|
1359
|
+
* started **after** it — a context already streaming keeps the settings it
|
|
1360
|
+
* began with, since generation parameters are bound when a context's engine
|
|
1361
|
+
* session opens. With the common one-context-per-turn pattern that means it
|
|
1362
|
+
* takes effect on the next turn. Per-context `cfgScale` / `maxNewTokens`
|
|
1363
|
+
* passed to {@link createContext} still win for that context.
|
|
1364
|
+
*
|
|
1365
|
+
* Resolves with the generation parameters now in effect (the server's echo).
|
|
1366
|
+
*
|
|
1367
|
+
* @throws {KugelAudioError} if no field is provided, the socket is not open,
|
|
1368
|
+
* or the server rejects the update.
|
|
1369
|
+
*/
|
|
1370
|
+
updateSettings(settings) {
|
|
1371
|
+
if (!this.ws || this.ws.readyState !== WS_OPEN) {
|
|
1372
|
+
throw new KugelAudioError(
|
|
1373
|
+
"MultiContextSession not connected. Call connect() first."
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
const body = buildSettingsUpdateBody(settings);
|
|
1377
|
+
return sendUpdateSettings(this.ws, body).then((effective) => {
|
|
1378
|
+
applyAcceptedSettingsUpdate(this.config, settings);
|
|
1379
|
+
return effective;
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1563
1382
|
/**
|
|
1564
1383
|
* Close the session and all contexts.
|
|
1565
1384
|
*/
|
|
@@ -1738,7 +1557,7 @@ var StreamingSession = class {
|
|
|
1738
1557
|
if (!this.configSent) {
|
|
1739
1558
|
if (this.config.voiceId !== void 0) msg.voice_id = this.config.voiceId;
|
|
1740
1559
|
if (this.config.modelId !== void 0) msg.model_id = this.config.modelId;
|
|
1741
|
-
if (this.config.cfgScale !== void 0) msg.cfg_scale = this.config.cfgScale;
|
|
1560
|
+
if (this.config.cfgScale !== void 0) msg.cfg_scale = clampCfgScale(this.config.cfgScale);
|
|
1742
1561
|
if (this.config.temperature !== void 0) msg.temperature = this.config.temperature;
|
|
1743
1562
|
if (this.config.maxNewTokens !== void 0) msg.max_new_tokens = this.config.maxNewTokens;
|
|
1744
1563
|
if (this.config.sampleRate !== void 0) msg.sample_rate = this.config.sampleRate;
|
|
@@ -1887,6 +1706,39 @@ var StreamingSession = class {
|
|
|
1887
1706
|
Object.assign(this.config, config);
|
|
1888
1707
|
this.configSent = false;
|
|
1889
1708
|
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Change generation parameters mid-connection without reconnecting (KUG-1166).
|
|
1711
|
+
*
|
|
1712
|
+
* Sends an `update_settings` message and resolves with the generation
|
|
1713
|
+
* parameters now in effect (the server's echo). Only the six fields of
|
|
1714
|
+
* {@link SettingsUpdate} are updatable; identity / audio-format fields
|
|
1715
|
+
* (`voiceId`, `modelId`, `sampleRate`, `outputFormat`, `dictionaryIds`) are
|
|
1716
|
+
* fixed for the connection — change those with {@link updateConfig} after
|
|
1717
|
+
* {@link endSession} instead.
|
|
1718
|
+
*
|
|
1719
|
+
* The change applies to the **next turn**: a turn already streaming keeps the
|
|
1720
|
+
* settings it started with, so call this between turns.
|
|
1721
|
+
*
|
|
1722
|
+
* @example
|
|
1723
|
+
* ```typescript
|
|
1724
|
+
* const effective = await session.updateSettings({ cfgScale: 1.5, speed: 1.1 });
|
|
1725
|
+
* ```
|
|
1726
|
+
*
|
|
1727
|
+
* @throws {KugelAudioError} if no field is provided, the socket is not open,
|
|
1728
|
+
* or the server rejects the update (e.g. a value out of range).
|
|
1729
|
+
*/
|
|
1730
|
+
updateSettings(settings) {
|
|
1731
|
+
if (!this.ws || this.ws.readyState !== WS_OPEN) {
|
|
1732
|
+
throw new KugelAudioError(
|
|
1733
|
+
"StreamingSession not connected. Call connect() first."
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
const body = buildSettingsUpdateBody(settings);
|
|
1737
|
+
return sendUpdateSettings(this.ws, body).then((effective) => {
|
|
1738
|
+
applyAcceptedSettingsUpdate(this.config, settings);
|
|
1739
|
+
return effective;
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1890
1742
|
/**
|
|
1891
1743
|
* Close the session and the WebSocket connection.
|
|
1892
1744
|
*
|