ForcePilot/backend/package/yuxi/channel/extensions/jira/errors.py
Kris c5d8e3550e feat(channel): 添加 Jira 渠道扩展
新增 Jira 渠道扩展,支持在 Yuxi 平台中集成 Jira 项目管理渠道。

包含以下功能模块:
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- outbound: 外发消息与工单管理
- streaming: 流式消息处理
- parse: Jira 内容解析
- format: 消息格式转换
- agent_tools: Agent 工具集成
- security: 安全校验
- dedupe: 消息去重
- loopbreaker: 循环响应防护
- monitor: 渠道状态监控
- status: 会话与工单状态管理
- types: 类型定义
2026-05-21 11:10:54 +08:00

77 lines
2.6 KiB
Python

from __future__ import annotations
from yuxi.channel.errors import ErrorSeverity
from yuxi.channel.protocols import ClassifiedError, ErrorHandlingProtocol
MAX_RETRIES = 3
def classify_error(
status_code: int | None, response_body: dict | str | None
) -> tuple[ErrorSeverity, str, float | None]:
description = ""
if isinstance(response_body, dict):
errors = response_body.get("errorMessages", response_body.get("errors", {}))
if isinstance(errors, list) and errors:
description = errors[0]
elif isinstance(errors, dict) and errors:
description = list(errors.values())[0]
elif isinstance(response_body, str):
description = response_body
if status_code == 429:
return ErrorSeverity.RATE_LIMITED, description, 30.0
if status_code == 401:
return ErrorSeverity.FORBIDDEN, description, None
if status_code == 403:
return ErrorSeverity.FORBIDDEN, description, None
if status_code == 404:
return ErrorSeverity.FATAL, description, None
if status_code == 400:
return ErrorSeverity.FATAL, description, None
if status_code and status_code >= 500:
return ErrorSeverity.RETRYABLE, description, None
if status_code is None:
return ErrorSeverity.NETWORK, description, None
return ErrorSeverity.FATAL, description, None
class JiraErrorHandler(ErrorHandlingProtocol):
def classify_error(self, error: BaseException) -> ClassifiedError:
import httpx
if isinstance(error, httpx.HTTPStatusError):
status_code = error.response.status_code
body = error.response.json() if error.response.content else {}
severity, desc, retry_after = classify_error(status_code, body)
return ClassifiedError(
severity=severity,
retry_after_ms=int(retry_after * 1000) if retry_after else 0,
original_error=error,
error_message=desc,
)
return ClassifiedError(
severity=ErrorSeverity.NETWORK,
retry_after_ms=0,
original_error=error,
error_message=str(error),
)
def is_retryable(self, error: BaseException) -> bool:
classified = self.classify_error(error)
return classified.severity in (ErrorSeverity.RETRYABLE, ErrorSeverity.RATE_LIMITED, ErrorSeverity.NETWORK)
def should_backoff(self, error: BaseException, attempt: int) -> int:
if not self.is_retryable(error):
return 0
if attempt >= MAX_RETRIES:
return 0
return min(2**attempt * 1000, 10000)