kugelaudio 0.6.0 → 0.7.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 CHANGED
@@ -1,3 +1,20 @@
1
+ ## [kugelaudio-v0.7.0](https://github.com/Kugelaudio/KugelAudio/compare/js-sdk-v0.6.1...js-sdk-v0.7.0) (2026-06-06)
2
+
3
+ ### Features
4
+
5
+ * **ingress:** add request observability metadata ([#1321](https://github.com/Kugelaudio/KugelAudio/issues/1321)) ([a9c5178](https://github.com/Kugelaudio/KugelAudio/commit/a9c5178193cb8b746a8bbd9b566b11f7b1d00f6d))
6
+ * **sdks:** default all SDKs to kugel-3 model ([#1323](https://github.com/Kugelaudio/KugelAudio/issues/1323)) ([c4de212](https://github.com/Kugelaudio/KugelAudio/commit/c4de212c91e16326a15dbee5622acacc83ed85bb))
7
+
8
+ ### Bug Fixes
9
+
10
+ * **js-sdk:** type SDK metadata fetch mock ([#1334](https://github.com/Kugelaudio/KugelAudio/issues/1334)) ([e8f6f59](https://github.com/Kugelaudio/KugelAudio/commit/e8f6f59595e123eaae8b44670c94fb4e7bc8d06c))
11
+
12
+ ## [kugelaudio-v0.6.1](https://github.com/Kugelaudio/KugelAudio/compare/js-sdk-v0.6.0...js-sdk-v0.6.1) (2026-06-04)
13
+
14
+ ### Bug Fixes
15
+
16
+ * **python-sdk:** propagate ingress errors through SDK integrations ([#1313](https://github.com/Kugelaudio/KugelAudio/issues/1313)) ([3ae2e03](https://github.com/Kugelaudio/KugelAudio/commit/3ae2e03745b49cca0712c20d9a658c160f4b6f38))
17
+
1
18
  ## [kugelaudio-v0.6.0](https://github.com/Kugelaudio/KugelAudio/compare/js-sdk-v0.5.0...js-sdk-v0.6.0) (2026-06-01)
2
19
 
3
20
  ### Features
package/dist/index.d.mts CHANGED
@@ -217,7 +217,7 @@ interface WordTimestamp {
217
217
  interface GenerateOptions {
218
218
  /** Text to synthesize */
219
219
  text: string;
220
- /** Model to use: 'kugel-1-turbo' (fast) or 'kugel-1' (premium). Default: 'kugel-1-turbo' */
220
+ /** Model to use. Default: 'kugel-3'. Legacy ids (kugel-2.5, kugel-1-turbo, ) still accepted; they alias to kugel-3 server-side. */
221
221
  modelId?: string;
222
222
  /** Voice ID to use */
223
223
  voiceId?: number;
@@ -296,7 +296,7 @@ interface GenerateOptions {
296
296
  interface StreamConfig {
297
297
  /** Voice ID to use */
298
298
  voiceId?: number;
299
- /** Model ID ('kugel-1-turbo' or 'kugel-1'). Default: 'kugel-1-turbo' */
299
+ /** Model ID. Default: 'kugel-3'. Legacy ids still accepted; they alias to kugel-3 server-side. */
300
300
  modelId?: string;
301
301
  /** CFG scale for generation */
302
302
  cfgScale?: number;
@@ -1224,6 +1224,8 @@ declare const ErrorCodes: {
1224
1224
  readonly VALIDATION: "VALIDATION_ERROR";
1225
1225
  readonly INTERNAL: "INTERNAL_ERROR";
1226
1226
  readonly NOT_FOUND: "NOT_FOUND";
1227
+ readonly MISSING_VOICE_ID: "MISSING_VOICE_ID";
1228
+ readonly TOO_MANY_CONTEXTS: "TOO_MANY_CONTEXTS";
1227
1229
  };
1228
1230
  type ErrorCode = typeof ErrorCodes[keyof typeof ErrorCodes];
1229
1231
  declare const WsCloseCodes: {
@@ -1305,11 +1307,12 @@ interface HttpResponseLike {
1305
1307
  declare function classifyHttpError(status: number, bodyText: string, headers: HttpResponseLike['headers']): KugelAudioError;
1306
1308
  /**
1307
1309
  * Build a `KugelAudioError` from a server-sent WebSocket error frame
1308
- * (`{error, error_code, retry_after}`).
1310
+ * (`{error, error_code, code}`).
1309
1311
  */
1310
1312
  declare function classifyWsFrame(data: {
1311
1313
  error?: string;
1312
1314
  error_code?: string;
1315
+ code?: number;
1313
1316
  retry_after?: number;
1314
1317
  }): KugelAudioError;
1315
1318
  /**
package/dist/index.d.ts CHANGED
@@ -217,7 +217,7 @@ interface WordTimestamp {
217
217
  interface GenerateOptions {
218
218
  /** Text to synthesize */
219
219
  text: string;
220
- /** Model to use: 'kugel-1-turbo' (fast) or 'kugel-1' (premium). Default: 'kugel-1-turbo' */
220
+ /** Model to use. Default: 'kugel-3'. Legacy ids (kugel-2.5, kugel-1-turbo, ) still accepted; they alias to kugel-3 server-side. */
221
221
  modelId?: string;
222
222
  /** Voice ID to use */
223
223
  voiceId?: number;
@@ -296,7 +296,7 @@ interface GenerateOptions {
296
296
  interface StreamConfig {
297
297
  /** Voice ID to use */
298
298
  voiceId?: number;
299
- /** Model ID ('kugel-1-turbo' or 'kugel-1'). Default: 'kugel-1-turbo' */
299
+ /** Model ID. Default: 'kugel-3'. Legacy ids still accepted; they alias to kugel-3 server-side. */
300
300
  modelId?: string;
301
301
  /** CFG scale for generation */
302
302
  cfgScale?: number;
@@ -1224,6 +1224,8 @@ declare const ErrorCodes: {
1224
1224
  readonly VALIDATION: "VALIDATION_ERROR";
1225
1225
  readonly INTERNAL: "INTERNAL_ERROR";
1226
1226
  readonly NOT_FOUND: "NOT_FOUND";
1227
+ readonly MISSING_VOICE_ID: "MISSING_VOICE_ID";
1228
+ readonly TOO_MANY_CONTEXTS: "TOO_MANY_CONTEXTS";
1227
1229
  };
1228
1230
  type ErrorCode = typeof ErrorCodes[keyof typeof ErrorCodes];
1229
1231
  declare const WsCloseCodes: {
@@ -1305,11 +1307,12 @@ interface HttpResponseLike {
1305
1307
  declare function classifyHttpError(status: number, bodyText: string, headers: HttpResponseLike['headers']): KugelAudioError;
1306
1308
  /**
1307
1309
  * Build a `KugelAudioError` from a server-sent WebSocket error frame
1308
- * (`{error, error_code, retry_after}`).
1310
+ * (`{error, error_code, code}`).
1309
1311
  */
1310
1312
  declare function classifyWsFrame(data: {
1311
1313
  error?: string;
1312
1314
  error_code?: string;
1315
+ code?: number;
1313
1316
  retry_after?: number;
1314
1317
  }): KugelAudioError;
1315
1318
  /**
package/dist/index.js CHANGED
@@ -233,7 +233,9 @@ var ErrorCodes = {
233
233
  EMPTY_AUDIO: "EMPTY_AUDIO",
234
234
  VALIDATION: "VALIDATION_ERROR",
235
235
  INTERNAL: "INTERNAL_ERROR",
236
- NOT_FOUND: "NOT_FOUND"
236
+ NOT_FOUND: "NOT_FOUND",
237
+ MISSING_VOICE_ID: "MISSING_VOICE_ID",
238
+ TOO_MANY_CONTEXTS: "TOO_MANY_CONTEXTS"
237
239
  };
238
240
  var WsCloseCodes = {
239
241
  UNAUTHORIZED: 4001,
@@ -317,10 +319,10 @@ function build(status, errorCode, message, opts = {}) {
317
319
  if (errorCode === ErrorCodes.INSUFFICIENT_CREDITS || status === 402) {
318
320
  return new InsufficientCreditsError(message || void 0, common);
319
321
  }
320
- if (errorCode === ErrorCodes.RATE_LIMITED || status === 429) {
322
+ if (errorCode === ErrorCodes.RATE_LIMITED || errorCode === ErrorCodes.TOO_MANY_CONTEXTS || status === 429) {
321
323
  return new RateLimitError(message || void 0, common);
322
324
  }
323
- if (errorCode === ErrorCodes.VALIDATION || status === 400) {
325
+ if (errorCode === ErrorCodes.VALIDATION || errorCode === ErrorCodes.MISSING_VOICE_ID || status === 400) {
324
326
  return new ValidationError(message || "Request validation failed.", common);
325
327
  }
326
328
  if (errorCode === ErrorCodes.MODEL_UNAVAILABLE || status === 503) {
@@ -380,8 +382,9 @@ function classifyHttpError(status, bodyText, headers) {
380
382
  function classifyWsFrame(data) {
381
383
  const errorCode = data.error_code;
382
384
  const message = data.error ?? "Server reported an error.";
385
+ const status = typeof data.code === "number" ? data.code : void 0;
383
386
  const retryAfter = typeof data.retry_after === "number" ? data.retry_after : void 0;
384
- return build(void 0, errorCode, message, { retryAfter });
387
+ return build(status, errorCode, message, { retryAfter });
385
388
  }
386
389
  function classifyWsClose(code, reason) {
387
390
  const reasonTxt = (reason ?? "").trim();
@@ -509,10 +512,75 @@ function getWebSocket() {
509
512
  );
510
513
  }
511
514
 
515
+ // package.json
516
+ var package_default = {
517
+ name: "kugelaudio",
518
+ version: "0.7.0",
519
+ description: "Official JavaScript/TypeScript SDK for KugelAudio TTS API",
520
+ main: "dist/index.js",
521
+ module: "dist/index.mjs",
522
+ types: "dist/index.d.ts",
523
+ exports: {
524
+ ".": {
525
+ types: "./dist/index.d.ts",
526
+ import: "./dist/index.mjs",
527
+ require: "./dist/index.js"
528
+ }
529
+ },
530
+ files: [
531
+ "dist",
532
+ "src",
533
+ "LICENSE",
534
+ "CHANGELOG.md"
535
+ ],
536
+ scripts: {
537
+ build: "tsup src/index.ts --format cjs,esm --dts",
538
+ dev: "tsup src/index.ts --format cjs,esm --dts --watch",
539
+ lint: "eslint src/",
540
+ test: "vitest run",
541
+ "test:watch": "vitest",
542
+ prepublishOnly: "npm run build"
543
+ },
544
+ keywords: [
545
+ "tts",
546
+ "text-to-speech",
547
+ "audio",
548
+ "streaming",
549
+ "websocket",
550
+ "kugelaudio"
551
+ ],
552
+ author: "KugelAudio <hello@kugelaudio.com>",
553
+ license: "MIT",
554
+ repository: {
555
+ type: "git",
556
+ url: "https://github.com/Kugelaudio/KugelAudio",
557
+ directory: "sdks/js"
558
+ },
559
+ homepage: "https://kugelaudio.com",
560
+ bugs: {
561
+ url: "https://github.com/Kugelaudio/KugelAudio/issues"
562
+ },
563
+ devDependencies: {
564
+ "@types/node": "^25.3.2",
565
+ tsup: "^8.0.0",
566
+ typescript: "^6.0.2",
567
+ vitest: "^4.0.18"
568
+ },
569
+ engines: {
570
+ node: ">=18.0.0"
571
+ },
572
+ dependencies: {
573
+ tsx: "^4.21.0",
574
+ ws: "^8.18.0"
575
+ }
576
+ };
577
+
512
578
  // src/client.ts
513
579
  var DEFAULT_API_URL = "https://api.kugelaudio.com";
514
580
  var EU_API_URL = "https://api.eu.kugelaudio.com";
515
581
  var SUPPORTED_REGIONS = ["eu", "us", "global"];
582
+ var SDK_NAME = "js";
583
+ var SDK_VERSION = package_default.version;
516
584
  var REGION_PREFIXES = ["eu-", "us-", "global-"];
517
585
  function parseApiKey(apiKey) {
518
586
  for (const prefix of REGION_PREFIXES) {
@@ -522,6 +590,16 @@ function parseApiKey(apiKey) {
522
590
  }
523
591
  return { cleanKey: apiKey };
524
592
  }
593
+ function sdkHeaders() {
594
+ return {
595
+ "X-KugelAudio-SDK": SDK_NAME,
596
+ "X-KugelAudio-SDK-Version": SDK_VERSION
597
+ };
598
+ }
599
+ function appendSdkQuery(url) {
600
+ const separator = url.includes("?") ? "&" : "?";
601
+ return `${url}${separator}sdk=${encodeURIComponent(SDK_NAME)}&sdk_version=${encodeURIComponent(SDK_VERSION)}`;
602
+ }
525
603
  function createWs(url) {
526
604
  const WS = getWebSocket();
527
605
  return new WS(url);
@@ -890,7 +968,7 @@ var TTSResource = class {
890
968
  if (this.client.orgId !== void 0) {
891
969
  url += `&org_id=${this.client.orgId}`;
892
970
  }
893
- return url;
971
+ return appendSdkQuery(url);
894
972
  }
895
973
  /**
896
974
  * Get or create a WebSocket connection for connection pooling.
@@ -1038,7 +1116,7 @@ var TTSResource = class {
1038
1116
  callbacks.onOpen?.();
1039
1117
  ws.send(JSON.stringify({
1040
1118
  text: options.text,
1041
- model_id: options.modelId || "kugel-1-turbo",
1119
+ model_id: options.modelId || "kugel-3",
1042
1120
  voice_id: options.voiceId,
1043
1121
  cfg_scale: options.cfgScale ?? 2,
1044
1122
  ...options.temperature !== void 0 && { temperature: options.temperature },
@@ -1064,7 +1142,7 @@ var TTSResource = class {
1064
1142
  callbacks.onOpen?.();
1065
1143
  ws.send(JSON.stringify({
1066
1144
  text: options.text,
1067
- model_id: options.modelId || "kugel-1-turbo",
1145
+ model_id: options.modelId || "kugel-3",
1068
1146
  voice_id: options.voiceId,
1069
1147
  cfg_scale: options.cfgScale ?? 2,
1070
1148
  max_new_tokens: options.maxNewTokens ?? 2048,
@@ -1294,7 +1372,7 @@ var MultiContextSession = class {
1294
1372
  } else {
1295
1373
  authParam = "api_key";
1296
1374
  }
1297
- const url = `${wsUrl}/ws/tts/multi?${authParam}=${this.client.apiKey}`;
1375
+ const url = appendSdkQuery(`${wsUrl}/ws/tts/multi?${authParam}=${this.client.apiKey}`);
1298
1376
  this.ws = createWs(url);
1299
1377
  const ws = this.ws;
1300
1378
  ws.onmessage = (event) => {
@@ -1303,7 +1381,7 @@ var MultiContextSession = class {
1303
1381
  const data = JSON.parse(messageData);
1304
1382
  if (data.error) {
1305
1383
  this.callbacks.onError?.(
1306
- new KugelAudioError(data.error),
1384
+ classifyWsFrame(data),
1307
1385
  data.context_id
1308
1386
  );
1309
1387
  return;
@@ -1515,7 +1593,7 @@ var StreamingSession = class {
1515
1593
  } else {
1516
1594
  authParam = "api_key";
1517
1595
  }
1518
- const url = `${wsUrl}/ws/tts/stream?${authParam}=${this.client.apiKey}`;
1596
+ const url = appendSdkQuery(`${wsUrl}/ws/tts/stream?${authParam}=${this.client.apiKey}`);
1519
1597
  this.ws = createWs(url);
1520
1598
  const ws = this.ws;
1521
1599
  ws.onmessage = (event) => {
@@ -1919,7 +1997,8 @@ var KugelAudio = class _KugelAudio {
1919
1997
  const headers = {
1920
1998
  "Content-Type": "application/json",
1921
1999
  "X-API-Key": this._apiKey,
1922
- "Authorization": `Bearer ${this._apiKey}`
2000
+ "Authorization": `Bearer ${this._apiKey}`,
2001
+ ...sdkHeaders()
1923
2002
  };
1924
2003
  const controller = new AbortController();
1925
2004
  const timeoutId = setTimeout(() => controller.abort(), this._timeout);
@@ -1959,7 +2038,8 @@ var KugelAudio = class _KugelAudio {
1959
2038
  const url = `${this._apiUrl}${path}`;
1960
2039
  const headers = {
1961
2040
  "X-API-Key": this._apiKey,
1962
- "Authorization": `Bearer ${this._apiKey}`
2041
+ "Authorization": `Bearer ${this._apiKey}`,
2042
+ ...sdkHeaders()
1963
2043
  };
1964
2044
  const controller = new AbortController();
1965
2045
  const timeoutId = setTimeout(() => controller.abort(), this._timeout);
package/dist/index.mjs CHANGED
@@ -195,7 +195,9 @@ var ErrorCodes = {
195
195
  EMPTY_AUDIO: "EMPTY_AUDIO",
196
196
  VALIDATION: "VALIDATION_ERROR",
197
197
  INTERNAL: "INTERNAL_ERROR",
198
- NOT_FOUND: "NOT_FOUND"
198
+ NOT_FOUND: "NOT_FOUND",
199
+ MISSING_VOICE_ID: "MISSING_VOICE_ID",
200
+ TOO_MANY_CONTEXTS: "TOO_MANY_CONTEXTS"
199
201
  };
200
202
  var WsCloseCodes = {
201
203
  UNAUTHORIZED: 4001,
@@ -279,10 +281,10 @@ function build(status, errorCode, message, opts = {}) {
279
281
  if (errorCode === ErrorCodes.INSUFFICIENT_CREDITS || status === 402) {
280
282
  return new InsufficientCreditsError(message || void 0, common);
281
283
  }
282
- if (errorCode === ErrorCodes.RATE_LIMITED || status === 429) {
284
+ if (errorCode === ErrorCodes.RATE_LIMITED || errorCode === ErrorCodes.TOO_MANY_CONTEXTS || status === 429) {
283
285
  return new RateLimitError(message || void 0, common);
284
286
  }
285
- if (errorCode === ErrorCodes.VALIDATION || status === 400) {
287
+ if (errorCode === ErrorCodes.VALIDATION || errorCode === ErrorCodes.MISSING_VOICE_ID || status === 400) {
286
288
  return new ValidationError(message || "Request validation failed.", common);
287
289
  }
288
290
  if (errorCode === ErrorCodes.MODEL_UNAVAILABLE || status === 503) {
@@ -342,8 +344,9 @@ function classifyHttpError(status, bodyText, headers) {
342
344
  function classifyWsFrame(data) {
343
345
  const errorCode = data.error_code;
344
346
  const message = data.error ?? "Server reported an error.";
347
+ const status = typeof data.code === "number" ? data.code : void 0;
345
348
  const retryAfter = typeof data.retry_after === "number" ? data.retry_after : void 0;
346
- return build(void 0, errorCode, message, { retryAfter });
349
+ return build(status, errorCode, message, { retryAfter });
347
350
  }
348
351
  function classifyWsClose(code, reason) {
349
352
  const reasonTxt = (reason ?? "").trim();
@@ -471,10 +474,75 @@ function getWebSocket() {
471
474
  );
472
475
  }
473
476
 
477
+ // package.json
478
+ var package_default = {
479
+ name: "kugelaudio",
480
+ version: "0.7.0",
481
+ description: "Official JavaScript/TypeScript SDK for KugelAudio TTS API",
482
+ main: "dist/index.js",
483
+ module: "dist/index.mjs",
484
+ types: "dist/index.d.ts",
485
+ exports: {
486
+ ".": {
487
+ types: "./dist/index.d.ts",
488
+ import: "./dist/index.mjs",
489
+ require: "./dist/index.js"
490
+ }
491
+ },
492
+ files: [
493
+ "dist",
494
+ "src",
495
+ "LICENSE",
496
+ "CHANGELOG.md"
497
+ ],
498
+ scripts: {
499
+ build: "tsup src/index.ts --format cjs,esm --dts",
500
+ dev: "tsup src/index.ts --format cjs,esm --dts --watch",
501
+ lint: "eslint src/",
502
+ test: "vitest run",
503
+ "test:watch": "vitest",
504
+ prepublishOnly: "npm run build"
505
+ },
506
+ keywords: [
507
+ "tts",
508
+ "text-to-speech",
509
+ "audio",
510
+ "streaming",
511
+ "websocket",
512
+ "kugelaudio"
513
+ ],
514
+ author: "KugelAudio <hello@kugelaudio.com>",
515
+ license: "MIT",
516
+ repository: {
517
+ type: "git",
518
+ url: "https://github.com/Kugelaudio/KugelAudio",
519
+ directory: "sdks/js"
520
+ },
521
+ homepage: "https://kugelaudio.com",
522
+ bugs: {
523
+ url: "https://github.com/Kugelaudio/KugelAudio/issues"
524
+ },
525
+ devDependencies: {
526
+ "@types/node": "^25.3.2",
527
+ tsup: "^8.0.0",
528
+ typescript: "^6.0.2",
529
+ vitest: "^4.0.18"
530
+ },
531
+ engines: {
532
+ node: ">=18.0.0"
533
+ },
534
+ dependencies: {
535
+ tsx: "^4.21.0",
536
+ ws: "^8.18.0"
537
+ }
538
+ };
539
+
474
540
  // src/client.ts
475
541
  var DEFAULT_API_URL = "https://api.kugelaudio.com";
476
542
  var EU_API_URL = "https://api.eu.kugelaudio.com";
477
543
  var SUPPORTED_REGIONS = ["eu", "us", "global"];
544
+ var SDK_NAME = "js";
545
+ var SDK_VERSION = package_default.version;
478
546
  var REGION_PREFIXES = ["eu-", "us-", "global-"];
479
547
  function parseApiKey(apiKey) {
480
548
  for (const prefix of REGION_PREFIXES) {
@@ -484,6 +552,16 @@ function parseApiKey(apiKey) {
484
552
  }
485
553
  return { cleanKey: apiKey };
486
554
  }
555
+ function sdkHeaders() {
556
+ return {
557
+ "X-KugelAudio-SDK": SDK_NAME,
558
+ "X-KugelAudio-SDK-Version": SDK_VERSION
559
+ };
560
+ }
561
+ function appendSdkQuery(url) {
562
+ const separator = url.includes("?") ? "&" : "?";
563
+ return `${url}${separator}sdk=${encodeURIComponent(SDK_NAME)}&sdk_version=${encodeURIComponent(SDK_VERSION)}`;
564
+ }
487
565
  function createWs(url) {
488
566
  const WS = getWebSocket();
489
567
  return new WS(url);
@@ -852,7 +930,7 @@ var TTSResource = class {
852
930
  if (this.client.orgId !== void 0) {
853
931
  url += `&org_id=${this.client.orgId}`;
854
932
  }
855
- return url;
933
+ return appendSdkQuery(url);
856
934
  }
857
935
  /**
858
936
  * Get or create a WebSocket connection for connection pooling.
@@ -1000,7 +1078,7 @@ var TTSResource = class {
1000
1078
  callbacks.onOpen?.();
1001
1079
  ws.send(JSON.stringify({
1002
1080
  text: options.text,
1003
- model_id: options.modelId || "kugel-1-turbo",
1081
+ model_id: options.modelId || "kugel-3",
1004
1082
  voice_id: options.voiceId,
1005
1083
  cfg_scale: options.cfgScale ?? 2,
1006
1084
  ...options.temperature !== void 0 && { temperature: options.temperature },
@@ -1026,7 +1104,7 @@ var TTSResource = class {
1026
1104
  callbacks.onOpen?.();
1027
1105
  ws.send(JSON.stringify({
1028
1106
  text: options.text,
1029
- model_id: options.modelId || "kugel-1-turbo",
1107
+ model_id: options.modelId || "kugel-3",
1030
1108
  voice_id: options.voiceId,
1031
1109
  cfg_scale: options.cfgScale ?? 2,
1032
1110
  max_new_tokens: options.maxNewTokens ?? 2048,
@@ -1256,7 +1334,7 @@ var MultiContextSession = class {
1256
1334
  } else {
1257
1335
  authParam = "api_key";
1258
1336
  }
1259
- const url = `${wsUrl}/ws/tts/multi?${authParam}=${this.client.apiKey}`;
1337
+ const url = appendSdkQuery(`${wsUrl}/ws/tts/multi?${authParam}=${this.client.apiKey}`);
1260
1338
  this.ws = createWs(url);
1261
1339
  const ws = this.ws;
1262
1340
  ws.onmessage = (event) => {
@@ -1265,7 +1343,7 @@ var MultiContextSession = class {
1265
1343
  const data = JSON.parse(messageData);
1266
1344
  if (data.error) {
1267
1345
  this.callbacks.onError?.(
1268
- new KugelAudioError(data.error),
1346
+ classifyWsFrame(data),
1269
1347
  data.context_id
1270
1348
  );
1271
1349
  return;
@@ -1477,7 +1555,7 @@ var StreamingSession = class {
1477
1555
  } else {
1478
1556
  authParam = "api_key";
1479
1557
  }
1480
- const url = `${wsUrl}/ws/tts/stream?${authParam}=${this.client.apiKey}`;
1558
+ const url = appendSdkQuery(`${wsUrl}/ws/tts/stream?${authParam}=${this.client.apiKey}`);
1481
1559
  this.ws = createWs(url);
1482
1560
  const ws = this.ws;
1483
1561
  ws.onmessage = (event) => {
@@ -1881,7 +1959,8 @@ var KugelAudio = class _KugelAudio {
1881
1959
  const headers = {
1882
1960
  "Content-Type": "application/json",
1883
1961
  "X-API-Key": this._apiKey,
1884
- "Authorization": `Bearer ${this._apiKey}`
1962
+ "Authorization": `Bearer ${this._apiKey}`,
1963
+ ...sdkHeaders()
1885
1964
  };
1886
1965
  const controller = new AbortController();
1887
1966
  const timeoutId = setTimeout(() => controller.abort(), this._timeout);
@@ -1921,7 +2000,8 @@ var KugelAudio = class _KugelAudio {
1921
2000
  const url = `${this._apiUrl}${path}`;
1922
2001
  const headers = {
1923
2002
  "X-API-Key": this._apiKey,
1924
- "Authorization": `Bearer ${this._apiKey}`
2003
+ "Authorization": `Bearer ${this._apiKey}`,
2004
+ ...sdkHeaders()
1925
2005
  };
1926
2006
  const controller = new AbortController();
1927
2007
  const timeoutId = setTimeout(() => controller.abort(), this._timeout);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kugelaudio",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Official JavaScript/TypeScript SDK for KugelAudio TTS API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
10
10
  import { KugelAudio } from './client';
11
+ import { RateLimitError } from './errors';
11
12
 
12
13
  // ---------------------------------------------------------------------------
13
14
  // Minimal WebSocket mock
@@ -17,6 +18,7 @@ type WsListener = (event: { data: string }) => void;
17
18
  type WsCloseListener = (event: { code: number }) => void;
18
19
 
19
20
  interface MockWs {
21
+ url: string;
20
22
  readyState: number;
21
23
  onopen: (() => void) | null;
22
24
  onmessage: WsListener | null;
@@ -32,6 +34,7 @@ let mockWs: MockWs;
32
34
  vi.mock('./websocket', () => ({
33
35
  getWebSocket: () => {
34
36
  return class MockWebSocket {
37
+ url: string;
35
38
  readyState = 0; // CONNECTING
36
39
  onopen: (() => void) | null = null;
37
40
  onmessage: WsListener | null = null;
@@ -41,7 +44,8 @@ vi.mock('./websocket', () => ({
41
44
  close = vi.fn();
42
45
  ping = vi.fn();
43
46
 
44
- constructor() {
47
+ constructor(url: string) {
48
+ this.url = url;
45
49
  mockWs = this as unknown as MockWs;
46
50
  // Simulate async open
47
51
  setTimeout(() => {
@@ -246,6 +250,43 @@ describe('KugelAudio multi-region', () => {
246
250
  });
247
251
  });
248
252
 
253
+ describe('KugelAudio SDK metadata', () => {
254
+ it('adds SDK metadata headers to HTTP requests', async () => {
255
+ const originalFetch = globalThis.fetch;
256
+ const fetchMock = vi.fn<typeof fetch>(async () => ({
257
+ ok: true,
258
+ json: async () => ({ models: [] }),
259
+ } as Response));
260
+ globalThis.fetch = fetchMock;
261
+
262
+ try {
263
+ const client = new KugelAudio({ apiKey: 'ka_test123' });
264
+ await client.models.list();
265
+ } finally {
266
+ globalThis.fetch = originalFetch;
267
+ }
268
+
269
+ expect(fetchMock).toHaveBeenCalledTimes(1);
270
+ const [, init] = fetchMock.mock.calls[0];
271
+ expect(init).toMatchObject({
272
+ headers: {
273
+ 'X-KugelAudio-SDK': 'js',
274
+ 'X-KugelAudio-SDK-Version': '0.6.1',
275
+ },
276
+ });
277
+ });
278
+
279
+ it('adds SDK metadata query params to WebSocket URLs', async () => {
280
+ const client = new KugelAudio({ apiKey: 'ka_test123' });
281
+
282
+ client.tts.toReadable({ text: 'metadata test' });
283
+ await new Promise<void>((r) => setTimeout(r, 10));
284
+
285
+ expect(mockWs.url).toContain('sdk=js');
286
+ expect(mockWs.url).toContain('sdk_version=0.6.1');
287
+ });
288
+ });
289
+
249
290
  // ---------------------------------------------------------------------------
250
291
  // Keepalive ping tests
251
292
  // ---------------------------------------------------------------------------
@@ -660,4 +701,27 @@ describe('MultiContextSession closeContext', () => {
660
701
  expect(sent.close_context).toBe(true);
661
702
  expect(sent.immediate).toBeUndefined();
662
703
  });
704
+
705
+ it('maps ingress context-cap errors to typed callback errors', async () => {
706
+ const errors: Array<{ contextId?: string; error: Error }> = [];
707
+ const session = client.tts.createMultiContextSession({ defaultVoiceId: 1 });
708
+ await session.connect({
709
+ onError: (error, contextId) => errors.push({ contextId, error }),
710
+ });
711
+
712
+ mockWs.onmessage?.({
713
+ data: JSON.stringify({
714
+ error: 'Too many concurrent contexts',
715
+ error_code: 'TOO_MANY_CONTEXTS',
716
+ code: 429,
717
+ context_id: 'ctx1',
718
+ }),
719
+ });
720
+
721
+ expect(errors).toHaveLength(1);
722
+ expect(errors[0].contextId).toBe('ctx1');
723
+ expect(errors[0].error).toBeInstanceOf(RateLimitError);
724
+ expect((errors[0].error as RateLimitError).statusCode).toBe(429);
725
+ expect((errors[0].error as RateLimitError).errorCode).toBe('TOO_MANY_CONTEXTS');
726
+ });
663
727
  });
package/src/client.ts CHANGED
@@ -33,10 +33,13 @@ import { base64ToArrayBuffer } from './utils';
33
33
  import { getWebSocket } from './websocket';
34
34
 
35
35
  import type { Region } from './types';
36
+ import packageJson from '../package.json';
36
37
 
37
38
  const DEFAULT_API_URL = 'https://api.kugelaudio.com';
38
39
  const EU_API_URL = 'https://api.eu.kugelaudio.com';
39
40
  const SUPPORTED_REGIONS = ['eu', 'us', 'global'] as const;
41
+ const SDK_NAME = 'js';
42
+ const SDK_VERSION = packageJson.version;
40
43
 
41
44
  const REGION_PREFIXES = ['eu-', 'us-', 'global-'] as const;
42
45
 
@@ -49,6 +52,18 @@ function parseApiKey(apiKey: string): { cleanKey: string; detectedRegion?: Regio
49
52
  return { cleanKey: apiKey };
50
53
  }
51
54
 
55
+ function sdkHeaders(): Record<string, string> {
56
+ return {
57
+ 'X-KugelAudio-SDK': SDK_NAME,
58
+ 'X-KugelAudio-SDK-Version': SDK_VERSION,
59
+ };
60
+ }
61
+
62
+ function appendSdkQuery(url: string): string {
63
+ const separator = url.includes('?') ? '&' : '?';
64
+ return `${url}${separator}sdk=${encodeURIComponent(SDK_NAME)}&sdk_version=${encodeURIComponent(SDK_VERSION)}`;
65
+ }
66
+
52
67
  /**
53
68
  * Create a new WebSocket instance.
54
69
  * Lazily resolves the constructor to avoid top-level side-effects
@@ -491,7 +506,7 @@ class TTSResource {
491
506
  if (this.client.orgId !== undefined) {
492
507
  url += `&org_id=${this.client.orgId}`;
493
508
  }
494
- return url;
509
+ return appendSdkQuery(url);
495
510
  }
496
511
 
497
512
  /**
@@ -692,7 +707,7 @@ class TTSResource {
692
707
 
693
708
  ws.send(JSON.stringify({
694
709
  text: options.text,
695
- model_id: options.modelId || 'kugel-1-turbo',
710
+ model_id: options.modelId || 'kugel-3',
696
711
  voice_id: options.voiceId,
697
712
  cfg_scale: options.cfgScale ?? 2.0,
698
713
  ...(options.temperature !== undefined && { temperature: options.temperature }),
@@ -724,7 +739,7 @@ class TTSResource {
724
739
  // Send TTS request
725
740
  ws.send(JSON.stringify({
726
741
  text: options.text,
727
- model_id: options.modelId || 'kugel-1-turbo',
742
+ model_id: options.modelId || 'kugel-3',
728
743
  voice_id: options.voiceId,
729
744
  cfg_scale: options.cfgScale ?? 2.0,
730
745
  max_new_tokens: options.maxNewTokens ?? 2048,
@@ -873,7 +888,7 @@ class TTSResource {
873
888
  }
874
889
  }
875
890
 
876
- private parseError(data: { error?: string; error_code?: string; retry_after?: number }): Error {
891
+ private parseError(data: { error?: string; error_code?: string; code?: number; retry_after?: number }): Error {
877
892
  return classifyWsFrame(data);
878
893
  }
879
894
 
@@ -1003,7 +1018,7 @@ class MultiContextSession {
1003
1018
  authParam = 'api_key';
1004
1019
  }
1005
1020
 
1006
- const url = `${wsUrl}/ws/tts/multi?${authParam}=${this.client.apiKey}`;
1021
+ const url = appendSdkQuery(`${wsUrl}/ws/tts/multi?${authParam}=${this.client.apiKey}`);
1007
1022
  this.ws = createWs(url);
1008
1023
  const ws = this.ws;
1009
1024
 
@@ -1019,7 +1034,7 @@ class MultiContextSession {
1019
1034
 
1020
1035
  if (data.error) {
1021
1036
  this.callbacks.onError?.(
1022
- new KugelAudioError(data.error),
1037
+ classifyWsFrame(data),
1023
1038
  data.context_id
1024
1039
  );
1025
1040
  return;
@@ -1317,7 +1332,7 @@ class StreamingSession {
1317
1332
  authParam = 'api_key';
1318
1333
  }
1319
1334
 
1320
- const url = `${wsUrl}/ws/tts/stream?${authParam}=${this.client.apiKey}`;
1335
+ const url = appendSdkQuery(`${wsUrl}/ws/tts/stream?${authParam}=${this.client.apiKey}`);
1321
1336
  this.ws = createWs(url);
1322
1337
  const ws = this.ws;
1323
1338
 
@@ -1865,6 +1880,7 @@ export class KugelAudio {
1865
1880
  'Content-Type': 'application/json',
1866
1881
  'X-API-Key': this._apiKey,
1867
1882
  'Authorization': `Bearer ${this._apiKey}`,
1883
+ ...sdkHeaders(),
1868
1884
  };
1869
1885
 
1870
1886
  const controller = new AbortController();
@@ -1913,6 +1929,7 @@ export class KugelAudio {
1913
1929
  const headers: Record<string, string> = {
1914
1930
  'X-API-Key': this._apiKey,
1915
1931
  'Authorization': `Bearer ${this._apiKey}`,
1932
+ ...sdkHeaders(),
1916
1933
  };
1917
1934
 
1918
1935
  const controller = new AbortController();
package/src/errors.ts CHANGED
@@ -17,6 +17,8 @@ export const ErrorCodes = {
17
17
  VALIDATION: 'VALIDATION_ERROR',
18
18
  INTERNAL: 'INTERNAL_ERROR',
19
19
  NOT_FOUND: 'NOT_FOUND',
20
+ MISSING_VOICE_ID: 'MISSING_VOICE_ID',
21
+ TOO_MANY_CONTEXTS: 'TOO_MANY_CONTEXTS',
20
22
  } as const;
21
23
  export type ErrorCode = typeof ErrorCodes[keyof typeof ErrorCodes];
22
24
 
@@ -175,10 +177,18 @@ function build(
175
177
  if (errorCode === ErrorCodes.INSUFFICIENT_CREDITS || status === 402) {
176
178
  return new InsufficientCreditsError(message || undefined, common);
177
179
  }
178
- if (errorCode === ErrorCodes.RATE_LIMITED || status === 429) {
180
+ if (
181
+ errorCode === ErrorCodes.RATE_LIMITED ||
182
+ errorCode === ErrorCodes.TOO_MANY_CONTEXTS ||
183
+ status === 429
184
+ ) {
179
185
  return new RateLimitError(message || undefined, common);
180
186
  }
181
- if (errorCode === ErrorCodes.VALIDATION || status === 400) {
187
+ if (
188
+ errorCode === ErrorCodes.VALIDATION ||
189
+ errorCode === ErrorCodes.MISSING_VOICE_ID ||
190
+ status === 400
191
+ ) {
182
192
  return new ValidationError(message || 'Request validation failed.', common);
183
193
  }
184
194
  if (errorCode === ErrorCodes.MODEL_UNAVAILABLE || status === 503) {
@@ -263,17 +273,19 @@ export function classifyHttpError(
263
273
 
264
274
  /**
265
275
  * Build a `KugelAudioError` from a server-sent WebSocket error frame
266
- * (`{error, error_code, retry_after}`).
276
+ * (`{error, error_code, code}`).
267
277
  */
268
278
  export function classifyWsFrame(data: {
269
279
  error?: string;
270
280
  error_code?: string;
281
+ code?: number;
271
282
  retry_after?: number;
272
283
  }): KugelAudioError {
273
284
  const errorCode = data.error_code;
274
285
  const message = data.error ?? 'Server reported an error.';
286
+ const status = typeof data.code === 'number' ? data.code : undefined;
275
287
  const retryAfter = typeof data.retry_after === 'number' ? data.retry_after : undefined;
276
- return build(undefined, errorCode, message, { retryAfter });
288
+ return build(status, errorCode, message, { retryAfter });
277
289
  }
278
290
 
279
291
  /**
package/src/types.ts CHANGED
@@ -240,7 +240,7 @@ export interface WordTimestamp {
240
240
  export interface GenerateOptions {
241
241
  /** Text to synthesize */
242
242
  text: string;
243
- /** Model to use: 'kugel-1-turbo' (fast) or 'kugel-1' (premium). Default: 'kugel-1-turbo' */
243
+ /** Model to use. Default: 'kugel-3'. Legacy ids (kugel-2.5, kugel-1-turbo, ) still accepted; they alias to kugel-3 server-side. */
244
244
  modelId?: string;
245
245
  /** Voice ID to use */
246
246
  voiceId?: number;
@@ -320,7 +320,7 @@ export interface GenerateOptions {
320
320
  export interface StreamConfig {
321
321
  /** Voice ID to use */
322
322
  voiceId?: number;
323
- /** Model ID ('kugel-1-turbo' or 'kugel-1'). Default: 'kugel-1-turbo' */
323
+ /** Model ID. Default: 'kugel-3'. Legacy ids still accepted; they alias to kugel-3 server-side. */
324
324
  modelId?: string;
325
325
  /** CFG scale for generation */
326
326
  cfgScale?: number;