import type { BrowserErrorKind } from "@umt/contracts"; /** * 浏览器适配层统一错误。message 只含方法、路径与状态,不含访问凭据、Cookie、页面正文。 * retryable:同一预算内是否值得重试(超时/网络/上游 5xx/不可用);认证、预算、控制权冲突一律不重试。 */ export class BrowserError extends Error { readonly kind: BrowserErrorKind; readonly httpStatus?: number; constructor(kind: BrowserErrorKind, message?: string, opts: { httpStatus?: number; cause?: unknown } = {}) { super(message ?? kind, opts.cause === undefined ? undefined : { cause: opts.cause }); this.name = "BrowserError"; this.kind = kind; if (opts.httpStatus !== undefined) this.httpStatus = opts.httpStatus; } get retryable(): boolean { return ( this.kind === "timeout" || this.kind === "network" || this.kind === "upstream" || this.kind === "unavailable" ); } } export const notImplemented = (method: string): BrowserError => new BrowserError("not_implemented", `${method} 未实现(M2 原型验证后启用)`); /** HTTP 状态 → 错误种类(camofox OpenAPI 1.17.0 的响应码:400/401/403/404/409/422/429/500/503) */ export function classifyHttpStatus(status: number): BrowserErrorKind { if (status === 401 || status === 403) return "auth"; // 410:camofox 1.17.0 对"浏览器重启前存在过的标签页"返回 410 browser_restarted(实测),与 404 同样按标签页已不存在处理 if (status === 404 || status === 410) return "not_found"; if (status === 408) return "timeout"; if (status === 429) return "rate_limited"; if (status === 503) return "unavailable"; if (status >= 500) return "upstream"; if (status >= 400) return "bad_request"; return "protocol"; }