"""HTTP 错误码唯一口径。 权威来源:`docs/05-接口文档.md` §3.5(HTTP 状态码)、§3.6(核心错误码)以及各接口小节 补充的 `SSE_NOT_ACCEPTABLE`、`FEEDBACK_ALREADY_EXISTS`、`INVALID_CURSOR` 等码。 实现不得再自造码;新增场景必须先落到文档已有码上。 `retryable=None` 表示按 `status_code >= 500` 推导,与文档表中标注的个例 (如 `RESOURCE_VERSION_CONFLICT` 为 409 但可重试)用 `retryable=False/True` 显式覆盖。 """ class AgentError(Exception): """基类。未分类内部错误按文档映射到 `AGENT_INTERNAL_ERROR` / 500。""" code = "AGENT_INTERNAL_ERROR" status_code = 500 # 文档 §3.6 的“是否可重试”列:500 标注“视情况”,基类按不可重试处理。 retryable: bool | None = False def __init__(self, message: str) -> None: super().__init__(message) self.message = message # None 表示未声明:按 status_code >= 500 推导,与文档主表一致。 declared = type(self).retryable self.retryable = self.status_code >= 500 if declared is None else declared @property def is_retryable(self) -> bool: """固化为布尔,供错误信封(文档 §3.4)直接序列化。""" return bool(self.retryable) # --- 输入、鉴权与权限 ------------------------------------------------------- class ValidationAgentError(AgentError): """已解析请求不满足字段或业务输入约束(文档 §3.5 的 422)。""" code = "AGENT_INPUT_INVALID" status_code = 422 class UnauthorizedAgentError(AgentError): """Token 缺失或无效(文档 §3.6 `AUTHENTICATION_REQUIRED`)。""" code = "AUTHENTICATION_REQUIRED" status_code = 401 class ForbiddenAgentError(AgentError): """角色、入口或数据范围不允许(文档 §3.6 `AGENT_PERMISSION_DENIED`)。""" code = "AGENT_PERMISSION_DENIED" status_code = 403 class OnboardingRequiredError(ForbiddenAgentError): """开户前置条件未满足,沿用文档登记的权限错误码。""" class AgentPermissionDeniedError(ForbiddenAgentError): """`AGENT_PERMISSION_DENIED` 的语义化别名,供新代码使用。""" # --- 404:必须按资源语义细分,禁止使用通用 404 ------------------------------ class ResourceNotFoundError(AgentError): """通用 404 基类,只用于继承与 `except`,禁止直接抛出。 文档 §3.6 没有与实现无关的通用 404 码,因此这里取主表中最接近“资源被隐藏” 语义的 `SESSION_NOT_FOUND`;`tests/unit/core/test_errors.py` 用 AST 校验 所有抛错点都使用语义正确的子类,绝不会落到这个兜底值上。 """ code = "SESSION_NOT_FOUND" status_code = 404 class SessionNotFoundError(ResourceNotFoundError): code = "SESSION_NOT_FOUND" class SessionNotAccessibleError(ResourceNotFoundError): """当前身份不能访问会话;按文档 §4.2 资源隐藏规则同样返回 404。""" code = "SESSION_NOT_ACCESSIBLE" class AgentTypeNotFoundError(ValidationAgentError): code = "AGENT_TYPE_NOT_FOUND" status_code = 404 class RunNotFoundError(ResourceNotFoundError): """运行不存在或不可见;越权与不存在必须返回同一码,不泄露存在性。""" code = "RUN_NOT_FOUND" class GenericResourceNotFoundError(ResourceNotFoundError): """已对当前身份隐藏的非会话、非运行资源(消息、工单、客户、管理面资源等)。 文档 §3.6 未给这类通用 404 单独定义码;按同一资源隐藏语义复用 `SESSION_NOT_FOUND`,避免实现再造新码。 """ code = "SESSION_NOT_FOUND" class ReferenceNotFoundError(GenericResourceNotFoundError): """知识引用不存在或不可解析时的语义化别名。""" # --- 409:幂等、版本与运行状态 ---------------------------------------------- class ConflictAgentError(AgentError): """通用 409 基类,只用于继承与 `except`,禁止直接抛出。 文档 §3.5 把非法状态转换归 409,主表里对应的码是 `RUN_NOT_CANCELLABLE`; 具体抛错点必须使用下面的语义子类。 """ code = "RUN_NOT_CANCELLABLE" status_code = 409 class IdempotencyConflictError(ConflictAgentError): code = "IDEMPOTENCY_CONFLICT" class ResourceVersionConflictError(ConflictAgentError): """`If-Match` 版本过期(文档 §5.3);文档标注可重试。""" code = "RESOURCE_VERSION_CONFLICT" retryable = True class RunNotCancellableError(ConflictAgentError): """运行已成功、失败或进入最终提交事务(文档 §6.4)。""" code = "RUN_NOT_CANCELLABLE" # 注意:文档 §3.6 主表列有 `RUN_CANCELLED`,但 §6.4 把它明确定义为 # `request_idempotency` 的**状态标识**(`status='failed'` + `error_code='RUN_CANCELLED'`), # 而不是 HTTP 响应错误码;同一运行重复取消必须幂等返回同一状态。因此这里 # **不提供** `RUN_CANCELLED` 的 HTTP 异常类,避免实现把它当成客户端错误返回。 class RunLeaseLostError(ConflictAgentError): """Worker 租约失效或不能覆盖运行终态。""" code = "RUN_NOT_CANCELLABLE" class InvalidStateError(ConflictAgentError): """非法状态转换且不属于运行取消/版本冲突场景(文档 §3.5 归 409)。""" code = "RUN_NOT_CANCELLABLE" class ResourceAlreadyExistsError(ConflictAgentError): """唯一性约束冲突(文档 §3.5 归 409)。""" code = "IDEMPOTENCY_CONFLICT" # --- 400、429、503、504 ----------------------------------------------------- class InvalidCursorError(AgentError): code = "INVALID_CURSOR" status_code = 400 class RateLimitedError(AgentError): code = "RATE_LIMITED" status_code = 429 retryable = True class RecoverableAgentError(AgentError): """必需依赖不可用(文档 §3.6 `DEPENDENCY_UNAVAILABLE`,可重试)。""" code = "DEPENDENCY_UNAVAILABLE" status_code = 503 retryable = True class DependencyUnavailableError(RecoverableAgentError): code = "DEPENDENCY_UNAVAILABLE" class UpstreamTimeoutError(RecoverableAgentError): """上游超过时间预算(文档 §3.6 `UPSTREAM_TIMEOUT`)。""" code = "UPSTREAM_TIMEOUT" status_code = 504 # --- SSE 与反馈 ------------------------------------------------------------- class SseNotAcceptableError(AgentError): """`Accept` 不接受 `text/event-stream`(文档 §2 运行事件接口)。""" code = "SSE_NOT_ACCEPTABLE" status_code = 406 class FeedbackAlreadyExistsError(ConflictAgentError): """同一用户对同一消息重复提交且内容不同(文档 §7.5)。""" code = "FEEDBACK_ALREADY_EXISTS"