from __future__ import annotations from typing import Any def build_gitlab_agent_tools(account_id: str, account: dict) -> list[dict[str, Any]]: return [ { "name": "gitlab_search_issues", "description": "搜索 GitLab 项目中的 Issue", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "搜索关键词"}, "state": {"type": "string", "enum": ["opened", "closed", "all"], "default": "opened"}, }, "required": ["query"], }, }, { "name": "gitlab_create_issue", "description": "在 GitLab 项目中创建新 Issue", "parameters": { "type": "object", "properties": { "title": {"type": "string", "description": "Issue 标题"}, "description": {"type": "string", "description": "Issue 描述"}, "labels": {"type": "string", "description": "逗号分隔的标签"}, }, "required": ["title"], }, }, { "name": "gitlab_close_issue", "description": "关闭 GitLab 项目中的 Issue", "parameters": { "type": "object", "properties": { "issue_iid": {"type": "integer", "description": "Issue 的 iid"}, }, "required": ["issue_iid"], }, }, { "name": "gitlab_reopen_issue", "description": "重新打开 GitLab 项目中的 Issue", "parameters": { "type": "object", "properties": { "issue_iid": {"type": "integer", "description": "Issue 的 iid"}, }, "required": ["issue_iid"], }, }, { "name": "gitlab_search_mr", "description": "搜索 GitLab 项目中的 Merge Request", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "搜索关键词(标题匹配)"}, "state": { "type": "string", "enum": ["opened", "closed", "locked", "merged", "all"], "default": "opened", }, }, "required": ["query"], }, }, { "name": "gitlab_list_mrs", "description": "列出 GitLab 项目中的 Merge Request", "parameters": { "type": "object", "properties": { "state": { "type": "string", "enum": ["opened", "closed", "locked", "merged", "all"], "default": "opened", }, }, "required": [], }, }, { "name": "gitlab_create_mr", "description": "在 GitLab 项目中创建 Merge Request", "parameters": { "type": "object", "properties": { "source_branch": {"type": "string", "description": "源分支名"}, "target_branch": {"type": "string", "description": "目标分支名", "default": "main"}, "title": {"type": "string", "description": "MR 标题"}, "description": {"type": "string", "description": "MR 描述"}, "labels": {"type": "string", "description": "逗号分隔的标签"}, }, "required": ["source_branch", "title"], }, }, { "name": "gitlab_merge_mr", "description": "合并 GitLab 项目中的 Merge Request", "parameters": { "type": "object", "properties": { "mr_iid": {"type": "integer", "description": "MR 的 iid"}, "squash": {"type": "boolean", "description": "是否 squash 合并", "default": False}, "remove_source_branch": {"type": "boolean", "description": "合并后删除源分支", "default": False}, }, "required": ["mr_iid"], }, }, { "name": "gitlab_comment", "description": "在 GitLab Issue 或 MR 中添加评论", "parameters": { "type": "object", "properties": { "noteable_type": {"type": "string", "enum": ["Issue", "MergeRequest"], "default": "Issue"}, "noteable_iid": {"type": "integer", "description": "Issue/MR 的 iid"}, "body": {"type": "string", "description": "评论内容(支持 GLFM)"}, }, "required": ["noteable_iid", "body"], }, }, { "name": "gitlab_search_code", "description": "在 GitLab 项目中搜索代码", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "搜索关键词"}, "scope": { "type": "string", "enum": ["blobs", "issues", "merge_requests", "commits", "wiki_blobs", "milestones", "notes"], "default": "blobs", }, }, "required": ["query"], }, }, { "name": "gitlab_get_pipeline", "description": "查看 GitLab Pipeline 详情", "parameters": { "type": "object", "properties": { "pipeline_id": {"type": "integer", "description": "Pipeline ID"}, }, "required": ["pipeline_id"], }, }, { "name": "gitlab_list_pipelines", "description": "列出 GitLab Pipeline 历史", "parameters": { "type": "object", "properties": { "status": { "type": "string", "enum": ["running", "pending", "success", "failed", "canceled", "skipped"], "description": "按状态过滤", }, }, "required": [], }, }, { "name": "gitlab_get_job_trace", "description": "查看 GitLab CI Job 的日志(尾部行数)", "parameters": { "type": "object", "properties": { "job_id": {"type": "integer", "description": "Job ID"}, "tail_lines": {"type": "integer", "description": "获取尾部日志行数", "default": 200}, }, "required": ["job_id"], }, }, { "name": "gitlab_get_repository_file", "description": "读取 GitLab 仓库中的文件内容", "parameters": { "type": "object", "properties": { "file_path": {"type": "string", "description": "文件路径(如 src/main.py)"}, "ref": {"type": "string", "description": "分支/Tag/Commit", "default": "main"}, }, "required": ["file_path"], }, }, { "name": "gitlab_list_branches", "description": "列出 GitLab 项目分支", "parameters": { "type": "object", "properties": {}, "required": [], }, }, { "name": "gitlab_list_tags", "description": "列出 GitLab 项目标签(Tag)", "parameters": { "type": "object", "properties": {}, "required": [], }, }, { "name": "gitlab_add_label", "description": "为 GitLab 项目创建新标签", "parameters": { "type": "object", "properties": { "name": {"type": "string", "description": "标签名称"}, "color": {"type": "string", "description": "标签颜色(如 #FF0000)"}, "description": {"type": "string", "description": "标签描述"}, }, "required": ["name", "color"], }, }, { "name": "gitlab_assign_issue", "description": "将 GitLab Issue 分配给用户", "parameters": { "type": "object", "properties": { "issue_iid": {"type": "integer", "description": "Issue 的 iid"}, "assignee_id": {"type": "integer", "description": "用户 ID(0 表示取消分配)"}, }, "required": ["issue_iid", "assignee_id"], }, }, { "name": "gitlab_set_due_date", "description": "设置 GitLab Issue 的截止日期", "parameters": { "type": "object", "properties": { "issue_iid": {"type": "integer", "description": "Issue 的 iid"}, "due_date": {"type": "string", "description": "截止日期(YYYY-MM-DD 格式)"}, }, "required": ["issue_iid", "due_date"], }, }, { "name": "gitlab_update_issue", "description": "更新 GitLab Issue 的标题、描述或标签", "parameters": { "type": "object", "properties": { "issue_iid": {"type": "integer", "description": "Issue 的 iid"}, "title": {"type": "string", "description": "新标题(可选)"}, "description": {"type": "string", "description": "新描述(可选)"}, "labels": {"type": "string", "description": "逗号分隔的标签(可选)"}, }, "required": ["issue_iid"], }, }, { "name": "gitlab_trigger_pipeline", "description": "触发 GitLab CI/CD Pipeline", "parameters": { "type": "object", "properties": { "ref": {"type": "string", "description": "分支名或 Tag"}, "variables": {"type": "object", "description": "Pipeline 变量"}, }, "required": ["ref"], }, }, ] async def execute_gitlab_tool(tool_name: str, arguments: dict, account: dict) -> dict: from yuxi.channel.extensions.gitlab.api import AsyncGitLabClient client = AsyncGitLabClient( base_url=account.get("base_url", "https://gitlab.com"), private_token=account.get("private_token", ""), ssl_verify=account.get("ssl_verify", True), ) project_id = account.get("project_id", 0) try: if tool_name == "gitlab_search_issues": result = await client.list_issues(project_id, state=arguments.get("state", "opened")) issues = [] for issue in result[:10]: attrs = issue.attributes if hasattr(issue, "attributes") else dict(issue) issues.append( { "iid": attrs.get("iid"), "title": attrs.get("title"), "state": attrs.get("state"), "web_url": attrs.get("web_url"), } ) return {"success": True, "issues": issues} elif tool_name == "gitlab_create_issue": data = {"title": arguments["title"]} if arguments.get("description"): data["description"] = arguments["description"] if arguments.get("labels"): data["labels"] = arguments["labels"] issue = await client.create_issue(project_id, data) attrs = issue.attributes if hasattr(issue, "attributes") else dict(issue) return { "success": True, "issue": { "iid": attrs.get("iid"), "title": attrs.get("title"), "web_url": attrs.get("web_url"), }, } elif tool_name == "gitlab_close_issue": issue_iid = arguments["issue_iid"] result = await client.update_issue(project_id, issue_iid, {"state_event": "close"}) attrs = result.attributes if hasattr(result, "attributes") else dict(result) return {"success": True, "issue": {"iid": attrs.get("iid"), "state": attrs.get("state")}} elif tool_name == "gitlab_reopen_issue": issue_iid = arguments["issue_iid"] result = await client.reopen_issue(project_id, issue_iid) attrs = result.attributes if hasattr(result, "attributes") else dict(result) return {"success": True, "issue": {"iid": attrs.get("iid"), "state": attrs.get("state")}} elif tool_name in ("gitlab_search_mr", "gitlab_list_mrs"): state = arguments.get("state", "opened") result = await client.list_mrs(project_id, state=state) mrs = [] for mr in result[:10]: attrs = mr.attributes if hasattr(mr, "attributes") else dict(mr) mrs.append( { "iid": attrs.get("iid"), "title": attrs.get("title"), "state": attrs.get("state"), "source_branch": attrs.get("source_branch"), "target_branch": attrs.get("target_branch"), "web_url": attrs.get("web_url"), } ) return {"success": True, "merge_requests": mrs} elif tool_name == "gitlab_create_mr": mr = await client.create_mr( project_id=project_id, source_branch=arguments["source_branch"], target_branch=arguments.get("target_branch", "main"), title=arguments["title"], description=arguments.get("description", ""), labels=arguments.get("labels", ""), ) attrs = mr.attributes if hasattr(mr, "attributes") else dict(mr) return { "success": True, "mr": { "iid": attrs.get("iid"), "title": attrs.get("title"), "web_url": attrs.get("web_url"), }, } elif tool_name == "gitlab_merge_mr": result = await client.merge_mr( project_id=project_id, mr_iid=arguments["mr_iid"], squash=arguments.get("squash", False), should_remove_source_branch=arguments.get("remove_source_branch", False), ) return {"success": True, "result": result} elif tool_name == "gitlab_comment": noteable_type = arguments.get("noteable_type", "Issue") noteable_iid = arguments["noteable_iid"] body = arguments["body"] if noteable_type == "MergeRequest": note = await client.create_mr_note(project_id, noteable_iid, body) else: note = await client.create_issue_note(project_id, noteable_iid, body) attrs = note.attributes if hasattr(note, "attributes") else dict(note) return {"success": True, "note_id": attrs.get("id")} elif tool_name == "gitlab_search_code": scope = arguments.get("scope", "blobs") query = arguments["query"] result = await client.search(project_id, scope, query) items = [] for item in result[:10]: attrs = item.attributes if hasattr(item, "attributes") else dict(item) items.append(attrs) return {"success": True, "results": items} elif tool_name == "gitlab_get_pipeline": pipeline = await client.get_pipeline(project_id, arguments["pipeline_id"]) attrs = pipeline.attributes if hasattr(pipeline, "attributes") else dict(pipeline) return { "success": True, "pipeline": { "id": attrs.get("id"), "status": attrs.get("status"), "ref": attrs.get("ref"), "web_url": attrs.get("web_url"), }, } elif tool_name == "gitlab_list_pipelines": status = arguments.get("status") kwargs = {} if status: kwargs["status"] = status result = await client.list_pipelines(project_id, **kwargs) pipelines = [] for p in result[:10]: attrs = p.attributes if hasattr(p, "attributes") else dict(p) pipelines.append( { "id": attrs.get("id"), "status": attrs.get("status"), "ref": attrs.get("ref"), "web_url": attrs.get("web_url"), } ) return {"success": True, "pipelines": pipelines} elif tool_name == "gitlab_get_job_trace": trace = await client.get_job_trace(project_id, arguments["job_id"], arguments.get("tail_lines", 200)) return {"success": True, "trace": trace[:10000]} elif tool_name == "gitlab_get_repository_file": f = await client.get_file(project_id, arguments["file_path"], arguments.get("ref", "main")) return { "success": True, "file": { "path": f.get("file_path"), "content": f.get("content"), "encoding": f.get("encoding"), }, } elif tool_name == "gitlab_list_branches": result = await client.list_branches(project_id) branches = [] for b in result[:20]: attrs = b.attributes if hasattr(b, "attributes") else dict(b) branches.append({"name": attrs.get("name"), "default": attrs.get("default", False)}) return {"success": True, "branches": branches} elif tool_name == "gitlab_list_tags": result = await client.list_tags(project_id) tags = [] for t in result[:20]: attrs = t.attributes if hasattr(t, "attributes") else dict(t) tags.append({"name": attrs.get("name"), "message": attrs.get("message", "")}) return {"success": True, "tags": tags} elif tool_name == "gitlab_add_label": label = await client.create_label( project_id, arguments["name"], arguments["color"], arguments.get("description", ""), ) attrs = label.attributes if hasattr(label, "attributes") else dict(label) return {"success": True, "label": {"name": attrs.get("name"), "color": attrs.get("color")}} elif tool_name == "gitlab_assign_issue": issue_iid = arguments["issue_iid"] assignee_id = arguments["assignee_id"] data = {"assignee_ids": [assignee_id] if assignee_id else []} result = await client.update_issue(project_id, issue_iid, data) attrs = result.attributes if hasattr(result, "attributes") else dict(result) return { "success": True, "issue": { "iid": attrs.get("iid"), "assignees": attrs.get("assignees", []), }, } elif tool_name == "gitlab_set_due_date": issue_iid = arguments["issue_iid"] due_date = arguments["due_date"] result = await client.update_issue(project_id, issue_iid, {"due_date": due_date}) attrs = result.attributes if hasattr(result, "attributes") else dict(result) return { "success": True, "issue": { "iid": attrs.get("iid"), "due_date": attrs.get("due_date"), }, } elif tool_name == "gitlab_update_issue": issue_iid = arguments["issue_iid"] data = {} if arguments.get("title"): data["title"] = arguments["title"] if arguments.get("description"): data["description"] = arguments["description"] if arguments.get("labels"): data["labels"] = arguments["labels"] result = await client.update_issue(project_id, issue_iid, data) attrs = result.attributes if hasattr(result, "attributes") else dict(result) return { "success": True, "issue": { "iid": attrs.get("iid"), "title": attrs.get("title"), "web_url": attrs.get("web_url"), }, } elif tool_name == "gitlab_trigger_pipeline": ref = arguments["ref"] variables = arguments.get("variables") result = await client.trigger_pipeline(project_id, ref, variables) attrs = result.attributes if hasattr(result, "attributes") else dict(result) return {"success": True, "pipeline": {"id": attrs.get("id"), "web_url": attrs.get("web_url")}} return {"success": False, "error": f"unknown tool: {tool_name}"} except Exception as e: return {"success": False, "error": str(e)}