mirror of https://github.com/openclaw/openclaw.git
fix(errors): guard HTML rate limit copy
This commit is contained in:
parent
e97efdd732
commit
755cff833c
|
|
@ -42,6 +42,7 @@ Docs: https://docs.openclaw.ai
|
|||
- WhatsApp: fix infinite echo loop in self-chat DM mode where the bot's own outbound replies were re-processed as new inbound user messages. (#54570) Thanks @joelnishanth
|
||||
- Discord/reconnect: drain stale gateway sockets, clear cached resume state before forced fresh reconnects, and fail closed when old sockets refuse to die so Discord recovery stops looping on poisoned resume state. (#54697) Thanks @ngutman.
|
||||
- BlueBubbles/groups: optionally enrich unnamed participant lists with local macOS Contacts names after group gating passes, so group member context can show names instead of only raw phone numbers.
|
||||
- Agents/errors: surface provider quota/reset details when available, but keep HTML/Cloudflare rate-limit pages on the generic fallback so raw error pages are not shown to users. (#54512) Thanks @bugkill3r.
|
||||
|
||||
## 2026.3.24
|
||||
|
||||
|
|
|
|||
|
|
@ -155,6 +155,24 @@ describe("formatAssistantErrorText", () => {
|
|||
expect(result).toBe("⚠️ Your quota has been exhausted, try again in 24 hours");
|
||||
});
|
||||
|
||||
it("falls back to generic copy for HTML quota pages", () => {
|
||||
const msg = makeAssistantError(
|
||||
"429 <!DOCTYPE html><html><body>Your quota is exhausted</body></html>",
|
||||
);
|
||||
expect(formatAssistantErrorText(msg)).toBe(
|
||||
"⚠️ API rate limit reached. Please try again later.",
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to generic copy for prefixed HTML rate-limit pages", () => {
|
||||
const msg = makeAssistantError(
|
||||
"Error: 521 <!DOCTYPE html><html><body>rate limit</body></html>",
|
||||
);
|
||||
expect(formatAssistantErrorText(msg)).toBe(
|
||||
"⚠️ API rate limit reached. Please try again later.",
|
||||
);
|
||||
});
|
||||
|
||||
it("returns a friendly message for empty stream chunk errors", () => {
|
||||
const msg = makeAssistantError("request ended without sending any chunks");
|
||||
expect(formatAssistantErrorText(msg)).toBe("LLM request timed out.");
|
||||
|
|
|
|||
|
|
@ -66,19 +66,30 @@ const RATE_LIMIT_SPECIFIC_HINT_RE =
|
|||
/\bmin(ute)?s?\b|\bhours?\b|\bseconds?\b|\btry again in\b|\breset\b|\bplan\b|\bquota\b/i;
|
||||
|
||||
function extractProviderRateLimitMessage(raw: string): string | undefined {
|
||||
const withoutPrefix = raw.replace(ERROR_PREFIX_RE, "").trim();
|
||||
// Try to pull a human-readable message out of a JSON error payload first.
|
||||
const info = parseApiErrorInfo(raw);
|
||||
const info = parseApiErrorInfo(raw) ?? parseApiErrorInfo(withoutPrefix);
|
||||
// When the raw string is not a JSON payload, strip any leading HTTP status
|
||||
// code (e.g. "429 ") so the surfaced message stays clean.
|
||||
const candidate = info?.message ?? (extractLeadingHttpStatus(raw.trim())?.rest || raw);
|
||||
const candidate =
|
||||
info?.message ?? (extractLeadingHttpStatus(withoutPrefix)?.rest || withoutPrefix);
|
||||
|
||||
if (!candidate || !RATE_LIMIT_SPECIFIC_HINT_RE.test(candidate)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Skip HTML/Cloudflare error pages even if the body mentions quota/plan text.
|
||||
if (isCloudflareOrHtmlErrorPage(withoutPrefix)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Avoid surfacing very long or clearly non-human-readable blobs.
|
||||
const trimmed = candidate.trim();
|
||||
if (trimmed.length > 300 || trimmed.startsWith("{")) {
|
||||
if (
|
||||
trimmed.length > 300 ||
|
||||
trimmed.startsWith("{") ||
|
||||
/^(?:<!doctype\s+html\b|<html\b)/i.test(trimmed)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue