@gullabs/xai 0.3.0 → 0.4.1
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 +25 -11
- package/dist/index.cjs +36 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -5
- package/dist/index.d.ts +30 -5
- package/dist/index.js +36 -28
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -5,10 +5,10 @@ xAI Grok provider adapter for any-llm. A thin mapping layer over the `openai` np
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
pnpm add @gullabs/xai @gullabs/core openai
|
|
8
|
+
pnpm add @gullabs/xai @gullabs/core openai # peer: openai ^6 || ^7
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
**Peer dependency:** `openai ^6`
|
|
11
|
+
**Peer dependency:** `openai ^6 || ^7`
|
|
12
12
|
|
|
13
13
|
xAI has no first-party TypeScript SDK. xAI's own quickstart recommends using the `openai` npm package with a `baseURL` override pointed at xAI's endpoint — that is the path this adapter takes. `buildXaiClient` is the only place in `packages/xai/src` that imports `openai`, so the rest of the adapter (and its tests) stay decoupled from the real SDK via the structural `XaiClientLike` interface.
|
|
14
14
|
|
|
@@ -32,6 +32,7 @@ xAI has no first-party TypeScript SDK. xAI's own quickstart recommends using the
|
|
|
32
32
|
| `XaiProviderOptions` | `{ promptCacheKey? }` — typed `providerOptions.xai` extension shape |
|
|
33
33
|
| `XaiFileStore` | Files API store: upload (TTL), get, list, idempotent delete, content |
|
|
34
34
|
| `XaiFileHandle` | `{ id, filename?, bytes?, expiresAt?, … }` returned by the store |
|
|
35
|
+
| `FileDeleteOptions` | `{ failClosed?, signal? }` — opt-in fail-closed delete for durable release gates |
|
|
35
36
|
| `XAI_FILE_TTL_*` | TTL bounds (`3600`…`2592000` seconds) and `XAI_FILE_MAX_BYTES` (48 MiB) |
|
|
36
37
|
|
|
37
38
|
## Quick example
|
|
@@ -88,19 +89,32 @@ const handle = await store.upload({
|
|
|
88
89
|
// Attach on generate via core FileRefPart:
|
|
89
90
|
// { kind: 'file-ref', fileId: handle.id }
|
|
90
91
|
|
|
91
|
-
await store.delete(handle.id) // 404 = success
|
|
92
|
+
await store.delete(handle.id) // default fail-open; 404 = success
|
|
92
93
|
await store.delete(handle.id) // safe to call twice
|
|
94
|
+
|
|
95
|
+
// Durable gate (Temporal release / orphan sweep) — mark DB only after success:
|
|
96
|
+
try {
|
|
97
|
+
await store.delete(handle.id, { failClosed: true })
|
|
98
|
+
await db.markReleased(handle.id)
|
|
99
|
+
} catch (err) {
|
|
100
|
+
// leave released_at null; do not rethrow from workflow finally
|
|
101
|
+
logger.warn({ err }, 'delete failed')
|
|
102
|
+
}
|
|
93
103
|
```
|
|
94
104
|
|
|
95
|
-
| Behavior
|
|
96
|
-
|
|
|
97
|
-
| TTL
|
|
98
|
-
| Delete
|
|
99
|
-
|
|
|
100
|
-
|
|
|
101
|
-
|
|
|
105
|
+
| Behavior | Detail |
|
|
106
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
107
|
+
| TTL | `expiresAfterSeconds` validated client-side; multipart sends `expires_after` **before** `file` (xAI requirement) |
|
|
108
|
+
| Delete (default) | Fail-open: non-404 errors call `onDeleteError` and resolve; 404 is silent success |
|
|
109
|
+
| Delete (`failClosed: true`) | Non-404 failures **throw** `LlmError`; `onDeleteError` is not called. Prefer **per-id** delete + markReleased when writing durable release state — fail-closed `deleteAll` does not cancel in-flight siblings |
|
|
110
|
+
| Empty `fileId` | Always throws `bad_request` (both modes) |
|
|
111
|
+
| Storage cost | ~$0.025/GiB/day — **not** injected into `computeCost` token lanes |
|
|
112
|
+
| ZDR teams | New uploads and `file_id` attachments are blocked by xAI; errors mention Zero Data Retention when detectable |
|
|
113
|
+
| Max size | 48 MiB (conservative vs docs 48–50 MB) |
|
|
114
|
+
|
|
115
|
+
**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. When xAI returns numeric counters such as `num_server_side_tools_used` / `num_sources_used`, they appear on `usage.details` under those raw names (and full payload in `usage.raw`) for host visibility — they are **not** folded into `computeXaiCost` token lanes yet (no tool Cost lane). Collections / public URL minting are out of scope for this store.
|
|
102
116
|
|
|
103
|
-
**
|
|
117
|
+
**Host tests:** `@gullabs/testing` exports `FakeXaiFileStore` (in-memory upload/get/delete with optional TTL clock and `failClosed`).
|
|
104
118
|
|
|
105
119
|
## Vision constraints
|
|
106
120
|
|
package/dist/index.cjs
CHANGED
|
@@ -739,35 +739,25 @@ var XaiFileStore = class {
|
|
|
739
739
|
}
|
|
740
740
|
/**
|
|
741
741
|
* Delete a file. Idempotent: HTTP 404 → success.
|
|
742
|
-
*
|
|
742
|
+
*
|
|
743
|
+
* Default (`failClosed` omitted/false): non-404 errors go to `onDeleteError`
|
|
744
|
+
* and resolve (P5 fail-open). With `failClosed: true`, non-404 errors throw
|
|
745
|
+
* typed `LlmError` and `onDeleteError` is not called.
|
|
746
|
+
*
|
|
747
|
+
* Empty/blank ids always throw `bad_request` (caller fault).
|
|
743
748
|
*/
|
|
744
|
-
async delete(fileIdOrHandle,
|
|
749
|
+
async delete(fileIdOrHandle, opts) {
|
|
745
750
|
const fileId = resolveFileId(fileIdOrHandle);
|
|
746
751
|
if (typeof fileId !== "string" || fileId.trim() === "") {
|
|
747
|
-
|
|
748
|
-
return;
|
|
752
|
+
throw badRequest("fileId must be a non-empty string.");
|
|
749
753
|
}
|
|
754
|
+
const failClosed = opts?.failClosed === true;
|
|
755
|
+
const signal = opts?.signal;
|
|
750
756
|
try {
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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
|
-
}
|
|
757
|
+
const res = await this.fetchImpl(
|
|
758
|
+
this.filesUrl(fileId),
|
|
759
|
+
this.requestInit("DELETE", signal !== void 0 ? { signal } : {})
|
|
760
|
+
);
|
|
771
761
|
if (res.status === 404) {
|
|
772
762
|
return;
|
|
773
763
|
}
|
|
@@ -778,14 +768,32 @@ var XaiFileStore = class {
|
|
|
778
768
|
if (isNotFoundError(err)) {
|
|
779
769
|
return;
|
|
780
770
|
}
|
|
781
|
-
|
|
771
|
+
const classified = signal?.aborted === true && !(err instanceof core.LlmError) ? new core.LlmError("xAI file delete aborted", {
|
|
772
|
+
kind: "aborted",
|
|
773
|
+
retryable: false,
|
|
774
|
+
provider: "xai",
|
|
775
|
+
cause: err
|
|
776
|
+
}) : classifyStoreError(err);
|
|
777
|
+
if (failClosed) {
|
|
778
|
+
throw classified;
|
|
779
|
+
}
|
|
780
|
+
this.onDeleteError(fileId, classified);
|
|
782
781
|
}
|
|
783
782
|
}
|
|
784
783
|
/**
|
|
785
|
-
* Delete many files.
|
|
784
|
+
* Delete many files.
|
|
785
|
+
*
|
|
786
|
+
* Fail-open (default): `Promise.allSettled` — each failure → `onDeleteError`.
|
|
787
|
+
* Fail-closed: `Promise.all` — first throw rejects; in-flight siblings are
|
|
788
|
+
* not cancelled (partial deletes may already have succeeded at the provider).
|
|
789
|
+
* Prefer per-id delete + host DB mark when gating durable release state.
|
|
786
790
|
*/
|
|
787
|
-
async deleteAll(ids,
|
|
788
|
-
|
|
791
|
+
async deleteAll(ids, opts) {
|
|
792
|
+
if (opts?.failClosed === true) {
|
|
793
|
+
await Promise.all(ids.map((id) => this.delete(id, opts)));
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
await Promise.allSettled(ids.map((id) => this.delete(id, opts)));
|
|
789
797
|
}
|
|
790
798
|
/** Download raw file bytes. */
|
|
791
799
|
async getContent(fileId, signal) {
|