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.
- package/CHANGELOG.md +25 -0
- package/README.md +98 -0
- package/dist/assemblyai.streaming.umd.js +28 -11
- package/dist/assemblyai.streaming.umd.min.js +1 -1
- package/dist/assemblyai.umd.js +488 -83
- package/dist/assemblyai.umd.min.js +1 -1
- package/dist/browser.mjs +430 -78
- package/dist/bun.mjs +420 -67
- package/dist/deno.mjs +420 -67
- package/dist/index.cjs +481 -76
- package/dist/index.mjs +479 -77
- package/dist/node.cjs +418 -62
- package/dist/node.mjs +416 -63
- package/dist/services/base.d.ts +1 -0
- package/dist/services/index.d.ts +7 -1
- package/dist/services/streaming/service.d.ts +2 -1
- package/dist/services/sync/index.d.ts +1 -0
- package/dist/services/sync/service.d.ts +48 -0
- package/dist/streaming.browser.mjs +24 -9
- package/dist/streaming.cjs +27 -10
- package/dist/streaming.mjs +27 -10
- package/dist/types/asyncapi.generated.d.ts +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/services/index.d.ts +1 -0
- package/dist/types/streaming/index.d.ts +14 -4
- package/dist/types/sync/index.d.ts +133 -0
- package/dist/utils/errors/index.d.ts +1 -0
- package/dist/utils/errors/sync.d.ts +20 -0
- package/dist/workerd.mjs +424 -71
- package/package.json +1 -1
- package/src/services/base.ts +18 -8
- package/src/services/index.ts +19 -0
- package/src/services/streaming/service.ts +22 -3
- package/src/services/sync/index.ts +1 -0
- package/src/services/sync/service.ts +369 -0
- package/src/types/asyncapi.generated.ts +6 -1
- package/src/types/index.ts +1 -0
- package/src/types/services/index.ts +1 -0
- package/src/types/streaming/index.ts +16 -3
- package/src/types/sync/index.ts +145 -0
- package/src/utils/errors/index.ts +2 -0
- package/src/utils/errors/sync.ts +25 -0
package/dist/node.cjs
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var web = require('stream/web');
|
|
4
|
-
var ws = require('ws');
|
|
5
3
|
var fs = require('fs');
|
|
6
4
|
var stream = require('stream');
|
|
5
|
+
var web = require('stream/web');
|
|
6
|
+
var ws = require('ws');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The default speech model for synchronous transcription.
|
|
10
|
+
*/
|
|
11
|
+
const defaultSyncSpeechModel = "universal-3-5-pro";
|
|
7
12
|
|
|
8
13
|
/**
|
|
9
14
|
* Thrown when `DualChannelCapture` is constructed in a non-browser environment
|
|
@@ -18,6 +23,8 @@ class BrowserOnlyError extends Error {
|
|
|
18
23
|
}
|
|
19
24
|
}
|
|
20
25
|
|
|
26
|
+
const readFile = async (path) => stream.Readable.toWeb(fs.createReadStream(path));
|
|
27
|
+
|
|
21
28
|
const DEFAULT_FETCH_INIT = {
|
|
22
29
|
cache: "no-store",
|
|
23
30
|
};
|
|
@@ -35,7 +42,7 @@ if (typeof navigator !== "undefined" && navigator.userAgent) {
|
|
|
35
42
|
defaultUserAgentString += navigator.userAgent;
|
|
36
43
|
}
|
|
37
44
|
const defaultUserAgent = {
|
|
38
|
-
sdk: { name: "JavaScript", version: "4.
|
|
45
|
+
sdk: { name: "JavaScript", version: "4.36.4" },
|
|
39
46
|
};
|
|
40
47
|
if (typeof process !== "undefined") {
|
|
41
48
|
if (process.versions.node && defaultUserAgentString.indexOf("Node") === -1) {
|
|
@@ -74,12 +81,15 @@ class BaseService {
|
|
|
74
81
|
this.userAgent = buildUserAgent(params.userAgent || {});
|
|
75
82
|
}
|
|
76
83
|
}
|
|
77
|
-
async
|
|
84
|
+
async fetchResponse(input, init) {
|
|
78
85
|
init = { ...DEFAULT_FETCH_INIT, ...init };
|
|
79
86
|
let headers = {
|
|
80
87
|
Authorization: this.params.apiKey,
|
|
81
|
-
"Content-Type": "application/json",
|
|
82
88
|
};
|
|
89
|
+
// FormData bodies must let fetch set the multipart boundary itself.
|
|
90
|
+
if (!(init.body instanceof FormData)) {
|
|
91
|
+
headers["Content-Type"] = "application/json";
|
|
92
|
+
}
|
|
83
93
|
if (DEFAULT_FETCH_INIT?.headers)
|
|
84
94
|
headers = { ...headers, ...DEFAULT_FETCH_INIT.headers };
|
|
85
95
|
if (init?.headers)
|
|
@@ -90,7 +100,10 @@ class BaseService {
|
|
|
90
100
|
init.headers = headers;
|
|
91
101
|
if (!input.startsWith("http"))
|
|
92
102
|
input = this.params.baseUrl + input;
|
|
93
|
-
|
|
103
|
+
return await fetch(input, init);
|
|
104
|
+
}
|
|
105
|
+
async fetch(input, init) {
|
|
106
|
+
const response = await this.fetchResponse(input, init);
|
|
94
107
|
if (response.status >= 400) {
|
|
95
108
|
let json;
|
|
96
109
|
const text = await response.text();
|
|
@@ -115,52 +128,342 @@ class BaseService {
|
|
|
115
128
|
}
|
|
116
129
|
}
|
|
117
130
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Error thrown when a synchronous transcription request fails.
|
|
133
|
+
*/
|
|
134
|
+
class SyncTranscriptError extends Error {
|
|
135
|
+
/**
|
|
136
|
+
* Create a new SyncTranscriptError.
|
|
137
|
+
* @param message - The human-readable error message.
|
|
138
|
+
* @param status - The HTTP status code of the failed request.
|
|
139
|
+
* @param errorCode - Machine-readable code — the snake_cased
|
|
140
|
+
* problem-details `title` from the server (e.g. `bad_audio`,
|
|
141
|
+
* `audio_too_large`, `capacity_exceeded`, `inference_timeout`).
|
|
142
|
+
* @param retryAfter - Seconds to wait before retrying, from the
|
|
143
|
+
* `Retry-After` header on 429/503 responses.
|
|
144
|
+
*/
|
|
145
|
+
constructor(message, status, errorCode, retryAfter) {
|
|
146
|
+
super(message);
|
|
147
|
+
this.status = status;
|
|
148
|
+
this.errorCode = errorCode;
|
|
149
|
+
this.retryAfter = retryAfter;
|
|
150
|
+
this.name = "SyncTranscriptError";
|
|
132
151
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getPath(path) {
|
|
155
|
+
if (path.startsWith("http"))
|
|
156
|
+
return null;
|
|
157
|
+
if (path.startsWith("https"))
|
|
158
|
+
return null;
|
|
159
|
+
if (path.startsWith("data:"))
|
|
160
|
+
return null;
|
|
161
|
+
if (path.startsWith("file://"))
|
|
162
|
+
return path.substring(7);
|
|
163
|
+
if (path.startsWith("file:"))
|
|
164
|
+
return path.substring(5);
|
|
165
|
+
return path;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Canonical paths since the sync API gained a /v1 prefix (#18103); the
|
|
169
|
+
// unprefixed routes remain served for SDK versions that predate it.
|
|
170
|
+
const transcribeEndpoint = "/v1/transcribe";
|
|
171
|
+
const warmEndpoint = "/v1/warm";
|
|
172
|
+
const modelHeader = "X-AAI-Model";
|
|
173
|
+
// Kept above the server's 30 s deadline so the client doesn't race it.
|
|
174
|
+
const defaultTimeoutMs = 60_000;
|
|
175
|
+
const warmTimeoutMs = 10_000;
|
|
176
|
+
const maxPromptLength = 4096;
|
|
177
|
+
const maxKeytermsPromptLength = 2048;
|
|
178
|
+
const maxContextTurns = 100;
|
|
179
|
+
const maxContextLength = 4096;
|
|
180
|
+
// Extensions that signal raw S16LE PCM rather than a WAV container.
|
|
181
|
+
const pcmSuffixes = [".pcm", ".raw"];
|
|
182
|
+
/**
|
|
183
|
+
* The synchronous transcription service: audio in, transcript out,
|
|
184
|
+
* one request.
|
|
185
|
+
*
|
|
186
|
+
* Unlike `client.transcripts` (which submits a job to the async API and
|
|
187
|
+
* polls for completion), `SyncTranscriber` posts the audio to the sync
|
|
188
|
+
* API and returns the finished transcript in the HTTP response. There is no
|
|
189
|
+
* job id or status to poll. Accepts a local file path, raw audio bytes, a
|
|
190
|
+
* Blob, or a readable stream — but not a URL.
|
|
191
|
+
*/
|
|
192
|
+
class SyncTranscriber extends BaseService {
|
|
193
|
+
/**
|
|
194
|
+
* Create a new synchronous transcription service.
|
|
195
|
+
* @param params - The parameters to use for the service.
|
|
196
|
+
*/
|
|
197
|
+
constructor(params) {
|
|
198
|
+
super(params);
|
|
139
199
|
}
|
|
140
|
-
|
|
141
|
-
|
|
200
|
+
/**
|
|
201
|
+
* Transcribe audio and return the finished transcript in one request.
|
|
202
|
+
* @param audio - A local file path, raw audio bytes, a Blob, or a readable
|
|
203
|
+
* stream. Raw PCM also requires `sample_rate` and `channels` on the config.
|
|
204
|
+
* @param config - Options for this transcription request.
|
|
205
|
+
* @param options - Client-side options, such as the request timeout.
|
|
206
|
+
* @returns A promise that resolves to the finished transcript.
|
|
207
|
+
* @throws SyncTranscriptError when the request fails.
|
|
208
|
+
*/
|
|
209
|
+
async transcribe(audio, config = {}, options = {}) {
|
|
210
|
+
const { bytes, filename, contentType } = await resolveAudio(audio, config);
|
|
211
|
+
const body = new FormData();
|
|
212
|
+
body.append("audio", new Blob([bytes], { type: contentType }), filename);
|
|
213
|
+
const configJson = buildConfigJson(config);
|
|
214
|
+
if (configJson) {
|
|
215
|
+
body.append("config", new Blob([JSON.stringify(configJson)], { type: "application/json" }));
|
|
216
|
+
}
|
|
217
|
+
const response = await this.fetchResponse(transcribeEndpoint, {
|
|
142
218
|
method: "POST",
|
|
143
|
-
body
|
|
144
|
-
|
|
219
|
+
body,
|
|
220
|
+
headers: { [modelHeader]: config.model ?? defaultSyncSpeechModel },
|
|
221
|
+
signal: AbortSignal.timeout(options.timeout ?? defaultTimeoutMs),
|
|
145
222
|
});
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return
|
|
223
|
+
if (response.status !== 200)
|
|
224
|
+
throw await errorFromResponse(response);
|
|
225
|
+
return (await response.json());
|
|
149
226
|
}
|
|
150
227
|
/**
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
228
|
+
* Open the connection to the sync API ahead of time.
|
|
229
|
+
*
|
|
230
|
+
* The sync API is a single request/response, so a `transcribe()` that
|
|
231
|
+
* opens its connection on demand pays the full DNS + TCP + TLS handshake
|
|
232
|
+
* on the critical path. Call `warm()` as soon as you know audio is coming —
|
|
233
|
+
* typically while the clip is still being recorded — so the next
|
|
234
|
+
* `transcribe()` reuses the already-open connection. `warm()` is idempotent
|
|
235
|
+
* and cheap; call it shortly before `transcribe()` so the pooled connection
|
|
236
|
+
* hasn't idled out.
|
|
237
|
+
* @param params - Optionally the model to route the probe to, so the warmed
|
|
238
|
+
* connection lands on the same backend as the eventual transcription.
|
|
239
|
+
* @returns A promise that resolves to `true` once the connection is open
|
|
240
|
+
* (any HTTP response — even a non-200 — means the socket is
|
|
241
|
+
* established), or `false` if the connection could not be opened.
|
|
154
242
|
*/
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
243
|
+
async warm(params) {
|
|
244
|
+
try {
|
|
245
|
+
await this.fetchResponse(warmEndpoint, {
|
|
246
|
+
method: "GET",
|
|
247
|
+
headers: { [modelHeader]: params?.model ?? defaultSyncSpeechModel },
|
|
248
|
+
signal: AbortSignal.timeout(warmTimeoutMs),
|
|
249
|
+
});
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
160
255
|
}
|
|
161
256
|
}
|
|
162
|
-
|
|
163
|
-
|
|
257
|
+
/**
|
|
258
|
+
* Read the audio input into bytes and decide its multipart content type.
|
|
259
|
+
*
|
|
260
|
+
* PCM is selected when the source has a `.pcm`/`.raw` extension or when
|
|
261
|
+
* `sample_rate`/`channels` are set on the config (the fields the sync API
|
|
262
|
+
* requires only for raw PCM) — and both must then be present. Everything
|
|
263
|
+
* else is treated as a WAV container. URLs are rejected — the sync API has
|
|
264
|
+
* no URL ingestion.
|
|
265
|
+
*/
|
|
266
|
+
async function resolveAudio(input, config) {
|
|
267
|
+
let bytes;
|
|
268
|
+
let filename;
|
|
269
|
+
let suffix = "";
|
|
270
|
+
if (typeof input === "string") {
|
|
271
|
+
if (/^https?:\/\//i.test(input)) {
|
|
272
|
+
throw new Error("SyncTranscriber does not accept URLs. Pass a local file path or " +
|
|
273
|
+
"audio bytes, or use client.transcripts for URL/async transcription.");
|
|
274
|
+
}
|
|
275
|
+
if (input.startsWith("data:")) {
|
|
276
|
+
bytes = dataUrlToBytes(input);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
const path = getPath(input) ?? input;
|
|
280
|
+
bytes = await readStream(await readFile(path));
|
|
281
|
+
filename = basename(path);
|
|
282
|
+
suffix = extname(filename);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
else if (input instanceof Uint8Array) {
|
|
286
|
+
bytes = input;
|
|
287
|
+
}
|
|
288
|
+
else if (input instanceof ArrayBuffer) {
|
|
289
|
+
bytes = new Uint8Array(input);
|
|
290
|
+
}
|
|
291
|
+
else if (input instanceof Blob) {
|
|
292
|
+
bytes = new Uint8Array(await input.arrayBuffer());
|
|
293
|
+
// File instances carry a name; the File global itself needs Node >= 20.
|
|
294
|
+
const name = input.name;
|
|
295
|
+
if (name) {
|
|
296
|
+
filename = basename(name);
|
|
297
|
+
suffix = extname(filename);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else if (isWebReadableStream(input)) {
|
|
301
|
+
bytes = await readStream(input);
|
|
302
|
+
}
|
|
303
|
+
else if (isAsyncIterable(input)) {
|
|
304
|
+
bytes = await readAsyncIterable(input);
|
|
305
|
+
// fs.ReadStream carries the path it was opened from.
|
|
306
|
+
const path = input.path;
|
|
307
|
+
if (typeof path === "string") {
|
|
308
|
+
filename = basename(path);
|
|
309
|
+
suffix = extname(filename);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
throw new TypeError("unsupported audio input type");
|
|
314
|
+
}
|
|
315
|
+
const wantsPcm = config.sample_rate !== undefined || config.channels !== undefined;
|
|
316
|
+
const isPcm = pcmSuffixes.includes(suffix) || wantsPcm;
|
|
317
|
+
if (isPcm &&
|
|
318
|
+
(config.sample_rate === undefined || config.channels === undefined)) {
|
|
319
|
+
throw new Error("raw PCM audio requires both sample_rate and channels in the config");
|
|
320
|
+
}
|
|
321
|
+
const contentType = isPcm ? "audio/pcm" : "audio/wav";
|
|
322
|
+
if (!filename)
|
|
323
|
+
filename = isPcm ? "audio.pcm" : "audio.wav";
|
|
324
|
+
return { bytes, filename, contentType };
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Serialize the config to the JSON `config` part, validating and normalizing
|
|
328
|
+
* field values to match the server's caps. The routing `model` is never
|
|
329
|
+
* included — it travels in the `X-AAI-Model` header. Returns `undefined`
|
|
330
|
+
* when there is nothing to send, so the part can be omitted entirely.
|
|
331
|
+
*/
|
|
332
|
+
function buildConfigJson(config) {
|
|
333
|
+
if (config.prompt !== undefined && config.prompt.length > maxPromptLength) {
|
|
334
|
+
throw new Error(`prompt exceeds ${maxPromptLength} characters (got ${config.prompt.length})`);
|
|
335
|
+
}
|
|
336
|
+
const json = {};
|
|
337
|
+
if (config.prompt !== undefined)
|
|
338
|
+
json["prompt"] = config.prompt;
|
|
339
|
+
const keytermsPrompt = normalizeKeytermsPrompt(config.keyterms_prompt);
|
|
340
|
+
if (keytermsPrompt)
|
|
341
|
+
json["keyterms_prompt"] = keytermsPrompt;
|
|
342
|
+
const context = normalizeConversationContext(config.conversation_context);
|
|
343
|
+
if (context)
|
|
344
|
+
json["conversation_context"] = context;
|
|
345
|
+
if (config.language_codes !== undefined)
|
|
346
|
+
json["language_codes"] = config.language_codes;
|
|
347
|
+
if (config.sample_rate !== undefined)
|
|
348
|
+
json["sample_rate"] = config.sample_rate;
|
|
349
|
+
if (config.channels !== undefined)
|
|
350
|
+
json["channels"] = config.channels;
|
|
351
|
+
if (config.timestamps !== undefined)
|
|
352
|
+
json["timestamps"] = config.timestamps;
|
|
353
|
+
return Object.keys(json).length > 0 ? json : undefined;
|
|
354
|
+
}
|
|
355
|
+
function normalizeKeytermsPrompt(keytermsPrompt) {
|
|
356
|
+
if (!keytermsPrompt)
|
|
357
|
+
return undefined;
|
|
358
|
+
const terms = keytermsPrompt
|
|
359
|
+
.map((term) => term.trim())
|
|
360
|
+
.filter((term) => term.length > 0);
|
|
361
|
+
const total = terms.reduce((sum, term) => sum + term.length, 0);
|
|
362
|
+
if (total > maxKeytermsPromptLength) {
|
|
363
|
+
throw new Error(`keyterms_prompt exceeds ${maxKeytermsPromptLength} characters (got ${total})`);
|
|
364
|
+
}
|
|
365
|
+
return terms.length > 0 ? terms : undefined;
|
|
366
|
+
}
|
|
367
|
+
function normalizeConversationContext(context) {
|
|
368
|
+
if (context === undefined)
|
|
369
|
+
return undefined;
|
|
370
|
+
let turns = (typeof context === "string" ? [context] : context)
|
|
371
|
+
.map((turn) => turn.trim())
|
|
372
|
+
.filter((turn) => turn.length > 0);
|
|
373
|
+
let total = turns.reduce((sum, turn) => sum + turn.length, 0);
|
|
374
|
+
// Over-cap context is trimmed oldest-first, never rejected.
|
|
375
|
+
while (turns.length > 0 &&
|
|
376
|
+
(turns.length > maxContextTurns || total > maxContextLength)) {
|
|
377
|
+
total -= turns[0].length;
|
|
378
|
+
turns = turns.slice(1);
|
|
379
|
+
}
|
|
380
|
+
return turns.length > 0 ? turns : undefined;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Build a SyncTranscriptError from a non-200 response. The primary format
|
|
384
|
+
* is an RFC 9457 problem-details body (`status`/`title`/`detail`); legacy
|
|
385
|
+
* `{error_code, message}` and `{detail}`-only bodies are also accepted.
|
|
386
|
+
*/
|
|
387
|
+
async function errorFromResponse(response) {
|
|
388
|
+
let errorCode;
|
|
389
|
+
let message;
|
|
390
|
+
const text = await response.text();
|
|
391
|
+
try {
|
|
392
|
+
const body = JSON.parse(text);
|
|
393
|
+
if (body && typeof body === "object" && !Array.isArray(body)) {
|
|
394
|
+
if (typeof body.error_code === "string")
|
|
395
|
+
errorCode = body.error_code;
|
|
396
|
+
if (errorCode === undefined && typeof body.title === "string") {
|
|
397
|
+
errorCode = body.title.toLowerCase().replace(/ /g, "_");
|
|
398
|
+
}
|
|
399
|
+
if (typeof body.detail === "string")
|
|
400
|
+
message = body.detail;
|
|
401
|
+
else if (typeof body.message === "string")
|
|
402
|
+
message = body.message;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
if (text)
|
|
407
|
+
message = text;
|
|
408
|
+
}
|
|
409
|
+
if (!message) {
|
|
410
|
+
message = `sync transcription failed with status ${response.status}`;
|
|
411
|
+
}
|
|
412
|
+
const retryHeader = response.headers.get("retry-after");
|
|
413
|
+
const retryAfter = retryHeader && /^\d+$/.test(retryHeader)
|
|
414
|
+
? parseInt(retryHeader, 10)
|
|
415
|
+
: undefined;
|
|
416
|
+
return new SyncTranscriptError(message, response.status, errorCode, retryAfter);
|
|
417
|
+
}
|
|
418
|
+
function basename(path) {
|
|
419
|
+
return path.split(/[\\/]/).pop() ?? path;
|
|
420
|
+
}
|
|
421
|
+
function extname(filename) {
|
|
422
|
+
const dotIndex = filename.lastIndexOf(".");
|
|
423
|
+
return dotIndex > 0 ? filename.slice(dotIndex).toLowerCase() : "";
|
|
424
|
+
}
|
|
425
|
+
function dataUrlToBytes(dataUrl) {
|
|
426
|
+
const base64 = dataUrl.split(",")[1];
|
|
427
|
+
const binary = atob(base64);
|
|
428
|
+
const bytes = new Uint8Array(binary.length);
|
|
429
|
+
for (let i = 0; i < binary.length; i++)
|
|
430
|
+
bytes[i] = binary.charCodeAt(i);
|
|
431
|
+
return bytes;
|
|
432
|
+
}
|
|
433
|
+
function isWebReadableStream(input) {
|
|
434
|
+
return typeof input?.getReader === "function";
|
|
435
|
+
}
|
|
436
|
+
function isAsyncIterable(input) {
|
|
437
|
+
return (typeof input?.[Symbol.asyncIterator] ===
|
|
438
|
+
"function");
|
|
439
|
+
}
|
|
440
|
+
async function readStream(stream) {
|
|
441
|
+
const chunks = [];
|
|
442
|
+
const reader = stream.getReader();
|
|
443
|
+
for (;;) {
|
|
444
|
+
const { done, value } = await reader.read();
|
|
445
|
+
if (done)
|
|
446
|
+
break;
|
|
447
|
+
chunks.push(value);
|
|
448
|
+
}
|
|
449
|
+
return concatChunks(chunks);
|
|
450
|
+
}
|
|
451
|
+
async function readAsyncIterable(iterable) {
|
|
452
|
+
const chunks = [];
|
|
453
|
+
for await (const chunk of iterable)
|
|
454
|
+
chunks.push(chunk);
|
|
455
|
+
return concatChunks(chunks);
|
|
456
|
+
}
|
|
457
|
+
function concatChunks(chunks) {
|
|
458
|
+
const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
459
|
+
const bytes = new Uint8Array(total);
|
|
460
|
+
let offset = 0;
|
|
461
|
+
for (const chunk of chunks) {
|
|
462
|
+
bytes.set(chunk, offset);
|
|
463
|
+
offset += chunk.length;
|
|
464
|
+
}
|
|
465
|
+
return bytes;
|
|
466
|
+
}
|
|
164
467
|
|
|
165
468
|
const RealtimeErrorType = {
|
|
166
469
|
BadSampleRate: 4000,
|
|
@@ -266,6 +569,53 @@ const StreamingErrorMessages = {
|
|
|
266
569
|
class StreamingError extends Error {
|
|
267
570
|
}
|
|
268
571
|
|
|
572
|
+
class LemurService extends BaseService {
|
|
573
|
+
summary(params, signal) {
|
|
574
|
+
return this.fetchJson("/lemur/v3/generate/summary", {
|
|
575
|
+
method: "POST",
|
|
576
|
+
body: JSON.stringify(params),
|
|
577
|
+
signal,
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
questionAnswer(params, signal) {
|
|
581
|
+
return this.fetchJson("/lemur/v3/generate/question-answer", {
|
|
582
|
+
method: "POST",
|
|
583
|
+
body: JSON.stringify(params),
|
|
584
|
+
signal,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
actionItems(params, signal) {
|
|
588
|
+
return this.fetchJson("/lemur/v3/generate/action-items", {
|
|
589
|
+
method: "POST",
|
|
590
|
+
body: JSON.stringify(params),
|
|
591
|
+
signal,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
task(params, signal) {
|
|
595
|
+
return this.fetchJson("/lemur/v3/generate/task", {
|
|
596
|
+
method: "POST",
|
|
597
|
+
body: JSON.stringify(params),
|
|
598
|
+
signal,
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
getResponse(id, signal) {
|
|
602
|
+
return this.fetchJson(`/lemur/v3/${id}`, { signal });
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Delete the data for a previously submitted LeMUR request.
|
|
606
|
+
* @param id - ID of the LeMUR request
|
|
607
|
+
* @param signal - Optional AbortSignal to cancel the request
|
|
608
|
+
*/
|
|
609
|
+
purgeRequestData(id, signal) {
|
|
610
|
+
return this.fetchJson(`/lemur/v3/${id}`, {
|
|
611
|
+
method: "DELETE",
|
|
612
|
+
signal,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const factory = (url, params) => new ws(url, params);
|
|
618
|
+
|
|
269
619
|
const defaultRealtimeUrl = "wss://api.assemblyai.com/v2/realtime/ws";
|
|
270
620
|
const forceEndOfUtteranceMessage = `{"force_end_utterance":true}`;
|
|
271
621
|
const terminateSessionMessage$1 = `{"terminate_session":true}`;
|
|
@@ -510,20 +860,6 @@ class RealtimeTranscriberFactory extends BaseService {
|
|
|
510
860
|
class RealtimeServiceFactory extends RealtimeTranscriberFactory {
|
|
511
861
|
}
|
|
512
862
|
|
|
513
|
-
function getPath(path) {
|
|
514
|
-
if (path.startsWith("http"))
|
|
515
|
-
return null;
|
|
516
|
-
if (path.startsWith("https"))
|
|
517
|
-
return null;
|
|
518
|
-
if (path.startsWith("data:"))
|
|
519
|
-
return null;
|
|
520
|
-
if (path.startsWith("file://"))
|
|
521
|
-
return path.substring(7);
|
|
522
|
-
if (path.startsWith("file:"))
|
|
523
|
-
return path.substring(5);
|
|
524
|
-
return path;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
863
|
class TranscriptService extends BaseService {
|
|
528
864
|
constructor(params, files) {
|
|
529
865
|
super(params);
|
|
@@ -751,8 +1087,6 @@ class TranscriptService extends BaseService {
|
|
|
751
1087
|
}
|
|
752
1088
|
}
|
|
753
1089
|
|
|
754
|
-
const readFile = async (path) => stream.Readable.toWeb(fs.createReadStream(path));
|
|
755
|
-
|
|
756
1090
|
class FileService extends BaseService {
|
|
757
1091
|
/**
|
|
758
1092
|
* Upload a local file to AssemblyAI.
|
|
@@ -1033,9 +1367,12 @@ class StreamingTranscriber {
|
|
|
1033
1367
|
if (!(this.token || this.apiKey)) {
|
|
1034
1368
|
throw new Error("API key or temporary token is required.");
|
|
1035
1369
|
}
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
|
|
1370
|
+
const isSelfDescribing = params.encoding === "opus" ||
|
|
1371
|
+
params.encoding === "ogg_opus" ||
|
|
1372
|
+
params.encoding === "aac";
|
|
1373
|
+
if (params.sampleRate === undefined &&
|
|
1374
|
+
(!isSelfDescribing || params.channels)) {
|
|
1375
|
+
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.');
|
|
1039
1376
|
}
|
|
1040
1377
|
if (params.channels) {
|
|
1041
1378
|
if (params.channels.length !== 2) {
|
|
@@ -1113,6 +1450,9 @@ class StreamingTranscriber {
|
|
|
1113
1450
|
if (this.params.formatTurns) {
|
|
1114
1451
|
searchParams.set("format_turns", this.params.formatTurns.toString());
|
|
1115
1452
|
}
|
|
1453
|
+
if (this.params.sessionHeartbeat !== undefined) {
|
|
1454
|
+
searchParams.set("session_heartbeat", this.params.sessionHeartbeat.toString());
|
|
1455
|
+
}
|
|
1116
1456
|
if (this.params.encoding) {
|
|
1117
1457
|
searchParams.set("encoding", this.params.encoding.toString());
|
|
1118
1458
|
}
|
|
@@ -1397,6 +1737,10 @@ class StreamingTranscriber {
|
|
|
1397
1737
|
this.listeners.warning?.(warning);
|
|
1398
1738
|
break;
|
|
1399
1739
|
}
|
|
1740
|
+
case "Heartbeat": {
|
|
1741
|
+
this.listeners.heartbeat?.(message);
|
|
1742
|
+
break;
|
|
1743
|
+
}
|
|
1400
1744
|
case "Termination": {
|
|
1401
1745
|
this.sessionTerminatedResolve?.();
|
|
1402
1746
|
break;
|
|
@@ -2014,6 +2358,7 @@ function float32ToPcm16(input) {
|
|
|
2014
2358
|
|
|
2015
2359
|
const defaultBaseUrl = "https://api.assemblyai.com";
|
|
2016
2360
|
const defaultStreamingUrl = "https://streaming.assemblyai.com";
|
|
2361
|
+
const defaultSyncUrl = "https://sync.assemblyai.com";
|
|
2017
2362
|
class AssemblyAI {
|
|
2018
2363
|
/**
|
|
2019
2364
|
* Create a new AssemblyAI client.
|
|
@@ -2032,6 +2377,14 @@ class AssemblyAI {
|
|
|
2032
2377
|
...params,
|
|
2033
2378
|
baseUrl: params.streamingBaseUrl || defaultStreamingUrl,
|
|
2034
2379
|
});
|
|
2380
|
+
let syncBaseUrl = params.syncBaseUrl || defaultSyncUrl;
|
|
2381
|
+
if (syncBaseUrl.endsWith("/")) {
|
|
2382
|
+
syncBaseUrl = syncBaseUrl.slice(0, -1);
|
|
2383
|
+
}
|
|
2384
|
+
this.sync = new SyncTranscriber({
|
|
2385
|
+
...params,
|
|
2386
|
+
baseUrl: syncBaseUrl,
|
|
2387
|
+
});
|
|
2035
2388
|
}
|
|
2036
2389
|
}
|
|
2037
2390
|
|
|
@@ -2047,9 +2400,12 @@ exports.RealtimeServiceFactory = RealtimeServiceFactory;
|
|
|
2047
2400
|
exports.RealtimeTranscriber = RealtimeTranscriber;
|
|
2048
2401
|
exports.RealtimeTranscriberFactory = RealtimeTranscriberFactory;
|
|
2049
2402
|
exports.StreamingTranscriber = StreamingTranscriber;
|
|
2403
|
+
exports.SyncTranscriber = SyncTranscriber;
|
|
2404
|
+
exports.SyncTranscriptError = SyncTranscriptError;
|
|
2050
2405
|
exports.TranscriptService = TranscriptService;
|
|
2051
2406
|
exports.VadTimeline = VadTimeline;
|
|
2052
2407
|
exports.attributeTurn = attributeTurn;
|
|
2053
2408
|
exports.attributeWord = attributeWord;
|
|
2409
|
+
exports.defaultSyncSpeechModel = defaultSyncSpeechModel;
|
|
2054
2410
|
exports.float32ToPcm16 = float32ToPcm16;
|
|
2055
2411
|
exports.rollUpTurnChannel = rollUpTurnChannel;
|