@ai-sdk/xai 4.0.5 → 4.0.6
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 +10 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.js +307 -3
- package/dist/index.js.map +1 -1
- package/docs/01-xai.mdx +30 -3
- package/package.json +6 -6
- package/src/xai-provider.ts +8 -0
- package/src/xai-transcription-model-options.ts +27 -0
- package/src/xai-transcription-model.ts +381 -2
package/docs/01-xai.mdx
CHANGED
|
@@ -725,11 +725,38 @@ const result = await transcribe({
|
|
|
725
725
|
|
|
726
726
|
Includes filler words such as `uh` and `um` in the transcript.
|
|
727
727
|
|
|
728
|
+
- **streaming** _object_
|
|
729
|
+
|
|
730
|
+
Options for streaming speech-to-text over WebSocket. Use with `experimental_streamTranscribe`.
|
|
731
|
+
- **interimResults** _boolean_
|
|
732
|
+
|
|
733
|
+
Emits partial transcripts while speech is being processed.
|
|
734
|
+
|
|
735
|
+
- **endpointing** _number_
|
|
736
|
+
|
|
737
|
+
Silence duration in milliseconds before an utterance-final event. Range: 0-5000.
|
|
738
|
+
|
|
739
|
+
- **smartTurn** _number_
|
|
740
|
+
|
|
741
|
+
End-of-turn detection threshold. When set, enables Smart Turn. Range: 0.0-1.0.
|
|
742
|
+
|
|
743
|
+
- **smartTurnTimeout** _number_
|
|
744
|
+
|
|
745
|
+
Maximum silence duration in milliseconds before forcing `speech_final`. Range: 1-5000.
|
|
746
|
+
|
|
728
747
|
### Model Capabilities
|
|
729
748
|
|
|
730
|
-
| Model | Word Timestamps | Diarization | Multichannel |
|
|
731
|
-
| --------- | ------------------- | ------------------- | ------------------- |
|
|
732
|
-
| `default` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
|
749
|
+
| Model | Request/Response | Streaming | Word Timestamps | Diarization | Multichannel |
|
|
750
|
+
| --------- | ------------------- | ------------------- | ------------------- | ------------------- | ------------------- |
|
|
751
|
+
| `default` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
|
752
|
+
|
|
753
|
+
<Note>
|
|
754
|
+
Word timestamps, diarization results, and segments are only returned on the
|
|
755
|
+
request/response path (`transcribe`). Streaming transcription
|
|
756
|
+
(`experimental_streamTranscribe`) emits partial and final transcript text with
|
|
757
|
+
utterance-level timing, but does not surface per-word timestamps, speaker
|
|
758
|
+
labels, or segments.
|
|
759
|
+
</Note>
|
|
733
760
|
|
|
734
761
|
## Image Models
|
|
735
762
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/xai",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -29,17 +29,17 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@ai-sdk/
|
|
33
|
-
"@ai-sdk/provider
|
|
34
|
-
"@ai-sdk/
|
|
32
|
+
"@ai-sdk/openai-compatible": "3.0.5",
|
|
33
|
+
"@ai-sdk/provider": "4.0.2",
|
|
34
|
+
"@ai-sdk/provider-utils": "5.0.5"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "22.19.19",
|
|
38
38
|
"tsup": "^8.5.1",
|
|
39
39
|
"typescript": "5.8.3",
|
|
40
40
|
"zod": "3.25.76",
|
|
41
|
-
"@
|
|
42
|
-
"@ai-
|
|
41
|
+
"@ai-sdk/test-server": "2.0.0",
|
|
42
|
+
"@vercel/ai-tsconfig": "0.0.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"zod": "^3.25.76 || ^4.1.8"
|
package/src/xai-provider.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
withoutTrailingSlash,
|
|
17
17
|
withUserAgentSuffix,
|
|
18
18
|
type FetchFunction,
|
|
19
|
+
type WebSocketConstructor,
|
|
19
20
|
} from '@ai-sdk/provider-utils';
|
|
20
21
|
import { XaiChatLanguageModel } from './xai-chat-language-model';
|
|
21
22
|
import type { XaiChatModelId } from './xai-chat-language-model-options';
|
|
@@ -129,6 +130,12 @@ export interface XaiProviderSettings {
|
|
|
129
130
|
* or to provide a custom fetch implementation for e.g. testing.
|
|
130
131
|
*/
|
|
131
132
|
fetch?: FetchFunction;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Custom WebSocket implementation. Required in runtimes whose native
|
|
136
|
+
* WebSocket constructor does not support headers for xAI streaming STT.
|
|
137
|
+
*/
|
|
138
|
+
webSocket?: WebSocketConstructor;
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
export function createXai(options: XaiProviderSettings = {}): XaiProvider {
|
|
@@ -210,6 +217,7 @@ export function createXai(options: XaiProviderSettings = {}): XaiProvider {
|
|
|
210
217
|
baseURL,
|
|
211
218
|
headers: getHeaders,
|
|
212
219
|
fetch: options.fetch,
|
|
220
|
+
webSocket: options.webSocket,
|
|
213
221
|
});
|
|
214
222
|
};
|
|
215
223
|
|
|
@@ -61,6 +61,33 @@ export const xaiTranscriptionModelOptionsSchema = lazySchema(() =>
|
|
|
61
61
|
* Include filler words such as "uh" and "um" in the transcript.
|
|
62
62
|
*/
|
|
63
63
|
fillerWords: z.boolean().nullish(),
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Options for streaming speech-to-text over WebSocket.
|
|
67
|
+
*/
|
|
68
|
+
streaming: z
|
|
69
|
+
.object({
|
|
70
|
+
/**
|
|
71
|
+
* Emit interim transcript results while speech is being processed.
|
|
72
|
+
*/
|
|
73
|
+
interimResults: z.boolean().optional(),
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Silence duration in milliseconds before an utterance-final event.
|
|
77
|
+
*/
|
|
78
|
+
endpointing: z.number().int().min(0).max(5000).optional(),
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* End-of-turn detection threshold. When set, enables Smart Turn.
|
|
82
|
+
*/
|
|
83
|
+
smartTurn: z.number().min(0).max(1).optional(),
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Maximum silence duration in milliseconds before forcing speech_final.
|
|
87
|
+
*/
|
|
88
|
+
smartTurnTimeout: z.number().int().min(1).max(5000).optional(),
|
|
89
|
+
})
|
|
90
|
+
.optional(),
|
|
64
91
|
}),
|
|
65
92
|
),
|
|
66
93
|
);
|
|
@@ -1,30 +1,56 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
InvalidArgumentError,
|
|
3
|
+
type Experimental_TranscriptionModelV4StreamOptions as TranscriptionModelV4StreamOptions,
|
|
4
|
+
type SharedV4Warning,
|
|
5
|
+
type TranscriptionModelV4,
|
|
6
|
+
} from '@ai-sdk/provider';
|
|
2
7
|
import {
|
|
3
8
|
combineHeaders,
|
|
4
9
|
convertBase64ToUint8Array,
|
|
5
10
|
createJsonResponseHandler,
|
|
11
|
+
getWebSocketConstructor,
|
|
6
12
|
mediaTypeToExtension,
|
|
7
13
|
parseProviderOptions,
|
|
8
14
|
postFormDataToApi,
|
|
15
|
+
readWebSocketMessageText,
|
|
16
|
+
safeParseJSON,
|
|
9
17
|
serializeModelOptions,
|
|
18
|
+
toWebSocketUrl,
|
|
10
19
|
WORKFLOW_DESERIALIZE,
|
|
11
20
|
WORKFLOW_SERIALIZE,
|
|
12
21
|
type FetchFunction,
|
|
22
|
+
type WebSocketConstructor,
|
|
13
23
|
} from '@ai-sdk/provider-utils';
|
|
14
24
|
import { z } from 'zod/v4';
|
|
15
25
|
import { xaiFailedResponseHandler } from './xai-error';
|
|
16
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
xaiTranscriptionModelOptionsSchema,
|
|
28
|
+
type XaiTranscriptionModelOptions,
|
|
29
|
+
} from './xai-transcription-model-options';
|
|
17
30
|
|
|
18
31
|
interface XaiTranscriptionModelConfig {
|
|
19
32
|
provider: string;
|
|
20
33
|
baseURL: string | undefined;
|
|
21
34
|
headers?: () => Record<string, string | undefined>;
|
|
22
35
|
fetch?: FetchFunction;
|
|
36
|
+
webSocket?: WebSocketConstructor;
|
|
23
37
|
_internal?: {
|
|
24
38
|
currentDate?: () => Date;
|
|
25
39
|
};
|
|
26
40
|
}
|
|
27
41
|
|
|
42
|
+
type XaiStreamingTranscriptionEvent = {
|
|
43
|
+
type?: string;
|
|
44
|
+
text?: string;
|
|
45
|
+
words?: Array<{ text?: string; start?: number; end?: number }>;
|
|
46
|
+
is_final?: boolean;
|
|
47
|
+
speech_final?: boolean;
|
|
48
|
+
start?: number;
|
|
49
|
+
duration?: number;
|
|
50
|
+
channel_index?: number;
|
|
51
|
+
message?: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
28
54
|
export class XaiTranscriptionModel implements TranscriptionModelV4 {
|
|
29
55
|
readonly specificationVersion = 'v4';
|
|
30
56
|
|
|
@@ -148,6 +174,359 @@ export class XaiTranscriptionModel implements TranscriptionModelV4 {
|
|
|
148
174
|
},
|
|
149
175
|
};
|
|
150
176
|
}
|
|
177
|
+
|
|
178
|
+
async doStream(
|
|
179
|
+
options: TranscriptionModelV4StreamOptions,
|
|
180
|
+
): Promise<
|
|
181
|
+
Awaited<ReturnType<NonNullable<TranscriptionModelV4['doStream']>>>
|
|
182
|
+
> {
|
|
183
|
+
const currentDate = this.config._internal?.currentDate?.() ?? new Date();
|
|
184
|
+
const warnings: SharedV4Warning[] = [];
|
|
185
|
+
const xaiOptions = await parseProviderOptions({
|
|
186
|
+
provider: 'xai',
|
|
187
|
+
providerOptions: options.providerOptions,
|
|
188
|
+
schema: xaiTranscriptionModelOptionsSchema,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (xaiOptions?.multichannel === true && xaiOptions.channels == null) {
|
|
192
|
+
throw new InvalidArgumentError({
|
|
193
|
+
argument: 'providerOptions',
|
|
194
|
+
message:
|
|
195
|
+
'providerOptions.xai.channels is required when providerOptions.xai.multichannel is true',
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (xaiOptions?.format != null) {
|
|
200
|
+
warnings.push({
|
|
201
|
+
type: 'unsupported',
|
|
202
|
+
feature: 'providerOptions.xai.format',
|
|
203
|
+
details: 'xAI streaming transcription does not support format.',
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (
|
|
208
|
+
xaiOptions?.audioFormat == null &&
|
|
209
|
+
!isKnownInputAudioFormat(options.inputAudioFormat.type)
|
|
210
|
+
) {
|
|
211
|
+
warnings.push({
|
|
212
|
+
type: 'other',
|
|
213
|
+
message:
|
|
214
|
+
`Unrecognized inputAudioFormat.type "${options.inputAudioFormat.type}"; ` +
|
|
215
|
+
`falling back to raw PCM encoding. ` +
|
|
216
|
+
`Use audio/pcm, audio/pcmu, or audio/pcma, ` +
|
|
217
|
+
`or set providerOptions.xai.audioFormat explicitly.`,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const url = buildXaiStreamingTranscriptionUrl({
|
|
222
|
+
baseURL: this.config.baseURL ?? 'https://api.x.ai/v1',
|
|
223
|
+
inputAudioFormat: options.inputAudioFormat,
|
|
224
|
+
providerOptions: xaiOptions,
|
|
225
|
+
});
|
|
226
|
+
const headers = combineHeaders(this.config.headers?.(), options.headers);
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
request: { body: url.toString() },
|
|
230
|
+
response: {
|
|
231
|
+
timestamp: currentDate,
|
|
232
|
+
modelId: this.modelId,
|
|
233
|
+
},
|
|
234
|
+
stream: createXaiStreamingTranscriptionStream({
|
|
235
|
+
webSocket: this.config.webSocket,
|
|
236
|
+
url,
|
|
237
|
+
headers,
|
|
238
|
+
warnings,
|
|
239
|
+
language: xaiOptions?.language ?? undefined,
|
|
240
|
+
expectedDoneCount:
|
|
241
|
+
xaiOptions?.multichannel === true ? xaiOptions.channels! : 1,
|
|
242
|
+
audio: options.audio,
|
|
243
|
+
abortSignal: options.abortSignal,
|
|
244
|
+
includeRawChunks: options.includeRawChunks,
|
|
245
|
+
}),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function createXaiStreamingTranscriptionStream({
|
|
251
|
+
webSocket,
|
|
252
|
+
url,
|
|
253
|
+
headers,
|
|
254
|
+
warnings,
|
|
255
|
+
language,
|
|
256
|
+
expectedDoneCount,
|
|
257
|
+
audio,
|
|
258
|
+
abortSignal,
|
|
259
|
+
includeRawChunks,
|
|
260
|
+
}: {
|
|
261
|
+
webSocket: WebSocketConstructor | undefined;
|
|
262
|
+
url: URL;
|
|
263
|
+
headers: Record<string, string | undefined>;
|
|
264
|
+
warnings: SharedV4Warning[];
|
|
265
|
+
language: string | undefined;
|
|
266
|
+
expectedDoneCount: number;
|
|
267
|
+
audio: ReadableStream<Uint8Array | string>;
|
|
268
|
+
abortSignal: AbortSignal | undefined;
|
|
269
|
+
includeRawChunks: boolean | undefined;
|
|
270
|
+
}) {
|
|
271
|
+
let finished = false;
|
|
272
|
+
let cleanup: (closeCode?: number) => void = () => {};
|
|
273
|
+
|
|
274
|
+
return new ReadableStream({
|
|
275
|
+
start: controller => {
|
|
276
|
+
const WebSocketConstructor = getWebSocketConstructor(webSocket);
|
|
277
|
+
const ws = new WebSocketConstructor(url, undefined, { headers });
|
|
278
|
+
const doneTexts = new Map<number, string>();
|
|
279
|
+
let doneDuration: number | undefined;
|
|
280
|
+
let audioReader:
|
|
281
|
+
| ReadableStreamDefaultReader<Uint8Array | string>
|
|
282
|
+
| undefined;
|
|
283
|
+
|
|
284
|
+
cleanup = (closeCode?: number) => {
|
|
285
|
+
abortSignal?.removeEventListener('abort', abort);
|
|
286
|
+
void audioReader?.cancel().catch(() => {});
|
|
287
|
+
try {
|
|
288
|
+
ws.close(closeCode);
|
|
289
|
+
} catch {}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const finishWithError = (error: unknown) => {
|
|
293
|
+
if (finished) return;
|
|
294
|
+
finished = true;
|
|
295
|
+
cleanup();
|
|
296
|
+
controller.error(error);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const maybeFinish = () => {
|
|
300
|
+
if (finished || doneTexts.size < expectedDoneCount) return;
|
|
301
|
+
finished = true;
|
|
302
|
+
const text = [...doneTexts.entries()]
|
|
303
|
+
.sort(([a], [b]) => a - b)
|
|
304
|
+
.map(([, value]) => value)
|
|
305
|
+
.join('\n');
|
|
306
|
+
controller.enqueue({
|
|
307
|
+
type: 'finish',
|
|
308
|
+
text,
|
|
309
|
+
segments: [],
|
|
310
|
+
language,
|
|
311
|
+
durationInSeconds: doneDuration,
|
|
312
|
+
});
|
|
313
|
+
controller.close();
|
|
314
|
+
cleanup(1000);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const abort = () => {
|
|
318
|
+
finishWithError(abortSignal?.reason ?? new Error('Aborted'));
|
|
319
|
+
};
|
|
320
|
+
if (abortSignal?.aborted) {
|
|
321
|
+
abort();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
abortSignal?.addEventListener('abort', abort, { once: true });
|
|
325
|
+
|
|
326
|
+
const sendAudio = async () => {
|
|
327
|
+
audioReader = audio.getReader();
|
|
328
|
+
try {
|
|
329
|
+
while (true) {
|
|
330
|
+
const { done, value } = await audioReader.read();
|
|
331
|
+
if (done || finished) break;
|
|
332
|
+
ws.send(
|
|
333
|
+
value instanceof Uint8Array
|
|
334
|
+
? value
|
|
335
|
+
: convertBase64ToUint8Array(value),
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
} finally {
|
|
339
|
+
audioReader.releaseLock();
|
|
340
|
+
}
|
|
341
|
+
if (!finished) {
|
|
342
|
+
ws.send(JSON.stringify({ type: 'audio.done' }));
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
ws.onmessage = event => {
|
|
347
|
+
void readWebSocketMessageText(event.data)
|
|
348
|
+
.then(async text => {
|
|
349
|
+
const parsed = await safeParseJSON({ text });
|
|
350
|
+
if (!parsed.success) return;
|
|
351
|
+
const raw = parsed.value as XaiStreamingTranscriptionEvent;
|
|
352
|
+
|
|
353
|
+
if (includeRawChunks) {
|
|
354
|
+
controller.enqueue({ type: 'raw', rawValue: raw });
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
switch (raw.type) {
|
|
358
|
+
case 'transcript.created': {
|
|
359
|
+
controller.enqueue({ type: 'stream-start', warnings });
|
|
360
|
+
void sendAudio().catch(finishWithError);
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
case 'transcript.partial': {
|
|
365
|
+
const id = channelId(raw.channel_index);
|
|
366
|
+
const timing = timingFromXaiEvent(raw);
|
|
367
|
+
if (raw.is_final) {
|
|
368
|
+
controller.enqueue({
|
|
369
|
+
type: 'transcript-final',
|
|
370
|
+
id,
|
|
371
|
+
text: raw.text ?? '',
|
|
372
|
+
...timing,
|
|
373
|
+
channelIndex: raw.channel_index,
|
|
374
|
+
});
|
|
375
|
+
} else {
|
|
376
|
+
controller.enqueue({
|
|
377
|
+
type: 'transcript-partial',
|
|
378
|
+
id,
|
|
379
|
+
text: raw.text ?? '',
|
|
380
|
+
startSecond: raw.start,
|
|
381
|
+
durationInSeconds: raw.duration,
|
|
382
|
+
channelIndex: raw.channel_index,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
case 'transcript.done': {
|
|
389
|
+
const channelIndex = raw.channel_index ?? 0;
|
|
390
|
+
doneTexts.set(channelIndex, raw.text ?? '');
|
|
391
|
+
doneDuration = raw.duration ?? doneDuration;
|
|
392
|
+
maybeFinish();
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
case 'error': {
|
|
397
|
+
// xAI STT errors are terminal: surface the server message
|
|
398
|
+
// instead of letting the socket close mask it.
|
|
399
|
+
finishWithError(new Error(raw.message ?? 'xAI STT error'));
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
})
|
|
404
|
+
.catch(finishWithError);
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
ws.onerror = () => {
|
|
408
|
+
finishWithError(
|
|
409
|
+
new Error(
|
|
410
|
+
'xAI streaming transcription error.' +
|
|
411
|
+
(webSocket == null
|
|
412
|
+
? ' Note: the native WebSocket implementation in browsers,' +
|
|
413
|
+
' Node.js, Deno, and Bun cannot send the Authorization' +
|
|
414
|
+
' header required by xAI. Pass a header-capable WebSocket' +
|
|
415
|
+
" implementation (e.g. the 'ws' package) via" +
|
|
416
|
+
' createXai({ webSocket }).'
|
|
417
|
+
: ''),
|
|
418
|
+
),
|
|
419
|
+
);
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
ws.onclose = () => {
|
|
423
|
+
if (finished) return;
|
|
424
|
+
finished = true;
|
|
425
|
+
cleanup();
|
|
426
|
+
controller.close();
|
|
427
|
+
};
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
cancel: () => {
|
|
431
|
+
if (finished) return;
|
|
432
|
+
finished = true;
|
|
433
|
+
cleanup();
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function buildXaiStreamingTranscriptionUrl({
|
|
439
|
+
baseURL,
|
|
440
|
+
inputAudioFormat,
|
|
441
|
+
providerOptions,
|
|
442
|
+
}: {
|
|
443
|
+
baseURL: string;
|
|
444
|
+
inputAudioFormat: TranscriptionModelV4StreamOptions['inputAudioFormat'];
|
|
445
|
+
providerOptions: XaiTranscriptionModelOptions | undefined;
|
|
446
|
+
}) {
|
|
447
|
+
const url = toWebSocketUrl(`${baseURL}/stt`);
|
|
448
|
+
|
|
449
|
+
appendSearchParam(
|
|
450
|
+
url,
|
|
451
|
+
'sample_rate',
|
|
452
|
+
providerOptions?.sampleRate ?? inputAudioFormat.rate,
|
|
453
|
+
);
|
|
454
|
+
appendSearchParam(
|
|
455
|
+
url,
|
|
456
|
+
'encoding',
|
|
457
|
+
providerOptions?.audioFormat ??
|
|
458
|
+
encodingFromInputAudioFormat(inputAudioFormat.type),
|
|
459
|
+
);
|
|
460
|
+
appendSearchParam(url, 'language', providerOptions?.language);
|
|
461
|
+
appendSearchParam(url, 'diarize', providerOptions?.diarize);
|
|
462
|
+
appendSearchParam(url, 'filler_words', providerOptions?.fillerWords);
|
|
463
|
+
appendSearchParam(url, 'multichannel', providerOptions?.multichannel);
|
|
464
|
+
appendSearchParam(url, 'channels', providerOptions?.channels);
|
|
465
|
+
appendSearchParam(
|
|
466
|
+
url,
|
|
467
|
+
'interim_results',
|
|
468
|
+
providerOptions?.streaming?.interimResults,
|
|
469
|
+
);
|
|
470
|
+
appendSearchParam(
|
|
471
|
+
url,
|
|
472
|
+
'endpointing',
|
|
473
|
+
providerOptions?.streaming?.endpointing,
|
|
474
|
+
);
|
|
475
|
+
appendSearchParam(url, 'smart_turn', providerOptions?.streaming?.smartTurn);
|
|
476
|
+
appendSearchParam(
|
|
477
|
+
url,
|
|
478
|
+
'smart_turn_timeout',
|
|
479
|
+
providerOptions?.streaming?.smartTurnTimeout,
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
if (providerOptions?.keyterm != null) {
|
|
483
|
+
const keyterms = Array.isArray(providerOptions.keyterm)
|
|
484
|
+
? providerOptions.keyterm
|
|
485
|
+
: [providerOptions.keyterm];
|
|
486
|
+
for (const keyterm of keyterms) {
|
|
487
|
+
url.searchParams.append('keyterm', keyterm);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return url;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function appendSearchParam(
|
|
495
|
+
url: URL,
|
|
496
|
+
key: string,
|
|
497
|
+
value: string | number | boolean | null | undefined,
|
|
498
|
+
) {
|
|
499
|
+
if (value != null) {
|
|
500
|
+
url.searchParams.set(key, String(value));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function isKnownInputAudioFormat(type: string): boolean {
|
|
505
|
+
return type === 'audio/pcm' || type === 'audio/pcmu' || type === 'audio/pcma';
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function encodingFromInputAudioFormat(type: string): 'pcm' | 'mulaw' | 'alaw' {
|
|
509
|
+
switch (type) {
|
|
510
|
+
case 'audio/pcmu':
|
|
511
|
+
return 'mulaw';
|
|
512
|
+
case 'audio/pcma':
|
|
513
|
+
return 'alaw';
|
|
514
|
+
default:
|
|
515
|
+
return 'pcm';
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function channelId(channelIndex: number | undefined): string | undefined {
|
|
520
|
+
return channelIndex == null ? undefined : `channel-${channelIndex}`;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function timingFromXaiEvent(event: XaiStreamingTranscriptionEvent) {
|
|
524
|
+
return {
|
|
525
|
+
...(event.start != null ? { startSecond: event.start } : {}),
|
|
526
|
+
...(event.start != null && event.duration != null
|
|
527
|
+
? { endSecond: event.start + event.duration }
|
|
528
|
+
: {}),
|
|
529
|
+
};
|
|
151
530
|
}
|
|
152
531
|
|
|
153
532
|
const xaiTranscriptionResponseSchema = z.object({
|