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/CHANGELOG.md +17 -0
- package/README.md +36 -0
- package/dist/index.d.mts +221 -3
- package/dist/index.d.ts +221 -3
- package/dist/index.js +283 -3
- package/dist/index.mjs +280 -3
- package/package.json +1 -1
- package/src/client.test.ts +115 -0
- package/src/client.ts +110 -3
- package/src/dictionaries.test.ts +212 -0
- package/src/dictionaries.ts +278 -0
- package/src/errors.ts +24 -1
- package/src/index.ts +11 -0
- package/src/types.ts +95 -0
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
|
-
|
|
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
|
-
|
|
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,
|