assemblyai 4.35.3 → 4.36.3

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +98 -0
  3. package/dist/assemblyai.streaming.umd.js +26 -11
  4. package/dist/assemblyai.streaming.umd.min.js +1 -1
  5. package/dist/assemblyai.umd.js +486 -83
  6. package/dist/assemblyai.umd.min.js +1 -1
  7. package/dist/browser.mjs +430 -80
  8. package/dist/bun.mjs +420 -69
  9. package/dist/deno.mjs +420 -69
  10. package/dist/index.cjs +479 -76
  11. package/dist/index.mjs +477 -77
  12. package/dist/node.cjs +418 -64
  13. package/dist/node.mjs +416 -65
  14. package/dist/services/base.d.ts +1 -0
  15. package/dist/services/index.d.ts +7 -1
  16. package/dist/services/sync/index.d.ts +1 -0
  17. package/dist/services/sync/service.d.ts +48 -0
  18. package/dist/streaming.browser.mjs +24 -11
  19. package/dist/streaming.cjs +25 -10
  20. package/dist/streaming.mjs +25 -10
  21. package/dist/types/index.d.ts +1 -0
  22. package/dist/types/services/index.d.ts +1 -0
  23. package/dist/types/streaming/index.d.ts +6 -1
  24. package/dist/types/sync/index.d.ts +133 -0
  25. package/dist/utils/errors/index.d.ts +1 -0
  26. package/dist/utils/errors/sync.d.ts +20 -0
  27. package/dist/workerd.mjs +424 -73
  28. package/package.json +1 -1
  29. package/src/services/base.ts +18 -8
  30. package/src/services/index.ts +19 -0
  31. package/src/services/streaming/service.ts +17 -6
  32. package/src/services/sync/index.ts +1 -0
  33. package/src/services/sync/service.ts +369 -0
  34. package/src/types/index.ts +1 -0
  35. package/src/types/services/index.ts +1 -0
  36. package/src/types/streaming/index.ts +6 -1
  37. package/src/types/sync/index.ts +145 -0
  38. package/src/utils/errors/index.ts +2 -0
  39. package/src/utils/errors/sync.ts +25 -0
@@ -4,6 +4,11 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.assemblyai = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
+ /**
8
+ * The default speech model for synchronous transcription.
9
+ */
10
+ const defaultSyncSpeechModel = "universal-3-5-pro";
11
+
7
12
  /**
8
13
  * Thrown when `DualChannelCapture` is constructed in a non-browser environment
9
14
  * (no `globalThis.AudioContext`). The helper is intentionally surfaced from the
@@ -56,11 +61,39 @@
56
61
  });
57
62
  }
58
63
 
64
+ function __values(o) {
65
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
66
+ if (m) return m.call(o);
67
+ if (o && typeof o.length === "number") return {
68
+ next: function () {
69
+ if (o && i >= o.length) o = void 0;
70
+ return { value: o && o[i++], done: !o };
71
+ }
72
+ };
73
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
74
+ }
75
+
76
+ function __asyncValues(o) {
77
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
78
+ var m = o[Symbol.asyncIterator], i;
79
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
80
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
81
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
82
+ }
83
+
59
84
  typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
60
85
  var e = new Error(message);
61
86
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
62
87
  };
63
88
 
89
+ const readFile = function (
90
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
91
+ path) {
92
+ return __awaiter(this, void 0, void 0, function* () {
93
+ throw new Error("Interacting with the file system is not supported in this environment.");
94
+ });
95
+ };
96
+
64
97
  const DEFAULT_FETCH_INIT = {
65
98
  cache: "no-store",
66
99
  };
@@ -78,7 +111,7 @@
78
111
  defaultUserAgentString += navigator.userAgent;
79
112
  }
80
113
  const defaultUserAgent = {
81
- sdk: { name: "JavaScript", version: "4.35.3" },
114
+ sdk: { name: "JavaScript", version: "4.36.3" },
82
115
  };
83
116
  if (typeof process !== "undefined") {
84
117
  if (process.versions.node && defaultUserAgentString.indexOf("Node") === -1) {
@@ -117,13 +150,16 @@
117
150
  this.userAgent = buildUserAgent(params.userAgent || {});
118
151
  }
119
152
  }
120
- fetch(input, init) {
153
+ fetchResponse(input, init) {
121
154
  return __awaiter(this, void 0, void 0, function* () {
122
155
  init = Object.assign(Object.assign({}, DEFAULT_FETCH_INIT), init);
123
156
  let headers = {
124
157
  Authorization: this.params.apiKey,
125
- "Content-Type": "application/json",
126
158
  };
159
+ // FormData bodies must let fetch set the multipart boundary itself.
160
+ if (!(init.body instanceof FormData)) {
161
+ headers["Content-Type"] = "application/json";
162
+ }
127
163
  if (DEFAULT_FETCH_INIT === null || DEFAULT_FETCH_INIT === void 0 ? void 0 : DEFAULT_FETCH_INIT.headers)
128
164
  headers = Object.assign(Object.assign({}, headers), DEFAULT_FETCH_INIT.headers);
129
165
  if (init === null || init === void 0 ? void 0 : init.headers)
@@ -133,15 +169,19 @@
133
169
  {
134
170
  // chromium browsers have a bug where the user agent can't be modified
135
171
  if (typeof window !== "undefined" && "chrome" in window) {
136
- headers["AssemblyAI-Agent"] =
137
- this.userAgent;
172
+ headers["AssemblyAI-Agent"] = this.userAgent;
138
173
  }
139
174
  }
140
175
  }
141
176
  init.headers = headers;
142
177
  if (!input.startsWith("http"))
143
178
  input = this.params.baseUrl + input;
144
- const response = yield fetch(input, init);
179
+ return yield fetch(input, init);
180
+ });
181
+ }
182
+ fetch(input, init) {
183
+ return __awaiter(this, void 0, void 0, function* () {
184
+ const response = yield this.fetchResponse(input, init);
145
185
  if (response.status >= 400) {
146
186
  let json;
147
187
  const text = yield response.text();
@@ -169,65 +209,373 @@
169
209
  }
170
210
  }
171
211
 
172
- class LemurService extends BaseService {
173
- summary(params, signal) {
174
- return this.fetchJson("/lemur/v3/generate/summary", {
175
- method: "POST",
176
- body: JSON.stringify(params),
177
- signal,
178
- });
179
- }
180
- questionAnswer(params, signal) {
181
- return this.fetchJson("/lemur/v3/generate/question-answer", {
182
- method: "POST",
183
- body: JSON.stringify(params),
184
- signal,
185
- });
212
+ /**
213
+ * Error thrown when a synchronous transcription request fails.
214
+ */
215
+ class SyncTranscriptError extends Error {
216
+ /**
217
+ * Create a new SyncTranscriptError.
218
+ * @param message - The human-readable error message.
219
+ * @param status - The HTTP status code of the failed request.
220
+ * @param errorCode - Machine-readable code — the snake_cased
221
+ * problem-details `title` from the server (e.g. `bad_audio`,
222
+ * `audio_too_large`, `capacity_exceeded`, `inference_timeout`).
223
+ * @param retryAfter - Seconds to wait before retrying, from the
224
+ * `Retry-After` header on 429/503 responses.
225
+ */
226
+ constructor(message, status, errorCode, retryAfter) {
227
+ super(message);
228
+ this.status = status;
229
+ this.errorCode = errorCode;
230
+ this.retryAfter = retryAfter;
231
+ this.name = "SyncTranscriptError";
186
232
  }
187
- actionItems(params, signal) {
188
- return this.fetchJson("/lemur/v3/generate/action-items", {
189
- method: "POST",
190
- body: JSON.stringify(params),
191
- signal,
192
- });
233
+ }
234
+
235
+ function getPath(path) {
236
+ if (path.startsWith("http"))
237
+ return null;
238
+ if (path.startsWith("https"))
239
+ return null;
240
+ if (path.startsWith("data:"))
241
+ return null;
242
+ if (path.startsWith("file://"))
243
+ return path.substring(7);
244
+ if (path.startsWith("file:"))
245
+ return path.substring(5);
246
+ return path;
247
+ }
248
+
249
+ // Canonical paths since the sync API gained a /v1 prefix (#18103); the
250
+ // unprefixed routes remain served for SDK versions that predate it.
251
+ const transcribeEndpoint = "/v1/transcribe";
252
+ const warmEndpoint = "/v1/warm";
253
+ const modelHeader = "X-AAI-Model";
254
+ // Kept above the server's 30 s deadline so the client doesn't race it.
255
+ const defaultTimeoutMs = 60000;
256
+ const warmTimeoutMs = 10000;
257
+ const maxPromptLength = 4096;
258
+ const maxKeytermsPromptLength = 2048;
259
+ const maxContextTurns = 100;
260
+ const maxContextLength = 4096;
261
+ // Extensions that signal raw S16LE PCM rather than a WAV container.
262
+ const pcmSuffixes = [".pcm", ".raw"];
263
+ /**
264
+ * The synchronous transcription service: audio in, transcript out,
265
+ * one request.
266
+ *
267
+ * Unlike `client.transcripts` (which submits a job to the async API and
268
+ * polls for completion), `SyncTranscriber` posts the audio to the sync
269
+ * API and returns the finished transcript in the HTTP response. There is no
270
+ * job id or status to poll. Accepts a local file path, raw audio bytes, a
271
+ * Blob, or a readable stream — but not a URL.
272
+ */
273
+ class SyncTranscriber extends BaseService {
274
+ /**
275
+ * Create a new synchronous transcription service.
276
+ * @param params - The parameters to use for the service.
277
+ */
278
+ constructor(params) {
279
+ super(params);
193
280
  }
194
- task(params, signal) {
195
- return this.fetchJson("/lemur/v3/generate/task", {
196
- method: "POST",
197
- body: JSON.stringify(params),
198
- signal,
281
+ /**
282
+ * Transcribe audio and return the finished transcript in one request.
283
+ * @param audio - A local file path, raw audio bytes, a Blob, or a readable
284
+ * stream. Raw PCM also requires `sample_rate` and `channels` on the config.
285
+ * @param config - Options for this transcription request.
286
+ * @param options - Client-side options, such as the request timeout.
287
+ * @returns A promise that resolves to the finished transcript.
288
+ * @throws SyncTranscriptError when the request fails.
289
+ */
290
+ transcribe(audio_1) {
291
+ return __awaiter(this, arguments, void 0, function* (audio, config = {}, options = {}) {
292
+ var _a, _b;
293
+ const { bytes, filename, contentType } = yield resolveAudio(audio, config);
294
+ const body = new FormData();
295
+ body.append("audio", new Blob([bytes], { type: contentType }), filename);
296
+ const configJson = buildConfigJson(config);
297
+ if (configJson) {
298
+ body.append("config", new Blob([JSON.stringify(configJson)], { type: "application/json" }));
299
+ }
300
+ const response = yield this.fetchResponse(transcribeEndpoint, {
301
+ method: "POST",
302
+ body,
303
+ headers: { [modelHeader]: (_a = config.model) !== null && _a !== void 0 ? _a : defaultSyncSpeechModel },
304
+ signal: AbortSignal.timeout((_b = options.timeout) !== null && _b !== void 0 ? _b : defaultTimeoutMs),
305
+ });
306
+ if (response.status !== 200)
307
+ throw yield errorFromResponse(response);
308
+ return (yield response.json());
199
309
  });
200
310
  }
201
- getResponse(id, signal) {
202
- return this.fetchJson(`/lemur/v3/${id}`, { signal });
203
- }
204
311
  /**
205
- * Delete the data for a previously submitted LeMUR request.
206
- * @param id - ID of the LeMUR request
207
- * @param signal - Optional AbortSignal to cancel the request
312
+ * Open the connection to the sync API ahead of time.
313
+ *
314
+ * The sync API is a single request/response, so a `transcribe()` that
315
+ * opens its connection on demand pays the full DNS + TCP + TLS handshake
316
+ * on the critical path. Call `warm()` as soon as you know audio is coming —
317
+ * typically while the clip is still being recorded — so the next
318
+ * `transcribe()` reuses the already-open connection. `warm()` is idempotent
319
+ * and cheap; call it shortly before `transcribe()` so the pooled connection
320
+ * hasn't idled out.
321
+ * @param params - Optionally the model to route the probe to, so the warmed
322
+ * connection lands on the same backend as the eventual transcription.
323
+ * @returns A promise that resolves to `true` once the connection is open
324
+ * (any HTTP response — even a non-200 — means the socket is
325
+ * established), or `false` if the connection could not be opened.
208
326
  */
209
- purgeRequestData(id, signal) {
210
- return this.fetchJson(`/lemur/v3/${id}`, {
211
- method: "DELETE",
212
- signal,
327
+ warm(params) {
328
+ return __awaiter(this, void 0, void 0, function* () {
329
+ var _a;
330
+ try {
331
+ yield this.fetchResponse(warmEndpoint, {
332
+ method: "GET",
333
+ headers: { [modelHeader]: (_a = params === null || params === void 0 ? void 0 : params.model) !== null && _a !== void 0 ? _a : defaultSyncSpeechModel },
334
+ signal: AbortSignal.timeout(warmTimeoutMs),
335
+ });
336
+ return true;
337
+ }
338
+ catch (_b) {
339
+ return false;
340
+ }
213
341
  });
214
342
  }
215
343
  }
216
-
217
- const { WritableStream } = typeof window !== "undefined"
218
- ? window
219
- : typeof global !== "undefined"
220
- ? global
221
- : globalThis;
222
-
223
- var _a, _b;
224
- const PolyfillWebSocket = (_b = (_a = WebSocket !== null && WebSocket !== void 0 ? WebSocket : global === null || global === void 0 ? void 0 : global.WebSocket) !== null && _a !== void 0 ? _a : window === null || window === void 0 ? void 0 : window.WebSocket) !== null && _b !== void 0 ? _b : self === null || self === void 0 ? void 0 : self.WebSocket;
225
- const factory = (url, params) => {
226
- if (params) {
227
- return new PolyfillWebSocket(url, params);
228
- }
229
- return new PolyfillWebSocket(url);
230
- };
344
+ /**
345
+ * Read the audio input into bytes and decide its multipart content type.
346
+ *
347
+ * PCM is selected when the source has a `.pcm`/`.raw` extension or when
348
+ * `sample_rate`/`channels` are set on the config (the fields the sync API
349
+ * requires only for raw PCM) — and both must then be present. Everything
350
+ * else is treated as a WAV container. URLs are rejected — the sync API has
351
+ * no URL ingestion.
352
+ */
353
+ function resolveAudio(input, config) {
354
+ return __awaiter(this, void 0, void 0, function* () {
355
+ var _a;
356
+ let bytes;
357
+ let filename;
358
+ let suffix = "";
359
+ if (typeof input === "string") {
360
+ if (/^https?:\/\//i.test(input)) {
361
+ throw new Error("SyncTranscriber does not accept URLs. Pass a local file path or " +
362
+ "audio bytes, or use client.transcripts for URL/async transcription.");
363
+ }
364
+ if (input.startsWith("data:")) {
365
+ bytes = dataUrlToBytes(input);
366
+ }
367
+ else {
368
+ const path = (_a = getPath(input)) !== null && _a !== void 0 ? _a : input;
369
+ bytes = yield readStream(yield readFile());
370
+ filename = basename(path);
371
+ suffix = extname(filename);
372
+ }
373
+ }
374
+ else if (input instanceof Uint8Array) {
375
+ bytes = input;
376
+ }
377
+ else if (input instanceof ArrayBuffer) {
378
+ bytes = new Uint8Array(input);
379
+ }
380
+ else if (input instanceof Blob) {
381
+ bytes = new Uint8Array(yield input.arrayBuffer());
382
+ // File instances carry a name; the File global itself needs Node >= 20.
383
+ const name = input.name;
384
+ if (name) {
385
+ filename = basename(name);
386
+ suffix = extname(filename);
387
+ }
388
+ }
389
+ else if (isWebReadableStream(input)) {
390
+ bytes = yield readStream(input);
391
+ }
392
+ else if (isAsyncIterable(input)) {
393
+ bytes = yield readAsyncIterable(input);
394
+ // fs.ReadStream carries the path it was opened from.
395
+ const path = input.path;
396
+ if (typeof path === "string") {
397
+ filename = basename(path);
398
+ suffix = extname(filename);
399
+ }
400
+ }
401
+ else {
402
+ throw new TypeError("unsupported audio input type");
403
+ }
404
+ const wantsPcm = config.sample_rate !== undefined || config.channels !== undefined;
405
+ const isPcm = pcmSuffixes.includes(suffix) || wantsPcm;
406
+ if (isPcm &&
407
+ (config.sample_rate === undefined || config.channels === undefined)) {
408
+ throw new Error("raw PCM audio requires both sample_rate and channels in the config");
409
+ }
410
+ const contentType = isPcm ? "audio/pcm" : "audio/wav";
411
+ if (!filename)
412
+ filename = isPcm ? "audio.pcm" : "audio.wav";
413
+ return { bytes, filename, contentType };
414
+ });
415
+ }
416
+ /**
417
+ * Serialize the config to the JSON `config` part, validating and normalizing
418
+ * field values to match the server's caps. The routing `model` is never
419
+ * included — it travels in the `X-AAI-Model` header. Returns `undefined`
420
+ * when there is nothing to send, so the part can be omitted entirely.
421
+ */
422
+ function buildConfigJson(config) {
423
+ if (config.prompt !== undefined && config.prompt.length > maxPromptLength) {
424
+ throw new Error(`prompt exceeds ${maxPromptLength} characters (got ${config.prompt.length})`);
425
+ }
426
+ const json = {};
427
+ if (config.prompt !== undefined)
428
+ json["prompt"] = config.prompt;
429
+ const keytermsPrompt = normalizeKeytermsPrompt(config.keyterms_prompt);
430
+ if (keytermsPrompt)
431
+ json["keyterms_prompt"] = keytermsPrompt;
432
+ const context = normalizeConversationContext(config.conversation_context);
433
+ if (context)
434
+ json["conversation_context"] = context;
435
+ if (config.language_codes !== undefined)
436
+ json["language_codes"] = config.language_codes;
437
+ if (config.sample_rate !== undefined)
438
+ json["sample_rate"] = config.sample_rate;
439
+ if (config.channels !== undefined)
440
+ json["channels"] = config.channels;
441
+ if (config.timestamps !== undefined)
442
+ json["timestamps"] = config.timestamps;
443
+ return Object.keys(json).length > 0 ? json : undefined;
444
+ }
445
+ function normalizeKeytermsPrompt(keytermsPrompt) {
446
+ if (!keytermsPrompt)
447
+ return undefined;
448
+ const terms = keytermsPrompt
449
+ .map((term) => term.trim())
450
+ .filter((term) => term.length > 0);
451
+ const total = terms.reduce((sum, term) => sum + term.length, 0);
452
+ if (total > maxKeytermsPromptLength) {
453
+ throw new Error(`keyterms_prompt exceeds ${maxKeytermsPromptLength} characters (got ${total})`);
454
+ }
455
+ return terms.length > 0 ? terms : undefined;
456
+ }
457
+ function normalizeConversationContext(context) {
458
+ if (context === undefined)
459
+ return undefined;
460
+ let turns = (typeof context === "string" ? [context] : context)
461
+ .map((turn) => turn.trim())
462
+ .filter((turn) => turn.length > 0);
463
+ let total = turns.reduce((sum, turn) => sum + turn.length, 0);
464
+ // Over-cap context is trimmed oldest-first, never rejected.
465
+ while (turns.length > 0 &&
466
+ (turns.length > maxContextTurns || total > maxContextLength)) {
467
+ total -= turns[0].length;
468
+ turns = turns.slice(1);
469
+ }
470
+ return turns.length > 0 ? turns : undefined;
471
+ }
472
+ /**
473
+ * Build a SyncTranscriptError from a non-200 response. The primary format
474
+ * is an RFC 9457 problem-details body (`status`/`title`/`detail`); legacy
475
+ * `{error_code, message}` and `{detail}`-only bodies are also accepted.
476
+ */
477
+ function errorFromResponse(response) {
478
+ return __awaiter(this, void 0, void 0, function* () {
479
+ let errorCode;
480
+ let message;
481
+ const text = yield response.text();
482
+ try {
483
+ const body = JSON.parse(text);
484
+ if (body && typeof body === "object" && !Array.isArray(body)) {
485
+ if (typeof body.error_code === "string")
486
+ errorCode = body.error_code;
487
+ if (errorCode === undefined && typeof body.title === "string") {
488
+ errorCode = body.title.toLowerCase().replace(/ /g, "_");
489
+ }
490
+ if (typeof body.detail === "string")
491
+ message = body.detail;
492
+ else if (typeof body.message === "string")
493
+ message = body.message;
494
+ }
495
+ }
496
+ catch (_a) {
497
+ if (text)
498
+ message = text;
499
+ }
500
+ if (!message) {
501
+ message = `sync transcription failed with status ${response.status}`;
502
+ }
503
+ const retryHeader = response.headers.get("retry-after");
504
+ const retryAfter = retryHeader && /^\d+$/.test(retryHeader)
505
+ ? parseInt(retryHeader, 10)
506
+ : undefined;
507
+ return new SyncTranscriptError(message, response.status, errorCode, retryAfter);
508
+ });
509
+ }
510
+ function basename(path) {
511
+ var _a;
512
+ return (_a = path.split(/[\\/]/).pop()) !== null && _a !== void 0 ? _a : path;
513
+ }
514
+ function extname(filename) {
515
+ const dotIndex = filename.lastIndexOf(".");
516
+ return dotIndex > 0 ? filename.slice(dotIndex).toLowerCase() : "";
517
+ }
518
+ function dataUrlToBytes(dataUrl) {
519
+ const base64 = dataUrl.split(",")[1];
520
+ const binary = atob(base64);
521
+ const bytes = new Uint8Array(binary.length);
522
+ for (let i = 0; i < binary.length; i++)
523
+ bytes[i] = binary.charCodeAt(i);
524
+ return bytes;
525
+ }
526
+ function isWebReadableStream(input) {
527
+ return typeof (input === null || input === void 0 ? void 0 : input.getReader) === "function";
528
+ }
529
+ function isAsyncIterable(input) {
530
+ return (typeof (input === null || input === void 0 ? void 0 : input[Symbol.asyncIterator]) ===
531
+ "function");
532
+ }
533
+ function readStream(stream) {
534
+ return __awaiter(this, void 0, void 0, function* () {
535
+ const chunks = [];
536
+ const reader = stream.getReader();
537
+ for (;;) {
538
+ const { done, value } = yield reader.read();
539
+ if (done)
540
+ break;
541
+ chunks.push(value);
542
+ }
543
+ return concatChunks(chunks);
544
+ });
545
+ }
546
+ function readAsyncIterable(iterable) {
547
+ return __awaiter(this, void 0, void 0, function* () {
548
+ var _a, iterable_1, iterable_1_1;
549
+ var _b, e_1, _c, _d;
550
+ const chunks = [];
551
+ try {
552
+ for (_a = true, iterable_1 = __asyncValues(iterable); iterable_1_1 = yield iterable_1.next(), _b = iterable_1_1.done, !_b; _a = true) {
553
+ _d = iterable_1_1.value;
554
+ _a = false;
555
+ const chunk = _d;
556
+ chunks.push(chunk);
557
+ }
558
+ }
559
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
560
+ finally {
561
+ try {
562
+ if (!_a && !_b && (_c = iterable_1.return)) yield _c.call(iterable_1);
563
+ }
564
+ finally { if (e_1) throw e_1.error; }
565
+ }
566
+ return concatChunks(chunks);
567
+ });
568
+ }
569
+ function concatChunks(chunks) {
570
+ const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
571
+ const bytes = new Uint8Array(total);
572
+ let offset = 0;
573
+ for (const chunk of chunks) {
574
+ bytes.set(chunk, offset);
575
+ offset += chunk.length;
576
+ }
577
+ return bytes;
578
+ }
231
579
 
232
580
  const RealtimeErrorType = {
233
581
  BadSampleRate: 4000,
@@ -333,6 +681,66 @@
333
681
  class StreamingError extends Error {
334
682
  }
335
683
 
684
+ class LemurService extends BaseService {
685
+ summary(params, signal) {
686
+ return this.fetchJson("/lemur/v3/generate/summary", {
687
+ method: "POST",
688
+ body: JSON.stringify(params),
689
+ signal,
690
+ });
691
+ }
692
+ questionAnswer(params, signal) {
693
+ return this.fetchJson("/lemur/v3/generate/question-answer", {
694
+ method: "POST",
695
+ body: JSON.stringify(params),
696
+ signal,
697
+ });
698
+ }
699
+ actionItems(params, signal) {
700
+ return this.fetchJson("/lemur/v3/generate/action-items", {
701
+ method: "POST",
702
+ body: JSON.stringify(params),
703
+ signal,
704
+ });
705
+ }
706
+ task(params, signal) {
707
+ return this.fetchJson("/lemur/v3/generate/task", {
708
+ method: "POST",
709
+ body: JSON.stringify(params),
710
+ signal,
711
+ });
712
+ }
713
+ getResponse(id, signal) {
714
+ return this.fetchJson(`/lemur/v3/${id}`, { signal });
715
+ }
716
+ /**
717
+ * Delete the data for a previously submitted LeMUR request.
718
+ * @param id - ID of the LeMUR request
719
+ * @param signal - Optional AbortSignal to cancel the request
720
+ */
721
+ purgeRequestData(id, signal) {
722
+ return this.fetchJson(`/lemur/v3/${id}`, {
723
+ method: "DELETE",
724
+ signal,
725
+ });
726
+ }
727
+ }
728
+
729
+ const { WritableStream } = typeof window !== "undefined"
730
+ ? window
731
+ : typeof global !== "undefined"
732
+ ? global
733
+ : globalThis;
734
+
735
+ var _a, _b;
736
+ const PolyfillWebSocket = (_b = (_a = WebSocket !== null && WebSocket !== void 0 ? WebSocket : global === null || global === void 0 ? void 0 : global.WebSocket) !== null && _a !== void 0 ? _a : window === null || window === void 0 ? void 0 : window.WebSocket) !== null && _b !== void 0 ? _b : self === null || self === void 0 ? void 0 : self.WebSocket;
737
+ const factory = (url, params) => {
738
+ if (params) {
739
+ return new PolyfillWebSocket(url, params);
740
+ }
741
+ return new PolyfillWebSocket(url);
742
+ };
743
+
336
744
  const defaultRealtimeUrl = "wss://api.assemblyai.com/v2/realtime/ws";
337
745
  const forceEndOfUtteranceMessage = `{"force_end_utterance":true}`;
338
746
  const terminateSessionMessage$1 = `{"terminate_session":true}`;
@@ -590,20 +998,6 @@ Learn more at https://github.com/AssemblyAI/assemblyai-node-sdk/blob/main/docs/c
590
998
  class RealtimeServiceFactory extends RealtimeTranscriberFactory {
591
999
  }
592
1000
 
593
- function getPath(path) {
594
- if (path.startsWith("http"))
595
- return null;
596
- if (path.startsWith("https"))
597
- return null;
598
- if (path.startsWith("data:"))
599
- return null;
600
- if (path.startsWith("file://"))
601
- return path.substring(7);
602
- if (path.startsWith("file:"))
603
- return path.substring(5);
604
- return path;
605
- }
606
-
607
1001
  class TranscriptService extends BaseService {
608
1002
  constructor(params, files) {
609
1003
  super(params);
@@ -850,14 +1244,6 @@ Learn more at https://github.com/AssemblyAI/assemblyai-node-sdk/blob/main/docs/c
850
1244
  }
851
1245
  }
852
1246
 
853
- const readFile = function (
854
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
855
- path) {
856
- return __awaiter(this, void 0, void 0, function* () {
857
- throw new Error("Interacting with the file system is not supported in this environment.");
858
- });
859
- };
860
-
861
1247
  class FileService extends BaseService {
862
1248
  /**
863
1249
  * Upload a local file to AssemblyAI.
@@ -1141,6 +1527,10 @@ Learn more at https://github.com/AssemblyAI/assemblyai-node-sdk/blob/main/docs/c
1141
1527
  if (!(this.token || this.apiKey)) {
1142
1528
  throw new Error("API key or temporary token is required.");
1143
1529
  }
1530
+ const isOpus = params.encoding === "opus" || params.encoding === "ogg_opus";
1531
+ if (params.sampleRate === undefined && (!isOpus || params.channels)) {
1532
+ throw new Error('`sampleRate` is required; it may only be omitted when `encoding` is "opus" or "ogg_opus" (the Opus stream is self-describing) and dual-channel mode is not used.');
1533
+ }
1144
1534
  if (params.channels) {
1145
1535
  if (params.channels.length !== 2) {
1146
1536
  throw new Error("StreamingTranscriber.channels must have exactly 2 entries.");
@@ -1166,10 +1556,12 @@ Learn more at https://github.com/AssemblyAI/assemblyai-node-sdk/blob/main/docs/c
1166
1556
  "speaker-history") {
1167
1557
  this.speakerHistory = new Map();
1168
1558
  }
1169
- // 20 ms VAD frames at the transcriber's target sample rate.
1170
- this.vadFrameSamples = Math.max(1, Math.round(params.sampleRate * 0.02));
1171
- this.minChunkSamples = Math.max(1, Math.round(params.sampleRate * (MIN_CHUNK_MS / 1000)));
1172
- this.maxChunkSamples = Math.max(this.minChunkSamples, Math.round(params.sampleRate * (MAX_CHUNK_MS / 1000)));
1559
+ // 20 ms VAD frames at the transcriber's target sample rate. The
1560
+ // constructor check above guarantees sampleRate in dual-channel mode.
1561
+ const sampleRate = params.sampleRate;
1562
+ this.vadFrameSamples = Math.max(1, Math.round(sampleRate * 0.02));
1563
+ this.minChunkSamples = Math.max(1, Math.round(sampleRate * (MIN_CHUNK_MS / 1000)));
1564
+ this.maxChunkSamples = Math.max(this.minChunkSamples, Math.round(sampleRate * (MAX_CHUNK_MS / 1000)));
1173
1565
  this.channelBuffers = new Map(names.map((n) => [n, []]));
1174
1566
  this.channelSamplesReceived = new Map(names.map((n) => [n, 0]));
1175
1567
  this.channelVadFloatBuffers = new Map(names.map((n) => [n, new Float32Array(this.vadFrameSamples)]));
@@ -1188,7 +1580,9 @@ Learn more at https://github.com/AssemblyAI/assemblyai-node-sdk/blob/main/docs/c
1188
1580
  if (this.token) {
1189
1581
  searchParams.set("token", this.token);
1190
1582
  }
1191
- searchParams.set("sample_rate", this.params.sampleRate.toString());
1583
+ if (this.params.sampleRate !== undefined) {
1584
+ searchParams.set("sample_rate", this.params.sampleRate.toString());
1585
+ }
1192
1586
  if (this.params.endOfTurnConfidenceThreshold) {
1193
1587
  searchParams.set("end_of_turn_confidence_threshold", this.params.endOfTurnConfidenceThreshold.toString());
1194
1588
  }
@@ -2138,6 +2532,7 @@ registerProcessor("aai-pcm16-encoder", Pcm16EncoderProcessor);
2138
2532
 
2139
2533
  const defaultBaseUrl = "https://api.assemblyai.com";
2140
2534
  const defaultStreamingUrl = "https://streaming.assemblyai.com";
2535
+ const defaultSyncUrl = "https://sync.assemblyai.com";
2141
2536
  class AssemblyAI {
2142
2537
  /**
2143
2538
  * Create a new AssemblyAI client.
@@ -2153,6 +2548,11 @@ registerProcessor("aai-pcm16-encoder", Pcm16EncoderProcessor);
2153
2548
  this.lemur = new LemurService(params);
2154
2549
  this.realtime = new RealtimeTranscriberFactory(params);
2155
2550
  this.streaming = new StreamingTranscriberFactory(Object.assign(Object.assign({}, params), { baseUrl: params.streamingBaseUrl || defaultStreamingUrl }));
2551
+ let syncBaseUrl = params.syncBaseUrl || defaultSyncUrl;
2552
+ if (syncBaseUrl.endsWith("/")) {
2553
+ syncBaseUrl = syncBaseUrl.slice(0, -1);
2554
+ }
2555
+ this.sync = new SyncTranscriber(Object.assign(Object.assign({}, params), { baseUrl: syncBaseUrl }));
2156
2556
  }
2157
2557
  }
2158
2558
 
@@ -2168,10 +2568,13 @@ registerProcessor("aai-pcm16-encoder", Pcm16EncoderProcessor);
2168
2568
  exports.RealtimeTranscriber = RealtimeTranscriber;
2169
2569
  exports.RealtimeTranscriberFactory = RealtimeTranscriberFactory;
2170
2570
  exports.StreamingTranscriber = StreamingTranscriber;
2571
+ exports.SyncTranscriber = SyncTranscriber;
2572
+ exports.SyncTranscriptError = SyncTranscriptError;
2171
2573
  exports.TranscriptService = TranscriptService;
2172
2574
  exports.VadTimeline = VadTimeline;
2173
2575
  exports.attributeTurn = attributeTurn;
2174
2576
  exports.attributeWord = attributeWord;
2577
+ exports.defaultSyncSpeechModel = defaultSyncSpeechModel;
2175
2578
  exports.float32ToPcm16 = float32ToPcm16;
2176
2579
  exports.rollUpTurnChannel = rollUpTurnChannel;
2177
2580