/** * xai.js — x.ai (Grok) provider * * OpenAI-compatible API. Rate-limit headers on authenticated responses: * x-ratelimit-limit-requests integer * x-ratelimit-remaining-requests integer * x-ratelimit-reset-requests ISO8601 or relative string * x-ratelimit-limit-tokens integer * x-ratelimit-remaining-tokens integer * x-ratelimit-reset-tokens ISO8601 or relative string * * Probe: POST /v1/chat/completions * Authorization: Bearer * model: 'grok-3-mini' * max_tokens: 1 * * Provider type: 'xai-api' */ /** * Compute severity for an x.ai result object. * * @param {Object} result — partially-built result (status + limit/remaining fields) * @returns {'critical'|'warning'|'ok'|'unknown'} */ function xaiSeverity(result) { if (result.status === 'rate_limited') return 'critical'; if (result.status === 'ok') { const reqPct = (result.requests_limit > 0) ? result.requests_remaining / result.requests_limit : null; const tokPct = (result.tokens_limit > 0) ? result.tokens_remaining / result.tokens_limit : null; if ((reqPct !== null && reqPct < 0.1) || (tokPct !== null && tokPct < 0.1)) return 'warning'; return 'ok'; } return 'unknown'; } /** * Parse x.ai rate-limit headers from a response. * Synchronous — suitable for unit tests with mock headers. * * @param {Object|null} headers — fetch Response.headers (or compatible mock with .get(name)) * @param {number} httpStatus — HTTP status code * @param {string|null} apiKey — API key (null/undefined → no_key result) * @returns {Object} normalized provider result */ export function parseXaiHeaders(headers, httpStatus, apiKey) { if (!apiKey) { return { type: 'xai-api', status: 'no_key', severity: 'unknown' }; } if (httpStatus === 401) { return { type: 'xai-api', status: 'invalid_key', severity: 'unknown' }; } if (httpStatus === 429) { const remaining = headers ? parseInt(headers.get('x-ratelimit-remaining-requests'), 10) : NaN; return { type: 'xai-api', status: 'rate_limited', requests_remaining: isNaN(remaining) ? 0 : remaining, severity: 'critical', }; } if (httpStatus === 200) { const h = (name) => (headers ? headers.get(name) : null); const reqLimit = parseInt(h('x-ratelimit-limit-requests'), 10); const reqRemaining = parseInt(h('x-ratelimit-remaining-requests'), 10); const tokLimit = parseInt(h('x-ratelimit-limit-tokens'), 10); const tokRemaining = parseInt(h('x-ratelimit-remaining-tokens'), 10); const result = { type: 'xai-api', status: 'ok', requests_limit: isNaN(reqLimit) ? null : reqLimit, requests_remaining: isNaN(reqRemaining) ? null : reqRemaining, tokens_limit: isNaN(tokLimit) ? null : tokLimit, tokens_remaining: isNaN(tokRemaining) ? null : tokRemaining, }; result.severity = xaiSeverity(result); return result; } // Other status codes (403, 5xx, etc.) return { type: 'xai-api', status: 'error', message: `HTTP ${httpStatus}`, severity: 'unknown', }; } /** * Probe an x.ai API endpoint. * * @param {string} providerName * @param {string} baseUrl — base URL (e.g. https://api.x.ai) * @param {string|null} apiKey — x.ai API key * @returns {Promise} normalized provider result */ export async function probeXaiProvider(providerName, baseUrl, apiKey) { if (!apiKey) { return { type: 'xai-api', status: 'no_key', severity: 'unknown' }; } const base = (baseUrl || 'https://api.x.ai').replace(/\/$/, ''); try { const response = await fetch(`${base}/v1/chat/completions`, { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'content-type': 'application/json', }, body: JSON.stringify({ model: 'grok-3-mini', max_tokens: 1, messages: [{ role: 'user', content: 'Hi' }], }), }); return parseXaiHeaders(response.headers, response.status, apiKey); } catch (err) { return { type: 'xai-api', status: 'error', message: err.message, severity: 'unknown', }; } }