assemblyai 4.35.4 → 4.36.4

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 (42) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +98 -0
  3. package/dist/assemblyai.streaming.umd.js +28 -11
  4. package/dist/assemblyai.streaming.umd.min.js +1 -1
  5. package/dist/assemblyai.umd.js +488 -83
  6. package/dist/assemblyai.umd.min.js +1 -1
  7. package/dist/browser.mjs +430 -78
  8. package/dist/bun.mjs +420 -67
  9. package/dist/deno.mjs +420 -67
  10. package/dist/index.cjs +481 -76
  11. package/dist/index.mjs +479 -77
  12. package/dist/node.cjs +418 -62
  13. package/dist/node.mjs +416 -63
  14. package/dist/services/base.d.ts +1 -0
  15. package/dist/services/index.d.ts +7 -1
  16. package/dist/services/streaming/service.d.ts +2 -1
  17. package/dist/services/sync/index.d.ts +1 -0
  18. package/dist/services/sync/service.d.ts +48 -0
  19. package/dist/streaming.browser.mjs +24 -9
  20. package/dist/streaming.cjs +27 -10
  21. package/dist/streaming.mjs +27 -10
  22. package/dist/types/asyncapi.generated.d.ts +1 -1
  23. package/dist/types/index.d.ts +1 -0
  24. package/dist/types/services/index.d.ts +1 -0
  25. package/dist/types/streaming/index.d.ts +14 -4
  26. package/dist/types/sync/index.d.ts +133 -0
  27. package/dist/utils/errors/index.d.ts +1 -0
  28. package/dist/utils/errors/sync.d.ts +20 -0
  29. package/dist/workerd.mjs +424 -71
  30. package/package.json +1 -1
  31. package/src/services/base.ts +18 -8
  32. package/src/services/index.ts +19 -0
  33. package/src/services/streaming/service.ts +22 -3
  34. package/src/services/sync/index.ts +1 -0
  35. package/src/services/sync/service.ts +369 -0
  36. package/src/types/asyncapi.generated.ts +6 -1
  37. package/src/types/index.ts +1 -0
  38. package/src/types/services/index.ts +1 -0
  39. package/src/types/streaming/index.ts +16 -3
  40. package/src/types/sync/index.ts +145 -0
  41. package/src/utils/errors/index.ts +2 -0
  42. package/src/utils/errors/sync.ts +25 -0
package/dist/index.cjs CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  var ws = require('ws');
4
4
 
5
+ /**
6
+ * The default speech model for synchronous transcription.
7
+ */
8
+ const defaultSyncSpeechModel = "universal-3-5-pro";
9
+
5
10
  /**
6
11
  * Thrown when `DualChannelCapture` is constructed in a non-browser environment
7
12
  * (no `globalThis.AudioContext`). The helper is intentionally surfaced from the
@@ -54,11 +59,39 @@ function __awaiter(thisArg, _arguments, P, generator) {
54
59
  });
55
60
  }
56
61
 
62
+ function __values(o) {
63
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
64
+ if (m) return m.call(o);
65
+ if (o && typeof o.length === "number") return {
66
+ next: function () {
67
+ if (o && i >= o.length) o = void 0;
68
+ return { value: o && o[i++], done: !o };
69
+ }
70
+ };
71
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
72
+ }
73
+
74
+ function __asyncValues(o) {
75
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
76
+ var m = o[Symbol.asyncIterator], i;
77
+ 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);
78
+ 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); }); }; }
79
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
80
+ }
81
+
57
82
  typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
58
83
  var e = new Error(message);
59
84
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
60
85
  };
61
86
 
87
+ const readFile = function (
88
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
89
+ path) {
90
+ return __awaiter(this, void 0, void 0, function* () {
91
+ throw new Error("Interacting with the file system is not supported in this environment.");
92
+ });
93
+ };
94
+
62
95
  const DEFAULT_FETCH_INIT = {
63
96
  cache: "no-store",
64
97
  };
@@ -76,7 +109,7 @@ if (typeof navigator !== "undefined" && navigator.userAgent) {
76
109
  defaultUserAgentString += navigator.userAgent;
77
110
  }
78
111
  const defaultUserAgent = {
79
- sdk: { name: "JavaScript", version: "4.35.4" },
112
+ sdk: { name: "JavaScript", version: "4.36.4" },
80
113
  };
81
114
  if (typeof process !== "undefined") {
82
115
  if (process.versions.node && defaultUserAgentString.indexOf("Node") === -1) {
@@ -115,13 +148,16 @@ class BaseService {
115
148
  this.userAgent = buildUserAgent(params.userAgent || {});
116
149
  }
117
150
  }
118
- fetch(input, init) {
151
+ fetchResponse(input, init) {
119
152
  return __awaiter(this, void 0, void 0, function* () {
120
153
  init = Object.assign(Object.assign({}, DEFAULT_FETCH_INIT), init);
121
154
  let headers = {
122
155
  Authorization: this.params.apiKey,
123
- "Content-Type": "application/json",
124
156
  };
157
+ // FormData bodies must let fetch set the multipart boundary itself.
158
+ if (!(init.body instanceof FormData)) {
159
+ headers["Content-Type"] = "application/json";
160
+ }
125
161
  if (DEFAULT_FETCH_INIT === null || DEFAULT_FETCH_INIT === void 0 ? void 0 : DEFAULT_FETCH_INIT.headers)
126
162
  headers = Object.assign(Object.assign({}, headers), DEFAULT_FETCH_INIT.headers);
127
163
  if (init === null || init === void 0 ? void 0 : init.headers)
@@ -131,15 +167,19 @@ class BaseService {
131
167
  {
132
168
  // chromium browsers have a bug where the user agent can't be modified
133
169
  if (typeof window !== "undefined" && "chrome" in window) {
134
- headers["AssemblyAI-Agent"] =
135
- this.userAgent;
170
+ headers["AssemblyAI-Agent"] = this.userAgent;
136
171
  }
137
172
  }
138
173
  }
139
174
  init.headers = headers;
140
175
  if (!input.startsWith("http"))
141
176
  input = this.params.baseUrl + input;
142
- const response = yield fetch(input, init);
177
+ return yield fetch(input, init);
178
+ });
179
+ }
180
+ fetch(input, init) {
181
+ return __awaiter(this, void 0, void 0, function* () {
182
+ const response = yield this.fetchResponse(input, init);
143
183
  if (response.status >= 400) {
144
184
  let json;
145
185
  const text = yield response.text();
@@ -167,58 +207,373 @@ class BaseService {
167
207
  }
168
208
  }
169
209
 
170
- class LemurService extends BaseService {
171
- summary(params, signal) {
172
- return this.fetchJson("/lemur/v3/generate/summary", {
173
- method: "POST",
174
- body: JSON.stringify(params),
175
- signal,
176
- });
177
- }
178
- questionAnswer(params, signal) {
179
- return this.fetchJson("/lemur/v3/generate/question-answer", {
180
- method: "POST",
181
- body: JSON.stringify(params),
182
- signal,
183
- });
210
+ /**
211
+ * Error thrown when a synchronous transcription request fails.
212
+ */
213
+ class SyncTranscriptError extends Error {
214
+ /**
215
+ * Create a new SyncTranscriptError.
216
+ * @param message - The human-readable error message.
217
+ * @param status - The HTTP status code of the failed request.
218
+ * @param errorCode - Machine-readable code — the snake_cased
219
+ * problem-details `title` from the server (e.g. `bad_audio`,
220
+ * `audio_too_large`, `capacity_exceeded`, `inference_timeout`).
221
+ * @param retryAfter - Seconds to wait before retrying, from the
222
+ * `Retry-After` header on 429/503 responses.
223
+ */
224
+ constructor(message, status, errorCode, retryAfter) {
225
+ super(message);
226
+ this.status = status;
227
+ this.errorCode = errorCode;
228
+ this.retryAfter = retryAfter;
229
+ this.name = "SyncTranscriptError";
184
230
  }
185
- actionItems(params, signal) {
186
- return this.fetchJson("/lemur/v3/generate/action-items", {
187
- method: "POST",
188
- body: JSON.stringify(params),
189
- signal,
190
- });
231
+ }
232
+
233
+ function getPath(path) {
234
+ if (path.startsWith("http"))
235
+ return null;
236
+ if (path.startsWith("https"))
237
+ return null;
238
+ if (path.startsWith("data:"))
239
+ return null;
240
+ if (path.startsWith("file://"))
241
+ return path.substring(7);
242
+ if (path.startsWith("file:"))
243
+ return path.substring(5);
244
+ return path;
245
+ }
246
+
247
+ // Canonical paths since the sync API gained a /v1 prefix (#18103); the
248
+ // unprefixed routes remain served for SDK versions that predate it.
249
+ const transcribeEndpoint = "/v1/transcribe";
250
+ const warmEndpoint = "/v1/warm";
251
+ const modelHeader = "X-AAI-Model";
252
+ // Kept above the server's 30 s deadline so the client doesn't race it.
253
+ const defaultTimeoutMs = 60000;
254
+ const warmTimeoutMs = 10000;
255
+ const maxPromptLength = 4096;
256
+ const maxKeytermsPromptLength = 2048;
257
+ const maxContextTurns = 100;
258
+ const maxContextLength = 4096;
259
+ // Extensions that signal raw S16LE PCM rather than a WAV container.
260
+ const pcmSuffixes = [".pcm", ".raw"];
261
+ /**
262
+ * The synchronous transcription service: audio in, transcript out,
263
+ * one request.
264
+ *
265
+ * Unlike `client.transcripts` (which submits a job to the async API and
266
+ * polls for completion), `SyncTranscriber` posts the audio to the sync
267
+ * API and returns the finished transcript in the HTTP response. There is no
268
+ * job id or status to poll. Accepts a local file path, raw audio bytes, a
269
+ * Blob, or a readable stream — but not a URL.
270
+ */
271
+ class SyncTranscriber extends BaseService {
272
+ /**
273
+ * Create a new synchronous transcription service.
274
+ * @param params - The parameters to use for the service.
275
+ */
276
+ constructor(params) {
277
+ super(params);
191
278
  }
192
- task(params, signal) {
193
- return this.fetchJson("/lemur/v3/generate/task", {
194
- method: "POST",
195
- body: JSON.stringify(params),
196
- signal,
279
+ /**
280
+ * Transcribe audio and return the finished transcript in one request.
281
+ * @param audio - A local file path, raw audio bytes, a Blob, or a readable
282
+ * stream. Raw PCM also requires `sample_rate` and `channels` on the config.
283
+ * @param config - Options for this transcription request.
284
+ * @param options - Client-side options, such as the request timeout.
285
+ * @returns A promise that resolves to the finished transcript.
286
+ * @throws SyncTranscriptError when the request fails.
287
+ */
288
+ transcribe(audio_1) {
289
+ return __awaiter(this, arguments, void 0, function* (audio, config = {}, options = {}) {
290
+ var _a, _b;
291
+ const { bytes, filename, contentType } = yield resolveAudio(audio, config);
292
+ const body = new FormData();
293
+ body.append("audio", new Blob([bytes], { type: contentType }), filename);
294
+ const configJson = buildConfigJson(config);
295
+ if (configJson) {
296
+ body.append("config", new Blob([JSON.stringify(configJson)], { type: "application/json" }));
297
+ }
298
+ const response = yield this.fetchResponse(transcribeEndpoint, {
299
+ method: "POST",
300
+ body,
301
+ headers: { [modelHeader]: (_a = config.model) !== null && _a !== void 0 ? _a : defaultSyncSpeechModel },
302
+ signal: AbortSignal.timeout((_b = options.timeout) !== null && _b !== void 0 ? _b : defaultTimeoutMs),
303
+ });
304
+ if (response.status !== 200)
305
+ throw yield errorFromResponse(response);
306
+ return (yield response.json());
197
307
  });
198
308
  }
199
- getResponse(id, signal) {
200
- return this.fetchJson(`/lemur/v3/${id}`, { signal });
201
- }
202
309
  /**
203
- * Delete the data for a previously submitted LeMUR request.
204
- * @param id - ID of the LeMUR request
205
- * @param signal - Optional AbortSignal to cancel the request
310
+ * Open the connection to the sync API ahead of time.
311
+ *
312
+ * The sync API is a single request/response, so a `transcribe()` that
313
+ * opens its connection on demand pays the full DNS + TCP + TLS handshake
314
+ * on the critical path. Call `warm()` as soon as you know audio is coming —
315
+ * typically while the clip is still being recorded — so the next
316
+ * `transcribe()` reuses the already-open connection. `warm()` is idempotent
317
+ * and cheap; call it shortly before `transcribe()` so the pooled connection
318
+ * hasn't idled out.
319
+ * @param params - Optionally the model to route the probe to, so the warmed
320
+ * connection lands on the same backend as the eventual transcription.
321
+ * @returns A promise that resolves to `true` once the connection is open
322
+ * (any HTTP response — even a non-200 — means the socket is
323
+ * established), or `false` if the connection could not be opened.
206
324
  */
207
- purgeRequestData(id, signal) {
208
- return this.fetchJson(`/lemur/v3/${id}`, {
209
- method: "DELETE",
210
- signal,
325
+ warm(params) {
326
+ return __awaiter(this, void 0, void 0, function* () {
327
+ var _a;
328
+ try {
329
+ yield this.fetchResponse(warmEndpoint, {
330
+ method: "GET",
331
+ headers: { [modelHeader]: (_a = params === null || params === void 0 ? void 0 : params.model) !== null && _a !== void 0 ? _a : defaultSyncSpeechModel },
332
+ signal: AbortSignal.timeout(warmTimeoutMs),
333
+ });
334
+ return true;
335
+ }
336
+ catch (_b) {
337
+ return false;
338
+ }
211
339
  });
212
340
  }
213
341
  }
214
-
215
- const { WritableStream } = typeof window !== "undefined"
216
- ? window
217
- : typeof global !== "undefined"
218
- ? global
219
- : globalThis;
220
-
221
- const factory = (url, params) => new ws(url, params);
342
+ /**
343
+ * Read the audio input into bytes and decide its multipart content type.
344
+ *
345
+ * PCM is selected when the source has a `.pcm`/`.raw` extension or when
346
+ * `sample_rate`/`channels` are set on the config (the fields the sync API
347
+ * requires only for raw PCM) — and both must then be present. Everything
348
+ * else is treated as a WAV container. URLs are rejected — the sync API has
349
+ * no URL ingestion.
350
+ */
351
+ function resolveAudio(input, config) {
352
+ return __awaiter(this, void 0, void 0, function* () {
353
+ var _a;
354
+ let bytes;
355
+ let filename;
356
+ let suffix = "";
357
+ if (typeof input === "string") {
358
+ if (/^https?:\/\//i.test(input)) {
359
+ throw new Error("SyncTranscriber does not accept URLs. Pass a local file path or " +
360
+ "audio bytes, or use client.transcripts for URL/async transcription.");
361
+ }
362
+ if (input.startsWith("data:")) {
363
+ bytes = dataUrlToBytes(input);
364
+ }
365
+ else {
366
+ const path = (_a = getPath(input)) !== null && _a !== void 0 ? _a : input;
367
+ bytes = yield readStream(yield readFile());
368
+ filename = basename(path);
369
+ suffix = extname(filename);
370
+ }
371
+ }
372
+ else if (input instanceof Uint8Array) {
373
+ bytes = input;
374
+ }
375
+ else if (input instanceof ArrayBuffer) {
376
+ bytes = new Uint8Array(input);
377
+ }
378
+ else if (input instanceof Blob) {
379
+ bytes = new Uint8Array(yield input.arrayBuffer());
380
+ // File instances carry a name; the File global itself needs Node >= 20.
381
+ const name = input.name;
382
+ if (name) {
383
+ filename = basename(name);
384
+ suffix = extname(filename);
385
+ }
386
+ }
387
+ else if (isWebReadableStream(input)) {
388
+ bytes = yield readStream(input);
389
+ }
390
+ else if (isAsyncIterable(input)) {
391
+ bytes = yield readAsyncIterable(input);
392
+ // fs.ReadStream carries the path it was opened from.
393
+ const path = input.path;
394
+ if (typeof path === "string") {
395
+ filename = basename(path);
396
+ suffix = extname(filename);
397
+ }
398
+ }
399
+ else {
400
+ throw new TypeError("unsupported audio input type");
401
+ }
402
+ const wantsPcm = config.sample_rate !== undefined || config.channels !== undefined;
403
+ const isPcm = pcmSuffixes.includes(suffix) || wantsPcm;
404
+ if (isPcm &&
405
+ (config.sample_rate === undefined || config.channels === undefined)) {
406
+ throw new Error("raw PCM audio requires both sample_rate and channels in the config");
407
+ }
408
+ const contentType = isPcm ? "audio/pcm" : "audio/wav";
409
+ if (!filename)
410
+ filename = isPcm ? "audio.pcm" : "audio.wav";
411
+ return { bytes, filename, contentType };
412
+ });
413
+ }
414
+ /**
415
+ * Serialize the config to the JSON `config` part, validating and normalizing
416
+ * field values to match the server's caps. The routing `model` is never
417
+ * included — it travels in the `X-AAI-Model` header. Returns `undefined`
418
+ * when there is nothing to send, so the part can be omitted entirely.
419
+ */
420
+ function buildConfigJson(config) {
421
+ if (config.prompt !== undefined && config.prompt.length > maxPromptLength) {
422
+ throw new Error(`prompt exceeds ${maxPromptLength} characters (got ${config.prompt.length})`);
423
+ }
424
+ const json = {};
425
+ if (config.prompt !== undefined)
426
+ json["prompt"] = config.prompt;
427
+ const keytermsPrompt = normalizeKeytermsPrompt(config.keyterms_prompt);
428
+ if (keytermsPrompt)
429
+ json["keyterms_prompt"] = keytermsPrompt;
430
+ const context = normalizeConversationContext(config.conversation_context);
431
+ if (context)
432
+ json["conversation_context"] = context;
433
+ if (config.language_codes !== undefined)
434
+ json["language_codes"] = config.language_codes;
435
+ if (config.sample_rate !== undefined)
436
+ json["sample_rate"] = config.sample_rate;
437
+ if (config.channels !== undefined)
438
+ json["channels"] = config.channels;
439
+ if (config.timestamps !== undefined)
440
+ json["timestamps"] = config.timestamps;
441
+ return Object.keys(json).length > 0 ? json : undefined;
442
+ }
443
+ function normalizeKeytermsPrompt(keytermsPrompt) {
444
+ if (!keytermsPrompt)
445
+ return undefined;
446
+ const terms = keytermsPrompt
447
+ .map((term) => term.trim())
448
+ .filter((term) => term.length > 0);
449
+ const total = terms.reduce((sum, term) => sum + term.length, 0);
450
+ if (total > maxKeytermsPromptLength) {
451
+ throw new Error(`keyterms_prompt exceeds ${maxKeytermsPromptLength} characters (got ${total})`);
452
+ }
453
+ return terms.length > 0 ? terms : undefined;
454
+ }
455
+ function normalizeConversationContext(context) {
456
+ if (context === undefined)
457
+ return undefined;
458
+ let turns = (typeof context === "string" ? [context] : context)
459
+ .map((turn) => turn.trim())
460
+ .filter((turn) => turn.length > 0);
461
+ let total = turns.reduce((sum, turn) => sum + turn.length, 0);
462
+ // Over-cap context is trimmed oldest-first, never rejected.
463
+ while (turns.length > 0 &&
464
+ (turns.length > maxContextTurns || total > maxContextLength)) {
465
+ total -= turns[0].length;
466
+ turns = turns.slice(1);
467
+ }
468
+ return turns.length > 0 ? turns : undefined;
469
+ }
470
+ /**
471
+ * Build a SyncTranscriptError from a non-200 response. The primary format
472
+ * is an RFC 9457 problem-details body (`status`/`title`/`detail`); legacy
473
+ * `{error_code, message}` and `{detail}`-only bodies are also accepted.
474
+ */
475
+ function errorFromResponse(response) {
476
+ return __awaiter(this, void 0, void 0, function* () {
477
+ let errorCode;
478
+ let message;
479
+ const text = yield response.text();
480
+ try {
481
+ const body = JSON.parse(text);
482
+ if (body && typeof body === "object" && !Array.isArray(body)) {
483
+ if (typeof body.error_code === "string")
484
+ errorCode = body.error_code;
485
+ if (errorCode === undefined && typeof body.title === "string") {
486
+ errorCode = body.title.toLowerCase().replace(/ /g, "_");
487
+ }
488
+ if (typeof body.detail === "string")
489
+ message = body.detail;
490
+ else if (typeof body.message === "string")
491
+ message = body.message;
492
+ }
493
+ }
494
+ catch (_a) {
495
+ if (text)
496
+ message = text;
497
+ }
498
+ if (!message) {
499
+ message = `sync transcription failed with status ${response.status}`;
500
+ }
501
+ const retryHeader = response.headers.get("retry-after");
502
+ const retryAfter = retryHeader && /^\d+$/.test(retryHeader)
503
+ ? parseInt(retryHeader, 10)
504
+ : undefined;
505
+ return new SyncTranscriptError(message, response.status, errorCode, retryAfter);
506
+ });
507
+ }
508
+ function basename(path) {
509
+ var _a;
510
+ return (_a = path.split(/[\\/]/).pop()) !== null && _a !== void 0 ? _a : path;
511
+ }
512
+ function extname(filename) {
513
+ const dotIndex = filename.lastIndexOf(".");
514
+ return dotIndex > 0 ? filename.slice(dotIndex).toLowerCase() : "";
515
+ }
516
+ function dataUrlToBytes(dataUrl) {
517
+ const base64 = dataUrl.split(",")[1];
518
+ const binary = atob(base64);
519
+ const bytes = new Uint8Array(binary.length);
520
+ for (let i = 0; i < binary.length; i++)
521
+ bytes[i] = binary.charCodeAt(i);
522
+ return bytes;
523
+ }
524
+ function isWebReadableStream(input) {
525
+ return typeof (input === null || input === void 0 ? void 0 : input.getReader) === "function";
526
+ }
527
+ function isAsyncIterable(input) {
528
+ return (typeof (input === null || input === void 0 ? void 0 : input[Symbol.asyncIterator]) ===
529
+ "function");
530
+ }
531
+ function readStream(stream) {
532
+ return __awaiter(this, void 0, void 0, function* () {
533
+ const chunks = [];
534
+ const reader = stream.getReader();
535
+ for (;;) {
536
+ const { done, value } = yield reader.read();
537
+ if (done)
538
+ break;
539
+ chunks.push(value);
540
+ }
541
+ return concatChunks(chunks);
542
+ });
543
+ }
544
+ function readAsyncIterable(iterable) {
545
+ return __awaiter(this, void 0, void 0, function* () {
546
+ var _a, iterable_1, iterable_1_1;
547
+ var _b, e_1, _c, _d;
548
+ const chunks = [];
549
+ try {
550
+ for (_a = true, iterable_1 = __asyncValues(iterable); iterable_1_1 = yield iterable_1.next(), _b = iterable_1_1.done, !_b; _a = true) {
551
+ _d = iterable_1_1.value;
552
+ _a = false;
553
+ const chunk = _d;
554
+ chunks.push(chunk);
555
+ }
556
+ }
557
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
558
+ finally {
559
+ try {
560
+ if (!_a && !_b && (_c = iterable_1.return)) yield _c.call(iterable_1);
561
+ }
562
+ finally { if (e_1) throw e_1.error; }
563
+ }
564
+ return concatChunks(chunks);
565
+ });
566
+ }
567
+ function concatChunks(chunks) {
568
+ const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
569
+ const bytes = new Uint8Array(total);
570
+ let offset = 0;
571
+ for (const chunk of chunks) {
572
+ bytes.set(chunk, offset);
573
+ offset += chunk.length;
574
+ }
575
+ return bytes;
576
+ }
222
577
 
223
578
  const RealtimeErrorType = {
224
579
  BadSampleRate: 4000,
@@ -324,6 +679,59 @@ const StreamingErrorMessages = {
324
679
  class StreamingError extends Error {
325
680
  }
326
681
 
682
+ class LemurService extends BaseService {
683
+ summary(params, signal) {
684
+ return this.fetchJson("/lemur/v3/generate/summary", {
685
+ method: "POST",
686
+ body: JSON.stringify(params),
687
+ signal,
688
+ });
689
+ }
690
+ questionAnswer(params, signal) {
691
+ return this.fetchJson("/lemur/v3/generate/question-answer", {
692
+ method: "POST",
693
+ body: JSON.stringify(params),
694
+ signal,
695
+ });
696
+ }
697
+ actionItems(params, signal) {
698
+ return this.fetchJson("/lemur/v3/generate/action-items", {
699
+ method: "POST",
700
+ body: JSON.stringify(params),
701
+ signal,
702
+ });
703
+ }
704
+ task(params, signal) {
705
+ return this.fetchJson("/lemur/v3/generate/task", {
706
+ method: "POST",
707
+ body: JSON.stringify(params),
708
+ signal,
709
+ });
710
+ }
711
+ getResponse(id, signal) {
712
+ return this.fetchJson(`/lemur/v3/${id}`, { signal });
713
+ }
714
+ /**
715
+ * Delete the data for a previously submitted LeMUR request.
716
+ * @param id - ID of the LeMUR request
717
+ * @param signal - Optional AbortSignal to cancel the request
718
+ */
719
+ purgeRequestData(id, signal) {
720
+ return this.fetchJson(`/lemur/v3/${id}`, {
721
+ method: "DELETE",
722
+ signal,
723
+ });
724
+ }
725
+ }
726
+
727
+ const { WritableStream } = typeof window !== "undefined"
728
+ ? window
729
+ : typeof global !== "undefined"
730
+ ? global
731
+ : globalThis;
732
+
733
+ const factory = (url, params) => new ws(url, params);
734
+
327
735
  const defaultRealtimeUrl = "wss://api.assemblyai.com/v2/realtime/ws";
328
736
  const forceEndOfUtteranceMessage = `{"force_end_utterance":true}`;
329
737
  const terminateSessionMessage$1 = `{"terminate_session":true}`;
@@ -577,20 +985,6 @@ class RealtimeTranscriberFactory extends BaseService {
577
985
  class RealtimeServiceFactory extends RealtimeTranscriberFactory {
578
986
  }
579
987
 
580
- function getPath(path) {
581
- if (path.startsWith("http"))
582
- return null;
583
- if (path.startsWith("https"))
584
- return null;
585
- if (path.startsWith("data:"))
586
- return null;
587
- if (path.startsWith("file://"))
588
- return path.substring(7);
589
- if (path.startsWith("file:"))
590
- return path.substring(5);
591
- return path;
592
- }
593
-
594
988
  class TranscriptService extends BaseService {
595
989
  constructor(params, files) {
596
990
  super(params);
@@ -837,14 +1231,6 @@ class TranscriptService extends BaseService {
837
1231
  }
838
1232
  }
839
1233
 
840
- const readFile = function (
841
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
842
- path) {
843
- return __awaiter(this, void 0, void 0, function* () {
844
- throw new Error("Interacting with the file system is not supported in this environment.");
845
- });
846
- };
847
-
848
1234
  class FileService extends BaseService {
849
1235
  /**
850
1236
  * Upload a local file to AssemblyAI.
@@ -1128,9 +1514,12 @@ class StreamingTranscriber {
1128
1514
  if (!(this.token || this.apiKey)) {
1129
1515
  throw new Error("API key or temporary token is required.");
1130
1516
  }
1131
- const isOpus = params.encoding === "opus" || params.encoding === "ogg_opus";
1132
- if (params.sampleRate === undefined && (!isOpus || params.channels)) {
1133
- 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.');
1517
+ const isSelfDescribing = params.encoding === "opus" ||
1518
+ params.encoding === "ogg_opus" ||
1519
+ params.encoding === "aac";
1520
+ if (params.sampleRate === undefined &&
1521
+ (!isSelfDescribing || params.channels)) {
1522
+ throw new Error('`sampleRate` is required; it may only be omitted when `encoding` is "opus", "ogg_opus", or "aac" (these streams are self-describing) and dual-channel mode is not used.');
1134
1523
  }
1135
1524
  if (params.channels) {
1136
1525
  if (params.channels.length !== 2) {
@@ -1208,6 +1597,9 @@ class StreamingTranscriber {
1208
1597
  if (this.params.formatTurns) {
1209
1598
  searchParams.set("format_turns", this.params.formatTurns.toString());
1210
1599
  }
1600
+ if (this.params.sessionHeartbeat !== undefined) {
1601
+ searchParams.set("session_heartbeat", this.params.sessionHeartbeat.toString());
1602
+ }
1211
1603
  if (this.params.encoding) {
1212
1604
  searchParams.set("encoding", this.params.encoding.toString());
1213
1605
  }
@@ -1439,7 +1831,7 @@ class StreamingTranscriber {
1439
1831
  (_c = (_b = this.listeners).error) === null || _c === void 0 ? void 0 : _c.call(_b, error);
1440
1832
  };
1441
1833
  this.socket.onmessage = ({ data }) => {
1442
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
1834
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
1443
1835
  const message = JSON.parse(data.toString());
1444
1836
  if ("error" in message) {
1445
1837
  const err = new StreamingError(message.error);
@@ -1499,8 +1891,12 @@ class StreamingTranscriber {
1499
1891
  (_p = (_o = this.listeners).warning) === null || _p === void 0 ? void 0 : _p.call(_o, warning);
1500
1892
  break;
1501
1893
  }
1894
+ case "Heartbeat": {
1895
+ (_r = (_q = this.listeners).heartbeat) === null || _r === void 0 ? void 0 : _r.call(_q, message);
1896
+ break;
1897
+ }
1502
1898
  case "Termination": {
1503
- (_q = this.sessionTerminatedResolve) === null || _q === void 0 ? void 0 : _q.call(this);
1899
+ (_s = this.sessionTerminatedResolve) === null || _s === void 0 ? void 0 : _s.call(this);
1504
1900
  break;
1505
1901
  }
1506
1902
  }
@@ -2129,6 +2525,7 @@ function float32ToPcm16(input) {
2129
2525
 
2130
2526
  const defaultBaseUrl = "https://api.assemblyai.com";
2131
2527
  const defaultStreamingUrl = "https://streaming.assemblyai.com";
2528
+ const defaultSyncUrl = "https://sync.assemblyai.com";
2132
2529
  class AssemblyAI {
2133
2530
  /**
2134
2531
  * Create a new AssemblyAI client.
@@ -2144,6 +2541,11 @@ class AssemblyAI {
2144
2541
  this.lemur = new LemurService(params);
2145
2542
  this.realtime = new RealtimeTranscriberFactory(params);
2146
2543
  this.streaming = new StreamingTranscriberFactory(Object.assign(Object.assign({}, params), { baseUrl: params.streamingBaseUrl || defaultStreamingUrl }));
2544
+ let syncBaseUrl = params.syncBaseUrl || defaultSyncUrl;
2545
+ if (syncBaseUrl.endsWith("/")) {
2546
+ syncBaseUrl = syncBaseUrl.slice(0, -1);
2547
+ }
2548
+ this.sync = new SyncTranscriber(Object.assign(Object.assign({}, params), { baseUrl: syncBaseUrl }));
2147
2549
  }
2148
2550
  }
2149
2551
 
@@ -2159,9 +2561,12 @@ exports.RealtimeServiceFactory = RealtimeServiceFactory;
2159
2561
  exports.RealtimeTranscriber = RealtimeTranscriber;
2160
2562
  exports.RealtimeTranscriberFactory = RealtimeTranscriberFactory;
2161
2563
  exports.StreamingTranscriber = StreamingTranscriber;
2564
+ exports.SyncTranscriber = SyncTranscriber;
2565
+ exports.SyncTranscriptError = SyncTranscriptError;
2162
2566
  exports.TranscriptService = TranscriptService;
2163
2567
  exports.VadTimeline = VadTimeline;
2164
2568
  exports.attributeTurn = attributeTurn;
2165
2569
  exports.attributeWord = attributeWord;
2570
+ exports.defaultSyncSpeechModel = defaultSyncSpeechModel;
2166
2571
  exports.float32ToPcm16 = float32ToPcm16;
2167
2572
  exports.rollUpTurnChannel = rollUpTurnChannel;