@ai-sdk/xai 4.0.44 → 4.0.47

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.
@@ -13,6 +13,7 @@ import {
13
13
  combineHeaders,
14
14
  createEventSourceResponseHandler,
15
15
  createJsonResponseHandler,
16
+ createProviderStreamError,
16
17
  isCustomReasoning,
17
18
  mapReasoningToProviderEffort,
18
19
  parseProviderOptions,
@@ -41,7 +42,7 @@ import {
41
42
  } from './xai-responses-language-model-options';
42
43
  import { prepareResponsesTools } from './xai-responses-prepare-tools';
43
44
 
44
- type XaiResponsesConfig = {
45
+ export type XaiResponsesConfig = {
45
46
  provider: string;
46
47
  baseURL: string | undefined;
47
48
  headers?: () => Record<string, string | undefined>;
@@ -49,6 +50,91 @@ type XaiResponsesConfig = {
49
50
  fetch?: FetchFunction;
50
51
  };
51
52
 
53
+ function createXaiResponsesStreamError({
54
+ message,
55
+ code,
56
+ eventType,
57
+ data,
58
+ }: {
59
+ message: string;
60
+ code?: string | null;
61
+ eventType: 'error' | 'response.failed';
62
+ data: unknown;
63
+ }) {
64
+ const statusCode = getHttpStatusCode(code);
65
+
66
+ return createProviderStreamError({
67
+ message,
68
+ type: eventType,
69
+ code: code ?? undefined,
70
+ ...(statusCode != null
71
+ ? {
72
+ statusCode,
73
+ isRetryable: isRetryableStatusCode(statusCode),
74
+ }
75
+ : getXaiResponsesStreamErrorMetadata(code)),
76
+ data,
77
+ });
78
+ }
79
+
80
+ function getXaiResponsesStreamErrorMetadata(code?: string | null): {
81
+ statusCode?: number;
82
+ isRetryable?: boolean;
83
+ } {
84
+ switch (code) {
85
+ case 'rate_limit_exceeded':
86
+ case 'rate_limit_error':
87
+ return { statusCode: 429, isRetryable: true };
88
+ case 'insufficient_quota':
89
+ return { statusCode: 429, isRetryable: false };
90
+ case 'api_error':
91
+ case 'internal_server_error':
92
+ case 'server_error':
93
+ return { statusCode: 500, isRetryable: true };
94
+ case 'overloaded_error':
95
+ case 'service_unavailable':
96
+ return { statusCode: 503, isRetryable: true };
97
+ case 'timeout':
98
+ case 'timeout_error':
99
+ return { statusCode: 504, isRetryable: true };
100
+ case 'authentication_error':
101
+ case 'invalid_api_key':
102
+ return { statusCode: 401, isRetryable: false };
103
+ case 'permission_error':
104
+ return { statusCode: 403, isRetryable: false };
105
+ case 'not_found_error':
106
+ case 'model_not_found':
107
+ return { statusCode: 404, isRetryable: false };
108
+ case 'bad_request':
109
+ case 'context_length_exceeded':
110
+ case 'invalid_request_error':
111
+ return { statusCode: 400, isRetryable: false };
112
+ default:
113
+ return {};
114
+ }
115
+ }
116
+
117
+ function getHttpStatusCode(value: unknown): number | undefined {
118
+ const statusCode =
119
+ typeof value === 'string' && /^\d{3}$/.test(value) ? Number(value) : value;
120
+
121
+ return typeof statusCode === 'number' &&
122
+ Number.isInteger(statusCode) &&
123
+ statusCode >= 400 &&
124
+ statusCode <= 599
125
+ ? statusCode
126
+ : undefined;
127
+ }
128
+
129
+ function isRetryableStatusCode(statusCode: number): boolean {
130
+ return (
131
+ statusCode === 408 ||
132
+ statusCode === 409 ||
133
+ statusCode === 429 ||
134
+ statusCode >= 500
135
+ );
136
+ }
137
+
52
138
  export class XaiResponsesLanguageModel implements LanguageModelV4 {
53
139
  readonly specificationVersion = 'v4';
54
140
 
@@ -88,7 +174,7 @@ export class XaiResponsesLanguageModel implements LanguageModelV4 {
88
174
  'text/*': [/^https?:\/\/.*$/],
89
175
  };
90
176
 
91
- private async getArgs({
177
+ protected async getArgs({
92
178
  prompt,
93
179
  maxOutputTokens,
94
180
  temperature,
@@ -820,11 +906,31 @@ export class XaiResponsesLanguageModel implements LanguageModelV4 {
820
906
  usage = convertXaiResponsesUsage(event.response.usage);
821
907
  }
822
908
 
909
+ if (event.response.error != null) {
910
+ controller.enqueue({
911
+ type: 'error',
912
+ error: createXaiResponsesStreamError({
913
+ message: event.response.error.message,
914
+ code: event.response.error.code,
915
+ eventType: event.type,
916
+ data: event,
917
+ }),
918
+ });
919
+ }
920
+
823
921
  return;
824
922
  }
825
923
 
826
924
  if (event.type === 'error') {
827
- controller.enqueue({ type: 'error', error: event });
925
+ controller.enqueue({
926
+ type: 'error',
927
+ error: createXaiResponsesStreamError({
928
+ message: event.message,
929
+ code: event.code,
930
+ eventType: event.type,
931
+ data: event,
932
+ }),
933
+ });
828
934
  return;
829
935
  }
830
936
 
@@ -672,31 +672,36 @@ export class XaiChatLanguageModel implements LanguageModelV4 {
672
672
  }
673
673
 
674
674
  // XAI API Response Schemas
675
- const xaiUsageSchema = z.object({
676
- prompt_tokens: z.number(),
677
- completion_tokens: z.number(),
678
- total_tokens: z.number(),
679
- prompt_tokens_details: z
680
- .object({
681
- text_tokens: z.number().nullish(),
682
- audio_tokens: z.number().nullish(),
683
- image_tokens: z.number().nullish(),
684
- cached_tokens: z.number().nullish(),
685
- })
686
- .nullish(),
687
- completion_tokens_details: z
688
- .object({
689
- reasoning_tokens: z.number().nullish(),
690
- audio_tokens: z.number().nullish(),
691
- accepted_prediction_tokens: z.number().nullish(),
692
- rejected_prediction_tokens: z.number().nullish(),
693
- })
694
- .nullish(),
695
- });
675
+ const xaiUsageSchema = z
676
+ .object({
677
+ prompt_tokens: z.number(),
678
+ completion_tokens: z.number(),
679
+ total_tokens: z.number(),
680
+ cost_in_usd_ticks: z.number().nullish(),
681
+ prompt_tokens_details: z
682
+ .object({
683
+ text_tokens: z.number().nullish(),
684
+ audio_tokens: z.number().nullish(),
685
+ image_tokens: z.number().nullish(),
686
+ cached_tokens: z.number().nullish(),
687
+ })
688
+ .catchall(z.json())
689
+ .nullish(),
690
+ completion_tokens_details: z
691
+ .object({
692
+ reasoning_tokens: z.number().nullish(),
693
+ audio_tokens: z.number().nullish(),
694
+ accepted_prediction_tokens: z.number().nullish(),
695
+ rejected_prediction_tokens: z.number().nullish(),
696
+ })
697
+ .catchall(z.json())
698
+ .nullish(),
699
+ })
700
+ .catchall(z.json());
696
701
 
697
702
  export type XaiChatUsage = z.infer<typeof xaiUsageSchema>;
698
703
 
699
- const xaiChatResponseSchema = z.object({
704
+ export const xaiChatResponseSchema = z.object({
700
705
  id: z.string().nullish(),
701
706
  created: z.number().nullish(),
702
707
  model: z.string().nullish(),
@@ -733,6 +738,8 @@ const xaiChatResponseSchema = z.object({
733
738
  error: z.string().nullish(),
734
739
  });
735
740
 
741
+ export type XaiChatResponse = z.infer<typeof xaiChatResponseSchema>;
742
+
736
743
  const xaiChatChunkSchema = z.object({
737
744
  id: z.string().nullish(),
738
745
  created: z.number().nullish(),
@@ -2,6 +2,7 @@ import {
2
2
  type Experimental_RealtimeFactoryV4 as RealtimeFactoryV4,
3
3
  type Experimental_RealtimeFactoryV4GetTokenOptions as RealtimeFactoryV4GetTokenOptions,
4
4
  type Experimental_VideoModelV4,
5
+ type Experimental_BatchLanguageModelV4 as BatchLanguageModelV4,
5
6
  type FilesV4,
6
7
  type ImageModelV4,
7
8
  type LanguageModelV4,
@@ -22,7 +23,7 @@ import { XaiChatLanguageModel } from './xai-chat-language-model';
22
23
  import type { XaiChatModelId } from './xai-chat-language-model-options';
23
24
  import { XaiImageModel } from './xai-image-model';
24
25
  import type { XaiImageModelId } from './xai-image-settings';
25
- import { XaiResponsesLanguageModel } from './responses/xai-responses-language-model';
26
+ import { XaiResponsesBatchLanguageModel } from './responses/xai-responses-batch';
26
27
  import type { XaiResponsesModelId } from './responses/xai-responses-language-model-options';
27
28
  import { XaiRealtimeModel } from './realtime/xai-realtime-model';
28
29
  import { xaiTools } from './tool';
@@ -34,12 +35,12 @@ import { XaiSpeechModel } from './xai-speech-model';
34
35
  import { XaiTranscriptionModel } from './xai-transcription-model';
35
36
 
36
37
  export interface XaiProvider extends ProviderV4 {
37
- (modelId: XaiResponsesModelId): LanguageModelV4;
38
+ (modelId: XaiResponsesModelId): BatchLanguageModelV4;
38
39
 
39
40
  /**
40
41
  * Creates an Xai language model for text generation.
41
42
  */
42
- languageModel(modelId: XaiResponsesModelId): LanguageModelV4;
43
+ languageModel(modelId: XaiResponsesModelId): BatchLanguageModelV4;
43
44
 
44
45
  /**
45
46
  * Creates an Xai chat model for text generation.
@@ -49,7 +50,7 @@ export interface XaiProvider extends ProviderV4 {
49
50
  /**
50
51
  * Creates an Xai responses model for text generation.
51
52
  */
52
- responses: (modelId: XaiResponsesModelId) => LanguageModelV4;
53
+ responses: (modelId: XaiResponsesModelId) => BatchLanguageModelV4;
53
54
 
54
55
  /**
55
56
  * Creates an Xai image model for image generation.
@@ -166,7 +167,7 @@ export function createXai(options: XaiProviderSettings = {}): XaiProvider {
166
167
  };
167
168
 
168
169
  const createResponsesLanguageModel = (modelId: XaiResponsesModelId) => {
169
- return new XaiResponsesLanguageModel(modelId, {
170
+ return new XaiResponsesBatchLanguageModel(modelId, {
170
171
  provider: 'xai.responses',
171
172
  baseURL,
172
173
  headers: getHeaders,
@@ -39,6 +39,17 @@ interface XaiVideoModelConfig {
39
39
  };
40
40
  }
41
41
 
42
+ function encodePathSegment(value: string): string {
43
+ const encodedValue = encodeURIComponent(value);
44
+
45
+ // URL parsing normalizes both literal and percent-encoded dot segments.
46
+ return encodedValue === '.'
47
+ ? '%252E'
48
+ : encodedValue === '..'
49
+ ? '%252E%252E'
50
+ : encodedValue;
51
+ }
52
+
42
53
  const RESOLUTION_MAP: Record<string, string> = {
43
54
  '1920x1080': '1080p',
44
55
  '1280x720': '720p',
@@ -538,7 +549,7 @@ export class XaiVideoModel implements VideoModelV4 {
538
549
  const baseURL = this.config.baseURL ?? 'https://api.x.ai/v1';
539
550
 
540
551
  const { value: statusResponse, responseHeaders } = await getFromApi({
541
- url: `${baseURL}/videos/${requestId}`,
552
+ url: `${baseURL}/videos/${encodePathSegment(requestId)}`,
542
553
  validateUrl: false,
543
554
  headers: combineHeaders(this.config.headers(), options.headers),
544
555
  successfulResponseHandler: xaiVideoStatusResponseHandler,