assemblyai 4.35.4 → 4.36.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +98 -0
- package/dist/assemblyai.streaming.umd.js +13 -6
- package/dist/assemblyai.streaming.umd.min.js +1 -1
- package/dist/assemblyai.umd.js +473 -78
- package/dist/assemblyai.umd.min.js +1 -1
- package/dist/browser.mjs +417 -75
- package/dist/bun.mjs +407 -64
- package/dist/deno.mjs +407 -64
- package/dist/index.cjs +466 -71
- package/dist/index.mjs +464 -72
- package/dist/node.cjs +405 -59
- package/dist/node.mjs +403 -60
- package/dist/services/base.d.ts +1 -0
- package/dist/services/index.d.ts +7 -1
- package/dist/services/sync/index.d.ts +1 -0
- package/dist/services/sync/service.d.ts +48 -0
- package/dist/streaming.browser.mjs +11 -6
- package/dist/streaming.cjs +12 -5
- package/dist/streaming.mjs +12 -5
- package/dist/types/index.d.ts +1 -0
- package/dist/types/services/index.d.ts +1 -0
- 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 +411 -68
- package/package.json +1 -1
- package/src/services/base.ts +18 -8
- package/src/services/index.ts +19 -0
- package/src/services/sync/index.ts +1 -0
- package/src/services/sync/service.ts +369 -0
- package/src/types/index.ts +1 -0
- package/src/types/services/index.ts +1 -0
- package/src/types/sync/index.ts +145 -0
- package/src/utils/errors/index.ts +2 -0
- package/src/utils/errors/sync.ts +25 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { readFile } from "#fs";
|
|
2
|
+
import { BaseService } from "../base";
|
|
3
|
+
import {
|
|
4
|
+
BaseServiceParams,
|
|
5
|
+
SyncAudioInput,
|
|
6
|
+
SyncTranscribeOptions,
|
|
7
|
+
SyncTranscriptResponse,
|
|
8
|
+
SyncTranscriptionConfig,
|
|
9
|
+
} from "../..";
|
|
10
|
+
import { defaultSyncSpeechModel } from "../../types/sync";
|
|
11
|
+
import { SyncTranscriptError } from "../../utils/errors/sync";
|
|
12
|
+
import { getPath } from "../../utils/path";
|
|
13
|
+
|
|
14
|
+
// Canonical paths since the sync API gained a /v1 prefix (#18103); the
|
|
15
|
+
// unprefixed routes remain served for SDK versions that predate it.
|
|
16
|
+
const transcribeEndpoint = "/v1/transcribe";
|
|
17
|
+
const warmEndpoint = "/v1/warm";
|
|
18
|
+
const modelHeader = "X-AAI-Model";
|
|
19
|
+
// Kept above the server's 30 s deadline so the client doesn't race it.
|
|
20
|
+
const defaultTimeoutMs = 60_000;
|
|
21
|
+
const warmTimeoutMs = 10_000;
|
|
22
|
+
const maxPromptLength = 4096;
|
|
23
|
+
const maxKeytermsPromptLength = 2048;
|
|
24
|
+
const maxContextTurns = 100;
|
|
25
|
+
const maxContextLength = 4096;
|
|
26
|
+
// Extensions that signal raw S16LE PCM rather than a WAV container.
|
|
27
|
+
const pcmSuffixes = [".pcm", ".raw"];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The synchronous transcription service: audio in, transcript out,
|
|
31
|
+
* one request.
|
|
32
|
+
*
|
|
33
|
+
* Unlike `client.transcripts` (which submits a job to the async API and
|
|
34
|
+
* polls for completion), `SyncTranscriber` posts the audio to the sync
|
|
35
|
+
* API and returns the finished transcript in the HTTP response. There is no
|
|
36
|
+
* job id or status to poll. Accepts a local file path, raw audio bytes, a
|
|
37
|
+
* Blob, or a readable stream — but not a URL.
|
|
38
|
+
*/
|
|
39
|
+
export class SyncTranscriber extends BaseService {
|
|
40
|
+
/**
|
|
41
|
+
* Create a new synchronous transcription service.
|
|
42
|
+
* @param params - The parameters to use for the service.
|
|
43
|
+
*/
|
|
44
|
+
constructor(params: BaseServiceParams) {
|
|
45
|
+
super(params);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Transcribe audio and return the finished transcript in one request.
|
|
50
|
+
* @param audio - A local file path, raw audio bytes, a Blob, or a readable
|
|
51
|
+
* stream. Raw PCM also requires `sample_rate` and `channels` on the config.
|
|
52
|
+
* @param config - Options for this transcription request.
|
|
53
|
+
* @param options - Client-side options, such as the request timeout.
|
|
54
|
+
* @returns A promise that resolves to the finished transcript.
|
|
55
|
+
* @throws SyncTranscriptError when the request fails.
|
|
56
|
+
*/
|
|
57
|
+
async transcribe(
|
|
58
|
+
audio: SyncAudioInput,
|
|
59
|
+
config: SyncTranscriptionConfig = {},
|
|
60
|
+
options: SyncTranscribeOptions = {},
|
|
61
|
+
): Promise<SyncTranscriptResponse> {
|
|
62
|
+
const { bytes, filename, contentType } = await resolveAudio(audio, config);
|
|
63
|
+
|
|
64
|
+
const body = new FormData();
|
|
65
|
+
body.append(
|
|
66
|
+
"audio",
|
|
67
|
+
new Blob([bytes as BlobPart], { type: contentType }),
|
|
68
|
+
filename,
|
|
69
|
+
);
|
|
70
|
+
const configJson = buildConfigJson(config);
|
|
71
|
+
if (configJson) {
|
|
72
|
+
body.append(
|
|
73
|
+
"config",
|
|
74
|
+
new Blob([JSON.stringify(configJson)], { type: "application/json" }),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const response = await this.fetchResponse(transcribeEndpoint, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
body,
|
|
81
|
+
headers: { [modelHeader]: config.model ?? defaultSyncSpeechModel },
|
|
82
|
+
signal: AbortSignal.timeout(options.timeout ?? defaultTimeoutMs),
|
|
83
|
+
});
|
|
84
|
+
if (response.status !== 200) throw await errorFromResponse(response);
|
|
85
|
+
return (await response.json()) as SyncTranscriptResponse;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Open the connection to the sync API ahead of time.
|
|
90
|
+
*
|
|
91
|
+
* The sync API is a single request/response, so a `transcribe()` that
|
|
92
|
+
* opens its connection on demand pays the full DNS + TCP + TLS handshake
|
|
93
|
+
* on the critical path. Call `warm()` as soon as you know audio is coming —
|
|
94
|
+
* typically while the clip is still being recorded — so the next
|
|
95
|
+
* `transcribe()` reuses the already-open connection. `warm()` is idempotent
|
|
96
|
+
* and cheap; call it shortly before `transcribe()` so the pooled connection
|
|
97
|
+
* hasn't idled out.
|
|
98
|
+
* @param params - Optionally the model to route the probe to, so the warmed
|
|
99
|
+
* connection lands on the same backend as the eventual transcription.
|
|
100
|
+
* @returns A promise that resolves to `true` once the connection is open
|
|
101
|
+
* (any HTTP response — even a non-200 — means the socket is
|
|
102
|
+
* established), or `false` if the connection could not be opened.
|
|
103
|
+
*/
|
|
104
|
+
async warm(params?: { model?: string }): Promise<boolean> {
|
|
105
|
+
try {
|
|
106
|
+
await this.fetchResponse(warmEndpoint, {
|
|
107
|
+
method: "GET",
|
|
108
|
+
headers: { [modelHeader]: params?.model ?? defaultSyncSpeechModel },
|
|
109
|
+
signal: AbortSignal.timeout(warmTimeoutMs),
|
|
110
|
+
});
|
|
111
|
+
return true;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
type ResolvedAudio = {
|
|
119
|
+
bytes: Uint8Array;
|
|
120
|
+
filename: string;
|
|
121
|
+
contentType: string;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Read the audio input into bytes and decide its multipart content type.
|
|
126
|
+
*
|
|
127
|
+
* PCM is selected when the source has a `.pcm`/`.raw` extension or when
|
|
128
|
+
* `sample_rate`/`channels` are set on the config (the fields the sync API
|
|
129
|
+
* requires only for raw PCM) — and both must then be present. Everything
|
|
130
|
+
* else is treated as a WAV container. URLs are rejected — the sync API has
|
|
131
|
+
* no URL ingestion.
|
|
132
|
+
*/
|
|
133
|
+
async function resolveAudio(
|
|
134
|
+
input: SyncAudioInput,
|
|
135
|
+
config: SyncTranscriptionConfig,
|
|
136
|
+
): Promise<ResolvedAudio> {
|
|
137
|
+
let bytes: Uint8Array;
|
|
138
|
+
let filename: string | undefined;
|
|
139
|
+
let suffix = "";
|
|
140
|
+
|
|
141
|
+
if (typeof input === "string") {
|
|
142
|
+
if (/^https?:\/\//i.test(input)) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
"SyncTranscriber does not accept URLs. Pass a local file path or " +
|
|
145
|
+
"audio bytes, or use client.transcripts for URL/async transcription.",
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (input.startsWith("data:")) {
|
|
149
|
+
bytes = dataUrlToBytes(input);
|
|
150
|
+
} else {
|
|
151
|
+
const path = getPath(input) ?? input;
|
|
152
|
+
bytes = await readStream(await readFile(path));
|
|
153
|
+
filename = basename(path);
|
|
154
|
+
suffix = extname(filename);
|
|
155
|
+
}
|
|
156
|
+
} else if (input instanceof Uint8Array) {
|
|
157
|
+
bytes = input;
|
|
158
|
+
} else if (input instanceof ArrayBuffer) {
|
|
159
|
+
bytes = new Uint8Array(input);
|
|
160
|
+
} else if (input instanceof Blob) {
|
|
161
|
+
bytes = new Uint8Array(await input.arrayBuffer());
|
|
162
|
+
// File instances carry a name; the File global itself needs Node >= 20.
|
|
163
|
+
const name = (input as { name?: string }).name;
|
|
164
|
+
if (name) {
|
|
165
|
+
filename = basename(name);
|
|
166
|
+
suffix = extname(filename);
|
|
167
|
+
}
|
|
168
|
+
} else if (isWebReadableStream(input)) {
|
|
169
|
+
bytes = await readStream(input);
|
|
170
|
+
} else if (isAsyncIterable(input)) {
|
|
171
|
+
bytes = await readAsyncIterable(input);
|
|
172
|
+
// fs.ReadStream carries the path it was opened from.
|
|
173
|
+
const path = (input as { path?: string | Buffer }).path;
|
|
174
|
+
if (typeof path === "string") {
|
|
175
|
+
filename = basename(path);
|
|
176
|
+
suffix = extname(filename);
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
throw new TypeError("unsupported audio input type");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const wantsPcm =
|
|
183
|
+
config.sample_rate !== undefined || config.channels !== undefined;
|
|
184
|
+
const isPcm = pcmSuffixes.includes(suffix) || wantsPcm;
|
|
185
|
+
if (
|
|
186
|
+
isPcm &&
|
|
187
|
+
(config.sample_rate === undefined || config.channels === undefined)
|
|
188
|
+
) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
"raw PCM audio requires both sample_rate and channels in the config",
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const contentType = isPcm ? "audio/pcm" : "audio/wav";
|
|
195
|
+
if (!filename) filename = isPcm ? "audio.pcm" : "audio.wav";
|
|
196
|
+
|
|
197
|
+
return { bytes, filename, contentType };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Serialize the config to the JSON `config` part, validating and normalizing
|
|
202
|
+
* field values to match the server's caps. The routing `model` is never
|
|
203
|
+
* included — it travels in the `X-AAI-Model` header. Returns `undefined`
|
|
204
|
+
* when there is nothing to send, so the part can be omitted entirely.
|
|
205
|
+
*/
|
|
206
|
+
function buildConfigJson(
|
|
207
|
+
config: SyncTranscriptionConfig,
|
|
208
|
+
): Record<string, unknown> | undefined {
|
|
209
|
+
if (config.prompt !== undefined && config.prompt.length > maxPromptLength) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`prompt exceeds ${maxPromptLength} characters (got ${config.prompt.length})`,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const json: Record<string, unknown> = {};
|
|
216
|
+
if (config.prompt !== undefined) json["prompt"] = config.prompt;
|
|
217
|
+
const keytermsPrompt = normalizeKeytermsPrompt(config.keyterms_prompt);
|
|
218
|
+
if (keytermsPrompt) json["keyterms_prompt"] = keytermsPrompt;
|
|
219
|
+
const context = normalizeConversationContext(config.conversation_context);
|
|
220
|
+
if (context) json["conversation_context"] = context;
|
|
221
|
+
if (config.language_codes !== undefined)
|
|
222
|
+
json["language_codes"] = config.language_codes;
|
|
223
|
+
if (config.sample_rate !== undefined)
|
|
224
|
+
json["sample_rate"] = config.sample_rate;
|
|
225
|
+
if (config.channels !== undefined) json["channels"] = config.channels;
|
|
226
|
+
if (config.timestamps !== undefined) json["timestamps"] = config.timestamps;
|
|
227
|
+
|
|
228
|
+
return Object.keys(json).length > 0 ? json : undefined;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function normalizeKeytermsPrompt(
|
|
232
|
+
keytermsPrompt?: string[],
|
|
233
|
+
): string[] | undefined {
|
|
234
|
+
if (!keytermsPrompt) return undefined;
|
|
235
|
+
const terms = keytermsPrompt
|
|
236
|
+
.map((term) => term.trim())
|
|
237
|
+
.filter((term) => term.length > 0);
|
|
238
|
+
const total = terms.reduce((sum, term) => sum + term.length, 0);
|
|
239
|
+
if (total > maxKeytermsPromptLength) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`keyterms_prompt exceeds ${maxKeytermsPromptLength} characters (got ${total})`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
return terms.length > 0 ? terms : undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function normalizeConversationContext(
|
|
248
|
+
context?: string | string[],
|
|
249
|
+
): string[] | undefined {
|
|
250
|
+
if (context === undefined) return undefined;
|
|
251
|
+
let turns = (typeof context === "string" ? [context] : context)
|
|
252
|
+
.map((turn) => turn.trim())
|
|
253
|
+
.filter((turn) => turn.length > 0);
|
|
254
|
+
let total = turns.reduce((sum, turn) => sum + turn.length, 0);
|
|
255
|
+
// Over-cap context is trimmed oldest-first, never rejected.
|
|
256
|
+
while (
|
|
257
|
+
turns.length > 0 &&
|
|
258
|
+
(turns.length > maxContextTurns || total > maxContextLength)
|
|
259
|
+
) {
|
|
260
|
+
total -= turns[0].length;
|
|
261
|
+
turns = turns.slice(1);
|
|
262
|
+
}
|
|
263
|
+
return turns.length > 0 ? turns : undefined;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Build a SyncTranscriptError from a non-200 response. The primary format
|
|
268
|
+
* is an RFC 9457 problem-details body (`status`/`title`/`detail`); legacy
|
|
269
|
+
* `{error_code, message}` and `{detail}`-only bodies are also accepted.
|
|
270
|
+
*/
|
|
271
|
+
async function errorFromResponse(
|
|
272
|
+
response: Response,
|
|
273
|
+
): Promise<SyncTranscriptError> {
|
|
274
|
+
let errorCode: string | undefined;
|
|
275
|
+
let message: string | undefined;
|
|
276
|
+
|
|
277
|
+
const text = await response.text();
|
|
278
|
+
try {
|
|
279
|
+
const body = JSON.parse(text);
|
|
280
|
+
if (body && typeof body === "object" && !Array.isArray(body)) {
|
|
281
|
+
if (typeof body.error_code === "string") errorCode = body.error_code;
|
|
282
|
+
if (errorCode === undefined && typeof body.title === "string") {
|
|
283
|
+
errorCode = body.title.toLowerCase().replace(/ /g, "_");
|
|
284
|
+
}
|
|
285
|
+
if (typeof body.detail === "string") message = body.detail;
|
|
286
|
+
else if (typeof body.message === "string") message = body.message;
|
|
287
|
+
}
|
|
288
|
+
} catch {
|
|
289
|
+
if (text) message = text;
|
|
290
|
+
}
|
|
291
|
+
if (!message) {
|
|
292
|
+
message = `sync transcription failed with status ${response.status}`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const retryHeader = response.headers.get("retry-after");
|
|
296
|
+
const retryAfter =
|
|
297
|
+
retryHeader && /^\d+$/.test(retryHeader)
|
|
298
|
+
? parseInt(retryHeader, 10)
|
|
299
|
+
: undefined;
|
|
300
|
+
|
|
301
|
+
return new SyncTranscriptError(
|
|
302
|
+
message,
|
|
303
|
+
response.status,
|
|
304
|
+
errorCode,
|
|
305
|
+
retryAfter,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function basename(path: string): string {
|
|
310
|
+
return path.split(/[\\/]/).pop() ?? path;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function extname(filename: string): string {
|
|
314
|
+
const dotIndex = filename.lastIndexOf(".");
|
|
315
|
+
return dotIndex > 0 ? filename.slice(dotIndex).toLowerCase() : "";
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function dataUrlToBytes(dataUrl: string): Uint8Array {
|
|
319
|
+
const base64 = dataUrl.split(",")[1];
|
|
320
|
+
const binary = atob(base64);
|
|
321
|
+
const bytes = new Uint8Array(binary.length);
|
|
322
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
323
|
+
return bytes;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function isWebReadableStream(
|
|
327
|
+
input: unknown,
|
|
328
|
+
): input is ReadableStream<Uint8Array> {
|
|
329
|
+
return typeof (input as ReadableStream<Uint8Array>)?.getReader === "function";
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function isAsyncIterable(input: unknown): input is AsyncIterable<Uint8Array> {
|
|
333
|
+
return (
|
|
334
|
+
typeof (input as AsyncIterable<Uint8Array>)?.[Symbol.asyncIterator] ===
|
|
335
|
+
"function"
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async function readStream(
|
|
340
|
+
stream: ReadableStream<Uint8Array>,
|
|
341
|
+
): Promise<Uint8Array> {
|
|
342
|
+
const chunks: Uint8Array[] = [];
|
|
343
|
+
const reader = stream.getReader();
|
|
344
|
+
for (;;) {
|
|
345
|
+
const { done, value } = await reader.read();
|
|
346
|
+
if (done) break;
|
|
347
|
+
chunks.push(value);
|
|
348
|
+
}
|
|
349
|
+
return concatChunks(chunks);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function readAsyncIterable(
|
|
353
|
+
iterable: AsyncIterable<Uint8Array>,
|
|
354
|
+
): Promise<Uint8Array> {
|
|
355
|
+
const chunks: Uint8Array[] = [];
|
|
356
|
+
for await (const chunk of iterable) chunks.push(chunk);
|
|
357
|
+
return concatChunks(chunks);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function concatChunks(chunks: Uint8Array[]): Uint8Array {
|
|
361
|
+
const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
362
|
+
const bytes = new Uint8Array(total);
|
|
363
|
+
let offset = 0;
|
|
364
|
+
for (const chunk of chunks) {
|
|
365
|
+
bytes.set(chunk, offset);
|
|
366
|
+
offset += chunk.length;
|
|
367
|
+
}
|
|
368
|
+
return bytes;
|
|
369
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { LiteralUnion } from "../helpers";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The speech models available on the synchronous transcription API.
|
|
5
|
+
*/
|
|
6
|
+
export type SyncSpeechModel = LiteralUnion<"universal-3-5-pro", string>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The default speech model for synchronous transcription.
|
|
10
|
+
*/
|
|
11
|
+
export const defaultSyncSpeechModel: SyncSpeechModel = "universal-3-5-pro";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Audio input for synchronous transcription: a local file path or
|
|
15
|
+
* data URL (file system access requires Node.js, Bun, or Deno), raw audio
|
|
16
|
+
* bytes, a Blob/File, or a readable stream.
|
|
17
|
+
*
|
|
18
|
+
* URLs are not accepted — the sync API has no URL ingestion; use
|
|
19
|
+
* `client.transcripts` for URL or asynchronous transcription.
|
|
20
|
+
*/
|
|
21
|
+
export type SyncAudioInput =
|
|
22
|
+
| string
|
|
23
|
+
| Uint8Array
|
|
24
|
+
| ArrayBuffer
|
|
25
|
+
| Blob
|
|
26
|
+
| ReadableStream<Uint8Array>
|
|
27
|
+
| NodeJS.ReadableStream;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Options for a synchronous transcription request.
|
|
31
|
+
*
|
|
32
|
+
* `sample_rate` and `channels` are required only for raw PCM audio — WAV
|
|
33
|
+
* carries them in its header. `model` is sent as the `X-AAI-Model` routing
|
|
34
|
+
* header and is never included in the request body.
|
|
35
|
+
*/
|
|
36
|
+
export type SyncTranscriptionConfig = {
|
|
37
|
+
/**
|
|
38
|
+
* The sync speech model to route to, sent as the `X-AAI-Model` header.
|
|
39
|
+
* Defaults to `"universal-3-5-pro"`.
|
|
40
|
+
*/
|
|
41
|
+
model?: SyncSpeechModel;
|
|
42
|
+
/**
|
|
43
|
+
* Custom transcription instruction. Maximum 4096 characters — longer
|
|
44
|
+
* prompts are rejected.
|
|
45
|
+
*/
|
|
46
|
+
prompt?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Terms to bias the decoder towards. Whitespace is stripped and empty
|
|
49
|
+
* terms are dropped. Maximum 2048 characters in total — longer lists are
|
|
50
|
+
* rejected.
|
|
51
|
+
*/
|
|
52
|
+
keyterms_prompt?: string[];
|
|
53
|
+
/**
|
|
54
|
+
* Prior turns from the same conversation, oldest first, most recent last.
|
|
55
|
+
* A single string is treated as one turn. Capped at 100 turns and 4096
|
|
56
|
+
* characters in total — over-cap context is trimmed (oldest turns dropped
|
|
57
|
+
* first), not rejected.
|
|
58
|
+
*/
|
|
59
|
+
conversation_context?: string | string[];
|
|
60
|
+
/**
|
|
61
|
+
* ISO 639-1 codes for the language(s) of the audio — a single-element
|
|
62
|
+
* array (e.g. `["es"]`) for monolingual audio, or several codes (e.g.
|
|
63
|
+
* `["en", "es"]`) for multilingual audio. Ignored when `prompt` is set.
|
|
64
|
+
* Defaults to English.
|
|
65
|
+
*/
|
|
66
|
+
language_codes?: string[];
|
|
67
|
+
/**
|
|
68
|
+
* The source sample rate in Hz. Required for raw PCM audio; ignored for
|
|
69
|
+
* WAV.
|
|
70
|
+
*/
|
|
71
|
+
sample_rate?: number;
|
|
72
|
+
/**
|
|
73
|
+
* The channel count (1 for mono, 2 for stereo). Required for raw PCM
|
|
74
|
+
* audio; ignored for WAV.
|
|
75
|
+
*/
|
|
76
|
+
channels?: number;
|
|
77
|
+
/**
|
|
78
|
+
* Whether to compute per-word `start`/`end` timestamps. When `true`,
|
|
79
|
+
* words carry accurate timestamps at a small latency cost. Defaults to
|
|
80
|
+
* `false`: no timestamps are returned.
|
|
81
|
+
*/
|
|
82
|
+
timestamps?: boolean;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Client-side options for a synchronous transcription request.
|
|
87
|
+
* These are not sent to the server.
|
|
88
|
+
*/
|
|
89
|
+
export type SyncTranscribeOptions = {
|
|
90
|
+
/**
|
|
91
|
+
* The request timeout in milliseconds. Defaults to 60 000, which is kept
|
|
92
|
+
* above the server's 30 s deadline so the client doesn't race it.
|
|
93
|
+
*/
|
|
94
|
+
timeout?: number;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* A single word in a sync transcript.
|
|
99
|
+
*
|
|
100
|
+
* `start`/`end` are in milliseconds and present only when the request set
|
|
101
|
+
* `timestamps: true`; otherwise they are omitted.
|
|
102
|
+
*/
|
|
103
|
+
export type SyncWord = {
|
|
104
|
+
/** The text of the word. */
|
|
105
|
+
text: string;
|
|
106
|
+
/**
|
|
107
|
+
* The start time of the word in milliseconds. Absent unless `timestamps`
|
|
108
|
+
* was requested.
|
|
109
|
+
*/
|
|
110
|
+
start?: number;
|
|
111
|
+
/**
|
|
112
|
+
* The end time of the word in milliseconds. Absent unless `timestamps`
|
|
113
|
+
* was requested.
|
|
114
|
+
*/
|
|
115
|
+
end?: number;
|
|
116
|
+
/** The confidence score of the word, in the range 0-1. */
|
|
117
|
+
confidence: number;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The result of a synchronous transcription request.
|
|
122
|
+
*/
|
|
123
|
+
export type SyncTranscriptResponse = {
|
|
124
|
+
/** The full transcript text. */
|
|
125
|
+
text: string;
|
|
126
|
+
/**
|
|
127
|
+
* Per-word confidence, plus `start`/`end` timings when the request set
|
|
128
|
+
* `timestamps: true`.
|
|
129
|
+
*/
|
|
130
|
+
words: SyncWord[];
|
|
131
|
+
/** The overall transcript confidence, in the range 0-1. */
|
|
132
|
+
confidence: number;
|
|
133
|
+
/** The total audio duration in milliseconds. */
|
|
134
|
+
audio_duration_ms: number;
|
|
135
|
+
/**
|
|
136
|
+
* The server-generated UUID for this request. Record it to correlate a
|
|
137
|
+
* request with support.
|
|
138
|
+
*/
|
|
139
|
+
session_id: string;
|
|
140
|
+
/**
|
|
141
|
+
* The end-to-end server-side request time in milliseconds. `undefined`
|
|
142
|
+
* when the server predates the field.
|
|
143
|
+
*/
|
|
144
|
+
request_time_ms?: number;
|
|
145
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown when a synchronous transcription request fails.
|
|
3
|
+
*/
|
|
4
|
+
export class SyncTranscriptError extends Error {
|
|
5
|
+
override name = "SyncTranscriptError";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create a new SyncTranscriptError.
|
|
9
|
+
* @param message - The human-readable error message.
|
|
10
|
+
* @param status - The HTTP status code of the failed request.
|
|
11
|
+
* @param errorCode - Machine-readable code — the snake_cased
|
|
12
|
+
* problem-details `title` from the server (e.g. `bad_audio`,
|
|
13
|
+
* `audio_too_large`, `capacity_exceeded`, `inference_timeout`).
|
|
14
|
+
* @param retryAfter - Seconds to wait before retrying, from the
|
|
15
|
+
* `Retry-After` header on 429/503 responses.
|
|
16
|
+
*/
|
|
17
|
+
constructor(
|
|
18
|
+
message: string,
|
|
19
|
+
public readonly status?: number,
|
|
20
|
+
public readonly errorCode?: string,
|
|
21
|
+
public readonly retryAfter?: number,
|
|
22
|
+
) {
|
|
23
|
+
super(message);
|
|
24
|
+
}
|
|
25
|
+
}
|