package browser import "errors" // 本包对外哨兵错误,一律用 errors.Is 判定;文案由调用方补齐上下文(AGENTS §10: // 错误一律 %w wrap,禁裸 return err)。**不设包级可变状态**(除这些 var ErrXxx)。 var ( // ErrCDP 表示 CDP 层失败:WebSocket 连不上 / 命令返回协议错误 / 连接被打断。 ErrCDP = errors.New("browser: CDP 调用失败") // ErrLaunch 表示启动本机浏览器失败:可执行文件拉不起来、进程立刻退出、 // 或者等不到调试端口就绪。 ErrLaunch = errors.New("browser: 启动本机浏览器失败") // ErrNavigate 表示导航失败(CDP 报 errorText,或等待页面就绪超时)。 ErrNavigate = errors.New("browser: 导航失败") // ErrEvaluate 表示在页面里求值失败(表达式异常 / 拿不到执行上下文)。 ErrEvaluate = errors.New("browser: 页面求值失败") // ErrBlockedURL 表示目标 URL 被 SSRF 守卫拒绝(回环/私网/链路本地等)。 ErrBlockedURL = errors.New("browser: URL 被安全守卫拒绝") // ErrScreenshotTooLarge 表示截图在两次尝试(PNG → JPEG)之后仍超过配置的字节上限。 // 超限**不截断**:半个 PNG 是坏图,模型读到的会是噪声(design/28 §3「超限如实 // 截断/缩放并标注」的落地口径 —— 能降编码就降,降不下来就如实报错)。 ErrScreenshotTooLarge = errors.New("browser: 截图超过字节上限") // ErrNoSession 表示管理器已被关闭,或当前没有可用会话(不可用时不假装可用)。 ErrNoSession = errors.New("browser: 没有可用的浏览器会话") // ErrUnavailable 表示这台机器上没有任何可用来源(没装 Chrome、也没有在跑的 // CDP 端点)。它由组合根在探测阶段判定,此处只用于「手里没有 dialer」这一态。 ErrUnavailable = errors.New("browser: 本机没有可用的浏览器来源") ) // Reason 是「这次为什么没成功」的**分类原因码**:上层据此如实降级与记日志, // 不许把它含糊成「失败」两个字(design/28 §6 坑 5)。取值稳定、可断言;人类可读 // 文案见 Message()。 type Reason string // 分类原因码。 const ( // ReasonLaunched 表示会话由本机 Chrome/Chromium 新起(默认有头、持久 profile)。 ReasonLaunched Reason = "launched-local-chrome" // ReasonRemoteCDP 表示会话复用了配置里显式给出的 CDP 端点(容器/手动实例)。 ReasonRemoteCDP Reason = "remote-cdp" // ReasonIdleRecycled 表示上一次会话因空闲超时被回收(下一个动作会重新起)。 ReasonIdleRecycled Reason = "idle-recycled" // ReasonLaunchFailed 表示本机浏览器没拉起来(可执行文件 / 端口未就绪)。 ReasonLaunchFailed Reason = "launch-failed" // ReasonNoPageTarget 表示连上了浏览器却找不到可用的 page target。 ReasonNoPageTarget Reason = "no-page-target" // ReasonBlockedURL 表示 URL 没过 SSRF 守卫。 ReasonBlockedURL Reason = "blocked-url" // ReasonNavigateFailed 表示导航失败或等待就绪超时。 ReasonNavigateFailed Reason = "navigate-failed" // ReasonShotTooLarge 表示截图超过字节上限(如实报,不交坏图)。 ReasonShotTooLarge Reason = "screenshot-too-large" // ReasonClosed 表示管理器已关闭。 ReasonClosed Reason = "closed" ) // Message 返回原因码对应的中文文案;未知码返回空串(调用方回落到错误的 Error())。 func (r Reason) Message() string { switch r { case ReasonLaunched: return "已启动本机浏览器(默认有头 + 持久 profile)" case ReasonRemoteCDP: return "已连接到配置指定的 CDP 端点" case ReasonIdleRecycled: return "空闲超时,上一次会话已回收" case ReasonLaunchFailed: return "本机浏览器未能启动" case ReasonNoPageTarget: return "连上了浏览器,但没有可用的 page target" case ReasonBlockedURL: return "目标 URL 未通过安全守卫" case ReasonNavigateFailed: return "导航失败或等待页面就绪超时" case ReasonShotTooLarge: return "截图超过配置的字节上限" case ReasonClosed: return "浏览器管理器已关闭" default: return "" } }