@ai-sdk/xai 5.0.13 → 5.0.15

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.13",
3
+ "version": "5.0.15",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -29,8 +29,8 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
- "@ai-sdk/provider": "4.0.20",
33
- "@ai-sdk/provider-utils": "5.0.52"
32
+ "@ai-sdk/provider": "4.0.22",
33
+ "@ai-sdk/provider-utils": "5.0.54"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@ai-sdk/test-server": "2.0.3",
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';
@@ -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,32 +93,11 @@ 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
@@ -208,20 +188,21 @@ export class XaiTranscriptionModel implements TranscriptionModelV4 {
208
188
 
209
189
  if (
210
190
  xaiOptions?.audioFormat == null &&
211
- !isKnownInputAudioFormat(options.inputAudioFormat.type)
191
+ !inputAudioFormatEncodings.has(options.inputAudioFormat.type)
212
192
  ) {
213
193
  warnings.push({
214
194
  type: 'other',
215
195
  message:
216
196
  `Unrecognized inputAudioFormat.type "${options.inputAudioFormat.type}"; ` +
217
197
  `falling back to raw PCM encoding. ` +
218
- `Use audio/pcm, audio/pcmu, or audio/pcma, ` +
198
+ `Use ${[...inputAudioFormatEncodings.keys()].join(', ')}, ` +
219
199
  `or set providerOptions.xai.audioFormat explicitly.`,
220
200
  });
221
201
  }
222
202
 
223
203
  const url = buildXaiStreamingTranscriptionUrl({
224
204
  baseURL: this.config.baseURL ?? 'https://api.x.ai/v1',
205
+ modelId: this.modelId,
225
206
  inputAudioFormat: options.inputAudioFormat,
226
207
  providerOptions: xaiOptions,
227
208
  });
@@ -466,85 +447,77 @@ function createXaiStreamingTranscriptionStream({
466
447
 
467
448
  function buildXaiStreamingTranscriptionUrl({
468
449
  baseURL,
450
+ modelId,
469
451
  inputAudioFormat,
470
452
  providerOptions,
471
453
  }: {
472
454
  baseURL: string;
455
+ modelId: XaiTranscriptionModelId;
473
456
  inputAudioFormat: TranscriptionModelV4StreamOptions['inputAudioFormat'];
474
457
  providerOptions: XaiTranscriptionModelOptions | undefined;
475
458
  }) {
476
459
  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
- }
460
+ const params = getXaiSttParams(modelId, providerOptions);
461
+
462
+ appendXaiSttParams(url.searchParams, {
463
+ ...params,
464
+ sample_rate: params.sample_rate ?? inputAudioFormat.rate,
465
+ encoding:
466
+ providerOptions?.audioFormat ??
467
+ inputAudioFormatEncodings.get(inputAudioFormat.type) ??
468
+ 'pcm',
469
+ interim_results: providerOptions?.streaming?.interimResults,
470
+ endpointing: providerOptions?.streaming?.endpointing,
471
+ smart_turn: providerOptions?.streaming?.smartTurn,
472
+ smart_turn_timeout: providerOptions?.streaming?.smartTurnTimeout,
473
+ });
519
474
 
520
475
  return url;
521
476
  }
522
477
 
523
- function appendSearchParam(
524
- url: URL,
525
- key: string,
526
- value: string | number | boolean | null | undefined,
478
+ function getXaiSttParams(
479
+ modelId: XaiTranscriptionModelId,
480
+ options: XaiTranscriptionModelOptions | undefined,
527
481
  ) {
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';
482
+ return {
483
+ model: modelId || undefined,
484
+ sample_rate: options?.sampleRate,
485
+ language: options?.language,
486
+ multichannel: options?.multichannel,
487
+ channels: options?.channels,
488
+ diarize: options?.diarize,
489
+ filler_words: options?.fillerWords,
490
+ vad_threshold: options?.vadThreshold,
491
+ keyterm: options?.keyterm,
492
+ };
535
493
  }
536
494
 
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';
495
+ function appendXaiSttParams(
496
+ target: { append(name: string, value: string): void },
497
+ params: Record<
498
+ string,
499
+ string | number | boolean | string[] | null | undefined
500
+ >,
501
+ ) {
502
+ for (const [key, value] of Object.entries(params)) {
503
+ for (const item of Array.isArray(value) ? value : [value]) {
504
+ if (item != null) {
505
+ target.append(key, String(item));
506
+ }
507
+ }
545
508
  }
546
509
  }
547
510
 
511
+ const inputAudioFormatEncodings = new Map<
512
+ string,
513
+ NonNullable<XaiTranscriptionModelOptions['audioFormat']>
514
+ >([
515
+ ['audio/pcm', 'pcm'],
516
+ ['audio/pcmu', 'mulaw'],
517
+ ['audio/pcma', 'alaw'],
518
+ ['audio/opus', 'opus'],
519
+ ]);
520
+
548
521
  function channelId(channelIndex: number | undefined): string | undefined {
549
522
  return channelIndex == null ? undefined : `channel-${channelIndex}`;
550
523
  }