mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
refactor(agents): absorb overflow, retry, and matcher duplicates into the failover substrate (#121817)
* refactor(ai): centralize context overflow matching * refactor(agents): add retry evidence and guarded status facets * refactor(agents): absorb assistant retry and key matchers * chore(plugin-sdk): refresh failover closure hashes * fix(agents): consume retry evidence at altitude zero * test(agents): narrow retry corpus provider fixtures * test(agents): keep retry corpus sorting immutable
This commit is contained in:
committed by
GitHub
parent
aba8f10bc3
commit
90ddeb4b40
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
extractErrorHttpStatus,
|
||||
extractLeadingHttpStatus,
|
||||
extractProviderWrappedHttpStatus,
|
||||
formatRawAssistantErrorForUi,
|
||||
@@ -58,6 +59,27 @@ describe("extractProviderWrappedHttpStatus", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractErrorHttpStatus", () => {
|
||||
it.each([
|
||||
["HTTP 429 too many requests", 429],
|
||||
["OpenAI API error (500): upstream failed", 500],
|
||||
["error, status code: 400, message: invalid request", 400],
|
||||
["unexpected status 503 from upstream", 503],
|
||||
["Error: HTTP status: 504, gateway timeout", 504],
|
||||
])("extracts guarded status from %s", (message, code) => {
|
||||
expect(extractErrorHttpStatus(message)?.code).toBe(code);
|
||||
});
|
||||
|
||||
it.each([
|
||||
"request id req-4291 failed",
|
||||
"input length 14295 tokens exceeds the model limit",
|
||||
"model model-x-500-preview not found",
|
||||
"Image width 500 exceeds the maximum allowed size",
|
||||
])("rejects embedded numeric text: %s", (message) => {
|
||||
expect(extractErrorHttpStatus(message)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("HTTP status consumers", () => {
|
||||
it("formats only status lines inside the HTTP range", () => {
|
||||
expect(formatRawAssistantErrorForUi("100 Continue")).toBe("HTTP 100: Continue");
|
||||
|
||||
@@ -16,6 +16,9 @@ const HTTP_STATUS_CODE_PREFIX_RE = new RegExp(
|
||||
// like model ids or image dimensions never become fake HTTP statuses.
|
||||
const PROVIDER_WRAPPED_HTTP_STATUS_RE =
|
||||
/^(?:[a-z][\w-]*(?:\s+[a-z][\w-]*){0,3}\s+)?api\s*error\s*\((\d{3})\)(?:\s*:\s*([\s\S]*))?$/i;
|
||||
const LABELED_HTTP_STATUS_RE =
|
||||
/^(?:status code|unexpected status|http status)\s*[:=]?\s*(\d{3})\b(?:\s*[:,]?\s*(?:message\s*:\s*)?([\s\S]*))?$/i;
|
||||
const ERROR_STATUS_ENVELOPE_RE = /^error\s*[:,]\s*/i;
|
||||
const HTML_ERROR_PREFIX_RE = /^\s*(?:<!doctype\s+html\b|<html\b)/i;
|
||||
const HTML_CLOSE_RE = /<\/html>/i;
|
||||
const CLOUDFLARE_HTML_ERROR_CODES = new Set([521, 522, 523, 524, 525, 526, 530]);
|
||||
@@ -122,6 +125,27 @@ export function extractProviderWrappedHttpStatus(
|
||||
return extractHttpStatusMatch(raw.match(PROVIDER_WRAPPED_HTTP_STATUS_RE));
|
||||
}
|
||||
|
||||
/** Extract an explicitly labeled provider HTTP status without matching embedded numeric text. */
|
||||
export function extractErrorHttpStatus(raw: string): { code: number; rest: string } | null {
|
||||
const trimmed = raw.trim();
|
||||
const direct =
|
||||
extractLeadingHttpStatus(trimmed) ??
|
||||
extractProviderWrappedHttpStatus(trimmed) ??
|
||||
extractHttpStatusMatch(trimmed.match(LABELED_HTTP_STATUS_RE));
|
||||
if (direct) {
|
||||
return direct;
|
||||
}
|
||||
const unwrapped = trimmed.replace(ERROR_STATUS_ENVELOPE_RE, "");
|
||||
if (unwrapped === trimmed) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
extractLeadingHttpStatus(unwrapped) ??
|
||||
extractProviderWrappedHttpStatus(unwrapped) ??
|
||||
extractHttpStatusMatch(unwrapped.match(LABELED_HTTP_STATUS_RE))
|
||||
);
|
||||
}
|
||||
|
||||
export function isCloudflareOrHtmlErrorPage(raw: string): boolean {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
|
||||
Reference in New Issue
Block a user