@@ -238,6 +238,13 @@ function textHasXaiQuotaMarkers(...parts: string[]): boolean {
238238 return XAI_QUOTA_BODY_MARKERS . some ( ( marker ) => combined . includes ( marker ) ) ;
239239}
240240
241+ /**
242+ * User-visible line for an attributable xAI / Grok capacity error. Worded
243+ * without "retrying" for the same reason as RATE_LIMIT_USER_MESSAGE — it also
244+ * surfaces terminally once retries are exhausted.
245+ */
246+ export const XAI_CAPACITY_USER_MESSAGE = "xAI at capacity" ;
247+
241248/**
242249 * xAI / Grok capacity and overload phrases that arrive as protocol_mismatch
243250 * (message-only or JSON raw) when the stream is not valid SSE. Exact
@@ -254,31 +261,57 @@ const XAI_CAPACITY_EXACT_MESSAGES = new Set([
254261 "service temporarily unavailable" ,
255262] ) ;
256263
264+ /**
265+ * Exact-match only — never substring, so quota-suffixed copy stays out. Checks
266+ * the part itself and common JSON message fields, since intx puts the server
267+ * body on `raw` while `message` carries parser detail.
268+ */
269+ function isXaiCapacityExactPhrase ( part : string ) : boolean {
270+ if ( XAI_CAPACITY_EXACT_MESSAGES . has ( part . trim ( ) . toLowerCase ( ) ) ) return true ;
271+ const parsed = tryParseJSON ( part ) ;
272+ if ( typeof parsed !== "object" || parsed === null ) return false ;
273+ const record = parsed as Record < string , unknown > ;
274+ const nested = record . error ;
275+ const candidates = [
276+ record . message ,
277+ typeof nested === "string" ? nested : undefined ,
278+ typeof nested === "object" && nested !== null
279+ ? ( nested as Record < string , unknown > ) . message
280+ : undefined ,
281+ ] ;
282+ return candidates . some (
283+ ( candidate ) =>
284+ typeof candidate === "string" &&
285+ XAI_CAPACITY_EXACT_MESSAGES . has ( candidate . trim ( ) . toLowerCase ( ) ) ,
286+ ) ;
287+ }
288+
257289function textSuggestsXaiCapacity ( ...parts : string [ ] ) : boolean {
258290 const combined = parts . join ( "\n" ) . toLowerCase ( ) ;
259291 if ( XAI_CAPACITY_TEXT_MARKERS . some ( ( marker ) => combined . includes ( marker ) ) ) {
260292 return true ;
261293 }
262- // Exact phrase is message-only; do not substring-match so quota suffixes stay out.
263- return XAI_CAPACITY_EXACT_MESSAGES . has ( parts [ 0 ] ?. trim ( ) . toLowerCase ( ) ?? "" ) ;
294+ return parts . some ( isXaiCapacityExactPhrase ) ;
264295}
265296
266297/**
267298 * Remap attributable xAI / Grok capacity protocol_mismatch errors to retryable.
268- * Unknown providers and OpenCode Go stay terminal. Never remaps quota_exhausted.
299+ * Unknown providers and OpenCode Go stay terminal. Quota markers anywhere in
300+ * the copy veto the remap — mixed capacity+quota text stays a real quota error.
269301 */
270302export function normalizeXaiCapacityError (
271303 error : InferenceErrorWithGoContext ,
272304) : InferenceError {
273305 if ( error . category !== "protocol_mismatch" ) return error ;
274306 if ( ! isKnownXaiProviderId ( error . providerId ) ) return error ;
275- if ( ! textSuggestsXaiCapacity ( error . message ?? "" , stringFromRaw ( error . raw ) ) ) {
276- return error ;
277- }
307+ const messageText = error . message ?? "" ;
308+ const rawText = stringFromRaw ( error . raw ) ;
309+ if ( textHasXaiQuotaMarkers ( messageText , rawText ) ) return error ;
310+ if ( ! textSuggestsXaiCapacity ( messageText , rawText ) ) return error ;
278311
279312 return {
280313 category : "retryable" ,
281- message : GATEWAY_OVERLOAD_USER_MESSAGE ,
314+ message : XAI_CAPACITY_USER_MESSAGE ,
282315 statusCode : error . statusCode ?? 503 ,
283316 ...( error . raw !== undefined ? { raw : error . raw } : { } ) ,
284317 ...( error . retryAfterMs !== undefined
0 commit comments