Some checks failed
E2E Health Check / e2e-health (push) Has been cancelled
Router 層禁止直接 httpx.AsyncClient,抽取到 Service 層: 新增 Services: - OpenClawHttpService: Error 分析/Code Review/CI 診斷 - GitHubApiService: PR Diff 取得 - HealthCheckService: HTTP/PostgreSQL/Redis 健康檢查 修改 Routers: - sentry_webhook.py: 使用 OpenClawHttpService - github_webhook.py: 使用 GitHubApiService + OpenClawHttpService - health.py: 使用 HealthCheckService 遵循規範: - Skill 09: Router 層禁止直接外部 API 呼叫 - feedback_lewooogo_modular_enforcement.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
118 lines
3.3 KiB
Python
118 lines
3.3 KiB
Python
"""
|
|
GitHub API Service - 統一 GitHub API 呼叫
|
|
=========================================
|
|
Phase 22 P0 修復: Router 層禁止直接 httpx.AsyncClient
|
|
|
|
遵循規範:
|
|
- Skill 09: Router 層禁止直接外部 API 呼叫
|
|
- feedback_lewooogo_modular_enforcement.md: Service 層封裝
|
|
|
|
功能:
|
|
- Fetch PR Diff (取得 PR 差異內容)
|
|
- 未來擴展: PR Comments, Status Checks, etc.
|
|
|
|
版本: v1.0
|
|
建立: 2026-03-31 (台北時區)
|
|
建立者: Claude Code (首席架構師 P0 修復)
|
|
"""
|
|
|
|
import httpx
|
|
import structlog
|
|
|
|
logger = structlog.get_logger(__name__)
|
|
|
|
|
|
# =============================================================================
|
|
# Constants
|
|
# =============================================================================
|
|
|
|
# 最大 diff 字元數 (約 12500 tokens)
|
|
MAX_DIFF_CHARS = 50000
|
|
|
|
|
|
# =============================================================================
|
|
# GitHub API Service
|
|
# =============================================================================
|
|
|
|
|
|
class GitHubApiService:
|
|
"""
|
|
GitHub API Service
|
|
|
|
統一 GitHub API 呼叫,符合 leWOOOgo 積木化原則
|
|
|
|
2026-03-31 Claude Code (Phase 22 P0 修復)
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
default_timeout: float = 30.0,
|
|
max_diff_chars: int = MAX_DIFF_CHARS,
|
|
):
|
|
self._default_timeout = default_timeout
|
|
self._max_diff_chars = max_diff_chars
|
|
|
|
async def fetch_pr_diff(
|
|
self,
|
|
diff_url: str,
|
|
timeout: float | None = None,
|
|
) -> str:
|
|
"""
|
|
取得 PR diff 內容
|
|
|
|
Args:
|
|
diff_url: GitHub diff URL
|
|
timeout: 超時秒數 (預設 30s)
|
|
|
|
Returns:
|
|
str: diff 內容 (超過限制會截斷)
|
|
"""
|
|
try:
|
|
async with httpx.AsyncClient(
|
|
timeout=timeout or self._default_timeout
|
|
) as client:
|
|
response = await client.get(
|
|
diff_url,
|
|
headers={"Accept": "application/vnd.github.v3.diff"},
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
diff_content = response.text
|
|
|
|
# 限制 diff 大小 (避免 LLM token 過多)
|
|
if len(diff_content) > self._max_diff_chars:
|
|
diff_content = (
|
|
diff_content[: self._max_diff_chars] + "\n... (truncated)"
|
|
)
|
|
|
|
return diff_content
|
|
else:
|
|
logger.warning(
|
|
"github_diff_fetch_failed",
|
|
url=diff_url,
|
|
status=response.status_code,
|
|
)
|
|
return ""
|
|
|
|
except httpx.TimeoutException:
|
|
logger.warning("github_diff_fetch_timeout", url=diff_url)
|
|
return ""
|
|
except Exception as e:
|
|
logger.warning("github_diff_fetch_error", error=str(e))
|
|
return ""
|
|
|
|
|
|
# =============================================================================
|
|
# Singleton
|
|
# =============================================================================
|
|
|
|
_github_api_service: GitHubApiService | None = None
|
|
|
|
|
|
def get_github_api_service() -> GitHubApiService:
|
|
"""取得 GitHubApiService singleton"""
|
|
global _github_api_service
|
|
if _github_api_service is None:
|
|
_github_api_service = GitHubApiService()
|
|
return _github_api_service
|