@ai-sdk/xai 4.0.21 → 4.0.23

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
@@ -92,10 +92,10 @@ and support structured data generation with [`Output`](/docs/reference/ai-sdk-co
92
92
 
93
93
  ### Reasoning Effort
94
94
 
95
- For reasoning-capable models you can control how much effort the model spends
96
- thinking before responding via `providerOptions.xai.reasoningEffort`. This
97
- works for both the Responses API (default) and the Chat Completions API
98
- (`xai.chat()`).
95
+ For models with configurable reasoning, you can control how much effort the
96
+ model spends thinking before responding via
97
+ `providerOptions.xai.reasoningEffort`. This works for both the Responses API
98
+ (default) and the Chat Completions API (`xai.chat()`).
99
99
 
100
100
  ```ts
101
101
  import { xai } from '@ai-sdk/xai';
@@ -110,13 +110,12 @@ const { text } = await generateText({
110
110
  });
111
111
  ```
112
112
 
113
- Supported values:
113
+ The AI SDK option accepts these values, but each xAI model supports a subset:
114
114
 
115
115
  - `'none'` — Disables reasoning entirely; no thinking tokens are used. Best
116
- for simple use cases that need a near-instant response. Supported by
117
- `grok-4.3` and newer reasoning models.
118
- - `'low'` (default) — Uses some reasoning tokens, but still fast. Good for
119
- general agentic use and tool calling.
116
+ for simple use cases that need a near-instant response.
117
+ - `'low'` — Uses some reasoning tokens, but still fast. Good for general
118
+ agentic use and tool calling.
120
119
  - `'medium'` — More thinking for less-latency-sensitive applications such as
121
120
  complex data analysis and long-context reasoning.
122
121
  - `'high'` — More reasoning tokens for deeper thinking. Suited for very
@@ -124,9 +123,15 @@ Supported values:
124
123
  tasks.
125
124
 
126
125
  <Note>
127
- Not every Grok model accepts every value. See xAI's [reasoning
128
- docs](https://docs.x.ai/docs/guides/reasoning) for the values supported by
129
- your selected model. `'none'` requires `grok-4.3` or newer.
126
+ Support and defaults are model-specific. `grok-4.3` supports `'none'`,
127
+ `'low'`, `'medium'`, and `'high'`. `grok-4.5` supports `'low'`, `'medium'`,
128
+ and `'high'`, defaults to `'high'`, and cannot disable reasoning. The
129
+ `grok-4.20-reasoning` and `grok-4.20-non-reasoning` variants do not accept
130
+ this option. For `grok-4.20-multi-agent`, `'low'`, `'medium'`, and `'high'`
131
+ control the number of agents instead of reasoning depth. See xAI's [reasoning
132
+ docs](https://docs.x.ai/developers/model-capabilities/text/reasoning) and
133
+ [Grok 4.3 model page](https://docs.x.ai/developers/models/grok-4.3) for
134
+ current details.
130
135
  </Note>
131
136
 
132
137
  ## Realtime Models
@@ -499,7 +504,7 @@ import { xai, type XaiLanguageModelResponsesOptions } from '@ai-sdk/xai';
499
504
  import { generateText } from 'ai';
500
505
 
501
506
  const result = await generateText({
502
- model: xai.responses('grok-4.20-non-reasoning'),
507
+ model: xai.responses('grok-4.5'),
503
508
  providerOptions: {
504
509
  xai: {
505
510
  reasoningEffort: 'high',
@@ -513,7 +518,7 @@ The following provider options are available:
513
518
 
514
519
  - **reasoningEffort** _'none' | 'low' | 'medium' | 'high'_
515
520
 
516
- Control the reasoning effort for the model. See [Reasoning Effort](#reasoning-effort) for details on each value. `'none'` disables reasoning entirely (requires `grok-4.3` or newer).
521
+ Control the reasoning effort for supported models. See [Reasoning Effort](#reasoning-effort) for model-specific values and defaults.
517
522
 
518
523
  - **logprobs** _boolean_
519
524
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/xai",
3
- "version": "4.0.21",
3
+ "version": "4.0.23",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -29,16 +29,16 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
+ "@ai-sdk/openai-compatible": "3.0.18",
32
33
  "@ai-sdk/provider": "4.0.4",
33
- "@ai-sdk/provider-utils": "5.0.14",
34
- "@ai-sdk/openai-compatible": "3.0.16"
34
+ "@ai-sdk/provider-utils": "5.0.16"
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
- "@ai-sdk/test-server": "2.0.0",
41
+ "@ai-sdk/test-server": "2.0.1",
42
42
  "@vercel/ai-tsconfig": "0.0.0"
43
43
  },
44
44
  "peerDependencies": {
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  AISDKError,
3
+ APICallError,
3
4
  type Experimental_VideoModelV4,
4
5
  type Experimental_VideoModelV4File,
5
6
  type SharedV4Warning,
6
7
  } from '@ai-sdk/provider';
7
8
  import {
8
- cancelResponseBody,
9
9
  combineHeaders,
10
10
  convertUint8ArrayToBase64,
11
11
  createJsonResponseHandler,
@@ -15,6 +15,7 @@ import {
15
15
  getTopLevelMediaType,
16
16
  parseProviderOptions,
17
17
  postJsonToApi,
18
+ safeParseJSON,
18
19
  type FetchFunction,
19
20
  type ResponseHandler,
20
21
  } from '@ai-sdk/provider-utils';
@@ -549,16 +550,80 @@ const xaiVideoStatusJsonResponseHandler = createJsonResponseHandler(
549
550
  xaiVideoStatusResponseSchema,
550
551
  );
551
552
 
553
+ // Generous bound for a `{status, progress}` payload of ~50 bytes.
554
+ const MAX_PENDING_BODY_BYTES = 1024 * 1024;
555
+
556
+ const textDecoder = new TextDecoder();
557
+
558
+ // Bounded replacement for `response.text()`. Throws on overflow without
559
+ // cancelling the body: cancelling a tee branch neither settles nor releases
560
+ // the underlying source while the sibling branch is live.
561
+ async function readPendingBody({
562
+ response,
563
+ url,
564
+ requestBodyValues,
565
+ }: Parameters<ResponseHandler<unknown>>[0]): Promise<string> {
566
+ if (response.body == null) {
567
+ return '';
568
+ }
569
+
570
+ const reader = response.body.getReader();
571
+ const chunks: Uint8Array[] = [];
572
+ let totalBytes = 0;
573
+
574
+ try {
575
+ while (true) {
576
+ const { done, value } = await reader.read();
577
+ if (done) break;
578
+
579
+ totalBytes += value.length;
580
+ if (totalBytes > MAX_PENDING_BODY_BYTES) {
581
+ throw new APICallError({
582
+ message: `xAI video status response exceeded ${MAX_PENDING_BODY_BYTES} bytes`,
583
+ url,
584
+ requestBodyValues,
585
+ statusCode: response.status,
586
+ responseHeaders: extractResponseHeaders(response),
587
+ });
588
+ }
589
+
590
+ chunks.push(value);
591
+ }
592
+ } finally {
593
+ reader.releaseLock();
594
+ }
595
+
596
+ const merged = new Uint8Array(totalBytes);
597
+ let offset = 0;
598
+ for (const chunk of chunks) {
599
+ merged.set(chunk, offset);
600
+ offset += chunk.length;
601
+ }
602
+ return textDecoder.decode(merged);
603
+ }
604
+
552
605
  const xaiVideoStatusResponseHandler: ResponseHandler<
553
606
  z.infer<typeof xaiVideoStatusResponseSchema>
554
607
  > = async options => {
608
+ // xAI answers 202 while a generation is still running, sometimes with an
609
+ // empty body. Read it rather than cancelling: `body.cancel()` never settles
610
+ // on a tee branch, which `Response.clone()` in fetch instrumentation creates.
555
611
  if (options.response.status === 202) {
556
612
  const responseHeaders = extractResponseHeaders(options.response);
557
- await cancelResponseBody(options.response);
613
+ const text = await readPendingBody(options);
614
+
615
+ if (text.trim().length === 0) {
616
+ return { responseHeaders, value: { status: 'pending' } };
617
+ }
618
+
619
+ const parsed = await safeParseJSON({
620
+ text,
621
+ schema: xaiVideoStatusResponseSchema,
622
+ });
558
623
 
559
624
  return {
560
625
  responseHeaders,
561
- value: { status: 'pending' },
626
+ value: parsed.success ? parsed.value : { status: 'pending' },
562
627
  };
563
628
  }
564
629