@ai-sdk/xai 5.0.14 → 5.0.16

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/docs/01-xai.mdx CHANGED
@@ -828,11 +828,13 @@ const { traceId, duration, audioTimestamps } = result.providerMetadata.xai;
828
828
  ## Transcription Models
829
829
 
830
830
  You can create models that call the [xAI Speech to Text API](https://docs.x.ai/developers/model-capabilities/audio/speech-to-text)
831
- using the `.transcription()` factory method. xAI's batch transcription endpoint
832
- does not require a model identifier.
831
+ using the `.transcription()` factory method. The model identifier is optional:
832
+ pass `grok-voice-transcribe-2.0` to pin a model, or omit it to use xAI's
833
+ default transcription model. `grok-voice-transcribe-1.0` is retired, and
834
+ requests to it are routed to `grok-voice-transcribe-2.0`.
833
835
 
834
836
  ```ts
835
- const model = xai.transcription();
837
+ const model = xai.transcription('grok-voice-transcribe-2.0');
836
838
  ```
837
839
 
838
840
  Use xAI transcription models with the `transcribe` function:
@@ -871,9 +873,13 @@ const result = await transcribe({
871
873
  });
872
874
  ```
873
875
 
874
- - **audioFormat** _`pcm` | `mulaw` | `alaw`_
876
+ - **audioFormat** _`pcm` | `mulaw` | `alaw` | `opus`_
875
877
 
876
- Audio encoding for raw, headerless input audio.
878
+ Input audio encoding for raw, headerless audio (`pcm`, `mulaw`, `alaw`).
879
+ Container formats such as Ogg/Opus are auto-detected, so do not set
880
+ `audioFormat` for them. `opus` is supported for streaming transcription only:
881
+ each audio chunk must be exactly one raw Opus packet (not Ogg), and an
882
+ `audio/opus` input audio type maps to `opus` automatically.
877
883
 
878
884
  - **sampleRate** _8000 | 16000 | 22050 | 24000 | 44100 | 48000_
879
885
 
@@ -907,6 +913,11 @@ const result = await transcribe({
907
913
 
908
914
  Includes filler words such as `uh` and `um` in the transcript.
909
915
 
916
+ - **vadThreshold** _number_
917
+
918
+ Voice activity detection threshold. Range: 0.0-1.0. Defaults to `0.5` for
919
+ `transcribe` and `0.08` for streaming. `0` disables the filter.
920
+
910
921
  - **streaming** _object_
911
922
 
912
923
  Options for streaming speech-to-text over WebSocket. Use with `experimental_streamTranscribe`.
@@ -928,9 +939,10 @@ const result = await transcribe({
928
939
 
929
940
  ### Model Capabilities
930
941
 
931
- | Model | Request/Response | Streaming | Word Timestamps | Diarization | Multichannel |
932
- | --------- | ---------------- | --------- | --------------- | ----------- | ------------ |
933
- | `default` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
942
+ | Model | Request/Response | Streaming | Word Timestamps | Diarization | Multichannel |
943
+ | --------------------------- | ---------------- | --------- | --------------- | ----------- | ------------ |
944
+ | `default` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
945
+ | `grok-voice-transcribe-2.0` | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
934
946
 
935
947
  <Note>
936
948
  Word timestamps, diarization results, and segments are only returned on the
@@ -1053,22 +1065,29 @@ const { images } = await generateImage({
1053
1065
  });
1054
1066
  ```
1055
1067
 
1056
- - **resolution** _'1k' | '2k'_
1068
+ - **resolution** _'1k' | '1.5k' | '2k'_
1057
1069
 
1058
- Output resolution. `1k` produces ~1024×1024 images, `2k` produces ~2048×2048
1059
- images (actual dimensions vary based on aspect ratio). Available for
1060
- `grok-imagine-image-pro`.
1070
+ Output resolution: `1k` (default, ~1024×1024), `1.5k` (~1408×1408), or `2k`
1071
+ (~2048×2048). Actual dimensions vary based on aspect ratio.
1072
+ `grok-imagine-image` does not support `1.5k`.
1061
1073
 
1062
- - **quality** _'low' | 'medium' | 'high'_
1074
+ - **quality** _'low' | 'medium' | 'high' | 'auto'_
1063
1075
 
1064
- Image quality level. Higher quality may increase generation time.
1076
+ Image quality level. Only supported by `grok-imagine-image-2.0`, which accepts
1077
+ `low`, `medium`, and `auto`. `auto` is the default when omitted: it serves
1078
+ `low` for generation and `medium` for editing, billed at the quality served.
1079
+ `high` is not documented by xAI but is still accepted by older models, and is
1080
+ kept for backward compatibility.
1065
1081
 
1066
1082
  ### Image Model Capabilities
1067
1083
 
1068
- | Model | Resolution | Aspect Ratios | Image Editing |
1069
- | ------------------------ | ---------- | ----------------------------------------------------------------------------------------------------------- | ------------- |
1070
- | `grok-imagine-image-pro` | `1k`, `2k` | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`, `2:1`, `1:2`, `19.5:9`, `9:19.5`, `20:9`, `9:20`, `auto` | <Check /> |
1071
- | `grok-imagine-image` | `1k` | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`, `2:1`, `1:2`, `19.5:9`, `9:19.5`, `20:9`, `9:20`, `auto` | <Check /> |
1084
+ | Model | Resolution | Aspect Ratios | Image Editing |
1085
+ | ------------------------ | ------------------ | -------------------------------------------------------------------------------------------------------------------------- | ------------- |
1086
+ | `grok-imagine-image-2.0` | `1k`, `1.5k`, `2k` | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`, `2:1`, `1:2`, `19.5:9`, `9:19.5`, `20:9`, `9:20`, `21:9`, `5:2`, `auto` | <Check /> |
1087
+ | `grok-imagine-image-pro` | `1k`, `1.5k`, `2k` | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`, `2:1`, `1:2`, `19.5:9`, `9:19.5`, `20:9`, `9:20`, `auto` | <Check /> |
1088
+ | `grok-imagine-image` | `1k` | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`, `2:1`, `1:2`, `19.5:9`, `9:19.5`, `20:9`, `9:20`, `auto` | <Check /> |
1089
+
1090
+ `grok-imagine-image-2.0` is xAI's latest image model.
1072
1091
 
1073
1092
  ## Video Models
1074
1093
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/xai",
3
- "version": "5.0.14",
3
+ "version": "5.0.16",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -29,15 +29,15 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
- "@ai-sdk/provider": "4.0.21",
33
- "@ai-sdk/provider-utils": "5.0.53"
32
+ "@ai-sdk/provider": "4.0.23",
33
+ "@ai-sdk/provider-utils": "5.0.55"
34
34
  },
35
35
  "devDependencies": {
36
- "@ai-sdk/test-server": "2.0.3",
36
+ "@ai-sdk/test-server": "2.0.4",
37
37
  "@types/node": "22.19.19",
38
38
  "@vercel/ai-tsconfig": "0.0.0",
39
39
  "tsdown": "^0.23.0",
40
- "typescript": "5.8.3",
40
+ "typescript": "6.0.3",
41
41
  "zod": "3.25.76"
42
42
  },
43
43
  "peerDependencies": {
@@ -152,7 +152,7 @@ export class XaiFiles implements FilesV4 {
152
152
  } else {
153
153
  const fileBytes = convertInlineFileDataToUint8Array(data);
154
154
 
155
- const blob = new Blob([fileBytes], {
155
+ const blob = new Blob([fileBytes as Uint8Array<ArrayBuffer>], {
156
156
  type: mediaType,
157
157
  });
158
158
 
package/src/index.ts CHANGED
@@ -17,7 +17,10 @@ export type {
17
17
  XaiVideoModelOptions as XaiVideoProviderOptions,
18
18
  } from './xai-video-model-options';
19
19
  export type { XaiSpeechModelOptions } from './xai-speech-model-options';
20
- export type { XaiTranscriptionModelOptions } from './xai-transcription-model-options';
20
+ export type {
21
+ XaiTranscriptionModelId,
22
+ XaiTranscriptionModelOptions,
23
+ } from './xai-transcription-model-options';
21
24
  export type { XaiFilesOptions } from './files/xai-files-options';
22
25
  export { createXai, xai } from './xai-provider';
23
26
  export type { XaiProvider, XaiProviderSettings } from './xai-provider';
@@ -297,6 +297,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV4 {
297
297
  medium: 'medium',
298
298
  high: 'high',
299
299
  xhigh: modelId === 'grok-4.6' ? 'xhigh' : 'high',
300
+ max: modelId === 'grok-4.6' ? 'xhigh' : 'high',
300
301
  },
301
302
  warnings,
302
303
  });
@@ -4,8 +4,8 @@ export const xaiImageModelOptions = z.object({
4
4
  aspect_ratio: z.string().optional(),
5
5
  output_format: z.string().optional(),
6
6
  sync_mode: z.boolean().optional(),
7
- resolution: z.enum(['1k', '2k']).optional(),
8
- quality: z.enum(['low', 'medium', 'high']).optional(),
7
+ resolution: z.enum(['1k', '1.5k', '2k']).optional(),
8
+ quality: z.enum(['low', 'medium', 'high', 'auto']).optional(),
9
9
  user: z.string().optional(),
10
10
  });
11
11
 
@@ -28,15 +28,18 @@ interface XaiImageModelConfig {
28
28
  };
29
29
  }
30
30
 
31
+ const fileInputModelIds: ReadonlySet<string> = new Set<XaiImageModelId>([
32
+ 'grok-imagine-image',
33
+ 'grok-imagine-image-2.0',
34
+ 'grok-imagine-image-pro',
35
+ ]);
36
+
31
37
  export class XaiImageModel implements ImageModelV4 {
32
38
  readonly specificationVersion = 'v4';
33
39
  readonly maxImagesPerCall = 3;
34
40
 
35
41
  get supportsFileInputs(): boolean | undefined {
36
- return this.modelId === 'grok-imagine-image' ||
37
- this.modelId === 'grok-imagine-image-pro'
38
- ? true
39
- : undefined;
42
+ return fileInputModelIds.has(this.modelId) ? true : undefined;
40
43
  }
41
44
 
42
45
  get supportsMaskInputs(): boolean | undefined {
@@ -1,4 +1,5 @@
1
1
  export type XaiImageModelId =
2
2
  | 'grok-imagine-image'
3
+ | 'grok-imagine-image-2.0'
3
4
  | 'grok-imagine-image-pro'
4
5
  | (string & {});
@@ -32,6 +32,7 @@ import { XaiVideoModel } from './xai-video-model';
32
32
  import type { XaiVideoModelId } from './xai-video-settings';
33
33
  import { XaiSpeechModel } from './xai-speech-model';
34
34
  import { XaiTranscriptionModel } from './xai-transcription-model';
35
+ import type { XaiTranscriptionModelId } from './xai-transcription-model-options';
35
36
 
36
37
  export interface XaiProvider extends ProviderV4 {
37
38
  (modelId: XaiResponsesModelId): LanguageModelV4;
@@ -88,13 +89,15 @@ export interface XaiProvider extends ProviderV4 {
88
89
 
89
90
  /**
90
91
  * Creates an xAI model for speech-to-text transcription.
92
+ * When `modelId` is omitted, xAI's default transcription model is used.
91
93
  */
92
- transcription(): TranscriptionModelV4;
94
+ transcription(modelId?: XaiTranscriptionModelId): TranscriptionModelV4;
93
95
 
94
96
  /**
95
97
  * Creates an xAI model for speech-to-text transcription.
98
+ * When `modelId` is omitted, xAI's default transcription model is used.
96
99
  */
97
- transcriptionModel(): TranscriptionModelV4;
100
+ transcriptionModel(modelId?: XaiTranscriptionModelId): TranscriptionModelV4;
98
101
 
99
102
  /**
100
103
  * Returns the xAI files interface for uploading files.
@@ -204,8 +207,8 @@ export function createXai(options: XaiProviderSettings = {}): XaiProvider {
204
207
  });
205
208
  };
206
209
 
207
- const createTranscriptionModel = () => {
208
- return new XaiTranscriptionModel('', {
210
+ const createTranscriptionModel = (modelId: XaiTranscriptionModelId = '') => {
211
+ return new XaiTranscriptionModel(modelId, {
209
212
  provider: 'xai.transcription',
210
213
  baseURL,
211
214
  headers: getHeaders,
@@ -5,13 +5,20 @@ import {
5
5
  } from '@ai-sdk/provider-utils';
6
6
  import { z } from 'zod/v4';
7
7
 
8
+ export type XaiTranscriptionModelId =
9
+ | 'grok-voice-transcribe-2.0'
10
+ | (string & {});
11
+
8
12
  export const xaiTranscriptionModelOptionsSchema = lazySchema(() =>
9
13
  zodSchema(
10
14
  z.object({
11
15
  /**
12
- * Audio encoding for raw, headerless input audio.
16
+ * Input audio encoding for raw, headerless audio: `pcm`, `mulaw`, or
17
+ * `alaw`. Container formats such as Ogg/Opus are auto-detected, so do
18
+ * not set this for them. `opus` is for streaming only, with one raw Opus
19
+ * packet per chunk.
13
20
  */
14
- audioFormat: z.enum(['pcm', 'mulaw', 'alaw']).nullish(),
21
+ audioFormat: z.enum(['pcm', 'mulaw', 'alaw', 'opus']).nullish(),
15
22
 
16
23
  /**
17
24
  * Sample rate of the input audio in Hz.
@@ -62,6 +69,12 @@ export const xaiTranscriptionModelOptionsSchema = lazySchema(() =>
62
69
  */
63
70
  fillerWords: z.boolean().nullish(),
64
71
 
72
+ /**
73
+ * Voice activity detection threshold between 0 and 1. Defaults to 0.5
74
+ * for `transcribe` and 0.08 for streaming. 0 disables the filter.
75
+ */
76
+ vadThreshold: z.number().min(0).max(1).optional(),
77
+
65
78
  /**
66
79
  * Options for streaming speech-to-text over WebSocket.
67
80
  */
@@ -27,6 +27,7 @@ import { z } from 'zod/v4';
27
27
  import { xaiFailedResponseHandler } from './xai-error';
28
28
  import {
29
29
  xaiTranscriptionModelOptionsSchema,
30
+ type XaiTranscriptionModelId,
30
31
  type XaiTranscriptionModelOptions,
31
32
  } from './xai-transcription-model-options';
32
33
 
@@ -64,7 +65,7 @@ export class XaiTranscriptionModel implements TranscriptionModelV4 {
64
65
  }
65
66
 
66
67
  static [WORKFLOW_DESERIALIZE](options: {
67
- modelId: '';
68
+ modelId: XaiTranscriptionModelId;
68
69
  config: XaiTranscriptionModelConfig;
69
70
  }) {
70
71
  return new XaiTranscriptionModel(options.modelId, options.config);
@@ -75,7 +76,7 @@ export class XaiTranscriptionModel implements TranscriptionModelV4 {
75
76
  }
76
77
 
77
78
  constructor(
78
- readonly modelId: '',
79
+ readonly modelId: XaiTranscriptionModelId,
79
80
  private readonly config: XaiTranscriptionModelConfig,
80
81
  ) {}
81
82
 
@@ -92,36 +93,15 @@ export class XaiTranscriptionModel implements TranscriptionModelV4 {
92
93
  });
93
94
 
94
95
  const formData = new FormData();
95
- const transcriptionOptions = {
96
+ appendXaiSttParams(formData, {
97
+ ...getXaiSttParams(this.modelId, xaiOptions),
96
98
  audio_format: xaiOptions?.audioFormat,
97
- sample_rate: xaiOptions?.sampleRate,
98
- language: xaiOptions?.language,
99
99
  format: xaiOptions?.format,
100
- multichannel: xaiOptions?.multichannel,
101
- channels: xaiOptions?.channels,
102
- diarize: xaiOptions?.diarize,
103
- filler_words: xaiOptions?.fillerWords,
104
- };
105
-
106
- for (const [key, value] of Object.entries(transcriptionOptions)) {
107
- if (value != null) {
108
- formData.append(key, String(value));
109
- }
110
- }
111
-
112
- if (xaiOptions?.keyterm != null) {
113
- const keyterms = Array.isArray(xaiOptions.keyterm)
114
- ? xaiOptions.keyterm
115
- : [xaiOptions.keyterm];
116
-
117
- for (const keyterm of keyterms) {
118
- formData.append('keyterm', keyterm);
119
- }
120
- }
100
+ });
121
101
 
122
102
  const blob =
123
103
  audio instanceof Uint8Array
124
- ? new Blob([audio])
104
+ ? new Blob([audio as Uint8Array<ArrayBuffer>])
125
105
  : new Blob([convertBase64ToUint8Array(audio)]);
126
106
  const fileExtension = mediaTypeToExtension(mediaType);
127
107
 
@@ -174,6 +154,13 @@ export class XaiTranscriptionModel implements TranscriptionModelV4 {
174
154
  headers: responseHeaders,
175
155
  body: rawResponse,
176
156
  },
157
+ ...(response.words?.some(word => word.speaker != null) && {
158
+ providerMetadata: {
159
+ xai: {
160
+ words: response.words,
161
+ },
162
+ },
163
+ }),
177
164
  };
178
165
  }
179
166
 
@@ -208,20 +195,21 @@ export class XaiTranscriptionModel implements TranscriptionModelV4 {
208
195
 
209
196
  if (
210
197
  xaiOptions?.audioFormat == null &&
211
- !isKnownInputAudioFormat(options.inputAudioFormat.type)
198
+ !inputAudioFormatEncodings.has(options.inputAudioFormat.type)
212
199
  ) {
213
200
  warnings.push({
214
201
  type: 'other',
215
202
  message:
216
203
  `Unrecognized inputAudioFormat.type "${options.inputAudioFormat.type}"; ` +
217
204
  `falling back to raw PCM encoding. ` +
218
- `Use audio/pcm, audio/pcmu, or audio/pcma, ` +
205
+ `Use ${[...inputAudioFormatEncodings.keys()].join(', ')}, ` +
219
206
  `or set providerOptions.xai.audioFormat explicitly.`,
220
207
  });
221
208
  }
222
209
 
223
210
  const url = buildXaiStreamingTranscriptionUrl({
224
211
  baseURL: this.config.baseURL ?? 'https://api.x.ai/v1',
212
+ modelId: this.modelId,
225
213
  inputAudioFormat: options.inputAudioFormat,
226
214
  providerOptions: xaiOptions,
227
215
  });
@@ -466,85 +454,77 @@ function createXaiStreamingTranscriptionStream({
466
454
 
467
455
  function buildXaiStreamingTranscriptionUrl({
468
456
  baseURL,
457
+ modelId,
469
458
  inputAudioFormat,
470
459
  providerOptions,
471
460
  }: {
472
461
  baseURL: string;
462
+ modelId: XaiTranscriptionModelId;
473
463
  inputAudioFormat: TranscriptionModelV4StreamOptions['inputAudioFormat'];
474
464
  providerOptions: XaiTranscriptionModelOptions | undefined;
475
465
  }) {
476
466
  const url = toWebSocketUrl(`${baseURL}/stt`);
477
-
478
- appendSearchParam(
479
- url,
480
- 'sample_rate',
481
- providerOptions?.sampleRate ?? inputAudioFormat.rate,
482
- );
483
- appendSearchParam(
484
- url,
485
- 'encoding',
486
- providerOptions?.audioFormat ??
487
- encodingFromInputAudioFormat(inputAudioFormat.type),
488
- );
489
- appendSearchParam(url, 'language', providerOptions?.language);
490
- appendSearchParam(url, 'diarize', providerOptions?.diarize);
491
- appendSearchParam(url, 'filler_words', providerOptions?.fillerWords);
492
- appendSearchParam(url, 'multichannel', providerOptions?.multichannel);
493
- appendSearchParam(url, 'channels', providerOptions?.channels);
494
- appendSearchParam(
495
- url,
496
- 'interim_results',
497
- providerOptions?.streaming?.interimResults,
498
- );
499
- appendSearchParam(
500
- url,
501
- 'endpointing',
502
- providerOptions?.streaming?.endpointing,
503
- );
504
- appendSearchParam(url, 'smart_turn', providerOptions?.streaming?.smartTurn);
505
- appendSearchParam(
506
- url,
507
- 'smart_turn_timeout',
508
- providerOptions?.streaming?.smartTurnTimeout,
509
- );
510
-
511
- if (providerOptions?.keyterm != null) {
512
- const keyterms = Array.isArray(providerOptions.keyterm)
513
- ? providerOptions.keyterm
514
- : [providerOptions.keyterm];
515
- for (const keyterm of keyterms) {
516
- url.searchParams.append('keyterm', keyterm);
517
- }
518
- }
467
+ const params = getXaiSttParams(modelId, providerOptions);
468
+
469
+ appendXaiSttParams(url.searchParams, {
470
+ ...params,
471
+ sample_rate: params.sample_rate ?? inputAudioFormat.rate,
472
+ encoding:
473
+ providerOptions?.audioFormat ??
474
+ inputAudioFormatEncodings.get(inputAudioFormat.type) ??
475
+ 'pcm',
476
+ interim_results: providerOptions?.streaming?.interimResults,
477
+ endpointing: providerOptions?.streaming?.endpointing,
478
+ smart_turn: providerOptions?.streaming?.smartTurn,
479
+ smart_turn_timeout: providerOptions?.streaming?.smartTurnTimeout,
480
+ });
519
481
 
520
482
  return url;
521
483
  }
522
484
 
523
- function appendSearchParam(
524
- url: URL,
525
- key: string,
526
- value: string | number | boolean | null | undefined,
485
+ function getXaiSttParams(
486
+ modelId: XaiTranscriptionModelId,
487
+ options: XaiTranscriptionModelOptions | undefined,
527
488
  ) {
528
- if (value != null) {
529
- url.searchParams.set(key, String(value));
530
- }
531
- }
532
-
533
- function isKnownInputAudioFormat(type: string): boolean {
534
- return type === 'audio/pcm' || type === 'audio/pcmu' || type === 'audio/pcma';
489
+ return {
490
+ model: modelId || undefined,
491
+ sample_rate: options?.sampleRate,
492
+ language: options?.language,
493
+ multichannel: options?.multichannel,
494
+ channels: options?.channels,
495
+ diarize: options?.diarize,
496
+ filler_words: options?.fillerWords,
497
+ vad_threshold: options?.vadThreshold,
498
+ keyterm: options?.keyterm,
499
+ };
535
500
  }
536
501
 
537
- function encodingFromInputAudioFormat(type: string): 'pcm' | 'mulaw' | 'alaw' {
538
- switch (type) {
539
- case 'audio/pcmu':
540
- return 'mulaw';
541
- case 'audio/pcma':
542
- return 'alaw';
543
- default:
544
- return 'pcm';
502
+ function appendXaiSttParams(
503
+ target: { append(name: string, value: string): void },
504
+ params: Record<
505
+ string,
506
+ string | number | boolean | string[] | null | undefined
507
+ >,
508
+ ) {
509
+ for (const [key, value] of Object.entries(params)) {
510
+ for (const item of Array.isArray(value) ? value : [value]) {
511
+ if (item != null) {
512
+ target.append(key, String(item));
513
+ }
514
+ }
545
515
  }
546
516
  }
547
517
 
518
+ const inputAudioFormatEncodings = new Map<
519
+ string,
520
+ NonNullable<XaiTranscriptionModelOptions['audioFormat']>
521
+ >([
522
+ ['audio/pcm', 'pcm'],
523
+ ['audio/pcmu', 'mulaw'],
524
+ ['audio/pcma', 'alaw'],
525
+ ['audio/opus', 'opus'],
526
+ ]);
527
+
548
528
  function channelId(channelIndex: number | undefined): string | undefined {
549
529
  return channelIndex == null ? undefined : `channel-${channelIndex}`;
550
530
  }
@@ -568,6 +548,7 @@ const xaiTranscriptionResponseSchema = z.object({
568
548
  text: z.string(),
569
549
  start: z.number(),
570
550
  end: z.number(),
551
+ speaker: z.number().nullish(),
571
552
  }),
572
553
  )
573
554
  .nullish(),