assemblyai
Version:
The AssemblyAI JavaScript SDK provides an easy-to-use interface for interacting with the AssemblyAI API, which supports async and real-time transcription, as well as the latest LeMUR models.
1,098 lines (1,071 loc) • 121 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.assemblyai = {}));
})(this, (function (exports) { 'use strict';
/**
* The default speech model for synchronous transcription.
*/
const defaultSyncSpeechModel = "universal-3-5-pro";
/**
* Thrown when `DualChannelCapture` is constructed in a non-browser environment
* (no `globalThis.AudioContext`). The helper is intentionally surfaced from the
* main entrypoint so the import path is uniform across runtimes; the runtime
* guard moves to construction time.
*/
class BrowserOnlyError extends Error {
constructor(message = "DualChannelCapture requires a browser environment (AudioContext is undefined).") {
super(message);
this.name = "BrowserOnlyError";
}
}
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __values(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
}
function __asyncValues(o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
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);
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); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
const readFile = function (
// eslint-disable-next-line @typescript-eslint/no-unused-vars
path) {
return __awaiter(this, void 0, void 0, function* () {
throw new Error("Interacting with the file system is not supported in this environment.");
});
};
const DEFAULT_FETCH_INIT = {
cache: "no-store",
};
const buildUserAgent = (userAgent) => defaultUserAgentString +
(userAgent === false
? ""
: " AssemblyAI/1.0 (" +
Object.entries(Object.assign(Object.assign({}, defaultUserAgent), userAgent))
.map(([key, item]) => item ? `${key}=${item.name}/${item.version}` : "")
.join(" ") +
")");
let defaultUserAgentString = "";
if (typeof navigator !== "undefined" && navigator.userAgent) {
defaultUserAgentString += navigator.userAgent;
}
const defaultUserAgent = {
sdk: { name: "JavaScript", version: "4.36.4" },
};
if (typeof process !== "undefined") {
if (process.versions.node && defaultUserAgentString.indexOf("Node") === -1) {
defaultUserAgent.runtime_env = {
name: "Node",
version: process.versions.node,
};
}
if (process.versions.bun && defaultUserAgentString.indexOf("Bun") === -1) {
defaultUserAgent.runtime_env = {
name: "Bun",
version: process.versions.bun,
};
}
}
if (typeof Deno !== "undefined") {
if (process.versions.bun && defaultUserAgentString.indexOf("Deno") === -1) {
defaultUserAgent.runtime_env = { name: "Deno", version: Deno.version.deno };
}
}
/**
* Base class for services that communicate with the API.
*/
class BaseService {
/**
* Create a new service.
* @param params - The parameters to use for the service.
*/
constructor(params) {
this.params = params;
if (params.userAgent === false) {
this.userAgent = undefined;
}
else {
this.userAgent = buildUserAgent(params.userAgent || {});
}
}
fetchResponse(input, init) {
return __awaiter(this, void 0, void 0, function* () {
init = Object.assign(Object.assign({}, DEFAULT_FETCH_INIT), init);
let headers = {
Authorization: this.params.apiKey,
};
// FormData bodies must let fetch set the multipart boundary itself.
if (!(init.body instanceof FormData)) {
headers["Content-Type"] = "application/json";
}
if (DEFAULT_FETCH_INIT === null || DEFAULT_FETCH_INIT === void 0 ? void 0 : DEFAULT_FETCH_INIT.headers)
headers = Object.assign(Object.assign({}, headers), DEFAULT_FETCH_INIT.headers);
if (init === null || init === void 0 ? void 0 : init.headers)
headers = Object.assign(Object.assign({}, headers), init.headers);
if (this.userAgent) {
headers["User-Agent"] = this.userAgent;
{
// chromium browsers have a bug where the user agent can't be modified
if (typeof window !== "undefined" && "chrome" in window) {
headers["AssemblyAI-Agent"] = this.userAgent;
}
}
}
init.headers = headers;
if (!input.startsWith("http"))
input = this.params.baseUrl + input;
return yield fetch(input, init);
});
}
fetch(input, init) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield this.fetchResponse(input, init);
if (response.status >= 400) {
let json;
const text = yield response.text();
if (text) {
try {
json = JSON.parse(text);
}
catch (_a) {
/* empty */
}
if (json === null || json === void 0 ? void 0 : json.error)
throw new Error(json.error);
throw new Error(text);
}
throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
}
return response;
});
}
fetchJson(input, init) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield this.fetch(input, init);
return response.json();
});
}
}
/**
* Error thrown when a synchronous transcription request fails.
*/
class SyncTranscriptError extends Error {
/**
* Create a new SyncTranscriptError.
* @param message - The human-readable error message.
* @param status - The HTTP status code of the failed request.
* @param errorCode - Machine-readable code — the snake_cased
* problem-details `title` from the server (e.g. `bad_audio`,
* `audio_too_large`, `capacity_exceeded`, `inference_timeout`).
* @param retryAfter - Seconds to wait before retrying, from the
* `Retry-After` header on 429/503 responses.
*/
constructor(message, status, errorCode, retryAfter) {
super(message);
this.status = status;
this.errorCode = errorCode;
this.retryAfter = retryAfter;
this.name = "SyncTranscriptError";
}
}
function getPath(path) {
if (path.startsWith("http"))
return null;
if (path.startsWith("https"))
return null;
if (path.startsWith("data:"))
return null;
if (path.startsWith("file://"))
return path.substring(7);
if (path.startsWith("file:"))
return path.substring(5);
return path;
}
// Canonical paths since the sync API gained a /v1 prefix (#18103); the
// unprefixed routes remain served for SDK versions that predate it.
const transcribeEndpoint = "/v1/transcribe";
const warmEndpoint = "/v1/warm";
const modelHeader = "X-AAI-Model";
// Kept above the server's 30 s deadline so the client doesn't race it.
const defaultTimeoutMs = 60000;
const warmTimeoutMs = 10000;
const maxPromptLength = 4096;
const maxKeytermsPromptLength = 2048;
const maxContextTurns = 100;
const maxContextLength = 4096;
// Extensions that signal raw S16LE PCM rather than a WAV container.
const pcmSuffixes = [".pcm", ".raw"];
/**
* The synchronous transcription service: audio in, transcript out,
* one request.
*
* Unlike `client.transcripts` (which submits a job to the async API and
* polls for completion), `SyncTranscriber` posts the audio to the sync
* API and returns the finished transcript in the HTTP response. There is no
* job id or status to poll. Accepts a local file path, raw audio bytes, a
* Blob, or a readable stream — but not a URL.
*/
class SyncTranscriber extends BaseService {
/**
* Create a new synchronous transcription service.
* @param params - The parameters to use for the service.
*/
constructor(params) {
super(params);
}
/**
* Transcribe audio and return the finished transcript in one request.
* @param audio - A local file path, raw audio bytes, a Blob, or a readable
* stream. Raw PCM also requires `sample_rate` and `channels` on the config.
* @param config - Options for this transcription request.
* @param options - Client-side options, such as the request timeout.
* @returns A promise that resolves to the finished transcript.
* @throws SyncTranscriptError when the request fails.
*/
transcribe(audio_1) {
return __awaiter(this, arguments, void 0, function* (audio, config = {}, options = {}) {
var _a, _b;
const { bytes, filename, contentType } = yield resolveAudio(audio, config);
const body = new FormData();
body.append("audio", new Blob([bytes], { type: contentType }), filename);
const configJson = buildConfigJson(config);
if (configJson) {
body.append("config", new Blob([JSON.stringify(configJson)], { type: "application/json" }));
}
const response = yield this.fetchResponse(transcribeEndpoint, {
method: "POST",
body,
headers: { [modelHeader]: (_a = config.model) !== null && _a !== void 0 ? _a : defaultSyncSpeechModel },
signal: AbortSignal.timeout((_b = options.timeout) !== null && _b !== void 0 ? _b : defaultTimeoutMs),
});
if (response.status !== 200)
throw yield errorFromResponse(response);
return (yield response.json());
});
}
/**
* Open the connection to the sync API ahead of time.
*
* The sync API is a single request/response, so a `transcribe()` that
* opens its connection on demand pays the full DNS + TCP + TLS handshake
* on the critical path. Call `warm()` as soon as you know audio is coming —
* typically while the clip is still being recorded — so the next
* `transcribe()` reuses the already-open connection. `warm()` is idempotent
* and cheap; call it shortly before `transcribe()` so the pooled connection
* hasn't idled out.
* @param params - Optionally the model to route the probe to, so the warmed
* connection lands on the same backend as the eventual transcription.
* @returns A promise that resolves to `true` once the connection is open
* (any HTTP response — even a non-200 — means the socket is
* established), or `false` if the connection could not be opened.
*/
warm(params) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
try {
yield this.fetchResponse(warmEndpoint, {
method: "GET",
headers: { [modelHeader]: (_a = params === null || params === void 0 ? void 0 : params.model) !== null && _a !== void 0 ? _a : defaultSyncSpeechModel },
signal: AbortSignal.timeout(warmTimeoutMs),
});
return true;
}
catch (_b) {
return false;
}
});
}
}
/**
* Read the audio input into bytes and decide its multipart content type.
*
* PCM is selected when the source has a `.pcm`/`.raw` extension or when
* `sample_rate`/`channels` are set on the config (the fields the sync API
* requires only for raw PCM) — and both must then be present. Everything
* else is treated as a WAV container. URLs are rejected — the sync API has
* no URL ingestion.
*/
function resolveAudio(input, config) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
let bytes;
let filename;
let suffix = "";
if (typeof input === "string") {
if (/^https?:\/\//i.test(input)) {
throw new Error("SyncTranscriber does not accept URLs. Pass a local file path or " +
"audio bytes, or use client.transcripts for URL/async transcription.");
}
if (input.startsWith("data:")) {
bytes = dataUrlToBytes(input);
}
else {
const path = (_a = getPath(input)) !== null && _a !== void 0 ? _a : input;
bytes = yield readStream(yield readFile());
filename = basename(path);
suffix = extname(filename);
}
}
else if (input instanceof Uint8Array) {
bytes = input;
}
else if (input instanceof ArrayBuffer) {
bytes = new Uint8Array(input);
}
else if (input instanceof Blob) {
bytes = new Uint8Array(yield input.arrayBuffer());
// File instances carry a name; the File global itself needs Node >= 20.
const name = input.name;
if (name) {
filename = basename(name);
suffix = extname(filename);
}
}
else if (isWebReadableStream(input)) {
bytes = yield readStream(input);
}
else if (isAsyncIterable(input)) {
bytes = yield readAsyncIterable(input);
// fs.ReadStream carries the path it was opened from.
const path = input.path;
if (typeof path === "string") {
filename = basename(path);
suffix = extname(filename);
}
}
else {
throw new TypeError("unsupported audio input type");
}
const wantsPcm = config.sample_rate !== undefined || config.channels !== undefined;
const isPcm = pcmSuffixes.includes(suffix) || wantsPcm;
if (isPcm &&
(config.sample_rate === undefined || config.channels === undefined)) {
throw new Error("raw PCM audio requires both sample_rate and channels in the config");
}
const contentType = isPcm ? "audio/pcm" : "audio/wav";
if (!filename)
filename = isPcm ? "audio.pcm" : "audio.wav";
return { bytes, filename, contentType };
});
}
/**
* Serialize the config to the JSON `config` part, validating and normalizing
* field values to match the server's caps. The routing `model` is never
* included — it travels in the `X-AAI-Model` header. Returns `undefined`
* when there is nothing to send, so the part can be omitted entirely.
*/
function buildConfigJson(config) {
if (config.prompt !== undefined && config.prompt.length > maxPromptLength) {
throw new Error(`prompt exceeds ${maxPromptLength} characters (got ${config.prompt.length})`);
}
const json = {};
if (config.prompt !== undefined)
json["prompt"] = config.prompt;
const keytermsPrompt = normalizeKeytermsPrompt(config.keyterms_prompt);
if (keytermsPrompt)
json["keyterms_prompt"] = keytermsPrompt;
const context = normalizeConversationContext(config.conversation_context);
if (context)
json["conversation_context"] = context;
if (config.language_codes !== undefined)
json["language_codes"] = config.language_codes;
if (config.sample_rate !== undefined)
json["sample_rate"] = config.sample_rate;
if (config.channels !== undefined)
json["channels"] = config.channels;
if (config.timestamps !== undefined)
json["timestamps"] = config.timestamps;
return Object.keys(json).length > 0 ? json : undefined;
}
function normalizeKeytermsPrompt(keytermsPrompt) {
if (!keytermsPrompt)
return undefined;
const terms = keytermsPrompt
.map((term) => term.trim())
.filter((term) => term.length > 0);
const total = terms.reduce((sum, term) => sum + term.length, 0);
if (total > maxKeytermsPromptLength) {
throw new Error(`keyterms_prompt exceeds ${maxKeytermsPromptLength} characters (got ${total})`);
}
return terms.length > 0 ? terms : undefined;
}
function normalizeConversationContext(context) {
if (context === undefined)
return undefined;
let turns = (typeof context === "string" ? [context] : context)
.map((turn) => turn.trim())
.filter((turn) => turn.length > 0);
let total = turns.reduce((sum, turn) => sum + turn.length, 0);
// Over-cap context is trimmed oldest-first, never rejected.
while (turns.length > 0 &&
(turns.length > maxContextTurns || total > maxContextLength)) {
total -= turns[0].length;
turns = turns.slice(1);
}
return turns.length > 0 ? turns : undefined;
}
/**
* Build a SyncTranscriptError from a non-200 response. The primary format
* is an RFC 9457 problem-details body (`status`/`title`/`detail`); legacy
* `{error_code, message}` and `{detail}`-only bodies are also accepted.
*/
function errorFromResponse(response) {
return __awaiter(this, void 0, void 0, function* () {
let errorCode;
let message;
const text = yield response.text();
try {
const body = JSON.parse(text);
if (body && typeof body === "object" && !Array.isArray(body)) {
if (typeof body.error_code === "string")
errorCode = body.error_code;
if (errorCode === undefined && typeof body.title === "string") {
errorCode = body.title.toLowerCase().replace(/ /g, "_");
}
if (typeof body.detail === "string")
message = body.detail;
else if (typeof body.message === "string")
message = body.message;
}
}
catch (_a) {
if (text)
message = text;
}
if (!message) {
message = `sync transcription failed with status ${response.status}`;
}
const retryHeader = response.headers.get("retry-after");
const retryAfter = retryHeader && /^\d+$/.test(retryHeader)
? parseInt(retryHeader, 10)
: undefined;
return new SyncTranscriptError(message, response.status, errorCode, retryAfter);
});
}
function basename(path) {
var _a;
return (_a = path.split(/[\\/]/).pop()) !== null && _a !== void 0 ? _a : path;
}
function extname(filename) {
const dotIndex = filename.lastIndexOf(".");
return dotIndex > 0 ? filename.slice(dotIndex).toLowerCase() : "";
}
function dataUrlToBytes(dataUrl) {
const base64 = dataUrl.split(",")[1];
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++)
bytes[i] = binary.charCodeAt(i);
return bytes;
}
function isWebReadableStream(input) {
return typeof (input === null || input === void 0 ? void 0 : input.getReader) === "function";
}
function isAsyncIterable(input) {
return (typeof (input === null || input === void 0 ? void 0 : input[Symbol.asyncIterator]) ===
"function");
}
function readStream(stream) {
return __awaiter(this, void 0, void 0, function* () {
const chunks = [];
const reader = stream.getReader();
for (;;) {
const { done, value } = yield reader.read();
if (done)
break;
chunks.push(value);
}
return concatChunks(chunks);
});
}
function readAsyncIterable(iterable) {
return __awaiter(this, void 0, void 0, function* () {
var _a, iterable_1, iterable_1_1;
var _b, e_1, _c, _d;
const chunks = [];
try {
for (_a = true, iterable_1 = __asyncValues(iterable); iterable_1_1 = yield iterable_1.next(), _b = iterable_1_1.done, !_b; _a = true) {
_d = iterable_1_1.value;
_a = false;
const chunk = _d;
chunks.push(chunk);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_a && !_b && (_c = iterable_1.return)) yield _c.call(iterable_1);
}
finally { if (e_1) throw e_1.error; }
}
return concatChunks(chunks);
});
}
function concatChunks(chunks) {
const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const bytes = new Uint8Array(total);
let offset = 0;
for (const chunk of chunks) {
bytes.set(chunk, offset);
offset += chunk.length;
}
return bytes;
}
const RealtimeErrorType = {
BadSampleRate: 4000,
AuthFailed: 4001,
/**
* @deprecated Use InsufficientFunds or FreeTierUser instead
*/
InsufficientFundsOrFreeAccount: 4002,
InsufficientFunds: 4002,
FreeTierUser: 4003,
NonexistentSessionId: 4004,
SessionExpired: 4008,
ClosedSession: 4010,
RateLimited: 4029,
UniqueSessionViolation: 4030,
SessionTimeout: 4031,
AudioTooShort: 4032,
AudioTooLong: 4033,
AudioTooSmallToTranscode: 4034,
/**
* @deprecated Don't use
*/
BadJson: 4100,
BadSchema: 4101,
TooManyStreams: 4102,
Reconnected: 4103,
/**
* @deprecated Don't use
*/
ReconnectAttemptsExhausted: 1013,
WordBoostParameterParsingFailed: 4104,
};
const RealtimeErrorMessages = {
[RealtimeErrorType.BadSampleRate]: "Sample rate must be a positive integer",
[RealtimeErrorType.AuthFailed]: "Not Authorized",
[RealtimeErrorType.InsufficientFunds]: "Insufficient funds",
[RealtimeErrorType.FreeTierUser]: "This feature is paid-only and requires you to add a credit card. Please visit https://app.assemblyai.com/ to add a credit card to your account.",
[RealtimeErrorType.NonexistentSessionId]: "Session ID does not exist",
[RealtimeErrorType.SessionExpired]: "Session has expired",
[RealtimeErrorType.ClosedSession]: "Session is closed",
[RealtimeErrorType.RateLimited]: "Rate limited",
[RealtimeErrorType.UniqueSessionViolation]: "Unique session violation",
[RealtimeErrorType.SessionTimeout]: "Session Timeout",
[RealtimeErrorType.AudioTooShort]: "Audio too short",
[RealtimeErrorType.AudioTooLong]: "Audio too long",
[RealtimeErrorType.AudioTooSmallToTranscode]: "Audio too small to transcode",
[RealtimeErrorType.BadJson]: "Bad JSON",
[RealtimeErrorType.BadSchema]: "Bad schema",
[RealtimeErrorType.TooManyStreams]: "Too many streams",
[RealtimeErrorType.Reconnected]: "This session has been reconnected. This WebSocket is no longer valid.",
[RealtimeErrorType.ReconnectAttemptsExhausted]: "Reconnect attempts exhausted",
[RealtimeErrorType.WordBoostParameterParsingFailed]: "Could not parse word boost parameter",
};
class RealtimeError extends Error {
}
const StreamingErrorType = {
BadSampleRate: 4000,
AuthFailed: 4001,
InsufficientFunds: 4002,
FreeTierUser: 4003,
NonexistentSessionId: 4004,
SessionExpired: 4008,
ClosedSession: 4010,
RateLimited: 4029,
UniqueSessionViolation: 4030,
SessionTimeout: 4031,
AudioTooShort: 4032,
AudioTooLong: 4033,
AudioTooSmallToTranscode: 4034,
BadSchema: 4101,
TooManyStreams: 4102,
Reconnected: 4103,
ServerError: 3005,
InputValidationError: 3006,
AudioChunkDurationViolation: 3007,
MaxSessionDurationExceeded: 3008,
ConcurrencyLimitExceeded: 3009,
};
const StreamingErrorMessages = {
[StreamingErrorType.ServerError]: "Server error",
[StreamingErrorType.InputValidationError]: "Input validation error",
[StreamingErrorType.AudioChunkDurationViolation]: "Audio chunk duration violation",
[StreamingErrorType.MaxSessionDurationExceeded]: "Session expired: maximum session duration exceeded",
[StreamingErrorType.ConcurrencyLimitExceeded]: "Too many concurrent sessions",
[StreamingErrorType.BadSampleRate]: "Sample rate must be a positive integer",
[StreamingErrorType.AuthFailed]: "Not Authorized",
[StreamingErrorType.InsufficientFunds]: "Insufficient funds",
[StreamingErrorType.FreeTierUser]: "This feature is paid-only and requires you to add a credit card. Please visit https://app.assemblyai.com/ to add a credit card to your account.",
[StreamingErrorType.NonexistentSessionId]: "Session ID does not exist",
[StreamingErrorType.SessionExpired]: "Session has expired",
[StreamingErrorType.ClosedSession]: "Session is closed",
[StreamingErrorType.RateLimited]: "Rate limited",
[StreamingErrorType.UniqueSessionViolation]: "Unique session violation",
[StreamingErrorType.SessionTimeout]: "Session Timeout",
[StreamingErrorType.AudioTooShort]: "Audio too short",
[StreamingErrorType.AudioTooLong]: "Audio too long",
[StreamingErrorType.AudioTooSmallToTranscode]: "Audio too small to transcode",
[StreamingErrorType.BadSchema]: "Bad schema",
[StreamingErrorType.TooManyStreams]: "Too many streams",
[StreamingErrorType.Reconnected]: "This session has been reconnected. This WebSocket is no longer valid.",
};
class StreamingError extends Error {
}
class LemurService extends BaseService {
summary(params, signal) {
return this.fetchJson("/lemur/v3/generate/summary", {
method: "POST",
body: JSON.stringify(params),
signal,
});
}
questionAnswer(params, signal) {
return this.fetchJson("/lemur/v3/generate/question-answer", {
method: "POST",
body: JSON.stringify(params),
signal,
});
}
actionItems(params, signal) {
return this.fetchJson("/lemur/v3/generate/action-items", {
method: "POST",
body: JSON.stringify(params),
signal,
});
}
task(params, signal) {
return this.fetchJson("/lemur/v3/generate/task", {
method: "POST",
body: JSON.stringify(params),
signal,
});
}
getResponse(id, signal) {
return this.fetchJson(`/lemur/v3/${id}`, { signal });
}
/**
* Delete the data for a previously submitted LeMUR request.
* @param id - ID of the LeMUR request
* @param signal - Optional AbortSignal to cancel the request
*/
purgeRequestData(id, signal) {
return this.fetchJson(`/lemur/v3/${id}`, {
method: "DELETE",
signal,
});
}
}
const { WritableStream } = typeof window !== "undefined"
? window
: typeof global !== "undefined"
? global
: globalThis;
var _a, _b;
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;
const factory = (url, params) => {
if (params) {
return new PolyfillWebSocket(url, params);
}
return new PolyfillWebSocket(url);
};
const defaultRealtimeUrl = "wss://api.assemblyai.com/v2/realtime/ws";
const forceEndOfUtteranceMessage = `{"force_end_utterance":true}`;
const terminateSessionMessage$1 = `{"terminate_session":true}`;
/**
* RealtimeTranscriber connects to the Streaming Speech-to-Text API and lets you transcribe audio in real-time.
*/
class RealtimeTranscriber {
/**
* Create a new RealtimeTranscriber.
* @param params - Parameters to configure the RealtimeTranscriber
*/
constructor(params) {
var _a, _b;
this.listeners = {};
this.realtimeUrl = (_a = params.realtimeUrl) !== null && _a !== void 0 ? _a : defaultRealtimeUrl;
this.sampleRate = (_b = params.sampleRate) !== null && _b !== void 0 ? _b : 16000;
this.wordBoost = params.wordBoost;
this.encoding = params.encoding;
this.endUtteranceSilenceThreshold = params.endUtteranceSilenceThreshold;
this.disablePartialTranscripts = params.disablePartialTranscripts;
if ("token" in params && params.token)
this.token = params.token;
if ("apiKey" in params && params.apiKey)
this.apiKey = params.apiKey;
if (!(this.token || this.apiKey)) {
throw new Error("API key or temporary token is required.");
}
}
connectionUrl() {
const url = new URL(this.realtimeUrl);
if (url.protocol !== "wss:") {
throw new Error("Invalid protocol, must be wss");
}
const searchParams = new URLSearchParams();
if (this.token) {
searchParams.set("token", this.token);
}
searchParams.set("sample_rate", this.sampleRate.toString());
if (this.wordBoost && this.wordBoost.length > 0) {
searchParams.set("word_boost", JSON.stringify(this.wordBoost));
}
if (this.encoding) {
searchParams.set("encoding", this.encoding);
}
searchParams.set("enable_extra_session_information", "true");
if (this.disablePartialTranscripts) {
searchParams.set("disable_partial_transcripts", this.disablePartialTranscripts.toString());
}
url.search = searchParams.toString();
return url;
}
/**
* Add a listener for an event.
* @param event - The event to listen for.
* @param listener - The function to call when the event is emitted.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
on(event, listener) {
this.listeners[event] = listener;
}
/**
* Connect to the server and begin a new session.
* @returns A promise that resolves when the connection is established and the session begins.
*/
connect() {
return new Promise((resolve) => {
if (this.socket) {
throw new Error("Already connected");
}
const url = this.connectionUrl();
if (this.token) {
this.socket = factory(url.toString());
}
else {
{
console.warn(`API key authentication is not supported for the RealtimeTranscriber in browser environment. Use temporary token authentication instead.
Learn more at https://github.com/AssemblyAI/assemblyai-node-sdk/blob/main/docs/compat.md#browser-compatibility.`);
}
this.socket = factory(url.toString(), {
headers: { Authorization: this.apiKey },
});
}
this.socket.binaryType = "arraybuffer";
this.socket.onopen = () => {
if (this.endUtteranceSilenceThreshold === undefined ||
this.endUtteranceSilenceThreshold === null) {
return;
}
this.configureEndUtteranceSilenceThreshold(this.endUtteranceSilenceThreshold);
};
this.socket.onclose = ({ code, reason }) => {
var _a, _b;
if (!reason) {
if (code in RealtimeErrorMessages) {
reason = RealtimeErrorMessages[code];
}
}
(_b = (_a = this.listeners).close) === null || _b === void 0 ? void 0 : _b.call(_a, code, reason);
};
this.socket.onerror = (event) => {
var _a, _b, _c, _d;
if (event.error)
(_b = (_a = this.listeners).error) === null || _b === void 0 ? void 0 : _b.call(_a, event.error);
else
(_d = (_c = this.listeners).error) === null || _d === void 0 ? void 0 : _d.call(_c, new Error(event.message));
};
this.socket.onmessage = ({ data }) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
const message = JSON.parse(data.toString());
if ("error" in message) {
(_b = (_a = this.listeners).error) === null || _b === void 0 ? void 0 : _b.call(_a, new RealtimeError(message.error));
return;
}
switch (message.message_type) {
case "SessionBegins": {
const openObject = {
sessionId: message.session_id,
expiresAt: new Date(message.expires_at),
};
resolve(openObject);
(_d = (_c = this.listeners).open) === null || _d === void 0 ? void 0 : _d.call(_c, openObject);
break;
}
case "PartialTranscript": {
// message.created is actually a string when coming from the socket
message.created = new Date(message.created);
(_f = (_e = this.listeners).transcript) === null || _f === void 0 ? void 0 : _f.call(_e, message);
(_h = (_g = this.listeners)["transcript.partial"]) === null || _h === void 0 ? void 0 : _h.call(_g, message);
break;
}
case "FinalTranscript": {
// message.created is actually a string when coming from the socket
message.created = new Date(message.created);
(_k = (_j = this.listeners).transcript) === null || _k === void 0 ? void 0 : _k.call(_j, message);
(_m = (_l = this.listeners)["transcript.final"]) === null || _m === void 0 ? void 0 : _m.call(_l, message);
break;
}
case "SessionInformation": {
(_p = (_o = this.listeners).session_information) === null || _p === void 0 ? void 0 : _p.call(_o, message);
break;
}
case "SessionTerminated": {
(_q = this.sessionTerminatedResolve) === null || _q === void 0 ? void 0 : _q.call(this);
break;
}
}
};
});
}
/**
* Send audio data to the server.
* @param audio - The audio data to send to the server.
*/
sendAudio(audio) {
this.send(audio);
}
/**
* Create a writable stream that can be used to send audio data to the server.
* @returns A writable stream that can be used to send audio data to the server.
*/
stream() {
return new WritableStream({
write: (chunk) => {
this.sendAudio(chunk);
},
});
}
/**
* Manually end an utterance
*/
forceEndUtterance() {
this.send(forceEndOfUtteranceMessage);
}
/**
* Configure the threshold for how long to wait before ending an utterance. Default is 700ms.
* @param threshold - The duration of the end utterance silence threshold in milliseconds.
* This value must be an integer between 0 and 20_000.
*/
configureEndUtteranceSilenceThreshold(threshold) {
this.send(`{"end_utterance_silence_threshold":${threshold}}`);
}
send(data) {
if (!this.socket || this.socket.readyState !== this.socket.OPEN) {
throw new Error("Socket is not open for communication");
}
this.socket.send(data);
}
/**
* Close the connection to the server.
* @param waitForSessionTermination - If true, the method will wait for the session to be terminated before closing the connection.
* While waiting for the session to be terminated, you will receive the final transcript and session information.
*/
close() {
return __awaiter(this, arguments, void 0, function* (waitForSessionTermination = true) {
var _a;
if (this.socket) {
if (this.socket.readyState === this.socket.OPEN) {
if (waitForSessionTermination) {
const sessionTerminatedPromise = new Promise((resolve) => {
this.sessionTerminatedResolve = resolve;
});
this.socket.send(terminateSessionMessage$1);
yield sessionTerminatedPromise;
}
else {
this.socket.send(terminateSessionMessage$1);
}
}
if ((_a = this.socket) === null || _a === void 0 ? void 0 : _a.removeAllListeners)
this.socket.removeAllListeners();
this.socket.close();
}
this.listeners = {};
this.socket = undefined;
});
}
}
/**
* @deprecated Use RealtimeTranscriber instead
*/
class RealtimeService extends RealtimeTranscriber {
}
class RealtimeTranscriberFactory extends BaseService {
constructor(params) {
super(params);
this.rtFactoryParams = params;
}
/**
* @deprecated Use transcriber(...) instead
*/
createService(params) {
return this.transcriber(params);
}
transcriber(params) {
const serviceParams = Object.assign({}, params);
if (!serviceParams.token && !serviceParams.apiKey) {
serviceParams.apiKey = this.rtFactoryParams.apiKey;
}
return new RealtimeTranscriber(serviceParams);
}
createTemporaryToken(params) {
return __awaiter(this, void 0, void 0, function* () {
const data = yield this.fetchJson("/v2/realtime/token", {
method: "POST",
body: JSON.stringify(params),
});
return data.token;
});
}
}
/**
* @deprecated Use RealtimeTranscriberFactory instead
*/
class RealtimeServiceFactory extends RealtimeTranscriberFactory {
}
class TranscriptService extends BaseService {
constructor(params, files) {
super(params);
this.files = files;
}
/**
* Transcribe an audio file. This will create a transcript and wait until the transcript status is "completed" or "error".
* @param params - The parameters to transcribe an audio file.
* @param options - The options to transcribe an audio file.
* @returns A promise that resolves to the transcript. The transcript status is "completed" or "error".
*/
transcribe(params, options) {
return __awaiter(this, void 0, void 0, function* () {
const transcript = yield this.submit(params);
return yield this.waitUntilReady(transcript.id, options);
});
}
/**
* Submits a transcription job for an audio file. This will not wait until the transcript status is "completed" or "error".
* @param params - The parameters to start the transcription of an audio file.
* @returns A promise that resolves to the queued transcript.
*/
submit(params) {
return __awaiter(this, void 0, void 0, function* () {
let audioUrl;
let transcriptParams = undefined;
if ("audio" in params) {
const { audio } = params, audioTranscriptParams = __rest(params, ["audio"]);
if (typeof audio === "string") {
const path = getPath(audio);
if (path !== null) {
// audio is local path, upload local file
audioUrl = yield this.files.upload(path);
}
else {
if (audio.startsWith("data:")) {
audioUrl = yield this.files.upload(audio);
}
else {
// audio is not a local path, and not a data-URI, assume it's a normal URL
audioUrl = audio;
}
}
}
else {
// audio is of uploadable type
audioUrl = yield this.files.upload(audio);
}
transcriptParams = Object.assign(Object.assign({}, audioTranscriptParams), { audio_url: audioUrl });
}
else {
transcriptParams = params;
}
const data = yield this.fetchJson("/v2/transcript", {
method: "POST",
body: JSON.stringify(transcriptParams),
});
return data;
});
}
/**
* Create a transcript.
* @param params - The parameters to create a transcript.
* @param options - The options used for creating the new transcript.
* @returns A promise that resolves to the transcript.
* @deprecated Use `transcribe` instead to transcribe a audio file that includes polling, or `submit` to transcribe a audio file without polling.
*/
create(params, options) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const path = getPath(params.audio_url);
if (path !== null) {
const uploadUrl = yield this.files.upload(path);
params.audio_url = uploadUrl;
}
const data = yield this.fetchJson("/v2/transcript", {
method: "POST",
body: JSON.stringify(params),
});
if ((_a = options === null || options === void 0 ? void 0 : options.poll) !== null && _a !== void 0 ? _a : true) {
return yield this.waitUntilReady(data.id, options);
}
return data;
});
}
/**
* Wait until the transcript ready, either the status is "completed" or "error".
* @param transcriptId - The ID of the transcript.
* @param options - The options to wait until the transcript is ready.
* @returns A promise that resolves to the transcript. The transcript status is "completed" or "error".
*/
waitUntilReady(transcriptId, options) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const pollingInterval = (_a = options === null || options === void 0 ? void 0 : options.pollingInterval) !== null && _a !== void 0 ? _a : 3000;
const pollingTimeout = (_b = options === null || options === void 0 ? void 0 : options.pollingTimeout) !== null && _b !== void 0 ? _b : -1;
const startTime = Date.now();