diff --git a/app/gitea_client.py b/app/gitea_client.py index 5c683e4..31f1c08 100644 --- a/app/gitea_client.py +++ b/app/gitea_client.py @@ -1,9 +1,10 @@ """ Gitea API 客户端 """ +import re import httpx import os -from typing import Tuple +from typing import Tuple, Optional class GiteaClient: @@ -12,7 +13,7 @@ class GiteaClient: def __init__(self, gitea_url: str = None, token: str = None): self.gitea_url = (gitea_url or os.getenv("GITEA_URL", "")).rstrip("/") self.token = token or os.getenv("GITEA_TOKEN", "") - self.base_api_url = f"{self.gitea_url}/api/v1" + self.base_api_url = f"{self.gitea_url}/api/v1" if self.gitea_url else "" self.client = httpx.Client(timeout=30) def _headers(self) -> dict: @@ -22,21 +23,31 @@ class GiteaClient: "Content-Type": "application/json", } - def _extract_owner_repo_from_pr_url(self, pr_url: str) -> Tuple[str, str, int]: + def _parse_pr_url(self, pr_url: str) -> Tuple[str, str, str, int]: """ - 从 PR URL 提取 owner, repo, pr_number + 从 PR URL 提取 base_url, owner, repo, pr_number 例如: https://gitea.airlabs.art/owner/repo/pulls/45 + 返回: ("https://gitea.airlabs.art", "owner", "repo", 45) """ - import re - match = re.search(r'([^/]+)/([^/]+)/pulls/(\d+)', pr_url) + match = re.search(r'(https?://[^/]+)/([^/]+)/([^/]+)/pulls/(\d+)', pr_url) if not match: raise ValueError(f"无法解析 PR URL: {pr_url}") - owner, repo, pr_number = match.groups() - return owner, repo, int(pr_number) + base_url, owner, repo, pr_number = match.groups() + return base_url, owner, repo, int(pr_number) + + def _get_api_url(self, pr_url: Optional[str] = None) -> str: + """获取 API base URL,优先从 pr_url 解析,否则用 self.base_api_url""" + if pr_url: + match = re.search(r'(https?://[^/]+)/', pr_url) + if match: + return f"{match.group(1)}/api/v1" + if self.base_api_url: + return self.base_api_url + raise ValueError("无法确定 Gitea API 地址:未配置 GITEA_URL 且 PR URL 中无法提取") def merge_pr( - self, owner: str, repo: str, pr_number: int + self, owner: str, repo: str, pr_number: int, api_base_url: str = None ) -> Tuple[bool, str]: """ 合并 PR @@ -45,11 +56,13 @@ class GiteaClient: owner: 仓库所有者 repo: 仓库名称 pr_number: PR 编号 + api_base_url: 可选,API base URL(从 pr_url 解析得到) Returns: (是否成功, 消息) """ - url = f"{self.base_api_url}/repos/{owner}/{repo}/pulls/{pr_number}/merge" + base = api_base_url or self.base_api_url + url = f"{base}/repos/{owner}/{repo}/pulls/{pr_number}/merge" payload = { "Do": "merge", # merge / squash / rebase "MergeMessageField": f"Merge PR #{pr_number} (approved via Log Center)", @@ -75,7 +88,7 @@ class GiteaClient: return False, str(e) def close_pr( - self, owner: str, repo: str, pr_number: int, reason: str = "" + self, owner: str, repo: str, pr_number: int, reason: str = "", api_base_url: str = None ) -> Tuple[bool, str]: """ 关闭 PR(可选添加评论说明原因) @@ -85,20 +98,24 @@ class GiteaClient: repo: 仓库名称 pr_number: PR 编号 reason: 关闭原因(将作为评论添加) + api_base_url: 可选,API base URL(从 pr_url 解析得到) Returns: (是否成功, 消息) """ + base = api_base_url or self.base_api_url + # Step 1: 如果提供了原因,先添加评论 if reason: comment_success, comment_msg = self.add_pr_comment( - owner, repo, pr_number, f"## ❌ 修复被拒绝\n\n**原因:**\n{reason}" + owner, repo, pr_number, f"## ❌ 修复被拒绝\n\n**原因:**\n{reason}", + api_base_url=base, ) if not comment_success: return False, f"添加评论失败: {comment_msg}" # Step 2: 关闭 PR - url = f"{self.base_api_url}/repos/{owner}/{repo}/pulls/{pr_number}" + url = f"{base}/repos/{owner}/{repo}/pulls/{pr_number}" payload = {"state": "closed"} try: @@ -111,7 +128,7 @@ class GiteaClient: return False, str(e) def add_pr_comment( - self, owner: str, repo: str, pr_number: int, comment: str + self, owner: str, repo: str, pr_number: int, comment: str, api_base_url: str = None ) -> Tuple[bool, str]: """ 添加 PR 评论 @@ -121,12 +138,14 @@ class GiteaClient: repo: 仓库名称 pr_number: PR 编号 comment: 评论内容 + api_base_url: 可选,API base URL Returns: (是否成功, 消息) """ + base = api_base_url or self.base_api_url # 注意: Gitea 中 PR 和 Issue 共用评论 API - url = f"{self.base_api_url}/repos/{owner}/{repo}/issues/{pr_number}/comments" + url = f"{base}/repos/{owner}/{repo}/issues/{pr_number}/comments" payload = {"body": comment} try: @@ -137,17 +156,19 @@ class GiteaClient: return False, str(e) def merge_pr_by_url(self, pr_url: str) -> Tuple[bool, str]: - """通过 PR URL 直接合并""" + """通过 PR URL 直接合并(从 URL 自动解析 Gitea 地址)""" try: - owner, repo, pr_number = self._extract_owner_repo_from_pr_url(pr_url) - return self.merge_pr(owner, repo, pr_number) + base_url, owner, repo, pr_number = self._parse_pr_url(pr_url) + api_base_url = f"{base_url}/api/v1" + return self.merge_pr(owner, repo, pr_number, api_base_url=api_base_url) except Exception as e: return False, str(e) def close_pr_by_url(self, pr_url: str, reason: str = "") -> Tuple[bool, str]: - """通过 PR URL 直接关闭""" + """通过 PR URL 直接关闭(从 URL 自动解析 Gitea 地址)""" try: - owner, repo, pr_number = self._extract_owner_repo_from_pr_url(pr_url) - return self.close_pr(owner, repo, pr_number, reason) + base_url, owner, repo, pr_number = self._parse_pr_url(pr_url) + api_base_url = f"{base_url}/api/v1" + return self.close_pr(owner, repo, pr_number, reason, api_base_url=api_base_url) except Exception as e: return False, str(e) diff --git a/k8s/api-deployment-prod.yaml b/k8s/api-deployment-prod.yaml index ba9d894..64351bd 100644 --- a/k8s/api-deployment-prod.yaml +++ b/k8s/api-deployment-prod.yaml @@ -32,6 +32,9 @@ spec: value: "log_center" - name: DB_PASSWORD value: "JogNQdtrd3WY8CBCAiYfYEGx" + # Gitea Token(URL 从 PR URL 自动解析) + - name: GITEA_TOKEN + value: "443f7f2f556b4832f90e46df9af3e21ccb06b8a3" resources: requests: memory: "128Mi" diff --git a/repair_agent/agent/claude_service.py b/repair_agent/agent/claude_service.py index c35fde6..ec92c23 100644 --- a/repair_agent/agent/claude_service.py +++ b/repair_agent/agent/claude_service.py @@ -38,6 +38,7 @@ class ClaudeService: cmd = [ self.cli_path, "-p", prompt, + "--model", settings.claude_model, "--dangerously-skip-permissions", ] diff --git a/repair_agent/config/settings.py b/repair_agent/config/settings.py index 9336052..b25e11a 100644 --- a/repair_agent/config/settings.py +++ b/repair_agent/config/settings.py @@ -19,6 +19,7 @@ class Settings(BaseSettings): # Claude CLI claude_cli_path: str = Field(default="claude", description="Claude CLI 路径") claude_timeout: int = Field(default=1000, description="Claude CLI 超时时间(秒)") + claude_model: str = Field(default="claude-opus-4-6", description="Claude 模型") # Git git_user_name: str = Field(default="repair-agent", description="Git 用户名")