@gullabs/xai 0.2.5 → 0.3.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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AuthMaterial, LlmError, ProviderAdapter, ModelDescriptor, ModelRegistry, Usage, Cost, PricingSource, ProviderPlugin } from '@gullabs/core';
1
+ import { AuthMaterial, LlmError, ProviderAdapter, JsonValue, Logger, ModelDescriptor, ModelRegistry, Usage, Cost, PricingSource, ProviderPlugin } from '@gullabs/core';
2
2
  import { z } from 'zod';
3
3
 
4
4
  /**
@@ -55,8 +55,19 @@ interface XaiInputImagePart {
55
55
  type: 'input_image';
56
56
  image_url: string;
57
57
  }
58
+ /**
59
+ * A file attachment content item within an xAI Responses API input message.
60
+ * Prefer `file_id` for private uploads (via {@link XaiFileStore}); `file_url`
61
+ * is for publicly reachable documents. Attaching either implicitly enables
62
+ * xAI's `attachment_search` agentic tool.
63
+ */
64
+ interface XaiInputFilePart {
65
+ type: 'input_file';
66
+ file_id?: string;
67
+ file_url?: string;
68
+ }
58
69
  /** Union of content-part shapes an input message may carry. */
59
- type XaiInputContentPart = XaiInputTextPart | XaiInputImagePart;
70
+ type XaiInputContentPart = XaiInputTextPart | XaiInputImagePart | XaiInputFilePart;
60
71
  /** A single role+content input item constructed by the (future) adapter. */
61
72
  interface XaiInputItem {
62
73
  role: 'user' | 'assistant' | 'system' | 'developer';
@@ -267,6 +278,120 @@ interface XaiAdapterOptions {
267
278
  */
268
279
  declare function xaiAdapter(opts?: XaiAdapterOptions): ProviderAdapter;
269
280
 
281
+ /**
282
+ * XaiFileStore — thin wrapper over the xAI Files REST API.
283
+ *
284
+ * Upload / get / list / delete / content over injectable `fetch`.
285
+ * No READY-state polling (upload returns metadata immediately).
286
+ * No ambient credential reads — auth is injected at construction.
287
+ *
288
+ * @module
289
+ */
290
+
291
+ /** xAI minimum `expires_after` (1 hour), inclusive. */
292
+ declare const XAI_FILE_TTL_MIN_SECONDS = 3600;
293
+ /** xAI maximum `expires_after` (30 days), inclusive. */
294
+ declare const XAI_FILE_TTL_MAX_SECONDS = 2592000;
295
+ /**
296
+ * Conservative max upload size (48 MiB). Managing-files docs say ~48 MB;
297
+ * upload REST says 50 MB — we take the lower bound.
298
+ */
299
+ declare const XAI_FILE_MAX_BYTES: number;
300
+ /** Default Files API base (includes `/v1`). */
301
+ declare const XAI_FILES_DEFAULT_BASE_URL = "https://api.x.ai/v1";
302
+ /** A handle to a file stored in the xAI Files API. */
303
+ interface XaiFileHandle {
304
+ /** File id, e.g. `"file_a128090d-…"`. Use as `FileRefPart.fileId`. */
305
+ id: string;
306
+ filename?: string;
307
+ bytes?: number;
308
+ purpose?: string;
309
+ createdAt?: Date;
310
+ /** Present when the file has a TTL; key omitted when permanent / unknown. */
311
+ expiresAt?: Date;
312
+ /** Full vendor JSON object for forward-compat (P2 raw lane). */
313
+ raw?: {
314
+ [k: string]: JsonValue;
315
+ };
316
+ }
317
+ interface XaiFileUploadInput {
318
+ data: Uint8Array | Blob;
319
+ filename: string;
320
+ mimeType?: string;
321
+ /**
322
+ * TTL in seconds. Must be an integer in
323
+ * `[XAI_FILE_TTL_MIN_SECONDS, XAI_FILE_TTL_MAX_SECONDS]` when set.
324
+ * Omit only for permanent storage (discouraged for ephemeral corpus).
325
+ */
326
+ expiresAfterSeconds?: number;
327
+ /** Default `"assistants"` (OpenAI SDK convention; xAI does not enforce). */
328
+ purpose?: string;
329
+ }
330
+ interface XaiFileListOptions {
331
+ /** 1..100. Server default is 100 when omitted. */
332
+ limit?: number;
333
+ order?: 'asc' | 'desc';
334
+ sortBy?: 'created_at' | 'filename' | 'size';
335
+ paginationToken?: string;
336
+ }
337
+ interface XaiFileListResult {
338
+ files: XaiFileHandle[];
339
+ paginationToken?: string;
340
+ }
341
+ interface XaiFileStoreOptions {
342
+ auth: AuthMaterial;
343
+ /** Default {@link XAI_FILES_DEFAULT_BASE_URL}. */
344
+ baseUrl?: string;
345
+ /** Injectable fetch for tests. Default: global `fetch`. */
346
+ fetch?: typeof fetch;
347
+ /**
348
+ * Delete failures that are NOT already-gone (404).
349
+ * Default: `logger.error` or `console.error` with a redacted message.
350
+ */
351
+ onDeleteError?: (fileId: string, err: unknown) => void;
352
+ logger?: Logger;
353
+ /** Injectable clock for tests. Default: `Date.now`. */
354
+ now?: () => number;
355
+ }
356
+ /**
357
+ * **Auth snapshot note:** captures `AuthMaterial` at construction and holds
358
+ * the resolved API key for the store lifetime. Correct for static API keys.
359
+ * Refreshable credentials would need a resolver callback (see GoogleFileStore
360
+ * / ADR-020).
361
+ */
362
+ declare class XaiFileStore {
363
+ private readonly apiKey;
364
+ private readonly baseUrl;
365
+ private readonly fetchImpl;
366
+ private readonly onDeleteError;
367
+ private readonly logger;
368
+ constructor(opts: XaiFileStoreOptions);
369
+ private authHeaders;
370
+ private filesUrl;
371
+ /** Build RequestInit without writing `signal: undefined` (exactOptionalPropertyTypes). */
372
+ private requestInit;
373
+ /**
374
+ * Upload bytes to xAI Files. Returns immediately with metadata (no poll).
375
+ *
376
+ * Multipart field order is load-bearing: `expires_after` then `purpose`
377
+ * then `file` — reversing order yields HTTP 400 from xAI.
378
+ */
379
+ upload(input: XaiFileUploadInput, signal?: AbortSignal): Promise<XaiFileHandle>;
380
+ get(fileId: string, signal?: AbortSignal): Promise<XaiFileHandle>;
381
+ list(opts?: XaiFileListOptions, signal?: AbortSignal): Promise<XaiFileListResult>;
382
+ /**
383
+ * Delete a file. Idempotent: HTTP 404 → success.
384
+ * Other errors are forwarded to `onDeleteError` and **not** rethrown (P5).
385
+ */
386
+ delete(fileIdOrHandle: string | Pick<XaiFileHandle, 'id'>, signal?: AbortSignal): Promise<void>;
387
+ /**
388
+ * Delete many files. Each failure is individually fail-open; none are thrown.
389
+ */
390
+ deleteAll(ids: ReadonlyArray<string | Pick<XaiFileHandle, 'id'>>, signal?: AbortSignal): Promise<void>;
391
+ /** Download raw file bytes. */
392
+ getContent(fileId: string, signal?: AbortSignal): Promise<Uint8Array>;
393
+ }
394
+
270
395
  /**
271
396
  * Strict Zod config schema for xAI's `grok-4.5` model.
272
397
  *
@@ -441,4 +566,4 @@ declare function xaiPricingSource(): PricingSource;
441
566
  */
442
567
  declare function xaiProvider(opts?: XaiAdapterOptions): ProviderPlugin;
443
568
 
444
- export { Grok45ConfigSchema, XAI_PRICING, type XaiAdapterOptions, type XaiClientLike, type XaiInputContentPart, type XaiInputImagePart, type XaiInputItem, type XaiInputTextPart, type XaiMessageOutputItem, type XaiModelRates, type XaiOutputItem, type XaiOutputTextPart, type XaiProviderOptions, type XaiReasoningOutputItem, type XaiReasoningSummaryPart, type XaiResponseCreateParams, type XaiResponseShape, type XaiTextFormat, type XaiUsageShape, buildXaiClient, classifyXaiError, computeXaiCost, grok45ModelDescriptor, requireApiKey, xaiAdapter, xaiModelDescriptors, xaiPricingSource, xaiPricingVersion, xaiProvider, xaiRegistry };
569
+ export { Grok45ConfigSchema, XAI_FILES_DEFAULT_BASE_URL, XAI_FILE_MAX_BYTES, XAI_FILE_TTL_MAX_SECONDS, XAI_FILE_TTL_MIN_SECONDS, XAI_PRICING, type XaiAdapterOptions, type XaiClientLike, type XaiFileHandle, type XaiFileListOptions, type XaiFileListResult, XaiFileStore, type XaiFileStoreOptions, type XaiFileUploadInput, type XaiInputContentPart, type XaiInputFilePart, type XaiInputImagePart, type XaiInputItem, type XaiInputTextPart, type XaiMessageOutputItem, type XaiModelRates, type XaiOutputItem, type XaiOutputTextPart, type XaiProviderOptions, type XaiReasoningOutputItem, type XaiReasoningSummaryPart, type XaiResponseCreateParams, type XaiResponseShape, type XaiTextFormat, type XaiUsageShape, buildXaiClient, classifyXaiError, computeXaiCost, grok45ModelDescriptor, requireApiKey, xaiAdapter, xaiModelDescriptors, xaiPricingSource, xaiPricingVersion, xaiProvider, xaiRegistry };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { zodToStandardSchema, toConfigJsonSchema, createModelRegistry, LlmError, classifyError, assertNever } from '@gullabs/core';
1
+ import { zodToStandardSchema, toConfigJsonSchema, createModelRegistry, LlmError, classifyError, redactSecrets, assertNever } from '@gullabs/core';
2
2
  import { z } from 'zod';
3
3
 
4
4
  // src/client.ts
@@ -62,8 +62,19 @@ function mapPart(p) {
62
62
  `xAI only accepts public http(s) image URLs via FileUriPart; got scheme of "${p.uri}" / mimeType "${p.mimeType}".`
63
63
  );
64
64
  }
65
+ if (p.uri.includes("generativelanguage.googleapis.com") || p.uri.includes("googleapis.com/v1beta/files")) {
66
+ throw badXaiRequest(
67
+ `xAI cannot dereference Gemini/Google Files URIs via FileUriPart; upload to xAI Files and use FileRefPart (file_id) instead. Got "${p.uri}".`
68
+ );
69
+ }
65
70
  return { type: "input_image", image_url: p.uri };
66
71
  }
72
+ case "file-ref": {
73
+ if (typeof p.fileId !== "string" || p.fileId.trim() === "") {
74
+ throw badXaiRequest("FileRefPart.fileId must be a non-empty string.");
75
+ }
76
+ return { type: "input_file", file_id: p.fileId };
77
+ }
67
78
  default:
68
79
  return assertNever(p);
69
80
  }
@@ -362,6 +373,453 @@ function xaiAdapter(opts) {
362
373
  }
363
374
  };
364
375
  }
376
+ var XAI_FILE_TTL_MIN_SECONDS = 3600;
377
+ var XAI_FILE_TTL_MAX_SECONDS = 2592e3;
378
+ var XAI_FILE_MAX_BYTES = 48 * 1024 * 1024;
379
+ var XAI_FILES_DEFAULT_BASE_URL = "https://api.x.ai/v1";
380
+ function isPlainRecord2(value) {
381
+ return typeof value === "object" && value !== null && !Array.isArray(value);
382
+ }
383
+ function badRequest(message) {
384
+ return new LlmError(message, {
385
+ kind: "bad_request",
386
+ retryable: false,
387
+ provider: "xai"
388
+ });
389
+ }
390
+ function resolveFileId(fileIdOrHandle) {
391
+ if (typeof fileIdOrHandle === "string") {
392
+ return fileIdOrHandle;
393
+ }
394
+ return fileIdOrHandle.id;
395
+ }
396
+ function unixSecondsToDate(value) {
397
+ if (typeof value !== "number" || !Number.isFinite(value)) {
398
+ return void 0;
399
+ }
400
+ return new Date(value * 1e3);
401
+ }
402
+ function makeHandle(raw) {
403
+ if (typeof raw.id !== "string" || raw.id.length === 0) {
404
+ throw new LlmError("File response missing required field (id)", {
405
+ kind: "server",
406
+ retryable: false,
407
+ provider: "xai"
408
+ });
409
+ }
410
+ const handle = {
411
+ id: raw.id,
412
+ raw
413
+ };
414
+ if (typeof raw.filename === "string" && raw.filename.length > 0) {
415
+ handle.filename = raw.filename;
416
+ }
417
+ if (typeof raw.bytes === "number" && Number.isFinite(raw.bytes)) {
418
+ handle.bytes = raw.bytes;
419
+ }
420
+ if (typeof raw.purpose === "string") {
421
+ handle.purpose = raw.purpose;
422
+ }
423
+ const createdAt = unixSecondsToDate(raw.created_at);
424
+ if (createdAt !== void 0) {
425
+ handle.createdAt = createdAt;
426
+ }
427
+ if (raw.expires_at !== null && raw.expires_at !== void 0) {
428
+ const expiresAt = unixSecondsToDate(raw.expires_at);
429
+ if (expiresAt !== void 0) {
430
+ handle.expiresAt = expiresAt;
431
+ }
432
+ }
433
+ return handle;
434
+ }
435
+ function byteLengthOf(data) {
436
+ return data instanceof Uint8Array ? data.byteLength : data.size;
437
+ }
438
+ function toBlob(data, mimeType) {
439
+ if (data instanceof Blob) {
440
+ return data;
441
+ }
442
+ const copy = Uint8Array.from(data);
443
+ return mimeType !== void 0 && mimeType.length > 0 ? new Blob([copy], { type: mimeType }) : new Blob([copy]);
444
+ }
445
+ var XaiFilesHttpError = class extends Error {
446
+ status;
447
+ error;
448
+ constructor(status, message, errorBody) {
449
+ super(message);
450
+ this.name = "XaiFilesHttpError";
451
+ this.status = status;
452
+ this.error = errorBody;
453
+ }
454
+ };
455
+ async function throwHttpFailure(res) {
456
+ const status = res.status;
457
+ let bodyText = "";
458
+ try {
459
+ bodyText = await res.text();
460
+ } catch {
461
+ bodyText = "";
462
+ }
463
+ let parsed = bodyText;
464
+ if (bodyText.length > 0) {
465
+ try {
466
+ parsed = JSON.parse(bodyText);
467
+ } catch {
468
+ parsed = bodyText;
469
+ }
470
+ }
471
+ let message = `xAI Files API HTTP ${status}`;
472
+ if (typeof parsed === "string" && parsed.length > 0) {
473
+ message = parsed;
474
+ } else if (isPlainRecord2(parsed) && typeof parsed["error"] === "string") {
475
+ message = parsed["error"];
476
+ }
477
+ throw new XaiFilesHttpError(status, message, parsed);
478
+ }
479
+ function isNotFoundError(err) {
480
+ if (err instanceof LlmError && err.httpStatus === 404) {
481
+ return true;
482
+ }
483
+ if (typeof err === "object" && err !== null) {
484
+ const status = err.status;
485
+ if (status === 404) return true;
486
+ const httpStatus = err.httpStatus;
487
+ if (httpStatus === 404) return true;
488
+ }
489
+ return false;
490
+ }
491
+ function notFoundError(fileId, operation) {
492
+ return new LlmError(`xAI file not found during ${operation}: "${fileId}"`, {
493
+ kind: "bad_request",
494
+ retryable: false,
495
+ httpStatus: 404,
496
+ provider: "xai"
497
+ });
498
+ }
499
+ function maybeZdrHint(message) {
500
+ if (/zero\s*data\s*retention|\bzdr\b|files?\s+(api\s+)?(disabled|unavailable|not supported)/i.test(
501
+ message
502
+ )) {
503
+ return `${message} (xAI Zero Data Retention blocks new file uploads and file_id attachments for this team.)`;
504
+ }
505
+ return message;
506
+ }
507
+ function extractThrownMessage(raw) {
508
+ if (raw === null || typeof raw !== "object") return void 0;
509
+ const obj = raw;
510
+ if (typeof obj["message"] === "string" && obj["message"].length > 0) {
511
+ return obj["message"];
512
+ }
513
+ const err = obj["error"];
514
+ if (typeof err === "string" && err.length > 0) return err;
515
+ if (typeof err === "object" && err !== null) {
516
+ const nested = err;
517
+ if (typeof nested["error"] === "string" && nested["error"].length > 0) {
518
+ return nested["error"];
519
+ }
520
+ if (typeof nested["message"] === "string" && nested["message"].length > 0) {
521
+ return nested["message"];
522
+ }
523
+ }
524
+ return void 0;
525
+ }
526
+ function classifyStoreError(raw) {
527
+ if (raw instanceof LlmError) {
528
+ return raw;
529
+ }
530
+ const classified = classifyXaiError(raw);
531
+ const bodyMessage = extractThrownMessage(raw);
532
+ const baseMessage = bodyMessage !== void 0 && (classified.message.startsWith("HTTP ") || classified.message.length === 0) ? bodyMessage : classified.message;
533
+ const hinted = maybeZdrHint(baseMessage);
534
+ if (hinted === classified.message && classified.provider === "xai" && bodyMessage === void 0) {
535
+ return classified;
536
+ }
537
+ return new LlmError(hinted, {
538
+ kind: classified.kind,
539
+ retryable: classified.retryable,
540
+ ...classified.httpStatus !== void 0 ? { httpStatus: classified.httpStatus } : {},
541
+ ...classified.retryAfterMs !== void 0 ? { retryAfterMs: classified.retryAfterMs } : {},
542
+ provider: "xai",
543
+ cause: classified.cause ?? raw
544
+ });
545
+ }
546
+ var XaiFileStore = class {
547
+ apiKey;
548
+ baseUrl;
549
+ fetchImpl;
550
+ onDeleteError;
551
+ logger;
552
+ constructor(opts) {
553
+ this.apiKey = requireApiKey(opts.auth);
554
+ this.baseUrl = (opts.baseUrl ?? XAI_FILES_DEFAULT_BASE_URL).replace(/\/+$/, "");
555
+ this.fetchImpl = opts.fetch ?? fetch;
556
+ this.logger = opts.logger;
557
+ this.onDeleteError = opts.onDeleteError ?? ((fileId, err) => {
558
+ const message = redactSecrets(classifyError(err).message);
559
+ if (this.logger !== void 0) {
560
+ this.logger.error({ fileId, error: message }, "xai.file.delete.failed");
561
+ } else {
562
+ console.error(`[XaiFileStore] delete failed for "${fileId}":`, message);
563
+ }
564
+ });
565
+ }
566
+ authHeaders() {
567
+ const headers = new Headers();
568
+ headers.set("Authorization", `Bearer ${this.apiKey}`);
569
+ return headers;
570
+ }
571
+ filesUrl(path = "") {
572
+ if (path.length === 0) return `${this.baseUrl}/files`;
573
+ return `${this.baseUrl}/files/${path.replace(/^\//, "")}`;
574
+ }
575
+ /** Build RequestInit without writing `signal: undefined` (exactOptionalPropertyTypes). */
576
+ requestInit(method, opts) {
577
+ const init = {
578
+ method,
579
+ headers: this.authHeaders()
580
+ };
581
+ if (opts?.body !== void 0) {
582
+ init.body = opts.body;
583
+ }
584
+ if (opts?.signal !== void 0) {
585
+ init.signal = opts.signal;
586
+ }
587
+ return init;
588
+ }
589
+ /**
590
+ * Upload bytes to xAI Files. Returns immediately with metadata (no poll).
591
+ *
592
+ * Multipart field order is load-bearing: `expires_after` then `purpose`
593
+ * then `file` — reversing order yields HTTP 400 from xAI.
594
+ */
595
+ async upload(input, signal) {
596
+ if (typeof input.filename !== "string" || input.filename.trim() === "") {
597
+ throw badRequest("XaiFileUploadInput.filename must be a non-empty string.");
598
+ }
599
+ const size = byteLengthOf(input.data);
600
+ if (size > XAI_FILE_MAX_BYTES) {
601
+ throw badRequest(
602
+ `xAI file uploads must be at most ${XAI_FILE_MAX_BYTES} bytes (48 MiB); got ${size}.`
603
+ );
604
+ }
605
+ if (input.expiresAfterSeconds !== void 0) {
606
+ const ttl = input.expiresAfterSeconds;
607
+ if (typeof ttl !== "number" || !Number.isInteger(ttl) || ttl < XAI_FILE_TTL_MIN_SECONDS || ttl > XAI_FILE_TTL_MAX_SECONDS) {
608
+ throw badRequest(
609
+ `expiresAfterSeconds must be an integer in [${XAI_FILE_TTL_MIN_SECONDS}, ${XAI_FILE_TTL_MAX_SECONDS}]; got ${String(ttl)}.`
610
+ );
611
+ }
612
+ }
613
+ const purpose = input.purpose ?? "assistants";
614
+ const form = new FormData();
615
+ if (input.expiresAfterSeconds !== void 0) {
616
+ form.append("expires_after", String(input.expiresAfterSeconds));
617
+ }
618
+ form.append("purpose", purpose);
619
+ form.append("file", toBlob(input.data, input.mimeType), input.filename);
620
+ let res;
621
+ try {
622
+ res = await this.fetchImpl(
623
+ this.filesUrl(),
624
+ this.requestInit("POST", {
625
+ body: form,
626
+ ...signal !== void 0 ? { signal } : {}
627
+ })
628
+ );
629
+ } catch (e) {
630
+ if (signal?.aborted === true) {
631
+ throw new LlmError("xAI file upload aborted", {
632
+ kind: "aborted",
633
+ retryable: false,
634
+ provider: "xai"
635
+ });
636
+ }
637
+ throw classifyStoreError(e);
638
+ }
639
+ if (!res.ok) {
640
+ try {
641
+ await throwHttpFailure(res);
642
+ } catch (e) {
643
+ throw classifyStoreError(e);
644
+ }
645
+ }
646
+ let json;
647
+ try {
648
+ json = await res.json();
649
+ } catch (e) {
650
+ throw new LlmError("xAI file upload returned non-JSON body", {
651
+ kind: "server",
652
+ retryable: false,
653
+ provider: "xai",
654
+ cause: e
655
+ });
656
+ }
657
+ return makeHandle(json);
658
+ }
659
+ async get(fileId, signal) {
660
+ if (typeof fileId !== "string" || fileId.trim() === "") {
661
+ throw badRequest("fileId must be a non-empty string.");
662
+ }
663
+ let res;
664
+ try {
665
+ res = await this.fetchImpl(
666
+ this.filesUrl(fileId),
667
+ this.requestInit("GET", signal !== void 0 ? { signal } : {})
668
+ );
669
+ } catch (e) {
670
+ if (signal?.aborted === true) {
671
+ throw new LlmError("xAI file get aborted", {
672
+ kind: "aborted",
673
+ retryable: false,
674
+ provider: "xai"
675
+ });
676
+ }
677
+ throw classifyStoreError(e);
678
+ }
679
+ if (res.status === 404) {
680
+ throw notFoundError(fileId, "get");
681
+ }
682
+ if (!res.ok) {
683
+ try {
684
+ await throwHttpFailure(res);
685
+ } catch (e) {
686
+ throw classifyStoreError(e);
687
+ }
688
+ }
689
+ const json = await res.json();
690
+ return makeHandle(json);
691
+ }
692
+ async list(opts = {}, signal) {
693
+ if (opts.limit !== void 0) {
694
+ if (typeof opts.limit !== "number" || !Number.isInteger(opts.limit) || opts.limit < 1 || opts.limit > 100) {
695
+ throw badRequest("list.limit must be an integer in [1, 100].");
696
+ }
697
+ }
698
+ const params = new URLSearchParams();
699
+ if (opts.limit !== void 0) params.set("limit", String(opts.limit));
700
+ if (opts.order !== void 0) params.set("order", opts.order);
701
+ if (opts.sortBy !== void 0) params.set("sort_by", opts.sortBy);
702
+ if (opts.paginationToken !== void 0) {
703
+ params.set("pagination_token", opts.paginationToken);
704
+ }
705
+ const qs = params.toString();
706
+ const url = qs.length > 0 ? `${this.filesUrl()}?${qs}` : this.filesUrl();
707
+ let res;
708
+ try {
709
+ res = await this.fetchImpl(
710
+ url,
711
+ this.requestInit("GET", signal !== void 0 ? { signal } : {})
712
+ );
713
+ } catch (e) {
714
+ if (signal?.aborted === true) {
715
+ throw new LlmError("xAI file list aborted", {
716
+ kind: "aborted",
717
+ retryable: false,
718
+ provider: "xai"
719
+ });
720
+ }
721
+ throw classifyStoreError(e);
722
+ }
723
+ if (!res.ok) {
724
+ try {
725
+ await throwHttpFailure(res);
726
+ } catch (e) {
727
+ throw classifyStoreError(e);
728
+ }
729
+ }
730
+ const json = await res.json();
731
+ const files = Array.isArray(json.data) ? json.data.map((f) => makeHandle(f)) : [];
732
+ const result = { files };
733
+ if (typeof json.pagination_token === "string" && json.pagination_token.length > 0) {
734
+ result.paginationToken = json.pagination_token;
735
+ }
736
+ return result;
737
+ }
738
+ /**
739
+ * Delete a file. Idempotent: HTTP 404 → success.
740
+ * Other errors are forwarded to `onDeleteError` and **not** rethrown (P5).
741
+ */
742
+ async delete(fileIdOrHandle, signal) {
743
+ const fileId = resolveFileId(fileIdOrHandle);
744
+ if (typeof fileId !== "string" || fileId.trim() === "") {
745
+ this.onDeleteError(String(fileId), badRequest("fileId must be a non-empty string."));
746
+ return;
747
+ }
748
+ try {
749
+ let res;
750
+ try {
751
+ res = await this.fetchImpl(
752
+ this.filesUrl(fileId),
753
+ this.requestInit("DELETE", signal !== void 0 ? { signal } : {})
754
+ );
755
+ } catch (e) {
756
+ if (signal?.aborted === true) {
757
+ this.onDeleteError(
758
+ fileId,
759
+ new LlmError("xAI file delete aborted", {
760
+ kind: "aborted",
761
+ retryable: false,
762
+ provider: "xai"
763
+ })
764
+ );
765
+ return;
766
+ }
767
+ throw e;
768
+ }
769
+ if (res.status === 404) {
770
+ return;
771
+ }
772
+ if (!res.ok) {
773
+ await throwHttpFailure(res);
774
+ }
775
+ } catch (err) {
776
+ if (isNotFoundError(err)) {
777
+ return;
778
+ }
779
+ this.onDeleteError(fileId, classifyStoreError(err));
780
+ }
781
+ }
782
+ /**
783
+ * Delete many files. Each failure is individually fail-open; none are thrown.
784
+ */
785
+ async deleteAll(ids, signal) {
786
+ await Promise.allSettled(ids.map((id) => this.delete(id, signal)));
787
+ }
788
+ /** Download raw file bytes. */
789
+ async getContent(fileId, signal) {
790
+ if (typeof fileId !== "string" || fileId.trim() === "") {
791
+ throw badRequest("fileId must be a non-empty string.");
792
+ }
793
+ let res;
794
+ try {
795
+ res = await this.fetchImpl(
796
+ this.filesUrl(`${fileId}/content`),
797
+ this.requestInit("GET", signal !== void 0 ? { signal } : {})
798
+ );
799
+ } catch (e) {
800
+ if (signal?.aborted === true) {
801
+ throw new LlmError("xAI file content download aborted", {
802
+ kind: "aborted",
803
+ retryable: false,
804
+ provider: "xai"
805
+ });
806
+ }
807
+ throw classifyStoreError(e);
808
+ }
809
+ if (res.status === 404) {
810
+ throw notFoundError(fileId, "getContent");
811
+ }
812
+ if (!res.ok) {
813
+ try {
814
+ await throwHttpFailure(res);
815
+ } catch (e) {
816
+ throw classifyStoreError(e);
817
+ }
818
+ }
819
+ const buf = await res.arrayBuffer();
820
+ return new Uint8Array(buf);
821
+ }
822
+ };
365
823
  var Grok45ConfigSchema = z.strictObject({
366
824
  temperature: z.number().optional().meta({
367
825
  title: "Temperature",
@@ -527,6 +985,6 @@ function xaiProvider(opts) {
527
985
  };
528
986
  }
529
987
 
530
- export { Grok45ConfigSchema, XAI_PRICING, buildXaiClient, classifyXaiError, computeXaiCost, grok45ModelDescriptor, requireApiKey, xaiAdapter, xaiModelDescriptors, xaiPricingSource, xaiPricingVersion, xaiProvider, xaiRegistry };
988
+ export { Grok45ConfigSchema, XAI_FILES_DEFAULT_BASE_URL, XAI_FILE_MAX_BYTES, XAI_FILE_TTL_MAX_SECONDS, XAI_FILE_TTL_MIN_SECONDS, XAI_PRICING, XaiFileStore, buildXaiClient, classifyXaiError, computeXaiCost, grok45ModelDescriptor, requireApiKey, xaiAdapter, xaiModelDescriptors, xaiPricingSource, xaiPricingVersion, xaiProvider, xaiRegistry };
531
989
  //# sourceMappingURL=index.js.map
532
990
  //# sourceMappingURL=index.js.map