(requestInput: RequestInfo | URL, init?: RequestInit)
| 475 | // around a user-configured gateway. |
| 476 | apiKey: OAUTH_DUMMY_KEY, |
| 477 | async fetch(requestInput: RequestInfo | URL, init?: RequestInit) { |
| 478 | let currentAuth = await getAuth() |
| 479 | // Auth can flip from oauth to api mid-session (user re-runs |
| 480 | // /connect with a pasted key). When that happens, pass the |
| 481 | // request through untouched so the AI SDK's own apiKey-based |
| 482 | // Authorization header reaches xAI unmodified. |
| 483 | if (currentAuth.type !== "oauth") return fetch(requestInput, init) |
| 484 | |
| 485 | // Refresh either when the stored expires timestamp is within the |
| 486 | // skew window, or — for JWT access tokens — when the JWT exp |
| 487 | // claim itself is. The stored expires field is best-effort |
| 488 | // (xAI doesn't always return expires_in) so the JWT check is the |
| 489 | // load-bearing one for tokens that lack a fresh stored deadline. |
| 490 | const expiresSoon = |
| 491 | !currentAuth.expires || |
| 492 | currentAuth.expires - Date.now() <= ACCESS_TOKEN_REFRESH_SKEW_MS || |
| 493 | accessTokenIsExpiring(currentAuth.access) |
| 494 | if (expiresSoon) { |
| 495 | if (!refreshPromise) { |
| 496 | const refreshToken = currentAuth.refresh |
| 497 | refreshPromise = refreshAccessToken(refreshToken, options) |
| 498 | .then(async (tokens) => { |
| 499 | const refreshedExpires = Date.now() + (tokens.expires_in ?? 3600) * 1000 |
| 500 | const refreshedRefresh = tokens.refresh_token || refreshToken |
| 501 | // Persist the rotated pair as best-effort. xAI has already consumed the |
| 502 | // old refresh_token by the time we get here; an auth.set failure leaves |
| 503 | // the on-disk state stale but the in-memory result is still valid for |
| 504 | // this turn. The next live refresh against the stale disk state will |
| 505 | // 4xx and force re-login — a known cross-process limitation. |
| 506 | await input.client.auth |
| 507 | .set({ |
| 508 | path: { id: "xai" }, |
| 509 | body: { |
| 510 | type: "oauth", |
| 511 | access: tokens.access_token, |
| 512 | refresh: refreshedRefresh, |
| 513 | expires: refreshedExpires, |
| 514 | }, |
| 515 | }) |
| 516 | .catch(() => {}) |
| 517 | return { access: tokens.access_token, refresh: refreshedRefresh, expires: refreshedExpires } |
| 518 | }) |
| 519 | .finally(() => { |
| 520 | refreshPromise = undefined |
| 521 | }) |
| 522 | } |
| 523 | const refreshed = await refreshPromise |
| 524 | currentAuth = { ...currentAuth, ...refreshed } |
| 525 | } |
| 526 | |
| 527 | // Copy the caller's headers into a fresh Headers (case-insensitive) |
| 528 | // so we never mutate the RequestInit the AI SDK may reuse on retry. |
| 529 | // Headers.set overwrites case-insensitively, which kills the dummy |
| 530 | // bearer the AI SDK injected from apiKey in a single line. |
| 531 | const headers = new Headers(requestInput instanceof Request ? requestInput.headers : undefined) |
| 532 | if (init?.headers) { |
| 533 | const entries = |
| 534 | init.headers instanceof Headers |
no test coverage detected