@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/README.md CHANGED
@@ -30,6 +30,9 @@ xAI has no first-party TypeScript SDK. xAI's own quickstart recommends using the
30
30
  | `XaiModelRates` | Per-model rate entry type (`inputPerM`, `cachedPerM`, `outputPerM`, optional `gt200k`) |
31
31
  | `Grok45ConfigSchema` | Strict Zod config schema for `grok-4.5` |
32
32
  | `XaiProviderOptions` | `{ promptCacheKey? }` — typed `providerOptions.xai` extension shape |
33
+ | `XaiFileStore` | Files API store: upload (TTL), get, list, idempotent delete, content |
34
+ | `XaiFileHandle` | `{ id, filename?, bytes?, expiresAt?, … }` returned by the store |
35
+ | `XAI_FILE_TTL_*` | TTL bounds (`3600`…`2592000` seconds) and `XAI_FILE_MAX_BYTES` (48 MiB) |
33
36
 
34
37
  ## Quick example
35
38
 
@@ -63,12 +66,49 @@ The default registry ships exactly one model: `grok-4.5` (500k token context win
63
66
  - **No penalties/stop** — `presence_penalty`, `frequency_penalty`, and `stop` are not in the config schema at all; xAI hard-rejects these on reasoning models, so the schema never admits them (reject-don't-map).
64
67
  - **No service tiers** — xAI has no service-tier concept for `grok-4.5`; setting `serviceTier` on the request throws `bad_request`.
65
68
 
69
+ ## Files store (`XaiFileStore`)
70
+
71
+ Thin REST wrapper over xAI Files (`POST/GET/DELETE /v1/files`). Auth is injected — the store never reads `process.env`.
72
+
73
+ ```ts
74
+ import { XaiFileStore } from '@gullabs/xai'
75
+
76
+ const store = new XaiFileStore({
77
+ auth: { apiKey: 'YOUR_XAI_API_KEY' },
78
+ // Optional: onDeleteError, logger, fetch, baseUrl
79
+ })
80
+
81
+ const handle = await store.upload({
82
+ data: pdfBytes,
83
+ filename: 'matter.pdf',
84
+ mimeType: 'application/pdf',
85
+ expiresAfterSeconds: 86_400, // 24h; range 3600…2592000
86
+ })
87
+
88
+ // Attach on generate via core FileRefPart:
89
+ // { kind: 'file-ref', fileId: handle.id }
90
+
91
+ await store.delete(handle.id) // 404 = success (idempotent)
92
+ await store.delete(handle.id) // safe to call twice
93
+ ```
94
+
95
+ | Behavior | Detail |
96
+ | ------------ | ---------------------------------------------------------------------------------------------------------------- |
97
+ | TTL | `expiresAfterSeconds` validated client-side; multipart sends `expires_after` **before** `file` (xAI requirement) |
98
+ | Delete | Fail-open: non-404 errors call `onDeleteError` and resolve; 404 is silent success |
99
+ | Storage cost | ~$0.025/GiB/day — **not** injected into `computeCost` token lanes |
100
+ | ZDR teams | New uploads and `file_id` attachments are blocked by xAI; errors mention Zero Data Retention when detectable |
101
+ | Max size | 48 MiB (conservative vs docs 48–50 MB) |
102
+
103
+ **Billing note:** attaching files on Responses implicitly enables xAI's `attachment_search` agentic tool. Expect tool-invocation fees and reasoning tokens beyond a plain completion. Collections / public URL minting are out of scope for this store.
104
+
66
105
  ## Vision constraints
67
106
 
68
- `grok-4.5` accepts image input as an `inline-media` or `file-uri` `Part`:
107
+ `grok-4.5` accepts image input as an `inline-media` or `file-uri` `Part`, and document attachments as a `file-ref` `Part`:
69
108
 
70
109
  - **`inline-media`** — only `image/jpeg` and `image/png` are accepted; anything else throws `bad_request`. The decoded payload must be at most 20 MiB (xAI's documented inline-image ceiling); larger images throw `bad_request` before the request is sent.
71
110
  - **`file-uri`** — only accepted when the URI is a public `http(s)://` URL **and** the declared `mimeType` is jpg/png. A provider-hosted URI from another provider — for example a Gemini Files API URI (`https://generativelanguage.googleapis.com/...`) — is technically `https://` but is not dereferenceable by xAI and is not portable across providers. The adapter rejects it rather than trying to map or proxy it (reject-don't-map).
111
+ - **`file-ref`** — maps to Responses `{ type: 'input_file', file_id }`. Upload first with `XaiFileStore`, then pass `{ kind: 'file-ref', fileId: handle.id }`. Empty ids throw `bad_request`.
72
112
  - **Undocumented minimum size** — xAI enforces an undocumented server-side minimum image size (observed ~8px/side, ~512 total px). This adapter does **not** pre-validate pixel dimensions; a too-small image surfaces as a live `bad_request` error from the xAI API itself, classified normally by `classifyXaiError`, not rejected client-side.
73
113
 
74
114
  ## Caching
@@ -96,7 +136,7 @@ xAI's own `/v1/models` listing surfaces `grok-4.5-latest` and `grok-build-latest
96
136
 
97
137
  ## Explicitly deferred (not built in v1)
98
138
 
99
- - Server-side agentic tools (`web_search`, `x_search`, `code_interpreter`, file/collections search, remote MCP) and their per-invocation billing (e.g. `web_search` at $5/1k) — no `Cost` lane exists for these yet.
139
+ - Server-side agentic tools as an explicit API (`web_search`, `x_search`, `code_interpreter`, collections search, remote MCP) and their per-invocation billing lanes in `computeCost`. Note: **file attachments still auto-enable `attachment_search`** on xAI's side — that implicit tool is documented above, not modeled as a first-class library tool surface.
100
140
  - `/v1/chat/completions` (documented by xAI as legacy) and `/v1/messages` (the Anthropic-compatible migration shim) — this adapter only targets `/v1/responses`.
101
141
  - Batch API (`grok-4.5` is not eligible at launch) and image generation models.
102
142
  - Stateful conversations — `store` is always sent as `false`, and `previous_response_id` is not supported.
package/dist/index.cjs CHANGED
@@ -64,8 +64,19 @@ function mapPart(p) {
64
64
  `xAI only accepts public http(s) image URLs via FileUriPart; got scheme of "${p.uri}" / mimeType "${p.mimeType}".`
65
65
  );
66
66
  }
67
+ if (p.uri.includes("generativelanguage.googleapis.com") || p.uri.includes("googleapis.com/v1beta/files")) {
68
+ throw badXaiRequest(
69
+ `xAI cannot dereference Gemini/Google Files URIs via FileUriPart; upload to xAI Files and use FileRefPart (file_id) instead. Got "${p.uri}".`
70
+ );
71
+ }
67
72
  return { type: "input_image", image_url: p.uri };
68
73
  }
74
+ case "file-ref": {
75
+ if (typeof p.fileId !== "string" || p.fileId.trim() === "") {
76
+ throw badXaiRequest("FileRefPart.fileId must be a non-empty string.");
77
+ }
78
+ return { type: "input_file", file_id: p.fileId };
79
+ }
69
80
  default:
70
81
  return core.assertNever(p);
71
82
  }
@@ -364,6 +375,453 @@ function xaiAdapter(opts) {
364
375
  }
365
376
  };
366
377
  }
378
+ var XAI_FILE_TTL_MIN_SECONDS = 3600;
379
+ var XAI_FILE_TTL_MAX_SECONDS = 2592e3;
380
+ var XAI_FILE_MAX_BYTES = 48 * 1024 * 1024;
381
+ var XAI_FILES_DEFAULT_BASE_URL = "https://api.x.ai/v1";
382
+ function isPlainRecord2(value) {
383
+ return typeof value === "object" && value !== null && !Array.isArray(value);
384
+ }
385
+ function badRequest(message) {
386
+ return new core.LlmError(message, {
387
+ kind: "bad_request",
388
+ retryable: false,
389
+ provider: "xai"
390
+ });
391
+ }
392
+ function resolveFileId(fileIdOrHandle) {
393
+ if (typeof fileIdOrHandle === "string") {
394
+ return fileIdOrHandle;
395
+ }
396
+ return fileIdOrHandle.id;
397
+ }
398
+ function unixSecondsToDate(value) {
399
+ if (typeof value !== "number" || !Number.isFinite(value)) {
400
+ return void 0;
401
+ }
402
+ return new Date(value * 1e3);
403
+ }
404
+ function makeHandle(raw) {
405
+ if (typeof raw.id !== "string" || raw.id.length === 0) {
406
+ throw new core.LlmError("File response missing required field (id)", {
407
+ kind: "server",
408
+ retryable: false,
409
+ provider: "xai"
410
+ });
411
+ }
412
+ const handle = {
413
+ id: raw.id,
414
+ raw
415
+ };
416
+ if (typeof raw.filename === "string" && raw.filename.length > 0) {
417
+ handle.filename = raw.filename;
418
+ }
419
+ if (typeof raw.bytes === "number" && Number.isFinite(raw.bytes)) {
420
+ handle.bytes = raw.bytes;
421
+ }
422
+ if (typeof raw.purpose === "string") {
423
+ handle.purpose = raw.purpose;
424
+ }
425
+ const createdAt = unixSecondsToDate(raw.created_at);
426
+ if (createdAt !== void 0) {
427
+ handle.createdAt = createdAt;
428
+ }
429
+ if (raw.expires_at !== null && raw.expires_at !== void 0) {
430
+ const expiresAt = unixSecondsToDate(raw.expires_at);
431
+ if (expiresAt !== void 0) {
432
+ handle.expiresAt = expiresAt;
433
+ }
434
+ }
435
+ return handle;
436
+ }
437
+ function byteLengthOf(data) {
438
+ return data instanceof Uint8Array ? data.byteLength : data.size;
439
+ }
440
+ function toBlob(data, mimeType) {
441
+ if (data instanceof Blob) {
442
+ return data;
443
+ }
444
+ const copy = Uint8Array.from(data);
445
+ return mimeType !== void 0 && mimeType.length > 0 ? new Blob([copy], { type: mimeType }) : new Blob([copy]);
446
+ }
447
+ var XaiFilesHttpError = class extends Error {
448
+ status;
449
+ error;
450
+ constructor(status, message, errorBody) {
451
+ super(message);
452
+ this.name = "XaiFilesHttpError";
453
+ this.status = status;
454
+ this.error = errorBody;
455
+ }
456
+ };
457
+ async function throwHttpFailure(res) {
458
+ const status = res.status;
459
+ let bodyText = "";
460
+ try {
461
+ bodyText = await res.text();
462
+ } catch {
463
+ bodyText = "";
464
+ }
465
+ let parsed = bodyText;
466
+ if (bodyText.length > 0) {
467
+ try {
468
+ parsed = JSON.parse(bodyText);
469
+ } catch {
470
+ parsed = bodyText;
471
+ }
472
+ }
473
+ let message = `xAI Files API HTTP ${status}`;
474
+ if (typeof parsed === "string" && parsed.length > 0) {
475
+ message = parsed;
476
+ } else if (isPlainRecord2(parsed) && typeof parsed["error"] === "string") {
477
+ message = parsed["error"];
478
+ }
479
+ throw new XaiFilesHttpError(status, message, parsed);
480
+ }
481
+ function isNotFoundError(err) {
482
+ if (err instanceof core.LlmError && err.httpStatus === 404) {
483
+ return true;
484
+ }
485
+ if (typeof err === "object" && err !== null) {
486
+ const status = err.status;
487
+ if (status === 404) return true;
488
+ const httpStatus = err.httpStatus;
489
+ if (httpStatus === 404) return true;
490
+ }
491
+ return false;
492
+ }
493
+ function notFoundError(fileId, operation) {
494
+ return new core.LlmError(`xAI file not found during ${operation}: "${fileId}"`, {
495
+ kind: "bad_request",
496
+ retryable: false,
497
+ httpStatus: 404,
498
+ provider: "xai"
499
+ });
500
+ }
501
+ function maybeZdrHint(message) {
502
+ if (/zero\s*data\s*retention|\bzdr\b|files?\s+(api\s+)?(disabled|unavailable|not supported)/i.test(
503
+ message
504
+ )) {
505
+ return `${message} (xAI Zero Data Retention blocks new file uploads and file_id attachments for this team.)`;
506
+ }
507
+ return message;
508
+ }
509
+ function extractThrownMessage(raw) {
510
+ if (raw === null || typeof raw !== "object") return void 0;
511
+ const obj = raw;
512
+ if (typeof obj["message"] === "string" && obj["message"].length > 0) {
513
+ return obj["message"];
514
+ }
515
+ const err = obj["error"];
516
+ if (typeof err === "string" && err.length > 0) return err;
517
+ if (typeof err === "object" && err !== null) {
518
+ const nested = err;
519
+ if (typeof nested["error"] === "string" && nested["error"].length > 0) {
520
+ return nested["error"];
521
+ }
522
+ if (typeof nested["message"] === "string" && nested["message"].length > 0) {
523
+ return nested["message"];
524
+ }
525
+ }
526
+ return void 0;
527
+ }
528
+ function classifyStoreError(raw) {
529
+ if (raw instanceof core.LlmError) {
530
+ return raw;
531
+ }
532
+ const classified = classifyXaiError(raw);
533
+ const bodyMessage = extractThrownMessage(raw);
534
+ const baseMessage = bodyMessage !== void 0 && (classified.message.startsWith("HTTP ") || classified.message.length === 0) ? bodyMessage : classified.message;
535
+ const hinted = maybeZdrHint(baseMessage);
536
+ if (hinted === classified.message && classified.provider === "xai" && bodyMessage === void 0) {
537
+ return classified;
538
+ }
539
+ return new core.LlmError(hinted, {
540
+ kind: classified.kind,
541
+ retryable: classified.retryable,
542
+ ...classified.httpStatus !== void 0 ? { httpStatus: classified.httpStatus } : {},
543
+ ...classified.retryAfterMs !== void 0 ? { retryAfterMs: classified.retryAfterMs } : {},
544
+ provider: "xai",
545
+ cause: classified.cause ?? raw
546
+ });
547
+ }
548
+ var XaiFileStore = class {
549
+ apiKey;
550
+ baseUrl;
551
+ fetchImpl;
552
+ onDeleteError;
553
+ logger;
554
+ constructor(opts) {
555
+ this.apiKey = requireApiKey(opts.auth);
556
+ this.baseUrl = (opts.baseUrl ?? XAI_FILES_DEFAULT_BASE_URL).replace(/\/+$/, "");
557
+ this.fetchImpl = opts.fetch ?? fetch;
558
+ this.logger = opts.logger;
559
+ this.onDeleteError = opts.onDeleteError ?? ((fileId, err) => {
560
+ const message = core.redactSecrets(core.classifyError(err).message);
561
+ if (this.logger !== void 0) {
562
+ this.logger.error({ fileId, error: message }, "xai.file.delete.failed");
563
+ } else {
564
+ console.error(`[XaiFileStore] delete failed for "${fileId}":`, message);
565
+ }
566
+ });
567
+ }
568
+ authHeaders() {
569
+ const headers = new Headers();
570
+ headers.set("Authorization", `Bearer ${this.apiKey}`);
571
+ return headers;
572
+ }
573
+ filesUrl(path = "") {
574
+ if (path.length === 0) return `${this.baseUrl}/files`;
575
+ return `${this.baseUrl}/files/${path.replace(/^\//, "")}`;
576
+ }
577
+ /** Build RequestInit without writing `signal: undefined` (exactOptionalPropertyTypes). */
578
+ requestInit(method, opts) {
579
+ const init = {
580
+ method,
581
+ headers: this.authHeaders()
582
+ };
583
+ if (opts?.body !== void 0) {
584
+ init.body = opts.body;
585
+ }
586
+ if (opts?.signal !== void 0) {
587
+ init.signal = opts.signal;
588
+ }
589
+ return init;
590
+ }
591
+ /**
592
+ * Upload bytes to xAI Files. Returns immediately with metadata (no poll).
593
+ *
594
+ * Multipart field order is load-bearing: `expires_after` then `purpose`
595
+ * then `file` — reversing order yields HTTP 400 from xAI.
596
+ */
597
+ async upload(input, signal) {
598
+ if (typeof input.filename !== "string" || input.filename.trim() === "") {
599
+ throw badRequest("XaiFileUploadInput.filename must be a non-empty string.");
600
+ }
601
+ const size = byteLengthOf(input.data);
602
+ if (size > XAI_FILE_MAX_BYTES) {
603
+ throw badRequest(
604
+ `xAI file uploads must be at most ${XAI_FILE_MAX_BYTES} bytes (48 MiB); got ${size}.`
605
+ );
606
+ }
607
+ if (input.expiresAfterSeconds !== void 0) {
608
+ const ttl = input.expiresAfterSeconds;
609
+ if (typeof ttl !== "number" || !Number.isInteger(ttl) || ttl < XAI_FILE_TTL_MIN_SECONDS || ttl > XAI_FILE_TTL_MAX_SECONDS) {
610
+ throw badRequest(
611
+ `expiresAfterSeconds must be an integer in [${XAI_FILE_TTL_MIN_SECONDS}, ${XAI_FILE_TTL_MAX_SECONDS}]; got ${String(ttl)}.`
612
+ );
613
+ }
614
+ }
615
+ const purpose = input.purpose ?? "assistants";
616
+ const form = new FormData();
617
+ if (input.expiresAfterSeconds !== void 0) {
618
+ form.append("expires_after", String(input.expiresAfterSeconds));
619
+ }
620
+ form.append("purpose", purpose);
621
+ form.append("file", toBlob(input.data, input.mimeType), input.filename);
622
+ let res;
623
+ try {
624
+ res = await this.fetchImpl(
625
+ this.filesUrl(),
626
+ this.requestInit("POST", {
627
+ body: form,
628
+ ...signal !== void 0 ? { signal } : {}
629
+ })
630
+ );
631
+ } catch (e) {
632
+ if (signal?.aborted === true) {
633
+ throw new core.LlmError("xAI file upload aborted", {
634
+ kind: "aborted",
635
+ retryable: false,
636
+ provider: "xai"
637
+ });
638
+ }
639
+ throw classifyStoreError(e);
640
+ }
641
+ if (!res.ok) {
642
+ try {
643
+ await throwHttpFailure(res);
644
+ } catch (e) {
645
+ throw classifyStoreError(e);
646
+ }
647
+ }
648
+ let json;
649
+ try {
650
+ json = await res.json();
651
+ } catch (e) {
652
+ throw new core.LlmError("xAI file upload returned non-JSON body", {
653
+ kind: "server",
654
+ retryable: false,
655
+ provider: "xai",
656
+ cause: e
657
+ });
658
+ }
659
+ return makeHandle(json);
660
+ }
661
+ async get(fileId, signal) {
662
+ if (typeof fileId !== "string" || fileId.trim() === "") {
663
+ throw badRequest("fileId must be a non-empty string.");
664
+ }
665
+ let res;
666
+ try {
667
+ res = await this.fetchImpl(
668
+ this.filesUrl(fileId),
669
+ this.requestInit("GET", signal !== void 0 ? { signal } : {})
670
+ );
671
+ } catch (e) {
672
+ if (signal?.aborted === true) {
673
+ throw new core.LlmError("xAI file get aborted", {
674
+ kind: "aborted",
675
+ retryable: false,
676
+ provider: "xai"
677
+ });
678
+ }
679
+ throw classifyStoreError(e);
680
+ }
681
+ if (res.status === 404) {
682
+ throw notFoundError(fileId, "get");
683
+ }
684
+ if (!res.ok) {
685
+ try {
686
+ await throwHttpFailure(res);
687
+ } catch (e) {
688
+ throw classifyStoreError(e);
689
+ }
690
+ }
691
+ const json = await res.json();
692
+ return makeHandle(json);
693
+ }
694
+ async list(opts = {}, signal) {
695
+ if (opts.limit !== void 0) {
696
+ if (typeof opts.limit !== "number" || !Number.isInteger(opts.limit) || opts.limit < 1 || opts.limit > 100) {
697
+ throw badRequest("list.limit must be an integer in [1, 100].");
698
+ }
699
+ }
700
+ const params = new URLSearchParams();
701
+ if (opts.limit !== void 0) params.set("limit", String(opts.limit));
702
+ if (opts.order !== void 0) params.set("order", opts.order);
703
+ if (opts.sortBy !== void 0) params.set("sort_by", opts.sortBy);
704
+ if (opts.paginationToken !== void 0) {
705
+ params.set("pagination_token", opts.paginationToken);
706
+ }
707
+ const qs = params.toString();
708
+ const url = qs.length > 0 ? `${this.filesUrl()}?${qs}` : this.filesUrl();
709
+ let res;
710
+ try {
711
+ res = await this.fetchImpl(
712
+ url,
713
+ this.requestInit("GET", signal !== void 0 ? { signal } : {})
714
+ );
715
+ } catch (e) {
716
+ if (signal?.aborted === true) {
717
+ throw new core.LlmError("xAI file list aborted", {
718
+ kind: "aborted",
719
+ retryable: false,
720
+ provider: "xai"
721
+ });
722
+ }
723
+ throw classifyStoreError(e);
724
+ }
725
+ if (!res.ok) {
726
+ try {
727
+ await throwHttpFailure(res);
728
+ } catch (e) {
729
+ throw classifyStoreError(e);
730
+ }
731
+ }
732
+ const json = await res.json();
733
+ const files = Array.isArray(json.data) ? json.data.map((f) => makeHandle(f)) : [];
734
+ const result = { files };
735
+ if (typeof json.pagination_token === "string" && json.pagination_token.length > 0) {
736
+ result.paginationToken = json.pagination_token;
737
+ }
738
+ return result;
739
+ }
740
+ /**
741
+ * Delete a file. Idempotent: HTTP 404 → success.
742
+ * Other errors are forwarded to `onDeleteError` and **not** rethrown (P5).
743
+ */
744
+ async delete(fileIdOrHandle, signal) {
745
+ const fileId = resolveFileId(fileIdOrHandle);
746
+ if (typeof fileId !== "string" || fileId.trim() === "") {
747
+ this.onDeleteError(String(fileId), badRequest("fileId must be a non-empty string."));
748
+ return;
749
+ }
750
+ try {
751
+ let res;
752
+ try {
753
+ res = await this.fetchImpl(
754
+ this.filesUrl(fileId),
755
+ this.requestInit("DELETE", signal !== void 0 ? { signal } : {})
756
+ );
757
+ } catch (e) {
758
+ if (signal?.aborted === true) {
759
+ this.onDeleteError(
760
+ fileId,
761
+ new core.LlmError("xAI file delete aborted", {
762
+ kind: "aborted",
763
+ retryable: false,
764
+ provider: "xai"
765
+ })
766
+ );
767
+ return;
768
+ }
769
+ throw e;
770
+ }
771
+ if (res.status === 404) {
772
+ return;
773
+ }
774
+ if (!res.ok) {
775
+ await throwHttpFailure(res);
776
+ }
777
+ } catch (err) {
778
+ if (isNotFoundError(err)) {
779
+ return;
780
+ }
781
+ this.onDeleteError(fileId, classifyStoreError(err));
782
+ }
783
+ }
784
+ /**
785
+ * Delete many files. Each failure is individually fail-open; none are thrown.
786
+ */
787
+ async deleteAll(ids, signal) {
788
+ await Promise.allSettled(ids.map((id) => this.delete(id, signal)));
789
+ }
790
+ /** Download raw file bytes. */
791
+ async getContent(fileId, signal) {
792
+ if (typeof fileId !== "string" || fileId.trim() === "") {
793
+ throw badRequest("fileId must be a non-empty string.");
794
+ }
795
+ let res;
796
+ try {
797
+ res = await this.fetchImpl(
798
+ this.filesUrl(`${fileId}/content`),
799
+ this.requestInit("GET", signal !== void 0 ? { signal } : {})
800
+ );
801
+ } catch (e) {
802
+ if (signal?.aborted === true) {
803
+ throw new core.LlmError("xAI file content download aborted", {
804
+ kind: "aborted",
805
+ retryable: false,
806
+ provider: "xai"
807
+ });
808
+ }
809
+ throw classifyStoreError(e);
810
+ }
811
+ if (res.status === 404) {
812
+ throw notFoundError(fileId, "getContent");
813
+ }
814
+ if (!res.ok) {
815
+ try {
816
+ await throwHttpFailure(res);
817
+ } catch (e) {
818
+ throw classifyStoreError(e);
819
+ }
820
+ }
821
+ const buf = await res.arrayBuffer();
822
+ return new Uint8Array(buf);
823
+ }
824
+ };
367
825
  var Grok45ConfigSchema = zod.z.strictObject({
368
826
  temperature: zod.z.number().optional().meta({
369
827
  title: "Temperature",
@@ -530,7 +988,12 @@ function xaiProvider(opts) {
530
988
  }
531
989
 
532
990
  exports.Grok45ConfigSchema = Grok45ConfigSchema;
991
+ exports.XAI_FILES_DEFAULT_BASE_URL = XAI_FILES_DEFAULT_BASE_URL;
992
+ exports.XAI_FILE_MAX_BYTES = XAI_FILE_MAX_BYTES;
993
+ exports.XAI_FILE_TTL_MAX_SECONDS = XAI_FILE_TTL_MAX_SECONDS;
994
+ exports.XAI_FILE_TTL_MIN_SECONDS = XAI_FILE_TTL_MIN_SECONDS;
533
995
  exports.XAI_PRICING = XAI_PRICING;
996
+ exports.XaiFileStore = XaiFileStore;
534
997
  exports.buildXaiClient = buildXaiClient;
535
998
  exports.classifyXaiError = classifyXaiError;
536
999
  exports.computeXaiCost = computeXaiCost;