@apicity/xai 0.6.6 → 0.7.0

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/dist/src/xai.js CHANGED
@@ -1,18 +1,54 @@
1
1
  import { XaiError, } from "./types.js";
2
- import { XaiChatRequestSchema, XaiImageGenerateRequestSchema, XaiImageEditRequestSchema, XaiVideoGenerateRequestSchema, XaiGrokImagineVideo15ImageToVideoRequestSchema, XaiVideoEditRequestSchema, XaiVideoExtendRequestSchema, XaiBatchCreateRequestSchema, XaiCollectionCreateRequestSchema, XaiCollectionUpdateRequestSchema, XaiDocumentSearchRequestSchema, XaiResponseRequestSchema, XaiResponseCompactRequestSchema, XaiTokenizeTextRequestSchema, XaiRealtimeClientSecretRequestSchema, XaiTtsRequestSchema, XaiSttRequestSchema, XaiCustomVoiceCreateRequestSchema, XaiBillingUsageRequestSchema, XAI_GROK_IMAGINE_VIDEO_1_5_PREVIEW, } from "./zod.js";
2
+ import { XaiChatRequestSchema, XaiImageGenerateRequestSchema, XaiImageEditRequestSchema, XaiFilePublicUrlRequestSchema, XaiVideoGenerateRequestSchema, XaiGrokImagineVideo15ImageToVideoRequestSchema, XaiVideoEditRequestSchema, XaiVideoExtendRequestSchema, XaiBatchCreateRequestSchema, XaiCollectionCreateRequestSchema, XaiCollectionUpdateRequestSchema, XaiDocumentSearchRequestSchema, XaiResponseRequestSchema, XaiResponseCompactRequestSchema, XaiTokenizeTextRequestSchema, XaiRealtimeClientSecretRequestSchema, XaiTtsRequestSchema, XaiSttRequestSchema, XaiCustomVoiceCreateRequestSchema, XaiCustomVoiceUpdateRequestSchema, XaiBillingUsageRequestSchema, XAI_GROK_IMAGINE_VIDEO_1_5_PREVIEW, } from "./zod.js";
3
3
  import { attachExamples } from "./example.js";
4
+ import { createTransport } from "./transport.js";
4
5
  import { withPaidGate } from "./with-paid-gate.js";
5
- // Helper function to safely handle AbortSignal across different environments
6
- function attachAbortHandler(signal, controller) {
7
- if (!signal)
8
- return;
9
- // Handle both standard AbortSignal and node-fetch's AbortSignal
10
- if (typeof signal.addEventListener === "function") {
11
- signal.addEventListener("abort", () => controller.abort(), { once: true });
6
+ function isXaiErrorEnvelope(body) {
7
+ return (typeof body === "object" &&
8
+ body !== null &&
9
+ "error" in body &&
10
+ typeof body.error === "object");
11
+ }
12
+ function parseXaiErrorBody(status, body) {
13
+ if (isXaiErrorEnvelope(body) && typeof body.error?.message === "string") {
14
+ return { message: `XAI API error ${status}: ${body.error.message}` };
15
+ }
16
+ return { message: `XAI API error: ${status}` };
17
+ }
18
+ async function wrapXaiTransportFailure(fn) {
19
+ try {
20
+ return await fn();
12
21
  }
13
- else if (signal.aborted) {
14
- // Already aborted, abort our controller too
15
- controller.abort();
22
+ catch (error) {
23
+ if (error instanceof XaiError)
24
+ throw error;
25
+ throw new XaiError(`XAI request failed: ${error}`, 500);
26
+ }
27
+ }
28
+ async function readJsonResponse(res) {
29
+ return await wrapXaiTransportFailure(async () => (await res.json()));
30
+ }
31
+ async function readArrayBufferResponse(res) {
32
+ return await wrapXaiTransportFailure(async () => await res.arrayBuffer());
33
+ }
34
+ async function jsonRequest(transport, method, path, body, signal) {
35
+ const res = await transport.raw(path, {
36
+ method,
37
+ headers: body === undefined ? undefined : { "Content-Type": "application/json" },
38
+ body: body === undefined ? undefined : JSON.stringify(body),
39
+ signal,
40
+ });
41
+ return await readJsonResponse(res);
42
+ }
43
+ function isAbortSignal(value) {
44
+ return (typeof value === "object" &&
45
+ value !== null &&
46
+ "aborted" in value &&
47
+ "addEventListener" in value);
48
+ }
49
+ function appendOptionalFormField(form, name, value) {
50
+ if (value !== undefined) {
51
+ form.append(name, value);
16
52
  }
17
53
  }
18
54
  const DEFAULT_VIDEO_POLL_INTERVAL_MS = 5000;
@@ -25,10 +61,22 @@ function normalizeImageReference(image) {
25
61
  return rest;
26
62
  }
27
63
  function normalizeImageEditRequest(req) {
64
+ const { image_file_id, image_file_ids, ...rest } = req;
65
+ const normalized = {
66
+ ...rest,
67
+ };
68
+ if (normalized.image === undefined && image_file_id !== undefined) {
69
+ normalized.image = { file_id: image_file_id };
70
+ }
71
+ if (normalized.images === undefined && image_file_ids !== undefined) {
72
+ normalized.images = image_file_ids.map((file_id) => ({ file_id }));
73
+ }
28
74
  return {
29
- ...req,
30
- image: req.image === undefined ? undefined : normalizeImageReference(req.image),
31
- images: req.images?.map(normalizeImageReference),
75
+ ...normalized,
76
+ image: normalized.image === undefined
77
+ ? undefined
78
+ : normalizeImageReference(normalized.image),
79
+ images: normalized.images?.map(normalizeImageReference),
32
80
  };
33
81
  }
34
82
  function normalizeVideoReference(image) {
@@ -36,11 +84,51 @@ function normalizeVideoReference(image) {
36
84
  return { url: image };
37
85
  return image;
38
86
  }
87
+ function normalizeVideoGenerateRequest(req) {
88
+ const { image_file_id, video_file_id, reference_image_file_ids, ...rest } = req;
89
+ const normalized = {
90
+ ...rest,
91
+ };
92
+ if (normalized.image === undefined && image_file_id !== undefined) {
93
+ normalized.image = { file_id: image_file_id };
94
+ }
95
+ if (normalized.video === undefined && video_file_id !== undefined) {
96
+ normalized.video = { file_id: video_file_id };
97
+ }
98
+ if (normalized.reference_images === undefined &&
99
+ reference_image_file_ids !== undefined) {
100
+ normalized.reference_images = reference_image_file_ids.map((file_id) => ({
101
+ file_id,
102
+ }));
103
+ }
104
+ return normalized;
105
+ }
106
+ function normalizeVideoEditRequest(req) {
107
+ const { video_file_id, ...rest } = req;
108
+ const normalized = {
109
+ ...rest,
110
+ };
111
+ if (normalized.video === undefined && video_file_id !== undefined) {
112
+ normalized.video = { file_id: video_file_id };
113
+ }
114
+ return normalized;
115
+ }
116
+ function normalizeVideoExtendRequest(req) {
117
+ const { video_file_id, ...rest } = req;
118
+ const normalized = {
119
+ ...rest,
120
+ };
121
+ if (normalized.video === undefined && video_file_id !== undefined) {
122
+ normalized.video = { file_id: video_file_id };
123
+ }
124
+ return normalized;
125
+ }
39
126
  function applyVideoGenerationDefaults(req) {
40
- if (req.model !== undefined)
41
- return req;
127
+ const normalized = normalizeVideoGenerateRequest(req);
128
+ if (normalized.model !== undefined)
129
+ return normalized;
42
130
  return {
43
- ...req,
131
+ ...normalized,
44
132
  model: XAI_GROK_IMAGINE_VIDEO_1_5_PREVIEW,
45
133
  };
46
134
  }
@@ -67,95 +155,39 @@ export function createXai(opts) {
67
155
  const managementBaseURL = opts.managementBaseURL ?? "https://management-api.x.ai/v1";
68
156
  const managementRootURL = managementBaseURL.replace(/\/v1\/?$/, "");
69
157
  const managementApiKey = opts.managementApiKey ?? opts.apiKey;
70
- const doFetch = opts.fetch ?? fetch;
71
158
  const timeout = opts.timeout ?? 30000;
159
+ const transport = createTransport({
160
+ baseUrl: baseURL,
161
+ timeoutMs: timeout,
162
+ fetchImpl: opts.fetch,
163
+ defaultHeaders: () => ({ Authorization: `Bearer ${opts.apiKey}` }),
164
+ parseErrorBody: parseXaiErrorBody,
165
+ errorClass: XaiError,
166
+ requestFailedPrefix: "XAI request failed",
167
+ });
168
+ const managementTransport = createTransport({
169
+ baseUrl: managementBaseURL,
170
+ timeoutMs: timeout,
171
+ fetchImpl: opts.fetch,
172
+ defaultHeaders: () => ({ Authorization: `Bearer ${managementApiKey}` }),
173
+ parseErrorBody: parseXaiErrorBody,
174
+ errorClass: XaiError,
175
+ requestFailedPrefix: "XAI request failed",
176
+ });
177
+ const managementRootTransport = createTransport({
178
+ baseUrl: managementRootURL,
179
+ timeoutMs: timeout,
180
+ fetchImpl: opts.fetch,
181
+ defaultHeaders: () => ({ Authorization: `Bearer ${managementApiKey}` }),
182
+ parseErrorBody: parseXaiErrorBody,
183
+ errorClass: XaiError,
184
+ requestFailedPrefix: "XAI request failed",
185
+ });
72
186
  async function makeRequest(method, path, body, signal) {
73
- const controller = new AbortController();
74
- const timeoutId = setTimeout(() => controller.abort(), timeout);
75
- if (signal) {
76
- attachAbortHandler(signal, controller);
77
- }
78
- try {
79
- const headers = {
80
- Authorization: `Bearer ${opts.apiKey}`,
81
- };
82
- const init = {
83
- method,
84
- headers,
85
- signal: controller.signal,
86
- };
87
- if (body !== undefined) {
88
- headers["Content-Type"] = "application/json";
89
- init.body = JSON.stringify(body);
90
- }
91
- const res = await doFetch(`${baseURL}${path}`, init);
92
- clearTimeout(timeoutId);
93
- if (!res.ok) {
94
- let message = `XAI API error: ${res.status}`;
95
- let body = null;
96
- try {
97
- body = await res.json();
98
- if (typeof body === "object" && body !== null && "error" in body) {
99
- const err = body.error;
100
- if (err?.message) {
101
- message = `XAI API error ${res.status}: ${err.message}`;
102
- }
103
- }
104
- }
105
- catch {
106
- // ignore parse errors
107
- }
108
- throw new XaiError(message, res.status, body);
109
- }
110
- return (await res.json());
111
- }
112
- catch (error) {
113
- clearTimeout(timeoutId);
114
- if (error instanceof XaiError)
115
- throw error;
116
- throw new XaiError(`XAI request failed: ${error}`, 500);
117
- }
187
+ return await jsonRequest(transport, method, path, body, signal);
118
188
  }
119
189
  async function makeGetTextRequest(path, signal) {
120
- const controller = new AbortController();
121
- const timeoutId = setTimeout(() => controller.abort(), timeout);
122
- if (signal) {
123
- attachAbortHandler(signal, controller);
124
- }
125
- try {
126
- const res = await doFetch(`${baseURL}${path}`, {
127
- method: "GET",
128
- headers: {
129
- Authorization: `Bearer ${opts.apiKey}`,
130
- },
131
- signal: controller.signal,
132
- });
133
- clearTimeout(timeoutId);
134
- if (!res.ok) {
135
- let message = `XAI API error: ${res.status}`;
136
- let body = null;
137
- try {
138
- body = await res.json();
139
- if (typeof body === "object" && body !== null && "error" in body) {
140
- const err = body.error;
141
- if (err?.message) {
142
- message = `XAI API error ${res.status}: ${err.message}`;
143
- }
144
- }
145
- }
146
- catch {
147
- // ignore parse errors
148
- }
149
- throw new XaiError(message, res.status, body);
150
- }
151
- return await res.text();
152
- }
153
- catch (error) {
154
- clearTimeout(timeoutId);
155
- if (error instanceof XaiError)
156
- throw error;
157
- throw new XaiError(`XAI request failed: ${error}`, 500);
158
- }
190
+ return await transport.getText(path, { signal });
159
191
  }
160
192
  function buildQuery(params) {
161
193
  const parts = [];
@@ -174,11 +206,19 @@ export function createXai(opts) {
174
206
  req.model !== XAI_GROK_IMAGINE_VIDEO_1_5_PREVIEW) {
175
207
  throw new XaiError(`Grok Imagine image-to-video uses ${XAI_GROK_IMAGINE_VIDEO_1_5_PREVIEW}`, 400, { model: req.model });
176
208
  }
177
- const { pollIntervalMs = DEFAULT_VIDEO_POLL_INTERVAL_MS, maxPolls = DEFAULT_VIDEO_MAX_POLLS, image, } = req;
209
+ const { pollIntervalMs = DEFAULT_VIDEO_POLL_INTERVAL_MS, maxPolls = DEFAULT_VIDEO_MAX_POLLS, image, image_file_id, } = req;
210
+ const imageReference = image !== undefined
211
+ ? normalizeVideoReference(image)
212
+ : image_file_id !== undefined
213
+ ? { file_id: image_file_id }
214
+ : undefined;
215
+ if (imageReference === undefined) {
216
+ throw new XaiError("XAI image-to-video requires image or image_file_id", 400);
217
+ }
178
218
  const generationRequest = {
179
219
  prompt: req.prompt,
180
220
  model: XAI_GROK_IMAGINE_VIDEO_1_5_PREVIEW,
181
- image: normalizeVideoReference(image),
221
+ image: imageReference,
182
222
  };
183
223
  if (req.duration !== undefined)
184
224
  generationRequest.duration = req.duration;
@@ -188,6 +228,9 @@ export function createXai(opts) {
188
228
  if (req.resolution !== undefined) {
189
229
  generationRequest.resolution = req.resolution;
190
230
  }
231
+ if (req.storage_options !== undefined) {
232
+ generationRequest.storage_options = req.storage_options;
233
+ }
191
234
  const start = await makeRequest("POST", "/videos/generations", generationRequest, signal);
192
235
  let lastStatus;
193
236
  for (let poll = 0; poll < maxPolls; poll++) {
@@ -218,102 +261,10 @@ export function createXai(opts) {
218
261
  throw new XaiError(`XAI video generation timed out after ${maxPolls} polls: ${start.request_id}`, 408, lastStatus ?? { request_id: start.request_id });
219
262
  }
220
263
  async function makeManagementRequest(method, path, body, signal) {
221
- const controller = new AbortController();
222
- const timeoutId = setTimeout(() => controller.abort(), timeout);
223
- if (signal) {
224
- attachAbortHandler(signal, controller);
225
- }
226
- try {
227
- const headers = {
228
- Authorization: `Bearer ${managementApiKey}`,
229
- };
230
- const init = {
231
- method,
232
- headers,
233
- signal: controller.signal,
234
- };
235
- if (body !== undefined) {
236
- headers["Content-Type"] = "application/json";
237
- init.body = JSON.stringify(body);
238
- }
239
- const res = await doFetch(`${managementBaseURL}${path}`, init);
240
- clearTimeout(timeoutId);
241
- if (!res.ok) {
242
- let message = `XAI API error: ${res.status}`;
243
- let errBody = null;
244
- try {
245
- errBody = await res.json();
246
- if (typeof errBody === "object" &&
247
- errBody !== null &&
248
- "error" in errBody) {
249
- const err = errBody.error;
250
- if (err?.message) {
251
- message = `XAI API error ${res.status}: ${err.message}`;
252
- }
253
- }
254
- }
255
- catch {
256
- // ignore parse errors
257
- }
258
- throw new XaiError(message, res.status, errBody);
259
- }
260
- return (await res.json());
261
- }
262
- catch (error) {
263
- clearTimeout(timeoutId);
264
- if (error instanceof XaiError)
265
- throw error;
266
- throw new XaiError(`XAI request failed: ${error}`, 500);
267
- }
264
+ return await jsonRequest(managementTransport, method, path, body, signal);
268
265
  }
269
266
  async function makeManagementRootRequest(method, path, body, signal) {
270
- const controller = new AbortController();
271
- const timeoutId = setTimeout(() => controller.abort(), timeout);
272
- if (signal) {
273
- attachAbortHandler(signal, controller);
274
- }
275
- try {
276
- const headers = {
277
- Authorization: `Bearer ${managementApiKey}`,
278
- };
279
- const init = {
280
- method,
281
- headers,
282
- signal: controller.signal,
283
- };
284
- if (body !== undefined) {
285
- headers["Content-Type"] = "application/json";
286
- init.body = JSON.stringify(body);
287
- }
288
- const res = await doFetch(`${managementRootURL}${path}`, init);
289
- clearTimeout(timeoutId);
290
- if (!res.ok) {
291
- let message = `XAI API error: ${res.status}`;
292
- let errBody = null;
293
- try {
294
- errBody = await res.json();
295
- if (typeof errBody === "object" &&
296
- errBody !== null &&
297
- "error" in errBody) {
298
- const err = errBody.error;
299
- if (err?.message) {
300
- message = `XAI API error ${res.status}: ${err.message}`;
301
- }
302
- }
303
- }
304
- catch {
305
- // ignore parse errors
306
- }
307
- throw new XaiError(message, res.status, errBody);
308
- }
309
- return (await res.json());
310
- }
311
- catch (error) {
312
- clearTimeout(timeoutId);
313
- if (error instanceof XaiError)
314
- throw error;
315
- throw new XaiError(`XAI request failed: ${error}`, 500);
316
- }
267
+ return await jsonRequest(managementRootTransport, method, path, body, signal);
317
268
  }
318
269
  function buildManagementQuery(params) {
319
270
  const parts = [];
@@ -332,91 +283,19 @@ export function createXai(opts) {
332
283
  return parts.length > 0 ? `?${parts.join("&")}` : "";
333
284
  }
334
285
  async function makeBinaryRequest(path, body, signal) {
335
- const controller = new AbortController();
336
- const timeoutId = setTimeout(() => controller.abort(), timeout);
337
- if (signal) {
338
- attachAbortHandler(signal, controller);
339
- }
340
- try {
341
- const res = await doFetch(`${baseURL}${path}`, {
342
- method: "POST",
343
- headers: {
344
- Authorization: `Bearer ${opts.apiKey}`,
345
- "Content-Type": "application/json",
346
- },
347
- body: JSON.stringify(body),
348
- signal: controller.signal,
349
- });
350
- clearTimeout(timeoutId);
351
- if (!res.ok) {
352
- let message = `XAI API error: ${res.status}`;
353
- let errBody = null;
354
- try {
355
- errBody = await res.json();
356
- if (typeof errBody === "object" &&
357
- errBody !== null &&
358
- "error" in errBody) {
359
- const err = errBody.error;
360
- if (err?.message) {
361
- message = `XAI API error ${res.status}: ${err.message}`;
362
- }
363
- }
364
- }
365
- catch {
366
- // ignore parse errors
367
- }
368
- throw new XaiError(message, res.status, errBody);
369
- }
370
- return await res.arrayBuffer();
371
- }
372
- catch (error) {
373
- clearTimeout(timeoutId);
374
- if (error instanceof XaiError)
375
- throw error;
376
- throw new XaiError(`XAI request failed: ${error}`, 500);
377
- }
286
+ const res = await transport.raw(path, {
287
+ method: "POST",
288
+ headers: { "Content-Type": "application/json" },
289
+ body: JSON.stringify(body),
290
+ signal,
291
+ });
292
+ return await readArrayBufferResponse(res);
293
+ }
294
+ async function makeGetBinaryRequest(path, signal) {
295
+ return await transport.getBinary(path, { signal });
378
296
  }
379
297
  async function makeMultipartRequest(path, form, signal) {
380
- const controller = new AbortController();
381
- const timeoutId = setTimeout(() => controller.abort(), timeout);
382
- if (signal) {
383
- attachAbortHandler(signal, controller);
384
- }
385
- try {
386
- const res = await doFetch(`${baseURL}${path}`, {
387
- method: "POST",
388
- headers: { Authorization: `Bearer ${opts.apiKey}` },
389
- body: form,
390
- signal: controller.signal,
391
- });
392
- clearTimeout(timeoutId);
393
- if (!res.ok) {
394
- let message = `XAI API error: ${res.status}`;
395
- let errBody = null;
396
- try {
397
- errBody = await res.json();
398
- if (typeof errBody === "object" &&
399
- errBody !== null &&
400
- "error" in errBody) {
401
- const err = errBody.error;
402
- if (err?.message) {
403
- message = `XAI API error ${res.status}: ${err.message}`;
404
- }
405
- }
406
- }
407
- catch {
408
- // ignore parse errors
409
- }
410
- throw new XaiError(message, res.status, errBody);
411
- }
412
- return (await res.json());
413
- }
414
- catch (error) {
415
- clearTimeout(timeoutId);
416
- if (error instanceof XaiError)
417
- throw error;
418
- throw new XaiError(`XAI request failed: ${error}`, 500);
419
- }
298
+ return await transport.postForm(path, form, { signal });
420
299
  }
421
300
  // POST https://api.x.ai/v1/batches
422
301
  // Docs: https://docs.x.ai/docs/api-reference
@@ -447,13 +326,20 @@ export function createXai(opts) {
447
326
  await makeManagementRequest("POST", `/collections/${collectionId}/documents/${fileId}`, req ?? {}, signal);
448
327
  },
449
328
  });
450
- // GET https://api.x.ai/v1/files/{fileIdOrSignal}
329
+ // GET https://api.x.ai/v1/files/{paramsOrFileIdOrSignal}
451
330
  // Docs: https://docs.x.ai/docs/api-reference
452
- async function getFiles(fileIdOrSignal, signal) {
453
- if (typeof fileIdOrSignal === "string") {
454
- return makeRequest("GET", `/files/${fileIdOrSignal}`, undefined, signal);
455
- }
456
- return makeRequest("GET", "/files", undefined, fileIdOrSignal);
331
+ async function getFiles(paramsOrFileIdOrSignal, signal) {
332
+ if (typeof paramsOrFileIdOrSignal === "string") {
333
+ return makeRequest("GET", `/files/${encodeURIComponent(paramsOrFileIdOrSignal)}`, undefined, signal);
334
+ }
335
+ const queryParams = isAbortSignal(paramsOrFileIdOrSignal)
336
+ ? {}
337
+ : (paramsOrFileIdOrSignal ?? {});
338
+ const requestSignal = isAbortSignal(paramsOrFileIdOrSignal)
339
+ ? paramsOrFileIdOrSignal
340
+ : signal;
341
+ const query = buildQuery(queryParams);
342
+ return makeRequest("GET", `/files${query}`, undefined, requestSignal);
457
343
  }
458
344
  const getFilesNamespace = Object.assign(getFiles, {
459
345
  // GET https://api.x.ai/v1/files/{fileId}/content
@@ -566,6 +452,56 @@ export function createXai(opts) {
566
452
  }, {
567
453
  schema: XaiCollectionUpdateRequestSchema,
568
454
  });
455
+ // POST https://api.x.ai/v1/custom-voices
456
+ // Docs: https://docs.x.ai/developers/model-capabilities/audio/custom-voices
457
+ const postCustomVoices = Object.assign(async function customVoices(req, signal) {
458
+ const form = new FormData();
459
+ form.append("file", req.file, req.filename ?? "reference");
460
+ appendOptionalFormField(form, "name", req.name);
461
+ appendOptionalFormField(form, "description", req.description);
462
+ appendOptionalFormField(form, "gender", req.gender);
463
+ appendOptionalFormField(form, "accent", req.accent);
464
+ appendOptionalFormField(form, "age", req.age);
465
+ appendOptionalFormField(form, "language", req.language);
466
+ appendOptionalFormField(form, "use_case", req.use_case);
467
+ appendOptionalFormField(form, "tone", req.tone);
468
+ return await makeMultipartRequest("/custom-voices", form, signal);
469
+ }, {
470
+ schema: XaiCustomVoiceCreateRequestSchema,
471
+ });
472
+ // GET https://api.x.ai/v1/custom-voices/{paramsOrVoiceIdOrSignal}
473
+ // Docs: https://docs.x.ai/developers/model-capabilities/audio/custom-voices
474
+ const getCustomVoices = Object.assign(async function listCustomVoices(paramsOrVoiceIdOrSignal, signal) {
475
+ if (typeof paramsOrVoiceIdOrSignal === "string") {
476
+ return await makeRequest("GET", `/custom-voices/${encodeURIComponent(paramsOrVoiceIdOrSignal)}`, undefined, signal);
477
+ }
478
+ const queryParams = isAbortSignal(paramsOrVoiceIdOrSignal)
479
+ ? {}
480
+ : (paramsOrVoiceIdOrSignal ?? {});
481
+ const requestSignal = isAbortSignal(paramsOrVoiceIdOrSignal)
482
+ ? paramsOrVoiceIdOrSignal
483
+ : signal;
484
+ const query = buildQuery(queryParams);
485
+ return await makeRequest("GET", `/custom-voices${query}`, undefined, requestSignal);
486
+ }, {
487
+ // GET https://api.x.ai/v1/custom-voices/{voiceId}/audio
488
+ // Docs: https://docs.x.ai/developers/model-capabilities/audio/custom-voices
489
+ audio: async function customVoiceAudio(voiceId, signal) {
490
+ return await makeGetBinaryRequest(`/custom-voices/${encodeURIComponent(voiceId)}/audio`, signal);
491
+ },
492
+ });
493
+ // PATCH https://api.x.ai/v1/custom-voices/{voiceId}
494
+ // Docs: https://docs.x.ai/developers/model-capabilities/audio/custom-voices
495
+ const patchCustomVoices = Object.assign(async function updateCustomVoice(voiceId, req, signal) {
496
+ return await makeRequest("PATCH", `/custom-voices/${encodeURIComponent(voiceId)}`, req, signal);
497
+ }, {
498
+ schema: XaiCustomVoiceUpdateRequestSchema,
499
+ });
500
+ // DELETE https://api.x.ai/v1/custom-voices/{voiceId}
501
+ // Docs: https://docs.x.ai/developers/model-capabilities/audio/custom-voices
502
+ async function deleteCustomVoices(voiceId, signal) {
503
+ return await makeRequest("DELETE", `/custom-voices/${encodeURIComponent(voiceId)}`, undefined, signal);
504
+ }
569
505
  return attachExamples(withPaidGate("xai", {
570
506
  post: {
571
507
  v1: {
@@ -622,14 +558,14 @@ export function createXai(opts) {
622
558
  // POST https://api.x.ai/v1/videos/edits
623
559
  // Docs: https://docs.x.ai/docs/api-reference
624
560
  edits: Object.assign(async function edits(req, signal) {
625
- return await makeRequest("POST", "/videos/edits", req, signal);
561
+ return await makeRequest("POST", "/videos/edits", normalizeVideoEditRequest(req), signal);
626
562
  }, {
627
563
  schema: XaiVideoEditRequestSchema,
628
564
  }),
629
565
  // POST https://api.x.ai/v1/videos/extensions
630
566
  // Docs: https://docs.x.ai/docs/api-reference
631
567
  extensions: Object.assign(async function extensions(req, signal) {
632
- return await makeRequest("POST", "/videos/extensions", req, signal);
568
+ return await makeRequest("POST", "/videos/extensions", normalizeVideoExtendRequest(req), signal);
633
569
  }, {
634
570
  schema: XaiVideoExtendRequestSchema,
635
571
  }),
@@ -637,52 +573,31 @@ export function createXai(opts) {
637
573
  // POST https://api.x.ai/v1/files
638
574
  // Docs: https://docs.x.ai/docs/api-reference
639
575
  files: Object.assign(async function postFiles(file, filename, purpose, signal) {
640
- const controller = new AbortController();
641
- const timeoutId = setTimeout(() => controller.abort(), timeout);
642
- if (signal) {
643
- attachAbortHandler(signal, controller);
644
- }
645
- try {
646
- const formData = new FormData();
647
- formData.append("file", file, filename);
648
- if (purpose !== undefined)
649
- formData.append("purpose", purpose);
650
- const res = await doFetch(`${baseURL}/files`, {
651
- method: "POST",
652
- headers: { Authorization: `Bearer ${opts.apiKey}` },
653
- body: formData,
654
- signal: controller.signal,
655
- });
656
- clearTimeout(timeoutId);
657
- if (!res.ok) {
658
- let message = `XAI API error: ${res.status}`;
659
- let body = null;
660
- try {
661
- body = await res.json();
662
- if (typeof body === "object" &&
663
- body !== null &&
664
- "error" in body) {
665
- const err = body
666
- .error;
667
- if (err?.message) {
668
- message = `XAI API error ${res.status}: ${err.message}`;
669
- }
670
- }
671
- }
672
- catch {
673
- // ignore parse errors
674
- }
675
- throw new XaiError(message, res.status, body);
676
- }
677
- return (await res.json());
678
- }
679
- catch (error) {
680
- clearTimeout(timeoutId);
681
- if (error instanceof XaiError)
682
- throw error;
683
- throw new XaiError(`XAI request failed: ${error}`, 500);
684
- }
685
- }, {}),
576
+ const formData = new FormData();
577
+ formData.append("file", file, filename);
578
+ if (purpose !== undefined)
579
+ formData.append("purpose", purpose);
580
+ return await makeMultipartRequest("/files", formData, signal);
581
+ }, {
582
+ // POST https://api.x.ai/v1/files/{fileId}/public-url
583
+ // Docs: https://docs.x.ai/developers/files/public-urls
584
+ publicUrl: Object.assign(async function createFilePublicUrl(fileId, reqOrSignal, signal) {
585
+ const req = isAbortSignal(reqOrSignal)
586
+ ? {}
587
+ : (reqOrSignal ?? {});
588
+ const requestSignal = isAbortSignal(reqOrSignal)
589
+ ? reqOrSignal
590
+ : signal;
591
+ return await makeRequest("POST", `/files/${encodeURIComponent(fileId)}/public-url`, req, requestSignal);
592
+ }, {
593
+ schema: XaiFilePublicUrlRequestSchema,
594
+ // POST https://api.x.ai/v1/files/{fileId}/public-url/revoke
595
+ // Docs: https://docs.x.ai/developers/files/public-urls
596
+ revoke: async function revokeFilePublicUrl(fileId, signal) {
597
+ return await makeRequest("POST", `/files/${encodeURIComponent(fileId)}/public-url/revoke`, {}, signal);
598
+ },
599
+ }),
600
+ }),
686
601
  batches: postBatches,
687
602
  documents: {
688
603
  // POST https://api.x.ai/v1/documents/search
@@ -728,17 +643,7 @@ export function createXai(opts) {
728
643
  }, {
729
644
  schema: XaiSttRequestSchema,
730
645
  }),
731
- // POST https://api.x.ai/v1/custom-voices
732
- // Docs: https://docs.x.ai/docs/api-reference
733
- customVoices: Object.assign(async function customVoices(req, signal) {
734
- const form = new FormData();
735
- form.append("file", req.file, req.filename ?? "reference");
736
- form.append("name", req.name);
737
- form.append("language", req.language);
738
- return await makeMultipartRequest("/custom-voices", form, signal);
739
- }, {
740
- schema: XaiCustomVoiceCreateRequestSchema,
741
- }),
646
+ customVoices: postCustomVoices,
742
647
  },
743
648
  managementApi: {
744
649
  v1: {
@@ -773,50 +678,12 @@ export function createXai(opts) {
773
678
  // GET https://api.x.ai/v1/chat/deferred-completion/{requestId}
774
679
  // Docs: https://docs.x.ai/docs/api-reference
775
680
  deferredCompletion: async function deferredCompletion(requestId, signal) {
776
- const controller = new AbortController();
777
- const timeoutId = setTimeout(() => controller.abort(), timeout);
778
- if (signal) {
779
- attachAbortHandler(signal, controller);
780
- }
781
- try {
782
- const res = await doFetch(`${baseURL}/chat/deferred-completion/${encodeURIComponent(requestId)}`, {
783
- method: "GET",
784
- headers: { Authorization: `Bearer ${opts.apiKey}` },
785
- signal: controller.signal,
786
- });
787
- clearTimeout(timeoutId);
788
- if (res.status === 202) {
789
- return { ready: false, data: null };
790
- }
791
- if (!res.ok) {
792
- let message = `XAI API error: ${res.status}`;
793
- let body = null;
794
- try {
795
- body = await res.json();
796
- if (typeof body === "object" &&
797
- body !== null &&
798
- "error" in body) {
799
- const err = body
800
- .error;
801
- if (err?.message) {
802
- message = `XAI API error ${res.status}: ${err.message}`;
803
- }
804
- }
805
- }
806
- catch {
807
- // ignore parse errors
808
- }
809
- throw new XaiError(message, res.status, body);
810
- }
811
- const data = (await res.json());
812
- return { ready: true, data };
813
- }
814
- catch (error) {
815
- clearTimeout(timeoutId);
816
- if (error instanceof XaiError)
817
- throw error;
818
- throw new XaiError(`XAI request failed: ${error}`, 500);
681
+ const res = await transport.raw(`/chat/deferred-completion/${encodeURIComponent(requestId)}`, { signal });
682
+ if (res.status === 202) {
683
+ return { ready: false, data: null };
819
684
  }
685
+ const data = await readJsonResponse(res);
686
+ return { ready: true, data };
820
687
  },
821
688
  },
822
689
  // GET https://api.x.ai/v1/videos/{requestId}
@@ -830,6 +697,7 @@ export function createXai(opts) {
830
697
  imageGenerationModels: getImageGenerationModels,
831
698
  videoGenerationModels: getVideoGenerationModels,
832
699
  batches: getBatchesNamespace,
700
+ customVoices: getCustomVoices,
833
701
  },
834
702
  managementApi: {
835
703
  v1: {
@@ -882,37 +750,9 @@ export function createXai(opts) {
882
750
  // DELETE https://api.x.ai/v1/files/{fileId}
883
751
  // Docs: https://docs.x.ai/docs/api-reference
884
752
  files: async function deleteFiles(fileId, signal) {
885
- const controller = new AbortController();
886
- const timeoutId = setTimeout(() => controller.abort(), timeout);
887
- if (signal) {
888
- attachAbortHandler(signal, controller);
889
- }
890
- try {
891
- const res = await doFetch(`${baseURL}/files/${fileId}`, {
892
- method: "DELETE",
893
- headers: { Authorization: `Bearer ${opts.apiKey}` },
894
- signal: controller.signal,
895
- });
896
- clearTimeout(timeoutId);
897
- if (!res.ok) {
898
- let deleteBody = null;
899
- try {
900
- deleteBody = await res.json();
901
- }
902
- catch {
903
- // ignore parse errors
904
- }
905
- throw new XaiError(`XAI API error: ${res.status}`, res.status, deleteBody);
906
- }
907
- return (await res.json());
908
- }
909
- catch (error) {
910
- clearTimeout(timeoutId);
911
- if (error instanceof XaiError)
912
- throw error;
913
- throw new XaiError(`XAI request failed: ${error}`, 500);
914
- }
753
+ return await makeRequest("DELETE", `/files/${fileId}`, undefined, signal);
915
754
  },
755
+ customVoices: deleteCustomVoices,
916
756
  },
917
757
  managementApi: {
918
758
  v1: {
@@ -928,6 +768,9 @@ export function createXai(opts) {
928
768
  },
929
769
  },
930
770
  patch: {
771
+ v1: {
772
+ customVoices: patchCustomVoices,
773
+ },
931
774
  managementApi: {
932
775
  v1: {
933
776
  collections: {