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
package/dist/index.mjs CHANGED
@@ -1,5 +1,10 @@
1
1
  import ws from 'ws';
2
2
 
3
+ /**
4
+ * The default speech model for synchronous transcription.
5
+ */
6
+ const defaultSyncSpeechModel = "universal-3-5-pro";
7
+
3
8
  /**
4
9
  * Thrown when `DualChannelCapture` is constructed in a non-browser environment
5
10
  * (no `globalThis.AudioContext`). The helper is intentionally surfaced from the
@@ -52,11 +57,39 @@ function __awaiter(thisArg, _arguments, P, generator) {
52
57
  });
53
58
  }
54
59
 
60
+ function __values(o) {
61
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
62
+ if (m) return m.call(o);
63
+ if (o && typeof o.length === "number") return {
64
+ next: function () {
65
+ if (o && i >= o.length) o = void 0;
66
+ return { value: o && o[i++], done: !o };
67
+ }
68
+ };
69
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
70
+ }
71
+
72
+ function __asyncValues(o) {
73
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
74
+ var m = o[Symbol.asyncIterator], i;
75
+ 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);
76
+ 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); }); }; }
77
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
78
+ }
79
+
55
80
  typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
56
81
  var e = new Error(message);
57
82
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
58
83
  };
59
84
 
85
+ const readFile = function (
86
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
87
+ path) {
88
+ return __awaiter(this, void 0, void 0, function* () {
89
+ throw new Error("Interacting with the file system is not supported in this environment.");
90
+ });
91
+ };
92
+
60
93
  const DEFAULT_FETCH_INIT = {
61
94
  cache: "no-store",
62
95
  };
@@ -74,7 +107,7 @@ if (typeof navigator !== "undefined" && navigator.userAgent) {
74
107
  defaultUserAgentString += navigator.userAgent;
75
108
  }
76
109
  const defaultUserAgent = {
77
- sdk: { name: "JavaScript", version: "4.35.3" },
110
+ sdk: { name: "JavaScript", version: "4.36.3" },
78
111
  };
79
112
  if (typeof process !== "undefined") {
80
113
  if (process.versions.node && defaultUserAgentString.indexOf("Node") === -1) {
@@ -113,13 +146,16 @@ class BaseService {
113
146
  this.userAgent = buildUserAgent(params.userAgent || {});
114
147
  }
115
148
  }
116
- fetch(input, init) {
149
+ fetchResponse(input, init) {
117
150
  return __awaiter(this, void 0, void 0, function* () {
118
151
  init = Object.assign(Object.assign({}, DEFAULT_FETCH_INIT), init);
119
152
  let headers = {
120
153
  Authorization: this.params.apiKey,
121
- "Content-Type": "application/json",
122
154
  };
155
+ // FormData bodies must let fetch set the multipart boundary itself.
156
+ if (!(init.body instanceof FormData)) {
157
+ headers["Content-Type"] = "application/json";
158
+ }
123
159
  if (DEFAULT_FETCH_INIT === null || DEFAULT_FETCH_INIT === void 0 ? void 0 : DEFAULT_FETCH_INIT.headers)
124
160
  headers = Object.assign(Object.assign({}, headers), DEFAULT_FETCH_INIT.headers);
125
161
  if (init === null || init === void 0 ? void 0 : init.headers)
@@ -129,15 +165,19 @@ class BaseService {
129
165
  {
130
166
  // chromium browsers have a bug where the user agent can't be modified
131
167
  if (typeof window !== "undefined" && "chrome" in window) {
132
- headers["AssemblyAI-Agent"] =
133
- this.userAgent;
168
+ headers["AssemblyAI-Agent"] = this.userAgent;
134
169
  }
135
170
  }
136
171
  }
137
172
  init.headers = headers;
138
173
  if (!input.startsWith("http"))
139
174
  input = this.params.baseUrl + input;
140
- const response = yield fetch(input, init);
175
+ return yield fetch(input, init);
176
+ });
177
+ }
178
+ fetch(input, init) {
179
+ return __awaiter(this, void 0, void 0, function* () {
180
+ const response = yield this.fetchResponse(input, init);
141
181
  if (response.status >= 400) {
142
182
  let json;
143
183
  const text = yield response.text();
@@ -165,58 +205,373 @@ class BaseService {
165
205
  }
166
206
  }
167
207
 
168
- class LemurService extends BaseService {
169
- summary(params, signal) {
170
- return this.fetchJson("/lemur/v3/generate/summary", {
171
- method: "POST",
172
- body: JSON.stringify(params),
173
- signal,
174
- });
175
- }
176
- questionAnswer(params, signal) {
177
- return this.fetchJson("/lemur/v3/generate/question-answer", {
178
- method: "POST",
179
- body: JSON.stringify(params),
180
- signal,
181
- });
208
+ /**
209
+ * Error thrown when a synchronous transcription request fails.
210
+ */
211
+ class SyncTranscriptError extends Error {
212
+ /**
213
+ * Create a new SyncTranscriptError.
214
+ * @param message - The human-readable error message.
215
+ * @param status - The HTTP status code of the failed request.
216
+ * @param errorCode - Machine-readable code — the snake_cased
217
+ * problem-details `title` from the server (e.g. `bad_audio`,
218
+ * `audio_too_large`, `capacity_exceeded`, `inference_timeout`).
219
+ * @param retryAfter - Seconds to wait before retrying, from the
220
+ * `Retry-After` header on 429/503 responses.
221
+ */
222
+ constructor(message, status, errorCode, retryAfter) {
223
+ super(message);
224
+ this.status = status;
225
+ this.errorCode = errorCode;
226
+ this.retryAfter = retryAfter;
227
+ this.name = "SyncTranscriptError";
182
228
  }
183
- actionItems(params, signal) {
184
- return this.fetchJson("/lemur/v3/generate/action-items", {
185
- method: "POST",
186
- body: JSON.stringify(params),
187
- signal,
188
- });
229
+ }
230
+
231
+ function getPath(path) {
232
+ if (path.startsWith("http"))
233
+ return null;
234
+ if (path.startsWith("https"))
235
+ return null;
236
+ if (path.startsWith("data:"))
237
+ return null;
238
+ if (path.startsWith("file://"))
239
+ return path.substring(7);
240
+ if (path.startsWith("file:"))
241
+ return path.substring(5);
242
+ return path;
243
+ }
244
+
245
+ // Canonical paths since the sync API gained a /v1 prefix (#18103); the
246
+ // unprefixed routes remain served for SDK versions that predate it.
247
+ const transcribeEndpoint = "/v1/transcribe";
248
+ const warmEndpoint = "/v1/warm";
249
+ const modelHeader = "X-AAI-Model";
250
+ // Kept above the server's 30 s deadline so the client doesn't race it.
251
+ const defaultTimeoutMs = 60000;
252
+ const warmTimeoutMs = 10000;
253
+ const maxPromptLength = 4096;
254
+ const maxKeytermsPromptLength = 2048;
255
+ const maxContextTurns = 100;
256
+ const maxContextLength = 4096;
257
+ // Extensions that signal raw S16LE PCM rather than a WAV container.
258
+ const pcmSuffixes = [".pcm", ".raw"];
259
+ /**
260
+ * The synchronous transcription service: audio in, transcript out,
261
+ * one request.
262
+ *
263
+ * Unlike `client.transcripts` (which submits a job to the async API and
264
+ * polls for completion), `SyncTranscriber` posts the audio to the sync
265
+ * API and returns the finished transcript in the HTTP response. There is no
266
+ * job id or status to poll. Accepts a local file path, raw audio bytes, a
267
+ * Blob, or a readable stream — but not a URL.
268
+ */
269
+ class SyncTranscriber extends BaseService {
270
+ /**
271
+ * Create a new synchronous transcription service.
272
+ * @param params - The parameters to use for the service.
273
+ */
274
+ constructor(params) {
275
+ super(params);
189
276
  }
190
- task(params, signal) {
191
- return this.fetchJson("/lemur/v3/generate/task", {
192
- method: "POST",
193
- body: JSON.stringify(params),
194
- signal,
277
+ /**
278
+ * Transcribe audio and return the finished transcript in one request.
279
+ * @param audio - A local file path, raw audio bytes, a Blob, or a readable
280
+ * stream. Raw PCM also requires `sample_rate` and `channels` on the config.
281
+ * @param config - Options for this transcription request.
282
+ * @param options - Client-side options, such as the request timeout.
283
+ * @returns A promise that resolves to the finished transcript.
284
+ * @throws SyncTranscriptError when the request fails.
285
+ */
286
+ transcribe(audio_1) {
287
+ return __awaiter(this, arguments, void 0, function* (audio, config = {}, options = {}) {
288
+ var _a, _b;
289
+ const { bytes, filename, contentType } = yield resolveAudio(audio, config);
290
+ const body = new FormData();
291
+ body.append("audio", new Blob([bytes], { type: contentType }), filename);
292
+ const configJson = buildConfigJson(config);
293
+ if (configJson) {
294
+ body.append("config", new Blob([JSON.stringify(configJson)], { type: "application/json" }));
295
+ }
296
+ const response = yield this.fetchResponse(transcribeEndpoint, {
297
+ method: "POST",
298
+ body,
299
+ headers: { [modelHeader]: (_a = config.model) !== null && _a !== void 0 ? _a : defaultSyncSpeechModel },
300
+ signal: AbortSignal.timeout((_b = options.timeout) !== null && _b !== void 0 ? _b : defaultTimeoutMs),
301
+ });
302
+ if (response.status !== 200)
303
+ throw yield errorFromResponse(response);
304
+ return (yield response.json());
195
305
  });
196
306
  }
197
- getResponse(id, signal) {
198
- return this.fetchJson(`/lemur/v3/${id}`, { signal });
199
- }
200
307
  /**
201
- * Delete the data for a previously submitted LeMUR request.
202
- * @param id - ID of the LeMUR request
203
- * @param signal - Optional AbortSignal to cancel the request
308
+ * Open the connection to the sync API ahead of time.
309
+ *
310
+ * The sync API is a single request/response, so a `transcribe()` that
311
+ * opens its connection on demand pays the full DNS + TCP + TLS handshake
312
+ * on the critical path. Call `warm()` as soon as you know audio is coming —
313
+ * typically while the clip is still being recorded — so the next
314
+ * `transcribe()` reuses the already-open connection. `warm()` is idempotent
315
+ * and cheap; call it shortly before `transcribe()` so the pooled connection
316
+ * hasn't idled out.
317
+ * @param params - Optionally the model to route the probe to, so the warmed
318
+ * connection lands on the same backend as the eventual transcription.
319
+ * @returns A promise that resolves to `true` once the connection is open
320
+ * (any HTTP response — even a non-200 — means the socket is
321
+ * established), or `false` if the connection could not be opened.
204
322
  */
205
- purgeRequestData(id, signal) {
206
- return this.fetchJson(`/lemur/v3/${id}`, {
207
- method: "DELETE",
208
- signal,
323
+ warm(params) {
324
+ return __awaiter(this, void 0, void 0, function* () {
325
+ var _a;
326
+ try {
327
+ yield this.fetchResponse(warmEndpoint, {
328
+ method: "GET",
329
+ headers: { [modelHeader]: (_a = params === null || params === void 0 ? void 0 : params.model) !== null && _a !== void 0 ? _a : defaultSyncSpeechModel },
330
+ signal: AbortSignal.timeout(warmTimeoutMs),
331
+ });
332
+ return true;
333
+ }
334
+ catch (_b) {
335
+ return false;
336
+ }
209
337
  });
210
338
  }
211
339
  }
212
-
213
- const { WritableStream } = typeof window !== "undefined"
214
- ? window
215
- : typeof global !== "undefined"
216
- ? global
217
- : globalThis;
218
-
219
- const factory = (url, params) => new ws(url, params);
340
+ /**
341
+ * Read the audio input into bytes and decide its multipart content type.
342
+ *
343
+ * PCM is selected when the source has a `.pcm`/`.raw` extension or when
344
+ * `sample_rate`/`channels` are set on the config (the fields the sync API
345
+ * requires only for raw PCM) — and both must then be present. Everything
346
+ * else is treated as a WAV container. URLs are rejected — the sync API has
347
+ * no URL ingestion.
348
+ */
349
+ function resolveAudio(input, config) {
350
+ return __awaiter(this, void 0, void 0, function* () {
351
+ var _a;
352
+ let bytes;
353
+ let filename;
354
+ let suffix = "";
355
+ if (typeof input === "string") {
356
+ if (/^https?:\/\//i.test(input)) {
357
+ throw new Error("SyncTranscriber does not accept URLs. Pass a local file path or " +
358
+ "audio bytes, or use client.transcripts for URL/async transcription.");
359
+ }
360
+ if (input.startsWith("data:")) {
361
+ bytes = dataUrlToBytes(input);
362
+ }
363
+ else {
364
+ const path = (_a = getPath(input)) !== null && _a !== void 0 ? _a : input;
365
+ bytes = yield readStream(yield readFile());
366
+ filename = basename(path);
367
+ suffix = extname(filename);
368
+ }
369
+ }
370
+ else if (input instanceof Uint8Array) {
371
+ bytes = input;
372
+ }
373
+ else if (input instanceof ArrayBuffer) {
374
+ bytes = new Uint8Array(input);
375
+ }
376
+ else if (input instanceof Blob) {
377
+ bytes = new Uint8Array(yield input.arrayBuffer());
378
+ // File instances carry a name; the File global itself needs Node >= 20.
379
+ const name = input.name;
380
+ if (name) {
381
+ filename = basename(name);
382
+ suffix = extname(filename);
383
+ }
384
+ }
385
+ else if (isWebReadableStream(input)) {
386
+ bytes = yield readStream(input);
387
+ }
388
+ else if (isAsyncIterable(input)) {
389
+ bytes = yield readAsyncIterable(input);
390
+ // fs.ReadStream carries the path it was opened from.
391
+ const path = input.path;
392
+ if (typeof path === "string") {
393
+ filename = basename(path);
394
+ suffix = extname(filename);
395
+ }
396
+ }
397
+ else {
398
+ throw new TypeError("unsupported audio input type");
399
+ }
400
+ const wantsPcm = config.sample_rate !== undefined || config.channels !== undefined;
401
+ const isPcm = pcmSuffixes.includes(suffix) || wantsPcm;
402
+ if (isPcm &&
403
+ (config.sample_rate === undefined || config.channels === undefined)) {
404
+ throw new Error("raw PCM audio requires both sample_rate and channels in the config");
405
+ }
406
+ const contentType = isPcm ? "audio/pcm" : "audio/wav";
407
+ if (!filename)
408
+ filename = isPcm ? "audio.pcm" : "audio.wav";
409
+ return { bytes, filename, contentType };
410
+ });
411
+ }
412
+ /**
413
+ * Serialize the config to the JSON `config` part, validating and normalizing
414
+ * field values to match the server's caps. The routing `model` is never
415
+ * included — it travels in the `X-AAI-Model` header. Returns `undefined`
416
+ * when there is nothing to send, so the part can be omitted entirely.
417
+ */
418
+ function buildConfigJson(config) {
419
+ if (config.prompt !== undefined && config.prompt.length > maxPromptLength) {
420
+ throw new Error(`prompt exceeds ${maxPromptLength} characters (got ${config.prompt.length})`);
421
+ }
422
+ const json = {};
423
+ if (config.prompt !== undefined)
424
+ json["prompt"] = config.prompt;
425
+ const keytermsPrompt = normalizeKeytermsPrompt(config.keyterms_prompt);
426
+ if (keytermsPrompt)
427
+ json["keyterms_prompt"] = keytermsPrompt;
428
+ const context = normalizeConversationContext(config.conversation_context);
429
+ if (context)
430
+ json["conversation_context"] = context;
431
+ if (config.language_codes !== undefined)
432
+ json["language_codes"] = config.language_codes;
433
+ if (config.sample_rate !== undefined)
434
+ json["sample_rate"] = config.sample_rate;
435
+ if (config.channels !== undefined)
436
+ json["channels"] = config.channels;
437
+ if (config.timestamps !== undefined)
438
+ json["timestamps"] = config.timestamps;
439
+ return Object.keys(json).length > 0 ? json : undefined;
440
+ }
441
+ function normalizeKeytermsPrompt(keytermsPrompt) {
442
+ if (!keytermsPrompt)
443
+ return undefined;
444
+ const terms = keytermsPrompt
445
+ .map((term) => term.trim())
446
+ .filter((term) => term.length > 0);
447
+ const total = terms.reduce((sum, term) => sum + term.length, 0);
448
+ if (total > maxKeytermsPromptLength) {
449
+ throw new Error(`keyterms_prompt exceeds ${maxKeytermsPromptLength} characters (got ${total})`);
450
+ }
451
+ return terms.length > 0 ? terms : undefined;
452
+ }
453
+ function normalizeConversationContext(context) {
454
+ if (context === undefined)
455
+ return undefined;
456
+ let turns = (typeof context === "string" ? [context] : context)
457
+ .map((turn) => turn.trim())
458
+ .filter((turn) => turn.length > 0);
459
+ let total = turns.reduce((sum, turn) => sum + turn.length, 0);
460
+ // Over-cap context is trimmed oldest-first, never rejected.
461
+ while (turns.length > 0 &&
462
+ (turns.length > maxContextTurns || total > maxContextLength)) {
463
+ total -= turns[0].length;
464
+ turns = turns.slice(1);
465
+ }
466
+ return turns.length > 0 ? turns : undefined;
467
+ }
468
+ /**
469
+ * Build a SyncTranscriptError from a non-200 response. The primary format
470
+ * is an RFC 9457 problem-details body (`status`/`title`/`detail`); legacy
471
+ * `{error_code, message}` and `{detail}`-only bodies are also accepted.
472
+ */
473
+ function errorFromResponse(response) {
474
+ return __awaiter(this, void 0, void 0, function* () {
475
+ let errorCode;
476
+ let message;
477
+ const text = yield response.text();
478
+ try {
479
+ const body = JSON.parse(text);
480
+ if (body && typeof body === "object" && !Array.isArray(body)) {
481
+ if (typeof body.error_code === "string")
482
+ errorCode = body.error_code;
483
+ if (errorCode === undefined && typeof body.title === "string") {
484
+ errorCode = body.title.toLowerCase().replace(/ /g, "_");
485
+ }
486
+ if (typeof body.detail === "string")
487
+ message = body.detail;
488
+ else if (typeof body.message === "string")
489
+ message = body.message;
490
+ }
491
+ }
492
+ catch (_a) {
493
+ if (text)
494
+ message = text;
495
+ }
496
+ if (!message) {
497
+ message = `sync transcription failed with status ${response.status}`;
498
+ }
499
+ const retryHeader = response.headers.get("retry-after");
500
+ const retryAfter = retryHeader && /^\d+$/.test(retryHeader)
501
+ ? parseInt(retryHeader, 10)
502
+ : undefined;
503
+ return new SyncTranscriptError(message, response.status, errorCode, retryAfter);
504
+ });
505
+ }
506
+ function basename(path) {
507
+ var _a;
508
+ return (_a = path.split(/[\\/]/).pop()) !== null && _a !== void 0 ? _a : path;
509
+ }
510
+ function extname(filename) {
511
+ const dotIndex = filename.lastIndexOf(".");
512
+ return dotIndex > 0 ? filename.slice(dotIndex).toLowerCase() : "";
513
+ }
514
+ function dataUrlToBytes(dataUrl) {
515
+ const base64 = dataUrl.split(",")[1];
516
+ const binary = atob(base64);
517
+ const bytes = new Uint8Array(binary.length);
518
+ for (let i = 0; i < binary.length; i++)
519
+ bytes[i] = binary.charCodeAt(i);
520
+ return bytes;
521
+ }
522
+ function isWebReadableStream(input) {
523
+ return typeof (input === null || input === void 0 ? void 0 : input.getReader) === "function";
524
+ }
525
+ function isAsyncIterable(input) {
526
+ return (typeof (input === null || input === void 0 ? void 0 : input[Symbol.asyncIterator]) ===
527
+ "function");
528
+ }
529
+ function readStream(stream) {
530
+ return __awaiter(this, void 0, void 0, function* () {
531
+ const chunks = [];
532
+ const reader = stream.getReader();
533
+ for (;;) {
534
+ const { done, value } = yield reader.read();
535
+ if (done)
536
+ break;
537
+ chunks.push(value);
538
+ }
539
+ return concatChunks(chunks);
540
+ });
541
+ }
542
+ function readAsyncIterable(iterable) {
543
+ return __awaiter(this, void 0, void 0, function* () {
544
+ var _a, iterable_1, iterable_1_1;
545
+ var _b, e_1, _c, _d;
546
+ const chunks = [];
547
+ try {
548
+ for (_a = true, iterable_1 = __asyncValues(iterable); iterable_1_1 = yield iterable_1.next(), _b = iterable_1_1.done, !_b; _a = true) {
549
+ _d = iterable_1_1.value;
550
+ _a = false;
551
+ const chunk = _d;
552
+ chunks.push(chunk);
553
+ }
554
+ }
555
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
556
+ finally {
557
+ try {
558
+ if (!_a && !_b && (_c = iterable_1.return)) yield _c.call(iterable_1);
559
+ }
560
+ finally { if (e_1) throw e_1.error; }
561
+ }
562
+ return concatChunks(chunks);
563
+ });
564
+ }
565
+ function concatChunks(chunks) {
566
+ const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
567
+ const bytes = new Uint8Array(total);
568
+ let offset = 0;
569
+ for (const chunk of chunks) {
570
+ bytes.set(chunk, offset);
571
+ offset += chunk.length;
572
+ }
573
+ return bytes;
574
+ }
220
575
 
221
576
  const RealtimeErrorType = {
222
577
  BadSampleRate: 4000,
@@ -322,6 +677,59 @@ const StreamingErrorMessages = {
322
677
  class StreamingError extends Error {
323
678
  }
324
679
 
680
+ class LemurService extends BaseService {
681
+ summary(params, signal) {
682
+ return this.fetchJson("/lemur/v3/generate/summary", {
683
+ method: "POST",
684
+ body: JSON.stringify(params),
685
+ signal,
686
+ });
687
+ }
688
+ questionAnswer(params, signal) {
689
+ return this.fetchJson("/lemur/v3/generate/question-answer", {
690
+ method: "POST",
691
+ body: JSON.stringify(params),
692
+ signal,
693
+ });
694
+ }
695
+ actionItems(params, signal) {
696
+ return this.fetchJson("/lemur/v3/generate/action-items", {
697
+ method: "POST",
698
+ body: JSON.stringify(params),
699
+ signal,
700
+ });
701
+ }
702
+ task(params, signal) {
703
+ return this.fetchJson("/lemur/v3/generate/task", {
704
+ method: "POST",
705
+ body: JSON.stringify(params),
706
+ signal,
707
+ });
708
+ }
709
+ getResponse(id, signal) {
710
+ return this.fetchJson(`/lemur/v3/${id}`, { signal });
711
+ }
712
+ /**
713
+ * Delete the data for a previously submitted LeMUR request.
714
+ * @param id - ID of the LeMUR request
715
+ * @param signal - Optional AbortSignal to cancel the request
716
+ */
717
+ purgeRequestData(id, signal) {
718
+ return this.fetchJson(`/lemur/v3/${id}`, {
719
+ method: "DELETE",
720
+ signal,
721
+ });
722
+ }
723
+ }
724
+
725
+ const { WritableStream } = typeof window !== "undefined"
726
+ ? window
727
+ : typeof global !== "undefined"
728
+ ? global
729
+ : globalThis;
730
+
731
+ const factory = (url, params) => new ws(url, params);
732
+
325
733
  const defaultRealtimeUrl = "wss://api.assemblyai.com/v2/realtime/ws";
326
734
  const forceEndOfUtteranceMessage = `{"force_end_utterance":true}`;
327
735
  const terminateSessionMessage$1 = `{"terminate_session":true}`;
@@ -575,20 +983,6 @@ class RealtimeTranscriberFactory extends BaseService {
575
983
  class RealtimeServiceFactory extends RealtimeTranscriberFactory {
576
984
  }
577
985
 
578
- function getPath(path) {
579
- if (path.startsWith("http"))
580
- return null;
581
- if (path.startsWith("https"))
582
- return null;
583
- if (path.startsWith("data:"))
584
- return null;
585
- if (path.startsWith("file://"))
586
- return path.substring(7);
587
- if (path.startsWith("file:"))
588
- return path.substring(5);
589
- return path;
590
- }
591
-
592
986
  class TranscriptService extends BaseService {
593
987
  constructor(params, files) {
594
988
  super(params);
@@ -835,14 +1229,6 @@ class TranscriptService extends BaseService {
835
1229
  }
836
1230
  }
837
1231
 
838
- const readFile = function (
839
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
840
- path) {
841
- return __awaiter(this, void 0, void 0, function* () {
842
- throw new Error("Interacting with the file system is not supported in this environment.");
843
- });
844
- };
845
-
846
1232
  class FileService extends BaseService {
847
1233
  /**
848
1234
  * Upload a local file to AssemblyAI.
@@ -1126,6 +1512,10 @@ class StreamingTranscriber {
1126
1512
  if (!(this.token || this.apiKey)) {
1127
1513
  throw new Error("API key or temporary token is required.");
1128
1514
  }
1515
+ const isOpus = params.encoding === "opus" || params.encoding === "ogg_opus";
1516
+ if (params.sampleRate === undefined && (!isOpus || params.channels)) {
1517
+ 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.');
1518
+ }
1129
1519
  if (params.channels) {
1130
1520
  if (params.channels.length !== 2) {
1131
1521
  throw new Error("StreamingTranscriber.channels must have exactly 2 entries.");
@@ -1151,10 +1541,12 @@ class StreamingTranscriber {
1151
1541
  "speaker-history") {
1152
1542
  this.speakerHistory = new Map();
1153
1543
  }
1154
- // 20 ms VAD frames at the transcriber's target sample rate.
1155
- this.vadFrameSamples = Math.max(1, Math.round(params.sampleRate * 0.02));
1156
- this.minChunkSamples = Math.max(1, Math.round(params.sampleRate * (MIN_CHUNK_MS / 1000)));
1157
- this.maxChunkSamples = Math.max(this.minChunkSamples, Math.round(params.sampleRate * (MAX_CHUNK_MS / 1000)));
1544
+ // 20 ms VAD frames at the transcriber's target sample rate. The
1545
+ // constructor check above guarantees sampleRate in dual-channel mode.
1546
+ const sampleRate = params.sampleRate;
1547
+ this.vadFrameSamples = Math.max(1, Math.round(sampleRate * 0.02));
1548
+ this.minChunkSamples = Math.max(1, Math.round(sampleRate * (MIN_CHUNK_MS / 1000)));
1549
+ this.maxChunkSamples = Math.max(this.minChunkSamples, Math.round(sampleRate * (MAX_CHUNK_MS / 1000)));
1158
1550
  this.channelBuffers = new Map(names.map((n) => [n, []]));
1159
1551
  this.channelSamplesReceived = new Map(names.map((n) => [n, 0]));
1160
1552
  this.channelVadFloatBuffers = new Map(names.map((n) => [n, new Float32Array(this.vadFrameSamples)]));
@@ -1173,7 +1565,9 @@ class StreamingTranscriber {
1173
1565
  if (this.token) {
1174
1566
  searchParams.set("token", this.token);
1175
1567
  }
1176
- searchParams.set("sample_rate", this.params.sampleRate.toString());
1568
+ if (this.params.sampleRate !== undefined) {
1569
+ searchParams.set("sample_rate", this.params.sampleRate.toString());
1570
+ }
1177
1571
  if (this.params.endOfTurnConfidenceThreshold) {
1178
1572
  searchParams.set("end_of_turn_confidence_threshold", this.params.endOfTurnConfidenceThreshold.toString());
1179
1573
  }
@@ -2119,6 +2513,7 @@ function float32ToPcm16(input) {
2119
2513
 
2120
2514
  const defaultBaseUrl = "https://api.assemblyai.com";
2121
2515
  const defaultStreamingUrl = "https://streaming.assemblyai.com";
2516
+ const defaultSyncUrl = "https://sync.assemblyai.com";
2122
2517
  class AssemblyAI {
2123
2518
  /**
2124
2519
  * Create a new AssemblyAI client.
@@ -2134,7 +2529,12 @@ class AssemblyAI {
2134
2529
  this.lemur = new LemurService(params);
2135
2530
  this.realtime = new RealtimeTranscriberFactory(params);
2136
2531
  this.streaming = new StreamingTranscriberFactory(Object.assign(Object.assign({}, params), { baseUrl: params.streamingBaseUrl || defaultStreamingUrl }));
2532
+ let syncBaseUrl = params.syncBaseUrl || defaultSyncUrl;
2533
+ if (syncBaseUrl.endsWith("/")) {
2534
+ syncBaseUrl = syncBaseUrl.slice(0, -1);
2535
+ }
2536
+ this.sync = new SyncTranscriber(Object.assign(Object.assign({}, params), { baseUrl: syncBaseUrl }));
2137
2537
  }
2138
2538
  }
2139
2539
 
2140
- export { AssemblyAI, BrowserOnlyError, DualChannelCapture, EnergyVad, FileService, LemurService, LinearResampler, RealtimeService, RealtimeServiceFactory, RealtimeTranscriber, RealtimeTranscriberFactory, StreamingTranscriber, TranscriptService, VadTimeline, attributeTurn, attributeWord, float32ToPcm16, rollUpTurnChannel };
2540
+ export { AssemblyAI, BrowserOnlyError, DualChannelCapture, EnergyVad, FileService, LemurService, LinearResampler, RealtimeService, RealtimeServiceFactory, RealtimeTranscriber, RealtimeTranscriberFactory, StreamingTranscriber, SyncTranscriber, SyncTranscriptError, TranscriptService, VadTimeline, attributeTurn, attributeWord, defaultSyncSpeechModel, float32ToPcm16, rollUpTurnChannel };