@ai-sdk/xai 4.0.51 → 4.0.52
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 +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +214 -37
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/files/xai-files-api.ts +11 -0
- package/src/files/xai-files-options.ts +6 -0
- package/src/files/xai-files.ts +254 -35
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/xai",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.52",
|
|
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.
|
|
33
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
32
|
+
"@ai-sdk/provider": "4.0.10",
|
|
33
|
+
"@ai-sdk/provider-utils": "5.0.35"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@ai-sdk/test-server": "2.0.1",
|
|
@@ -8,9 +8,20 @@ export const xaiFilesResponseSchema = lazySchema(() =>
|
|
|
8
8
|
object: z.string().nullish(),
|
|
9
9
|
bytes: z.number().nullish(),
|
|
10
10
|
created_at: z.number().nullish(),
|
|
11
|
+
expires_at: z.number().nullish(),
|
|
11
12
|
filename: z.string().nullish(),
|
|
12
13
|
purpose: z.string().nullish(),
|
|
13
14
|
status: z.string().nullish(),
|
|
14
15
|
}),
|
|
15
16
|
),
|
|
16
17
|
);
|
|
18
|
+
|
|
19
|
+
export const xaiFileDeleteResponseSchema = lazySchema(() =>
|
|
20
|
+
zodSchema(
|
|
21
|
+
z.object({
|
|
22
|
+
id: z.string(),
|
|
23
|
+
object: z.string().nullish(),
|
|
24
|
+
deleted: z.boolean(),
|
|
25
|
+
}),
|
|
26
|
+
),
|
|
27
|
+
);
|
|
@@ -10,6 +10,12 @@ export const xaiFilesOptionsSchema = lazySchema(() =>
|
|
|
10
10
|
z.looseObject({
|
|
11
11
|
teamId: z.string().optional(),
|
|
12
12
|
filePath: z.string().optional(),
|
|
13
|
+
/**
|
|
14
|
+
* TTL in seconds measured from upload time; xAI accepts integers
|
|
15
|
+
* between 3600 (1 hour) and 2592000 (30 days) inclusive.
|
|
16
|
+
* Omit to keep the file until it is deleted.
|
|
17
|
+
*/
|
|
18
|
+
expiresAfter: z.number().int().min(3600).max(2_592_000).optional(),
|
|
13
19
|
}),
|
|
14
20
|
),
|
|
15
21
|
);
|
package/src/files/xai-files.ts
CHANGED
|
@@ -1,22 +1,40 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
InvalidArgumentError,
|
|
3
|
+
type FilesV4,
|
|
4
|
+
type FilesV4DeleteFileCallOptions,
|
|
5
|
+
type FilesV4DeleteFileResult,
|
|
6
|
+
type FilesV4DownloadFileCallOptions,
|
|
7
|
+
type FilesV4DownloadFileResult,
|
|
8
|
+
type FilesV4GetFileMetadataCallOptions,
|
|
9
|
+
type FilesV4GetFileMetadataResult,
|
|
10
|
+
type FilesV4UploadFileCallOptions,
|
|
11
|
+
type FilesV4UploadFileResult,
|
|
12
|
+
type SharedV4ProviderReference,
|
|
5
13
|
} from '@ai-sdk/provider';
|
|
6
14
|
import {
|
|
7
15
|
combineHeaders,
|
|
8
16
|
convertInlineFileDataToUint8Array,
|
|
17
|
+
createBinaryStreamResponseHandler,
|
|
9
18
|
createJsonResponseHandler,
|
|
19
|
+
deleteFromApi,
|
|
20
|
+
getFromApi,
|
|
10
21
|
parseProviderOptions,
|
|
11
22
|
postFormDataToApi,
|
|
23
|
+
postMultipartStreamToApi,
|
|
12
24
|
type FetchFunction,
|
|
25
|
+
type InferSchema,
|
|
26
|
+
type MultipartStreamPart,
|
|
13
27
|
} from '@ai-sdk/provider-utils';
|
|
14
28
|
import { xaiFailedResponseHandler } from '../xai-error';
|
|
15
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
xaiFileDeleteResponseSchema,
|
|
31
|
+
xaiFilesResponseSchema,
|
|
32
|
+
} from './xai-files-api';
|
|
16
33
|
import {
|
|
17
34
|
xaiFilesOptionsSchema,
|
|
18
35
|
type XaiFilesOptions,
|
|
19
36
|
} from './xai-files-options';
|
|
37
|
+
|
|
20
38
|
interface XaiFilesConfig {
|
|
21
39
|
provider: string;
|
|
22
40
|
baseURL: string | undefined;
|
|
@@ -24,6 +42,19 @@ interface XaiFilesConfig {
|
|
|
24
42
|
fetch?: FetchFunction;
|
|
25
43
|
}
|
|
26
44
|
|
|
45
|
+
type XaiFilesResponse = InferSchema<typeof xaiFilesResponseSchema>;
|
|
46
|
+
|
|
47
|
+
function encodePathSegment(value: string): string {
|
|
48
|
+
const encodedValue = encodeURIComponent(value);
|
|
49
|
+
|
|
50
|
+
// URL parsing normalizes both literal and percent-encoded dot segments.
|
|
51
|
+
return encodedValue === '.'
|
|
52
|
+
? '%252E'
|
|
53
|
+
: encodedValue === '..'
|
|
54
|
+
? '%252E%252E'
|
|
55
|
+
: encodedValue;
|
|
56
|
+
}
|
|
57
|
+
|
|
27
58
|
export class XaiFiles implements FilesV4 {
|
|
28
59
|
readonly specificationVersion = 'v4';
|
|
29
60
|
|
|
@@ -33,62 +64,250 @@ export class XaiFiles implements FilesV4 {
|
|
|
33
64
|
|
|
34
65
|
constructor(private readonly config: XaiFilesConfig) {}
|
|
35
66
|
|
|
67
|
+
private getFileId(file: SharedV4ProviderReference): string {
|
|
68
|
+
const fileId = file.xai;
|
|
69
|
+
if (fileId == null || fileId.trim() === '') {
|
|
70
|
+
throw new InvalidArgumentError({
|
|
71
|
+
argument: 'file',
|
|
72
|
+
message: "file reference is missing an 'xai' file id.",
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return fileId;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private getHeaders(
|
|
79
|
+
headers: Record<string, string | undefined> | undefined,
|
|
80
|
+
): Record<string, string | undefined> {
|
|
81
|
+
return combineHeaders(this.config.headers(), headers);
|
|
82
|
+
}
|
|
83
|
+
|
|
36
84
|
async uploadFile({
|
|
37
85
|
data,
|
|
38
86
|
mediaType,
|
|
39
87
|
filename,
|
|
88
|
+
abortSignal,
|
|
89
|
+
headers,
|
|
40
90
|
providerOptions,
|
|
41
91
|
}: FilesV4UploadFileCallOptions): Promise<FilesV4UploadFileResult> {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
92
|
+
let xaiOptions: XaiFilesOptions | undefined;
|
|
93
|
+
try {
|
|
94
|
+
xaiOptions = (await parseProviderOptions({
|
|
95
|
+
provider: 'xai',
|
|
96
|
+
providerOptions,
|
|
97
|
+
schema: xaiFilesOptionsSchema,
|
|
98
|
+
})) as XaiFilesOptions | undefined;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// rejected before any request: release the caller's stream
|
|
101
|
+
if (data.type === 'stream') {
|
|
102
|
+
await data.stream.cancel(error).catch(() => {});
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
47
106
|
|
|
48
|
-
const
|
|
107
|
+
const requestHeaders = this.getHeaders(headers);
|
|
108
|
+
const url = `${this.config.baseURL}/files`;
|
|
49
109
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
110
|
+
let response: XaiFilesResponse;
|
|
111
|
+
|
|
112
|
+
if (data.type === 'stream') {
|
|
113
|
+
// xAI rejects uploads where expires_after arrives after the file part,
|
|
114
|
+
// so all fields precede the file.
|
|
115
|
+
const parts: Array<MultipartStreamPart> = [];
|
|
53
116
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
117
|
+
if (xaiOptions?.expiresAfter != null) {
|
|
118
|
+
parts.push({
|
|
119
|
+
type: 'field',
|
|
120
|
+
name: 'expires_after',
|
|
121
|
+
value: String(xaiOptions.expiresAfter),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (xaiOptions?.teamId != null) {
|
|
126
|
+
parts.push({
|
|
127
|
+
type: 'field',
|
|
128
|
+
name: 'team_id',
|
|
129
|
+
value: xaiOptions.teamId,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
parts.push({
|
|
134
|
+
type: 'file',
|
|
135
|
+
name: 'file',
|
|
136
|
+
filename,
|
|
137
|
+
mediaType,
|
|
138
|
+
content: data.stream,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
({ value: response } = await postMultipartStreamToApi({
|
|
142
|
+
url,
|
|
143
|
+
headers: requestHeaders,
|
|
144
|
+
parts,
|
|
145
|
+
failedResponseHandler: xaiFailedResponseHandler,
|
|
146
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
147
|
+
xaiFilesResponseSchema,
|
|
148
|
+
),
|
|
149
|
+
abortSignal,
|
|
150
|
+
fetch: this.config.fetch,
|
|
151
|
+
}));
|
|
57
152
|
} else {
|
|
58
|
-
|
|
59
|
-
}
|
|
153
|
+
const fileBytes = convertInlineFileDataToUint8Array(data);
|
|
60
154
|
|
|
61
|
-
|
|
62
|
-
|
|
155
|
+
const blob = new Blob([fileBytes], {
|
|
156
|
+
type: mediaType,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// FormData serializes in append order; expires_after must precede file.
|
|
160
|
+
const formData = new FormData();
|
|
161
|
+
|
|
162
|
+
if (xaiOptions?.expiresAfter != null) {
|
|
163
|
+
formData.append('expires_after', String(xaiOptions.expiresAfter));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (xaiOptions?.teamId != null) {
|
|
167
|
+
formData.append('team_id', xaiOptions.teamId);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (filename != null) {
|
|
171
|
+
formData.append('file', blob, filename);
|
|
172
|
+
} else {
|
|
173
|
+
formData.append('file', blob);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
({ value: response } = await postFormDataToApi({
|
|
177
|
+
url,
|
|
178
|
+
headers: requestHeaders,
|
|
179
|
+
formData,
|
|
180
|
+
failedResponseHandler: xaiFailedResponseHandler,
|
|
181
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
182
|
+
xaiFilesResponseSchema,
|
|
183
|
+
),
|
|
184
|
+
abortSignal,
|
|
185
|
+
fetch: this.config.fetch,
|
|
186
|
+
}));
|
|
63
187
|
}
|
|
64
188
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
189
|
+
return {
|
|
190
|
+
warnings: [],
|
|
191
|
+
providerReference: { xai: response.id },
|
|
192
|
+
...((response.filename ?? filename)
|
|
193
|
+
? { filename: response.filename ?? filename }
|
|
194
|
+
: {}),
|
|
195
|
+
...(mediaType != null ? { mediaType } : {}),
|
|
196
|
+
...(response.bytes != null ? { byteSize: response.bytes } : {}),
|
|
197
|
+
...(response.created_at != null
|
|
198
|
+
? { createdAt: new Date(response.created_at * 1000) }
|
|
199
|
+
: {}),
|
|
200
|
+
...(response.expires_at != null
|
|
201
|
+
? { expiresAt: new Date(response.expires_at * 1000) }
|
|
202
|
+
: {}),
|
|
203
|
+
providerMetadata: {
|
|
204
|
+
xai: this.toFileMetadata(response),
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async getFileMetadata({
|
|
210
|
+
file,
|
|
211
|
+
abortSignal,
|
|
212
|
+
headers,
|
|
213
|
+
}: FilesV4GetFileMetadataCallOptions): Promise<FilesV4GetFileMetadataResult> {
|
|
214
|
+
const fileId = this.getFileId(file);
|
|
215
|
+
|
|
216
|
+
const { value: response } = await getFromApi({
|
|
217
|
+
url: `${this.config.baseURL}/files/${encodePathSegment(fileId)}`,
|
|
218
|
+
headers: this.getHeaders(headers),
|
|
69
219
|
failedResponseHandler: xaiFailedResponseHandler,
|
|
70
220
|
successfulResponseHandler: createJsonResponseHandler(
|
|
71
221
|
xaiFilesResponseSchema,
|
|
72
222
|
),
|
|
223
|
+
abortSignal,
|
|
73
224
|
fetch: this.config.fetch,
|
|
225
|
+
validateUrl: false,
|
|
74
226
|
});
|
|
75
227
|
|
|
76
228
|
return {
|
|
77
229
|
warnings: [],
|
|
78
230
|
providerReference: { xai: response.id },
|
|
79
|
-
...(
|
|
80
|
-
|
|
231
|
+
...(response.filename != null ? { filename: response.filename } : {}),
|
|
232
|
+
...(response.bytes != null ? { byteSize: response.bytes } : {}),
|
|
233
|
+
...(response.created_at != null
|
|
234
|
+
? { createdAt: new Date(response.created_at * 1000) }
|
|
235
|
+
: {}),
|
|
236
|
+
...(response.expires_at != null
|
|
237
|
+
? { expiresAt: new Date(response.expires_at * 1000) }
|
|
81
238
|
: {}),
|
|
82
|
-
...(mediaType != null ? { mediaType } : {}),
|
|
83
239
|
providerMetadata: {
|
|
84
|
-
xai:
|
|
85
|
-
...(response.filename != null ? { filename: response.filename } : {}),
|
|
86
|
-
...(response.bytes != null ? { bytes: response.bytes } : {}),
|
|
87
|
-
...(response.created_at != null
|
|
88
|
-
? { createdAt: response.created_at }
|
|
89
|
-
: {}),
|
|
90
|
-
},
|
|
240
|
+
xai: this.toFileMetadata(response),
|
|
91
241
|
},
|
|
92
242
|
};
|
|
93
243
|
}
|
|
244
|
+
|
|
245
|
+
async downloadFile({
|
|
246
|
+
file,
|
|
247
|
+
abortSignal,
|
|
248
|
+
headers,
|
|
249
|
+
}: FilesV4DownloadFileCallOptions): Promise<FilesV4DownloadFileResult> {
|
|
250
|
+
const fileId = this.getFileId(file);
|
|
251
|
+
|
|
252
|
+
const { value: content, responseHeaders } = await getFromApi({
|
|
253
|
+
url: `${this.config.baseURL}/files/${encodePathSegment(fileId)}/content`,
|
|
254
|
+
headers: this.getHeaders(headers),
|
|
255
|
+
failedResponseHandler: xaiFailedResponseHandler,
|
|
256
|
+
successfulResponseHandler: createBinaryStreamResponseHandler(),
|
|
257
|
+
abortSignal,
|
|
258
|
+
fetch: this.config.fetch,
|
|
259
|
+
validateUrl: false,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// media type from the content endpoint's Content-Type, without parameters
|
|
263
|
+
const mediaType = responseHeaders?.['content-type']?.split(';')[0].trim();
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
warnings: [],
|
|
267
|
+
content,
|
|
268
|
+
...(mediaType ? { mediaType } : {}),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async deleteFile({
|
|
273
|
+
file,
|
|
274
|
+
abortSignal,
|
|
275
|
+
headers,
|
|
276
|
+
}: FilesV4DeleteFileCallOptions): Promise<FilesV4DeleteFileResult> {
|
|
277
|
+
const fileId = this.getFileId(file);
|
|
278
|
+
|
|
279
|
+
const { value: response } = await deleteFromApi({
|
|
280
|
+
url: `${this.config.baseURL}/files/${encodePathSegment(fileId)}`,
|
|
281
|
+
headers: this.getHeaders(headers),
|
|
282
|
+
failedResponseHandler: xaiFailedResponseHandler,
|
|
283
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
284
|
+
xaiFileDeleteResponseSchema,
|
|
285
|
+
),
|
|
286
|
+
abortSignal,
|
|
287
|
+
fetch: this.config.fetch,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
warnings: [],
|
|
292
|
+
providerReference: { xai: response.id },
|
|
293
|
+
deleted: response.deleted,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private toFileMetadata(
|
|
298
|
+
response: XaiFilesResponse,
|
|
299
|
+
): Record<string, string | number> {
|
|
300
|
+
return {
|
|
301
|
+
...(response.filename != null ? { filename: response.filename } : {}),
|
|
302
|
+
...(response.purpose != null ? { purpose: response.purpose } : {}),
|
|
303
|
+
...(response.bytes != null ? { bytes: response.bytes } : {}),
|
|
304
|
+
...(response.created_at != null
|
|
305
|
+
? { createdAt: response.created_at }
|
|
306
|
+
: {}),
|
|
307
|
+
...(response.status != null ? { status: response.status } : {}),
|
|
308
|
+
...(response.expires_at != null
|
|
309
|
+
? { expiresAt: response.expires_at }
|
|
310
|
+
: {}),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
94
313
|
}
|