isTransientNetworkError function
Whether message is a transient failure worth replaying: socket-level
drops and gateway 5xx. Rate limits stay with the roles layer (the
FallbackStreamFunction rotation policy — a 429 may quote "please try
again later"), budget/spending exhaustion is terminal and wins over
every net below it (issue #926 — a gateway-wrapped budget error quotes
"500 … please try again later"), auth failures stand, context overflow
belongs to compaction, and the idle watchdog's own TimeoutException
wording deliberately does NOT match (that error means "the endpoint
went silent", which a retry re-arms anyway) — while the connect
watchdog's zero-byte wording DOES via connectWatchdogTag (issue
#1121: a never-started request is replayed, a silent mid-stream one
is not).
Implementation
bool isTransientNetworkError(AssistantMessage message) {
if (message.stopReason != StopReason.error) return false;
final text = message.errorMessage;
if (text == null || text.isEmpty) return false;
if (text.toLowerCase().contains('certificate')) return false;
if (isContextOverflow(message)) return false;
if (isBudgetExhaustion(message)) return false;
// A resumed-exhaustion terminal (issue #1168) is a consumed verdict:
// its per-attempt evidence quotes raw transport wordings, but the
// chain already spent its budget - upper layers must neither rewrap
// ("not retried" would be false) nor rotate on a false pretext.
if (isTransientExhaustionStory(text)) return false;
if (_rateLimitGuardPatterns.any((pattern) => pattern.hasMatch(text))) {
return false;
}
return _transientNetworkPatterns.any((pattern) => pattern.hasMatch(text));
}