实现了 GitLab 全功能集成,包含 Webhook 处理、API 客户端、消息收发、会话管理、安全校验、限流器等完整模块,支持 Issues、Merge Requests、流水线等事件监听与交互。
529 lines
22 KiB
Python
529 lines
22 KiB
Python
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)}
|