kugelaudio 0.4.0 → 0.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/dist/index.js CHANGED
@@ -22,10 +22,13 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AuthenticationError: () => AuthenticationError,
24
24
  ConnectionError: () => ConnectionError,
25
+ DictionariesResource: () => DictionariesResource,
26
+ DictionaryEntriesResource: () => DictionaryEntriesResource,
25
27
  ErrorCodes: () => ErrorCodes,
26
28
  InsufficientCreditsError: () => InsufficientCreditsError,
27
29
  KugelAudio: () => KugelAudio,
28
30
  KugelAudioError: () => KugelAudioError,
31
+ NotFoundError: () => NotFoundError,
29
32
  RateLimitError: () => RateLimitError,
30
33
  ValidationError: () => ValidationError,
31
34
  WsCloseCodes: () => WsCloseCodes,
@@ -40,6 +43,187 @@ __export(index_exports, {
40
43
  });
41
44
  module.exports = __toCommonJS(index_exports);
42
45
 
46
+ // src/dictionaries.ts
47
+ function mapDictionary(raw) {
48
+ return {
49
+ id: raw.id,
50
+ projectId: raw.project_id,
51
+ name: raw.name,
52
+ description: raw.description ?? void 0,
53
+ language: raw.language ?? void 0,
54
+ isActive: raw.is_active ?? true,
55
+ createdAt: raw.created_at,
56
+ updatedAt: raw.updated_at
57
+ };
58
+ }
59
+ function mapEntry(raw) {
60
+ return {
61
+ id: raw.id,
62
+ dictionaryId: raw.dictionary_id,
63
+ word: raw.word,
64
+ replacement: raw.replacement,
65
+ ipa: raw.ipa ?? void 0,
66
+ caseSensitive: raw.case_sensitive ?? false,
67
+ createdAt: raw.created_at,
68
+ updatedAt: raw.updated_at
69
+ };
70
+ }
71
+ function entryPayload(input) {
72
+ const payload = {
73
+ word: input.word,
74
+ replacement: input.replacement
75
+ };
76
+ if (input.ipa !== void 0) payload.ipa = input.ipa;
77
+ if (input.caseSensitive !== void 0) {
78
+ payload.case_sensitive = input.caseSensitive;
79
+ }
80
+ return payload;
81
+ }
82
+ function buildPath(base, params) {
83
+ const search = new URLSearchParams();
84
+ for (const [key, val] of Object.entries(params)) {
85
+ if (val === void 0 || val === null) continue;
86
+ search.set(key, String(val));
87
+ }
88
+ const query = search.toString();
89
+ return query ? `${base}?${query}` : base;
90
+ }
91
+ var DictionaryEntriesResource = class {
92
+ constructor(client) {
93
+ this.client = client;
94
+ }
95
+ /** List entries with optional search + pagination. */
96
+ async list(dictionaryId, options) {
97
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}/entries`, {
98
+ search: options?.search,
99
+ limit: options?.limit,
100
+ offset: options?.offset,
101
+ project_id: options?.projectId
102
+ });
103
+ const raw = await this.client.request("GET", path);
104
+ return {
105
+ entries: raw.entries.map(mapEntry),
106
+ total: raw.total,
107
+ limit: raw.limit,
108
+ offset: raw.offset
109
+ };
110
+ }
111
+ /** Add a single entry to a dictionary. */
112
+ async add(dictionaryId, entry, options) {
113
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}/entries`, {
114
+ project_id: options?.projectId
115
+ });
116
+ const raw = await this.client.request(
117
+ "POST",
118
+ path,
119
+ entryPayload(entry)
120
+ );
121
+ return mapEntry(raw);
122
+ }
123
+ /** Update an existing entry. */
124
+ async update(dictionaryId, entryId, updates, options) {
125
+ const payload = {};
126
+ if (updates.word !== void 0) payload.word = updates.word;
127
+ if (updates.replacement !== void 0) payload.replacement = updates.replacement;
128
+ if (updates.ipa !== void 0) payload.ipa = updates.ipa;
129
+ if (updates.caseSensitive !== void 0) {
130
+ payload.case_sensitive = updates.caseSensitive;
131
+ }
132
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}/entries/${entryId}`, {
133
+ project_id: options?.projectId
134
+ });
135
+ const raw = await this.client.request(
136
+ "PATCH",
137
+ path,
138
+ payload
139
+ );
140
+ return mapEntry(raw);
141
+ }
142
+ /** Delete a single entry. */
143
+ async delete(dictionaryId, entryId, options) {
144
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}/entries/${entryId}`, {
145
+ project_id: options?.projectId
146
+ });
147
+ await this.client.request("DELETE", path);
148
+ }
149
+ /**
150
+ * Replace every entry in the dictionary atomically.
151
+ *
152
+ * Each item must have ``word`` and ``replacement``; existing entries
153
+ * whose ``word`` is not in the supplied list are deleted. Idempotent.
154
+ */
155
+ async replaceAll(dictionaryId, entries, options) {
156
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}/entries`, {
157
+ project_id: options?.projectId
158
+ });
159
+ const raw = await this.client.request("PUT", path, {
160
+ entries: entries.map(entryPayload)
161
+ });
162
+ return raw;
163
+ }
164
+ };
165
+ var DictionariesResource = class {
166
+ constructor(client) {
167
+ this.client = client;
168
+ this.entries = new DictionaryEntriesResource(client);
169
+ }
170
+ /** List every dictionary in the caller's project. */
171
+ async list(options) {
172
+ const path = buildPath("/v1/dictionaries", {
173
+ project_id: options?.projectId
174
+ });
175
+ const raw = await this.client.request("GET", path);
176
+ return raw.dictionaries.map(mapDictionary);
177
+ }
178
+ /** Create a new dictionary scoped to the caller's project. */
179
+ async create(body, options) {
180
+ const path = buildPath("/v1/dictionaries", {
181
+ project_id: options?.projectId
182
+ });
183
+ const payload = { name: body.name };
184
+ if (body.description !== void 0) payload.description = body.description;
185
+ if (body.language !== void 0) payload.language = body.language;
186
+ const raw = await this.client.request(
187
+ "POST",
188
+ path,
189
+ payload
190
+ );
191
+ return mapDictionary(raw);
192
+ }
193
+ /** Fetch a single dictionary. */
194
+ async get(dictionaryId, options) {
195
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}`, {
196
+ project_id: options?.projectId
197
+ });
198
+ const raw = await this.client.request("GET", path);
199
+ return mapDictionary(raw);
200
+ }
201
+ /** Update name / description / language / isActive. */
202
+ async update(dictionaryId, updates, options) {
203
+ const payload = {};
204
+ if (updates.name !== void 0) payload.name = updates.name;
205
+ if (updates.description !== void 0) payload.description = updates.description;
206
+ if (updates.language !== void 0) payload.language = updates.language;
207
+ if (updates.isActive !== void 0) payload.is_active = updates.isActive;
208
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}`, {
209
+ project_id: options?.projectId
210
+ });
211
+ const raw = await this.client.request(
212
+ "PATCH",
213
+ path,
214
+ payload
215
+ );
216
+ return mapDictionary(raw);
217
+ }
218
+ /** Delete a dictionary (cascades to its entries). */
219
+ async delete(dictionaryId, options) {
220
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}`, {
221
+ project_id: options?.projectId
222
+ });
223
+ await this.client.request("DELETE", path);
224
+ }
225
+ };
226
+
43
227
  // src/errors.ts
44
228
  var ErrorCodes = {
45
229
  UNAUTHORIZED: "UNAUTHORIZED",
@@ -112,6 +296,17 @@ var ConnectionError = class _ConnectionError extends KugelAudioError {
112
296
  Object.setPrototypeOf(this, _ConnectionError.prototype);
113
297
  }
114
298
  };
299
+ var NotFoundError = class _NotFoundError extends KugelAudioError {
300
+ constructor(message, options = {}) {
301
+ super(message ?? "Not found.", {
302
+ statusCode: 404,
303
+ errorCode: ErrorCodes.NOT_FOUND,
304
+ ...options
305
+ });
306
+ this.name = "NotFoundError";
307
+ Object.setPrototypeOf(this, _NotFoundError.prototype);
308
+ }
309
+ };
115
310
  function build(status, errorCode, message, opts = {}) {
116
311
  const common = { ...opts };
117
312
  if (status !== void 0) common.statusCode = status;
@@ -135,6 +330,9 @@ function build(status, errorCode, message, opts = {}) {
135
330
  common
136
331
  );
137
332
  }
333
+ if (errorCode === ErrorCodes.NOT_FOUND || status === 404) {
334
+ return new NotFoundError(message || void 0, common);
335
+ }
138
336
  return new KugelAudioError(message || `HTTP ${status}`, common);
139
337
  }
140
338
  function readHeader(headers, name) {
@@ -1240,13 +1438,21 @@ var MultiContextSession = class {
1240
1438
  }
1241
1439
  /**
1242
1440
  * Close a specific context.
1441
+ *
1442
+ * @param contextId - The context to close.
1443
+ * @param immediate - When `true`, **barge-in**: the server cancels the
1444
+ * context's in-flight generation immediately and discards any buffered or
1445
+ * queued text instead of draining it. Use this when the end user speaks
1446
+ * over the agent. When `false` (default), queued sentences finish first.
1243
1447
  */
1244
- closeContext(contextId) {
1448
+ closeContext(contextId, immediate = false) {
1245
1449
  if (!this.ws || this.ws.readyState !== WS_OPEN) return;
1246
- this.ws.send(JSON.stringify({
1450
+ const msg = {
1247
1451
  close_context: true,
1248
1452
  context_id: contextId
1249
- }));
1453
+ };
1454
+ if (immediate) msg.immediate = true;
1455
+ this.ws.send(JSON.stringify(msg));
1250
1456
  }
1251
1457
  /**
1252
1458
  * Send keep-alive to reset a context's inactivity timeout.
@@ -1351,6 +1557,9 @@ var StreamingSession = class {
1351
1557
  if (data.generation_started) {
1352
1558
  this.callbacks.onGenerationStarted?.(data.chunk_id ?? 0, data.text ?? "");
1353
1559
  }
1560
+ if (data.interrupted) {
1561
+ this.callbacks.onInterrupted?.();
1562
+ }
1354
1563
  if (data.session_closed) {
1355
1564
  this.callbacks.onSessionClosed?.(
1356
1565
  data.total_audio_seconds ?? 0,
@@ -1431,6 +1640,73 @@ var StreamingSession = class {
1431
1640
  }
1432
1641
  this.ws.send(JSON.stringify(msg));
1433
1642
  }
1643
+ /**
1644
+ * Interrupt (barge-in) the current generation without closing the socket.
1645
+ *
1646
+ * Use this when the end user starts speaking over the agent: it tells the
1647
+ * server to **stop generating audio for the current turn immediately** and
1648
+ * drop any text that was buffered or queued but not yet spoken. Unlike
1649
+ * {@link endSession}, no remaining text is flushed — the turn is abandoned.
1650
+ *
1651
+ * The WebSocket stays open and a fresh session is ready, so you can call
1652
+ * {@link send} for the next user turn right away (config is re-sent
1653
+ * automatically on that first `send`).
1654
+ *
1655
+ * The returned promise resolves once the server acknowledges with an
1656
+ * `interrupted` frame (which also fires
1657
+ * {@link StreamingSessionCallbacks.onInterrupted}), or after a 5 s **quiet**
1658
+ * timeout — i.e. 5 s elapse without any server message arriving. The timer
1659
+ * resets on every incoming frame, so a few in-flight audio chunks still
1660
+ * draining at the moment of cancellation do not trip it prematurely.
1661
+ *
1662
+ * @example
1663
+ * ```typescript
1664
+ * // VAD detected the user speaking over the agent:
1665
+ * await session.cancelCurrent();
1666
+ * // Socket is still open — start the next turn immediately:
1667
+ * session.send(nextLlmToken);
1668
+ * ```
1669
+ */
1670
+ cancelCurrent() {
1671
+ if (!this.ws || this.ws.readyState !== WS_OPEN) return Promise.resolve();
1672
+ const ws = this.ws;
1673
+ const QUIET_TIMEOUT_MS = 5e3;
1674
+ return new Promise((resolve) => {
1675
+ let settled = false;
1676
+ let timer;
1677
+ const prevMessage = ws.onmessage;
1678
+ const prevClose = ws.onclose;
1679
+ const done = () => {
1680
+ if (settled) return;
1681
+ settled = true;
1682
+ clearTimeout(timer);
1683
+ ws.onmessage = prevMessage;
1684
+ ws.onclose = prevClose;
1685
+ this.configSent = false;
1686
+ resolve();
1687
+ };
1688
+ const armQuietTimer = () => {
1689
+ clearTimeout(timer);
1690
+ timer = setTimeout(done, QUIET_TIMEOUT_MS);
1691
+ };
1692
+ armQuietTimer();
1693
+ ws.onmessage = (event) => {
1694
+ armQuietTimer();
1695
+ if (prevMessage) prevMessage.call(ws, event);
1696
+ try {
1697
+ const raw = typeof event.data === "string" ? event.data : event.data instanceof Buffer ? event.data.toString() : String(event.data);
1698
+ if (JSON.parse(raw).interrupted) done();
1699
+ } catch {
1700
+ }
1701
+ };
1702
+ ws.onclose = (event) => {
1703
+ this.ws = null;
1704
+ if (prevClose) prevClose.call(ws, event);
1705
+ done();
1706
+ };
1707
+ ws.send(JSON.stringify({ cancel: true }));
1708
+ });
1709
+ }
1434
1710
  /**
1435
1711
  * End the current session but keep the WebSocket connection open.
1436
1712
  *
@@ -1553,6 +1829,7 @@ var KugelAudio = class _KugelAudio {
1553
1829
  this._keepalivePingInterval = options.keepalivePingInterval !== void 0 ? options.keepalivePingInterval : 2e4;
1554
1830
  this.models = new ModelsResource(this);
1555
1831
  this.voices = new VoicesResource(this);
1832
+ this.dictionaries = new DictionariesResource(this);
1556
1833
  this.tts = new TTSResource(this);
1557
1834
  }
1558
1835
  /**
@@ -1719,10 +1996,13 @@ var KugelAudio = class _KugelAudio {
1719
1996
  0 && (module.exports = {
1720
1997
  AuthenticationError,
1721
1998
  ConnectionError,
1999
+ DictionariesResource,
2000
+ DictionaryEntriesResource,
1722
2001
  ErrorCodes,
1723
2002
  InsufficientCreditsError,
1724
2003
  KugelAudio,
1725
2004
  KugelAudioError,
2005
+ NotFoundError,
1726
2006
  RateLimitError,
1727
2007
  ValidationError,
1728
2008
  WsCloseCodes,
package/dist/index.mjs CHANGED
@@ -5,6 +5,187 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
5
5
  throw Error('Dynamic require of "' + x + '" is not supported');
6
6
  });
7
7
 
8
+ // src/dictionaries.ts
9
+ function mapDictionary(raw) {
10
+ return {
11
+ id: raw.id,
12
+ projectId: raw.project_id,
13
+ name: raw.name,
14
+ description: raw.description ?? void 0,
15
+ language: raw.language ?? void 0,
16
+ isActive: raw.is_active ?? true,
17
+ createdAt: raw.created_at,
18
+ updatedAt: raw.updated_at
19
+ };
20
+ }
21
+ function mapEntry(raw) {
22
+ return {
23
+ id: raw.id,
24
+ dictionaryId: raw.dictionary_id,
25
+ word: raw.word,
26
+ replacement: raw.replacement,
27
+ ipa: raw.ipa ?? void 0,
28
+ caseSensitive: raw.case_sensitive ?? false,
29
+ createdAt: raw.created_at,
30
+ updatedAt: raw.updated_at
31
+ };
32
+ }
33
+ function entryPayload(input) {
34
+ const payload = {
35
+ word: input.word,
36
+ replacement: input.replacement
37
+ };
38
+ if (input.ipa !== void 0) payload.ipa = input.ipa;
39
+ if (input.caseSensitive !== void 0) {
40
+ payload.case_sensitive = input.caseSensitive;
41
+ }
42
+ return payload;
43
+ }
44
+ function buildPath(base, params) {
45
+ const search = new URLSearchParams();
46
+ for (const [key, val] of Object.entries(params)) {
47
+ if (val === void 0 || val === null) continue;
48
+ search.set(key, String(val));
49
+ }
50
+ const query = search.toString();
51
+ return query ? `${base}?${query}` : base;
52
+ }
53
+ var DictionaryEntriesResource = class {
54
+ constructor(client) {
55
+ this.client = client;
56
+ }
57
+ /** List entries with optional search + pagination. */
58
+ async list(dictionaryId, options) {
59
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}/entries`, {
60
+ search: options?.search,
61
+ limit: options?.limit,
62
+ offset: options?.offset,
63
+ project_id: options?.projectId
64
+ });
65
+ const raw = await this.client.request("GET", path);
66
+ return {
67
+ entries: raw.entries.map(mapEntry),
68
+ total: raw.total,
69
+ limit: raw.limit,
70
+ offset: raw.offset
71
+ };
72
+ }
73
+ /** Add a single entry to a dictionary. */
74
+ async add(dictionaryId, entry, options) {
75
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}/entries`, {
76
+ project_id: options?.projectId
77
+ });
78
+ const raw = await this.client.request(
79
+ "POST",
80
+ path,
81
+ entryPayload(entry)
82
+ );
83
+ return mapEntry(raw);
84
+ }
85
+ /** Update an existing entry. */
86
+ async update(dictionaryId, entryId, updates, options) {
87
+ const payload = {};
88
+ if (updates.word !== void 0) payload.word = updates.word;
89
+ if (updates.replacement !== void 0) payload.replacement = updates.replacement;
90
+ if (updates.ipa !== void 0) payload.ipa = updates.ipa;
91
+ if (updates.caseSensitive !== void 0) {
92
+ payload.case_sensitive = updates.caseSensitive;
93
+ }
94
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}/entries/${entryId}`, {
95
+ project_id: options?.projectId
96
+ });
97
+ const raw = await this.client.request(
98
+ "PATCH",
99
+ path,
100
+ payload
101
+ );
102
+ return mapEntry(raw);
103
+ }
104
+ /** Delete a single entry. */
105
+ async delete(dictionaryId, entryId, options) {
106
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}/entries/${entryId}`, {
107
+ project_id: options?.projectId
108
+ });
109
+ await this.client.request("DELETE", path);
110
+ }
111
+ /**
112
+ * Replace every entry in the dictionary atomically.
113
+ *
114
+ * Each item must have ``word`` and ``replacement``; existing entries
115
+ * whose ``word`` is not in the supplied list are deleted. Idempotent.
116
+ */
117
+ async replaceAll(dictionaryId, entries, options) {
118
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}/entries`, {
119
+ project_id: options?.projectId
120
+ });
121
+ const raw = await this.client.request("PUT", path, {
122
+ entries: entries.map(entryPayload)
123
+ });
124
+ return raw;
125
+ }
126
+ };
127
+ var DictionariesResource = class {
128
+ constructor(client) {
129
+ this.client = client;
130
+ this.entries = new DictionaryEntriesResource(client);
131
+ }
132
+ /** List every dictionary in the caller's project. */
133
+ async list(options) {
134
+ const path = buildPath("/v1/dictionaries", {
135
+ project_id: options?.projectId
136
+ });
137
+ const raw = await this.client.request("GET", path);
138
+ return raw.dictionaries.map(mapDictionary);
139
+ }
140
+ /** Create a new dictionary scoped to the caller's project. */
141
+ async create(body, options) {
142
+ const path = buildPath("/v1/dictionaries", {
143
+ project_id: options?.projectId
144
+ });
145
+ const payload = { name: body.name };
146
+ if (body.description !== void 0) payload.description = body.description;
147
+ if (body.language !== void 0) payload.language = body.language;
148
+ const raw = await this.client.request(
149
+ "POST",
150
+ path,
151
+ payload
152
+ );
153
+ return mapDictionary(raw);
154
+ }
155
+ /** Fetch a single dictionary. */
156
+ async get(dictionaryId, options) {
157
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}`, {
158
+ project_id: options?.projectId
159
+ });
160
+ const raw = await this.client.request("GET", path);
161
+ return mapDictionary(raw);
162
+ }
163
+ /** Update name / description / language / isActive. */
164
+ async update(dictionaryId, updates, options) {
165
+ const payload = {};
166
+ if (updates.name !== void 0) payload.name = updates.name;
167
+ if (updates.description !== void 0) payload.description = updates.description;
168
+ if (updates.language !== void 0) payload.language = updates.language;
169
+ if (updates.isActive !== void 0) payload.is_active = updates.isActive;
170
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}`, {
171
+ project_id: options?.projectId
172
+ });
173
+ const raw = await this.client.request(
174
+ "PATCH",
175
+ path,
176
+ payload
177
+ );
178
+ return mapDictionary(raw);
179
+ }
180
+ /** Delete a dictionary (cascades to its entries). */
181
+ async delete(dictionaryId, options) {
182
+ const path = buildPath(`/v1/dictionaries/${dictionaryId}`, {
183
+ project_id: options?.projectId
184
+ });
185
+ await this.client.request("DELETE", path);
186
+ }
187
+ };
188
+
8
189
  // src/errors.ts
9
190
  var ErrorCodes = {
10
191
  UNAUTHORIZED: "UNAUTHORIZED",
@@ -77,6 +258,17 @@ var ConnectionError = class _ConnectionError extends KugelAudioError {
77
258
  Object.setPrototypeOf(this, _ConnectionError.prototype);
78
259
  }
79
260
  };
261
+ var NotFoundError = class _NotFoundError extends KugelAudioError {
262
+ constructor(message, options = {}) {
263
+ super(message ?? "Not found.", {
264
+ statusCode: 404,
265
+ errorCode: ErrorCodes.NOT_FOUND,
266
+ ...options
267
+ });
268
+ this.name = "NotFoundError";
269
+ Object.setPrototypeOf(this, _NotFoundError.prototype);
270
+ }
271
+ };
80
272
  function build(status, errorCode, message, opts = {}) {
81
273
  const common = { ...opts };
82
274
  if (status !== void 0) common.statusCode = status;
@@ -100,6 +292,9 @@ function build(status, errorCode, message, opts = {}) {
100
292
  common
101
293
  );
102
294
  }
295
+ if (errorCode === ErrorCodes.NOT_FOUND || status === 404) {
296
+ return new NotFoundError(message || void 0, common);
297
+ }
103
298
  return new KugelAudioError(message || `HTTP ${status}`, common);
104
299
  }
105
300
  function readHeader(headers, name) {
@@ -1205,13 +1400,21 @@ var MultiContextSession = class {
1205
1400
  }
1206
1401
  /**
1207
1402
  * Close a specific context.
1403
+ *
1404
+ * @param contextId - The context to close.
1405
+ * @param immediate - When `true`, **barge-in**: the server cancels the
1406
+ * context's in-flight generation immediately and discards any buffered or
1407
+ * queued text instead of draining it. Use this when the end user speaks
1408
+ * over the agent. When `false` (default), queued sentences finish first.
1208
1409
  */
1209
- closeContext(contextId) {
1410
+ closeContext(contextId, immediate = false) {
1210
1411
  if (!this.ws || this.ws.readyState !== WS_OPEN) return;
1211
- this.ws.send(JSON.stringify({
1412
+ const msg = {
1212
1413
  close_context: true,
1213
1414
  context_id: contextId
1214
- }));
1415
+ };
1416
+ if (immediate) msg.immediate = true;
1417
+ this.ws.send(JSON.stringify(msg));
1215
1418
  }
1216
1419
  /**
1217
1420
  * Send keep-alive to reset a context's inactivity timeout.
@@ -1316,6 +1519,9 @@ var StreamingSession = class {
1316
1519
  if (data.generation_started) {
1317
1520
  this.callbacks.onGenerationStarted?.(data.chunk_id ?? 0, data.text ?? "");
1318
1521
  }
1522
+ if (data.interrupted) {
1523
+ this.callbacks.onInterrupted?.();
1524
+ }
1319
1525
  if (data.session_closed) {
1320
1526
  this.callbacks.onSessionClosed?.(
1321
1527
  data.total_audio_seconds ?? 0,
@@ -1396,6 +1602,73 @@ var StreamingSession = class {
1396
1602
  }
1397
1603
  this.ws.send(JSON.stringify(msg));
1398
1604
  }
1605
+ /**
1606
+ * Interrupt (barge-in) the current generation without closing the socket.
1607
+ *
1608
+ * Use this when the end user starts speaking over the agent: it tells the
1609
+ * server to **stop generating audio for the current turn immediately** and
1610
+ * drop any text that was buffered or queued but not yet spoken. Unlike
1611
+ * {@link endSession}, no remaining text is flushed — the turn is abandoned.
1612
+ *
1613
+ * The WebSocket stays open and a fresh session is ready, so you can call
1614
+ * {@link send} for the next user turn right away (config is re-sent
1615
+ * automatically on that first `send`).
1616
+ *
1617
+ * The returned promise resolves once the server acknowledges with an
1618
+ * `interrupted` frame (which also fires
1619
+ * {@link StreamingSessionCallbacks.onInterrupted}), or after a 5 s **quiet**
1620
+ * timeout — i.e. 5 s elapse without any server message arriving. The timer
1621
+ * resets on every incoming frame, so a few in-flight audio chunks still
1622
+ * draining at the moment of cancellation do not trip it prematurely.
1623
+ *
1624
+ * @example
1625
+ * ```typescript
1626
+ * // VAD detected the user speaking over the agent:
1627
+ * await session.cancelCurrent();
1628
+ * // Socket is still open — start the next turn immediately:
1629
+ * session.send(nextLlmToken);
1630
+ * ```
1631
+ */
1632
+ cancelCurrent() {
1633
+ if (!this.ws || this.ws.readyState !== WS_OPEN) return Promise.resolve();
1634
+ const ws = this.ws;
1635
+ const QUIET_TIMEOUT_MS = 5e3;
1636
+ return new Promise((resolve) => {
1637
+ let settled = false;
1638
+ let timer;
1639
+ const prevMessage = ws.onmessage;
1640
+ const prevClose = ws.onclose;
1641
+ const done = () => {
1642
+ if (settled) return;
1643
+ settled = true;
1644
+ clearTimeout(timer);
1645
+ ws.onmessage = prevMessage;
1646
+ ws.onclose = prevClose;
1647
+ this.configSent = false;
1648
+ resolve();
1649
+ };
1650
+ const armQuietTimer = () => {
1651
+ clearTimeout(timer);
1652
+ timer = setTimeout(done, QUIET_TIMEOUT_MS);
1653
+ };
1654
+ armQuietTimer();
1655
+ ws.onmessage = (event) => {
1656
+ armQuietTimer();
1657
+ if (prevMessage) prevMessage.call(ws, event);
1658
+ try {
1659
+ const raw = typeof event.data === "string" ? event.data : event.data instanceof Buffer ? event.data.toString() : String(event.data);
1660
+ if (JSON.parse(raw).interrupted) done();
1661
+ } catch {
1662
+ }
1663
+ };
1664
+ ws.onclose = (event) => {
1665
+ this.ws = null;
1666
+ if (prevClose) prevClose.call(ws, event);
1667
+ done();
1668
+ };
1669
+ ws.send(JSON.stringify({ cancel: true }));
1670
+ });
1671
+ }
1399
1672
  /**
1400
1673
  * End the current session but keep the WebSocket connection open.
1401
1674
  *
@@ -1518,6 +1791,7 @@ var KugelAudio = class _KugelAudio {
1518
1791
  this._keepalivePingInterval = options.keepalivePingInterval !== void 0 ? options.keepalivePingInterval : 2e4;
1519
1792
  this.models = new ModelsResource(this);
1520
1793
  this.voices = new VoicesResource(this);
1794
+ this.dictionaries = new DictionariesResource(this);
1521
1795
  this.tts = new TTSResource(this);
1522
1796
  }
1523
1797
  /**
@@ -1683,10 +1957,13 @@ var KugelAudio = class _KugelAudio {
1683
1957
  export {
1684
1958
  AuthenticationError,
1685
1959
  ConnectionError,
1960
+ DictionariesResource,
1961
+ DictionaryEntriesResource,
1686
1962
  ErrorCodes,
1687
1963
  InsufficientCreditsError,
1688
1964
  KugelAudio,
1689
1965
  KugelAudioError,
1966
+ NotFoundError,
1690
1967
  RateLimitError,
1691
1968
  ValidationError,
1692
1969
  WsCloseCodes,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kugelaudio",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Official JavaScript/TypeScript SDK for KugelAudio TTS API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",