@gullabs/xai 0.5.0 → 0.5.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/dist/index.d.cts CHANGED
@@ -240,28 +240,23 @@ declare function buildXaiClient(auth: AuthMaterial): Promise<XaiClientLike>;
240
240
  * Classify a raw error thrown from the xAI Responses API call into a typed
241
241
  * {@link LlmError}.
242
242
  *
243
- * xAI's Responses API returns HTTP 400 (NOT 401) for an invalid API key, so
244
- * generic {@link classifyHttpStatus}-based classification (which maps 400 →
245
- * `bad_request`) is wrong for this one case. This function special-cases it:
246
- * a 400 response whose STRUCTURED parsed body matches the exact recorded
247
- * xAI auth-failure signature (`code: 'invalid-argument'` AND message prefix
248
- * `"Incorrect API key provided"` — see fixture 09) is reclassified as
249
- * `invalid_auth`. Free-form `Error.message` text is never scanned, so a 400
250
- * whose message merely *mentions* an API key (e.g. schema validation echoing
251
- * user content) stays `bad_request`. When the structured body is unavailable
252
- * or unparseable, classification falls through to the status-based
253
- * `classifyError` from `@gullabs/core`.
254
- *
255
- * A second special case widens `classifyError`'s generic fallback: when the
256
- * base classification lands on `kind: 'unknown'` (no HTTP status to route
257
- * by) AND the raw error matches a known transport-failure signature (see
258
- * {@link isXaiTransportError}), it is reclassified `kind: 'server',
259
- * retryable: true` — the same "provider fault, not caller fault, safe to
260
- * retry" bucket this adapter's `countTokens` path already uses for
261
- * provider-side failures with no HTTP status. A connection that never
262
- * reached xAI is not the caller's fault and is safe to retry; it must never
263
- * be surfaced as the non-retryable `unknown` kind, which Temporal treats as
264
- * fatal and uses to kill the run outright.
243
+ * HTTP status is a hint, not a kind. Overlays inspect the STRUCTURED parsed
244
+ * body only — never free-form `Error.message` — so echoed user content cannot
245
+ * change classification.
246
+ *
247
+ * 1. Already an {@link LlmError} → returned unchanged (including an untagged
248
+ * one).
249
+ * 2. HTTP 400 whose structured body starts with
250
+ * `"Incorrect API key provided"` (fixture 09; prefix only — the SDK may
251
+ * drop `code`) → `invalid_auth`.
252
+ * 3. HTTP 403 whose structured body starts with
253
+ * `"Content violates usage guidelines"` (fixture 15; `SAFETY_CHECK_TYPE_*`
254
+ * suffixes vary) → `content_filter`. A bare 403 without that body stays
255
+ * the core default, `invalid_auth`.
256
+ * 4. `kind: 'unknown'` with a known transport-failure signature (see
257
+ * {@link isXaiTransportError}) → `server`, retryable. A connection that
258
+ * never reached xAI is not the caller's fault.
259
+ * 5. Else rebuild the core classification tagged `provider: 'xai'`.
265
260
  */
266
261
  declare function classifyXaiError(rawErr: unknown): LlmError;
267
262
  interface XaiAdapterOptions {
package/dist/index.d.ts CHANGED
@@ -240,28 +240,23 @@ declare function buildXaiClient(auth: AuthMaterial): Promise<XaiClientLike>;
240
240
  * Classify a raw error thrown from the xAI Responses API call into a typed
241
241
  * {@link LlmError}.
242
242
  *
243
- * xAI's Responses API returns HTTP 400 (NOT 401) for an invalid API key, so
244
- * generic {@link classifyHttpStatus}-based classification (which maps 400 →
245
- * `bad_request`) is wrong for this one case. This function special-cases it:
246
- * a 400 response whose STRUCTURED parsed body matches the exact recorded
247
- * xAI auth-failure signature (`code: 'invalid-argument'` AND message prefix
248
- * `"Incorrect API key provided"` — see fixture 09) is reclassified as
249
- * `invalid_auth`. Free-form `Error.message` text is never scanned, so a 400
250
- * whose message merely *mentions* an API key (e.g. schema validation echoing
251
- * user content) stays `bad_request`. When the structured body is unavailable
252
- * or unparseable, classification falls through to the status-based
253
- * `classifyError` from `@gullabs/core`.
254
- *
255
- * A second special case widens `classifyError`'s generic fallback: when the
256
- * base classification lands on `kind: 'unknown'` (no HTTP status to route
257
- * by) AND the raw error matches a known transport-failure signature (see
258
- * {@link isXaiTransportError}), it is reclassified `kind: 'server',
259
- * retryable: true` — the same "provider fault, not caller fault, safe to
260
- * retry" bucket this adapter's `countTokens` path already uses for
261
- * provider-side failures with no HTTP status. A connection that never
262
- * reached xAI is not the caller's fault and is safe to retry; it must never
263
- * be surfaced as the non-retryable `unknown` kind, which Temporal treats as
264
- * fatal and uses to kill the run outright.
243
+ * HTTP status is a hint, not a kind. Overlays inspect the STRUCTURED parsed
244
+ * body only — never free-form `Error.message` — so echoed user content cannot
245
+ * change classification.
246
+ *
247
+ * 1. Already an {@link LlmError} → returned unchanged (including an untagged
248
+ * one).
249
+ * 2. HTTP 400 whose structured body starts with
250
+ * `"Incorrect API key provided"` (fixture 09; prefix only — the SDK may
251
+ * drop `code`) → `invalid_auth`.
252
+ * 3. HTTP 403 whose structured body starts with
253
+ * `"Content violates usage guidelines"` (fixture 15; `SAFETY_CHECK_TYPE_*`
254
+ * suffixes vary) → `content_filter`. A bare 403 without that body stays
255
+ * the core default, `invalid_auth`.
256
+ * 4. `kind: 'unknown'` with a known transport-failure signature (see
257
+ * {@link isXaiTransportError}) → `server`, retryable. A connection that
258
+ * never reached xAI is not the caller's fault.
259
+ * 5. Else rebuild the core classification tagged `provider: 'xai'`.
265
260
  */
266
261
  declare function classifyXaiError(rawErr: unknown): LlmError;
267
262
  interface XaiAdapterOptions {
package/dist/index.js CHANGED
@@ -171,6 +171,11 @@ function isXaiAuthFailureBody(rawErr) {
171
171
  const text = extractXaiErrorBodyText(rawErr);
172
172
  return text !== void 0 && text.startsWith(XAI_AUTH_ERROR_MESSAGE_PREFIX);
173
173
  }
174
+ var XAI_SAFETY_CHECK_MESSAGE_PREFIX = "Content violates usage guidelines";
175
+ function isXaiSafetyCheckBody(rawErr) {
176
+ const text = extractXaiErrorBodyText(rawErr);
177
+ return text !== void 0 && text.startsWith(XAI_SAFETY_CHECK_MESSAGE_PREFIX);
178
+ }
174
179
  var XAI_TRANSPORT_ERROR_PATTERN = /connection error|econnreset|econnrefused|etimedout|eai_again|epipe|socket hang up|fetch failed/i;
175
180
  function matchesXaiTransportSignature(err) {
176
181
  if (!(err instanceof Error)) return false;
@@ -209,6 +214,16 @@ function classifyXaiError(rawErr) {
209
214
  cause: base.cause ?? rawErr
210
215
  });
211
216
  }
217
+ if (base.httpStatus === 403 && isXaiSafetyCheckBody(rawErr)) {
218
+ const bodyText = extractXaiErrorBodyText(rawErr);
219
+ return new LlmError(bodyText ?? base.message, {
220
+ kind: "content_filter",
221
+ retryable: false,
222
+ httpStatus: base.httpStatus,
223
+ provider: "xai",
224
+ cause: base.cause ?? rawErr
225
+ });
226
+ }
212
227
  if (base.kind === "unknown" && isXaiTransportError(rawErr)) {
213
228
  return new LlmError(base.message, {
214
229
  kind: "server",