ForcePilot/backend/package/yuxi/channel/extensions/clickup/agent_tools.py

1087 lines
42 KiB
Python
Raw Normal View History

import logging
import httpx
logger = logging.getLogger(__name__)
API_V2_BASE = "https://api.clickup.com/api/v2"
def build_clickup_agent_tools(account: dict) -> list[dict]:
_ = account.get("workspace_id", "")
return [
{
"name": "clickup_create_task",
"description": "在 ClickUp 列表中创建新任务。需要 list_id、任务名称和可选的描述、优先级、负责人等信息。",
"parameters": {
"type": "object",
"properties": {
"list_id": {
"type": "string",
"description": "ClickUp 列表 ID数字字符串。可通过 clickup_get_lists 工具获取。",
},
"name": {
"type": "string",
"description": "任务名称/标题",
},
"description": {
"type": "string",
"description": "任务描述(支持 Markdown可选",
},
"priority": {
"type": "integer",
"description": "任务优先级1=紧急, 2=高, 3=正常, 4=低。默认 3正常",
"default": 3,
},
"assignees": {
"type": "array",
"items": {"type": "string"},
"description": "负责人用户 ID 列表,可选",
},
"tags": {
"type": "array",
"items": {"type": "string"},
"description": "标签名称列表,可选",
},
"due_date": {
"type": "string",
"description": "截止日期时间Unix 毫秒时间戳字符串),可选",
},
},
"required": ["list_id", "name"],
},
},
{
"name": "clickup_get_task",
"description": "获取 ClickUp 任务的详细信息,包括状态、负责人、描述、评论等。",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "ClickUp 任务 ID如 #task_id 或 9 位数字字符串)",
},
},
"required": ["task_id"],
},
},
{
"name": "clickup_update_task",
"description": "更新 ClickUp 任务的信息,如名称、描述、状态、优先级、负责人等。",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "ClickUp 任务 ID",
},
"name": {
"type": "string",
"description": "新的任务名称,可选",
},
"description": {
"type": "string",
"description": "新的任务描述(支持 Markdown可选",
},
"status": {
"type": "string",
"description": "新的任务状态名称,可选",
},
"priority": {
"type": "integer",
"description": "新的优先级1=紧急, 2=高, 3=正常, 4=低,可选",
},
"assignees": {
"type": "object",
"properties": {
"add": {
"type": "array",
"items": {"type": "string"},
"description": "添加的负责人 ID 列表",
},
"rem": {
"type": "array",
"items": {"type": "string"},
"description": "移除的负责人 ID 列表",
},
},
"description": "负责人变更,可选",
},
},
"required": ["task_id"],
},
},
{
"name": "clickup_list_tasks",
"description": "获取 ClickUp 列表中的任务列表,支持筛选状态和分页。",
"parameters": {
"type": "object",
"properties": {
"list_id": {
"type": "string",
"description": "ClickUp 列表 ID",
},
"status": {
"type": "string",
"description": "按状态筛选(如 'open', 'in progress', 'closed'),可选",
},
"page": {
"type": "integer",
"description": "分页页码(从 0 开始)",
"default": 0,
},
"limit": {
"type": "integer",
"description": "每页任务数(最大 100",
"default": 25,
},
},
"required": ["list_id"],
},
},
{
"name": "clickup_search_tasks",
"description": "在 ClickUp Workspace 中搜索任务。可通过关键词、状态、负责人等条件搜索。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词",
},
"status": {
"type": "string",
"description": "按状态筛选,可选",
},
"assignee": {
"type": "string",
"description": "按负责人 ID 筛选,可选",
},
"list_id": {
"type": "string",
"description": "限定搜索的列表 ID可选",
},
},
"required": ["query"],
},
},
{
"name": "clickup_add_task_comment",
"description": "为 ClickUp 任务添加评论。",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "ClickUp 任务 ID",
},
"content": {
"type": "string",
"description": "评论内容(支持 Markdown",
},
},
"required": ["task_id", "content"],
},
},
{
"name": "clickup_get_task_comments",
"description": "获取 ClickUp 任务的评论列表。",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "ClickUp 任务 ID",
},
},
"required": ["task_id"],
},
},
{
"name": "clickup_get_lists",
"description": "获取 ClickUp Workspace 中的列表清单,用于确定任务操作的 list_id。",
"parameters": {
"type": "object",
"properties": {
"space_id": {
"type": "string",
"description": "限定 Space ID 筛选,可选。不提供则获取整个 Workspace 的列表。",
},
},
"required": [],
},
},
{
"name": "clickup_delete_task",
"description": "删除 ClickUp 任务。删除后无法恢复,请谨慎操作。",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "ClickUp 任务 ID如 #task_id 或 9 位数字字符串)",
},
},
"required": ["task_id"],
},
},
{
"name": "clickup_get_spaces",
"description": "获取 ClickUp Workspace 中所有 Space 的列表,用于了解项目结构并定位 space_id。",
"parameters": {
"type": "object",
"properties": {},
"required": [],
},
},
{
"name": "clickup_create_list",
"description": "在 ClickUp Space 或 Folder 中创建新列表。需要 space_id可选 folder_id。",
"parameters": {
"type": "object",
"properties": {
"space_id": {
"type": "string",
"description": "Space ID数字字符串通过 clickup_get_spaces 获取",
},
"name": {
"type": "string",
"description": "列表名称",
},
"folder_id": {
"type": "string",
"description": "Folder ID可选。不提供则创建在 Space 根目录下",
},
},
"required": ["space_id", "name"],
},
},
{
"name": "clickup_update_list",
"description": "更新 ClickUp 列表的名称或其他属性。",
"parameters": {
"type": "object",
"properties": {
"list_id": {
"type": "string",
"description": "列表 ID",
},
"name": {
"type": "string",
"description": "新的列表名称,可选",
},
},
"required": ["list_id"],
},
},
{
"name": "clickup_get_custom_fields",
"description": "获取指定 ClickUp 列表的自定义字段定义,返回字段名称、类型、可选值等信息。",
"parameters": {
"type": "object",
"properties": {
"list_id": {
"type": "string",
"description": "ClickUp 列表 ID",
},
},
"required": ["list_id"],
},
},
{
"name": "clickup_set_custom_field",
"description": "设置 ClickUp 任务的自定义字段值。需要先通过 clickup_get_custom_fields 了解字段 ID 和类型。",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "ClickUp 任务 ID",
},
"field_id": {
"type": "string",
"description": "自定义字段 IDUUID 格式),通过 clickup_get_custom_fields 获取",
},
"value": {
"description": "字段值,类型取决于字段定义(文本传字符串,下拉传选项名,数字传数字等)",
},
},
"required": ["task_id", "field_id", "value"],
},
},
{
"name": "clickup_get_members",
"description": "获取 ClickUp Workspace 中的成员列表,用于查找用户 ID、分配任务负责人等。",
"parameters": {
"type": "object",
"properties": {},
"required": [],
},
},
{
"name": "clickup_move_task",
"description": "将 ClickUp 任务移动到新列表。",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "ClickUp 任务 ID",
},
"target_list_id": {
"type": "string",
"description": "目标列表 ID",
},
},
"required": ["task_id", "target_list_id"],
},
},
{
"name": "clickup_add_task_tag",
"description": "为 ClickUp 任务添加标签。",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "ClickUp 任务 ID",
},
"tag_name": {
"type": "string",
"description": "标签名称",
},
},
"required": ["task_id", "tag_name"],
},
},
{
"name": "clickup_remove_task_tag",
"description": "移除 ClickUp 任务的标签。",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "ClickUp 任务 ID",
},
"tag_name": {
"type": "string",
"description": "标签名称",
},
},
"required": ["task_id", "tag_name"],
},
},
{
"name": "clickup_create_checklist",
"description": "为 ClickUp 任务创建检查清单。",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "ClickUp 任务 ID",
},
"name": {
"type": "string",
"description": "检查清单名称",
},
},
"required": ["task_id", "name"],
},
},
{
"name": "clickup_add_checklist_item",
"description": "向 ClickUp 检查清单添加条目。",
"parameters": {
"type": "object",
"properties": {
"checklist_id": {
"type": "string",
"description": "检查清单 ID",
},
"name": {
"type": "string",
"description": "条目名称",
},
},
"required": ["checklist_id", "name"],
},
},
{
"name": "clickup_add_task_dependency",
"description": "为 ClickUp 任务添加依赖关系。",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "当前任务 ID",
},
"depends_on": {
"type": "string",
"description": "依赖的任务 ID",
},
},
"required": ["task_id", "depends_on"],
},
},
{
"name": "clickup_delete_task_dependency",
"description": "删除 ClickUp 任务的依赖关系。",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "当前任务 ID",
},
"depends_on": {
"type": "string",
"description": "依赖的任务 ID",
},
},
"required": ["task_id", "depends_on"],
},
},
{
"name": "clickup_add_task_link",
"description": "关联两个 ClickUp 任务。",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "当前任务 ID",
},
"links_to": {
"type": "string",
"description": "要关联的目标任务 ID",
},
},
"required": ["task_id", "links_to"],
},
},
{
"name": "clickup_delete_task_link",
"description": "删除 ClickUp 任务之间的关联。",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"description": "当前任务 ID",
},
"links_to": {
"type": "string",
"description": "要取消关联的目标任务 ID",
},
},
"required": ["task_id", "links_to"],
},
},
{
"name": "clickup_update_comment",
"description": "更新 ClickUp 任务评论。",
"parameters": {
"type": "object",
"properties": {
"comment_id": {
"type": "string",
"description": "评论 ID",
},
"content": {
"type": "string",
"description": "新的评论内容",
},
},
"required": ["comment_id", "content"],
},
},
{
"name": "clickup_delete_comment",
"description": "删除 ClickUp 任务评论。",
"parameters": {
"type": "object",
"properties": {
"comment_id": {
"type": "string",
"description": "评论 ID",
},
},
"required": ["comment_id"],
},
},
{
"name": "clickup_get_folders",
"description": "获取 ClickUp Space 中的文件夹列表。",
"parameters": {
"type": "object",
"properties": {
"space_id": {
"type": "string",
"description": "Space ID",
},
},
"required": ["space_id"],
},
},
]
async def execute_clickup_tool(
tool_name: str,
arguments: dict,
account: dict,
) -> dict:
api_token = account.get("api_token", "")
workspace_id = account.get("workspace_id", "")
if not api_token or not workspace_id:
return {"success": False, "error": "ClickUp 账户未配置(缺少 api_token 或 workspace_id"}
tool_map = {
"clickup_create_task": _create_task,
"clickup_get_task": _get_task,
"clickup_update_task": _update_task,
"clickup_list_tasks": _list_tasks,
"clickup_search_tasks": _search_tasks,
"clickup_add_task_comment": _add_task_comment,
"clickup_get_task_comments": _get_task_comments,
"clickup_get_lists": _get_lists,
"clickup_delete_task": _delete_task,
"clickup_get_spaces": _get_spaces,
"clickup_create_list": _create_list,
"clickup_update_list": _update_list,
"clickup_get_custom_fields": _get_custom_fields,
"clickup_set_custom_field": _set_custom_field,
"clickup_get_members": _get_members,
"clickup_move_task": _move_task,
"clickup_add_task_tag": _add_task_tag,
"clickup_remove_task_tag": _remove_task_tag,
"clickup_create_checklist": _create_checklist,
"clickup_add_checklist_item": _add_checklist_item,
"clickup_add_task_dependency": _add_task_dependency,
"clickup_delete_task_dependency": _delete_task_dependency,
"clickup_add_task_link": _add_task_link,
"clickup_delete_task_link": _delete_task_link,
"clickup_update_comment": _update_comment,
"clickup_delete_comment": _delete_comment,
"clickup_get_folders": _get_folders,
}
handler = tool_map.get(tool_name)
if not handler:
return {"success": False, "error": f"未知工具: {tool_name}"}
try:
result = await handler(arguments, api_token, workspace_id)
return {"success": True, "result": result}
except Exception as e:
logger.exception("clickup tool %s failed", tool_name)
return {"success": False, "error": str(e)}
async def _create_task(arguments: dict, api_token: str, workspace_id: str) -> dict:
list_id = arguments["list_id"]
url = f"{API_V2_BASE}/list/{list_id}/task"
headers = {"Authorization": api_token, "Content-Type": "application/json"}
payload: dict = {
"name": arguments["name"],
}
if "description" in arguments:
payload["description"] = arguments["description"]
if "priority" in arguments:
payload["priority"] = arguments["priority"]
if "assignees" in arguments and arguments["assignees"]:
payload["assignees"] = arguments["assignees"]
if "tags" in arguments and arguments["tags"]:
payload["tags"] = arguments["tags"]
if "due_date" in arguments:
payload["due_date"] = arguments["due_date"]
async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.post(url, json=payload, headers=headers)
if resp.status_code in (200, 201):
return resp.json()
error_text = resp.text[:500]
logger.error("clickup create_task failed: status=%d body=%s", resp.status_code, error_text)
raise RuntimeError(f"创建任务失败 (HTTP {resp.status_code}): {error_text}")
async def _get_task(arguments: dict, api_token: str, workspace_id: str) -> dict:
task_id = _clean_task_id(arguments["task_id"])
url = f"{API_V2_BASE}/task/{task_id}"
headers = {"Authorization": api_token}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.get(url, headers=headers)
if resp.status_code == 200:
return resp.json()
error_text = resp.text[:500]
logger.error("clickup get_task failed: status=%d body=%s", resp.status_code, error_text)
raise RuntimeError(f"获取任务失败 (HTTP {resp.status_code}): {error_text}")
async def _update_task(arguments: dict, api_token: str, workspace_id: str) -> dict:
task_id = _clean_task_id(arguments["task_id"])
url = f"{API_V2_BASE}/task/{task_id}"
headers = {"Authorization": api_token, "Content-Type": "application/json"}
payload: dict = {}
if "name" in arguments:
payload["name"] = arguments["name"]
if "description" in arguments:
payload["description"] = arguments["description"]
if "status" in arguments:
payload["status"] = arguments["status"]
if "priority" in arguments:
payload["priority"] = arguments["priority"]
if "assignees" in arguments:
payload["assignees"] = arguments["assignees"]
async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.put(url, json=payload, headers=headers)
if resp.status_code == 200:
return resp.json()
error_text = resp.text[:500]
logger.error("clickup update_task failed: status=%d body=%s", resp.status_code, error_text)
raise RuntimeError(f"更新任务失败 (HTTP {resp.status_code}): {error_text}")
async def _list_tasks(arguments: dict, api_token: str, workspace_id: str) -> dict:
list_id = arguments["list_id"]
url = f"{API_V2_BASE}/list/{list_id}/task"
headers = {"Authorization": api_token}
params: dict = {
"page": arguments.get("page", 0),
}
if "status" in arguments and arguments["status"]:
params["statuses"] = [arguments["status"]]
limit = min(arguments.get("limit", 25), 100)
params["limit"] = limit
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.get(url, params=params, headers=headers)
if resp.status_code == 200:
data = resp.json()
tasks = data.get("tasks", [])
return {
"tasks": tasks,
"total": len(tasks),
"page": arguments.get("page", 0),
}
error_text = resp.text[:500]
logger.error("clickup list_tasks failed: status=%d body=%s", resp.status_code, error_text)
raise RuntimeError(f"获取任务列表失败 (HTTP {resp.status_code}): {error_text}")
async def _search_tasks(arguments: dict, api_token: str, workspace_id: str) -> dict:
query = arguments["query"]
url = f"{API_V2_BASE}/team/{workspace_id}/task"
headers = {"Authorization": api_token}
params: dict = {
"page": 0,
"order_by": "updated",
"reverse": True,
}
if "status" in arguments and arguments["status"]:
params["statuses"] = [arguments["status"]]
if "assignee" in arguments and arguments["assignee"]:
params["assignees"] = [arguments["assignee"]]
if "list_id" in arguments:
params["list_ids"] = [arguments["list_id"]]
if "query" in arguments:
params["query"] = arguments["query"]
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.get(url, params=params, headers=headers)
if resp.status_code == 200:
data = resp.json()
tasks = data.get("tasks", [])
if query:
tasks = [t for t in tasks if query.lower() in t.get("name", "").lower()]
return {
"tasks": tasks,
"total": len(tasks),
}
error_text = resp.text[:500]
logger.error("clickup search_tasks failed: status=%d body=%s", resp.status_code, error_text)
raise RuntimeError(f"搜索任务失败 (HTTP {resp.status_code}): {error_text}")
async def _add_task_comment(arguments: dict, api_token: str, workspace_id: str) -> dict:
task_id = _clean_task_id(arguments["task_id"])
url = f"{API_V2_BASE}/task/{task_id}/comment"
headers = {"Authorization": api_token, "Content-Type": "application/json"}
payload = {
"comment_text": arguments["content"],
}
async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.post(url, json=payload, headers=headers)
if resp.status_code in (200, 201):
return resp.json()
error_text = resp.text[:500]
logger.error("clickup add_task_comment failed: status=%d body=%s", resp.status_code, error_text)
raise RuntimeError(f"添加任务评论失败 (HTTP {resp.status_code}): {error_text}")
async def _get_task_comments(arguments: dict, api_token: str, workspace_id: str) -> dict:
task_id = _clean_task_id(arguments["task_id"])
url = f"{API_V2_BASE}/task/{task_id}/comment"
headers = {"Authorization": api_token}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.get(url, headers=headers)
if resp.status_code == 200:
data = resp.json()
return {
"comments": data.get("comments", []),
"total": len(data.get("comments", [])),
}
error_text = resp.text[:500]
logger.error("clickup get_task_comments failed: status=%d body=%s", resp.status_code, error_text)
raise RuntimeError(f"获取任务评论失败 (HTTP {resp.status_code}): {error_text}")
async def _get_lists(arguments: dict, api_token: str, workspace_id: str) -> dict:
space_id = arguments.get("space_id")
if space_id:
url = f"{API_V2_BASE}/space/{space_id}/list"
else:
url = f"{API_V2_BASE}/team/{workspace_id}/list"
headers = {"Authorization": api_token}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.get(url, headers=headers)
if resp.status_code == 200:
data = resp.json()
return {
"lists": data.get("lists", []),
"total": len(data.get("lists", [])),
}
error_text = resp.text[:500]
logger.error("clickup get_lists failed: status=%d body=%s", resp.status_code, error_text)
raise RuntimeError(f"获取列表清单失败 (HTTP {resp.status_code}): {error_text}")
async def _delete_task(arguments: dict, api_token: str, workspace_id: str) -> dict:
task_id = _clean_task_id(arguments["task_id"])
url = f"{API_V2_BASE}/task/{task_id}"
headers = {"Authorization": api_token}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.delete(url, headers=headers)
if resp.status_code in (200, 204):
return {"deleted": True, "task_id": task_id}
error_text = resp.text[:500]
logger.error("clickup delete_task failed: status=%d body=%s", resp.status_code, error_text)
raise RuntimeError(f"删除任务失败 (HTTP {resp.status_code}): {error_text}")
async def _get_spaces(arguments: dict, api_token: str, workspace_id: str) -> dict:
url = f"{API_V2_BASE}/team/{workspace_id}/space"
headers = {"Authorization": api_token}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.get(url, headers=headers)
if resp.status_code == 200:
data = resp.json()
return {
"spaces": data.get("spaces", []),
"total": len(data.get("spaces", [])),
}
error_text = resp.text[:500]
logger.error("clickup get_spaces failed: status=%d body=%s", resp.status_code, error_text)
raise RuntimeError(f"获取 Space 列表失败 (HTTP {resp.status_code}): {error_text}")
async def _create_list(arguments: dict, api_token: str, workspace_id: str) -> dict:
space_id = arguments["space_id"]
folder_id = arguments.get("folder_id")
if folder_id:
url = f"{API_V2_BASE}/folder/{folder_id}/list"
else:
url = f"{API_V2_BASE}/space/{space_id}/list"
headers = {"Authorization": api_token, "Content-Type": "application/json"}
payload = {"name": arguments["name"]}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.post(url, json=payload, headers=headers)
if resp.status_code in (200, 201):
return resp.json()
error_text = resp.text[:500]
raise RuntimeError(f"创建列表失败 (HTTP {resp.status_code}): {error_text}")
async def _update_list(arguments: dict, api_token: str, workspace_id: str) -> dict:
list_id = arguments["list_id"]
url = f"{API_V2_BASE}/list/{list_id}"
headers = {"Authorization": api_token, "Content-Type": "application/json"}
payload: dict = {}
if "name" in arguments:
payload["name"] = arguments["name"]
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.put(url, json=payload, headers=headers)
if resp.status_code == 200:
return resp.json()
error_text = resp.text[:500]
raise RuntimeError(f"更新列表失败 (HTTP {resp.status_code}): {error_text}")
async def _get_custom_fields(arguments: dict, api_token: str, workspace_id: str) -> dict:
list_id = arguments["list_id"]
url = f"{API_V2_BASE}/list/{list_id}/field"
headers = {"Authorization": api_token}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.get(url, headers=headers)
if resp.status_code == 200:
data = resp.json()
return {
"fields": data.get("fields", []),
"total": len(data.get("fields", [])),
}
error_text = resp.text[:500]
raise RuntimeError(f"获取自定义字段失败 (HTTP {resp.status_code}): {error_text}")
async def _set_custom_field(arguments: dict, api_token: str, workspace_id: str) -> dict:
task_id = _clean_task_id(arguments["task_id"])
field_id = arguments["field_id"]
url = f"{API_V2_BASE}/task/{task_id}/field/{field_id}"
headers = {"Authorization": api_token, "Content-Type": "application/json"}
payload = {"value": arguments["value"]}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.post(url, json=payload, headers=headers)
if resp.status_code in (200, 201):
return resp.json()
error_text = resp.text[:500]
raise RuntimeError(f"设置自定义字段失败 (HTTP {resp.status_code}): {error_text}")
async def _get_members(arguments: dict, api_token: str, workspace_id: str) -> dict:
url = f"{API_V2_BASE}/team/{workspace_id}/user"
headers = {"Authorization": api_token}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.get(url, headers=headers)
if resp.status_code == 200:
data = resp.json()
users = data.get("users", [])
members = [
{"id": u.get("id"), "username": u.get("username", ""), "email": u.get("email", "")} for u in users
]
return {"members": members, "total": len(members)}
error_text = resp.text[:500]
raise RuntimeError(f"获取成员列表失败 (HTTP {resp.status_code}): {error_text}")
async def _move_task(arguments: dict, api_token: str, workspace_id: str) -> dict:
task_id = _clean_task_id(arguments["task_id"])
target_list_id = arguments["target_list_id"]
url = f"{API_V2_BASE}/task/{task_id}/list/{target_list_id}"
headers = {"Authorization": api_token}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.post(url, headers=headers)
if resp.status_code in (200, 201):
return resp.json()
error_text = resp.text[:500]
raise RuntimeError(f"移动任务失败 (HTTP {resp.status_code}): {error_text}")
async def _add_task_tag(arguments: dict, api_token: str, workspace_id: str) -> dict:
task_id = _clean_task_id(arguments["task_id"])
tag_name = arguments["tag_name"]
url = f"{API_V2_BASE}/task/{task_id}/tag/{tag_name}"
headers = {"Authorization": api_token}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.post(url, headers=headers)
if resp.status_code in (200, 201):
return resp.json()
error_text = resp.text[:500]
raise RuntimeError(f"添加标签失败 (HTTP {resp.status_code}): {error_text}")
async def _remove_task_tag(arguments: dict, api_token: str, workspace_id: str) -> dict:
task_id = _clean_task_id(arguments["task_id"])
tag_name = arguments["tag_name"]
url = f"{API_V2_BASE}/task/{task_id}/tag/{tag_name}"
headers = {"Authorization": api_token}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.delete(url, headers=headers)
if resp.status_code in (200, 204):
return {"removed": True, "tag": tag_name}
error_text = resp.text[:500]
raise RuntimeError(f"移除标签失败 (HTTP {resp.status_code}): {error_text}")
async def _create_checklist(arguments: dict, api_token: str, workspace_id: str) -> dict:
task_id = _clean_task_id(arguments["task_id"])
url = f"{API_V2_BASE}/task/{task_id}/checklist"
headers = {"Authorization": api_token, "Content-Type": "application/json"}
payload = {"name": arguments["name"]}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.post(url, json=payload, headers=headers)
if resp.status_code in (200, 201):
return resp.json()
error_text = resp.text[:500]
raise RuntimeError(f"创建检查清单失败 (HTTP {resp.status_code}): {error_text}")
async def _add_checklist_item(arguments: dict, api_token: str, workspace_id: str) -> dict:
checklist_id = arguments["checklist_id"]
url = f"{API_V2_BASE}/checklist/{checklist_id}/checklist_item"
headers = {"Authorization": api_token, "Content-Type": "application/json"}
payload = {"name": arguments["name"]}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.post(url, json=payload, headers=headers)
if resp.status_code in (200, 201):
return resp.json()
error_text = resp.text[:500]
raise RuntimeError(f"添加检查清单条目失败 (HTTP {resp.status_code}): {error_text}")
async def _add_task_dependency(arguments: dict, api_token: str, workspace_id: str) -> dict:
task_id = _clean_task_id(arguments["task_id"])
depends_on = _clean_task_id(arguments["depends_on"])
url = f"{API_V2_BASE}/task/{task_id}/dependency"
headers = {"Authorization": api_token, "Content-Type": "application/json"}
payload = {"depends_on": depends_on}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.post(url, json=payload, headers=headers)
if resp.status_code in (200, 201):
return resp.json()
error_text = resp.text[:500]
raise RuntimeError(f"添加依赖失败 (HTTP {resp.status_code}): {error_text}")
async def _delete_task_dependency(arguments: dict, api_token: str, workspace_id: str) -> dict:
task_id = _clean_task_id(arguments["task_id"])
depends_on = _clean_task_id(arguments["depends_on"])
url = f"{API_V2_BASE}/task/{task_id}/dependency"
headers = {"Authorization": api_token, "Content-Type": "application/json"}
payload = {"depends_on": depends_on}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.delete(url, json=payload, headers=headers)
if resp.status_code in (200, 204):
return {"deleted": True, "depends_on": depends_on}
error_text = resp.text[:500]
raise RuntimeError(f"删除依赖失败 (HTTP {resp.status_code}): {error_text}")
async def _add_task_link(arguments: dict, api_token: str, workspace_id: str) -> dict:
task_id = _clean_task_id(arguments["task_id"])
links_to = _clean_task_id(arguments["links_to"])
url = f"{API_V2_BASE}/task/{task_id}/link/{links_to}"
headers = {"Authorization": api_token}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.post(url, headers=headers)
if resp.status_code in (200, 201):
return resp.json()
error_text = resp.text[:500]
raise RuntimeError(f"关联任务失败 (HTTP {resp.status_code}): {error_text}")
async def _delete_task_link(arguments: dict, api_token: str, workspace_id: str) -> dict:
task_id = _clean_task_id(arguments["task_id"])
links_to = _clean_task_id(arguments["links_to"])
url = f"{API_V2_BASE}/task/{task_id}/link/{links_to}"
headers = {"Authorization": api_token}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.delete(url, headers=headers)
if resp.status_code in (200, 204):
return {"deleted": True, "links_to": links_to}
error_text = resp.text[:500]
raise RuntimeError(f"删除关联失败 (HTTP {resp.status_code}): {error_text}")
async def _update_comment(arguments: dict, api_token: str, workspace_id: str) -> dict:
comment_id = arguments["comment_id"]
url = f"{API_V2_BASE}/comment/{comment_id}"
headers = {"Authorization": api_token, "Content-Type": "application/json"}
payload = {"comment_text": arguments["content"]}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.put(url, json=payload, headers=headers)
if resp.status_code == 200:
return resp.json()
error_text = resp.text[:500]
raise RuntimeError(f"更新评论失败 (HTTP {resp.status_code}): {error_text}")
async def _delete_comment(arguments: dict, api_token: str, workspace_id: str) -> dict:
comment_id = arguments["comment_id"]
url = f"{API_V2_BASE}/comment/{comment_id}"
headers = {"Authorization": api_token}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.delete(url, headers=headers)
if resp.status_code in (200, 204):
return {"deleted": True, "comment_id": comment_id}
error_text = resp.text[:500]
raise RuntimeError(f"删除评论失败 (HTTP {resp.status_code}): {error_text}")
async def _get_folders(arguments: dict, api_token: str, workspace_id: str) -> dict:
space_id = arguments["space_id"]
url = f"{API_V2_BASE}/space/{space_id}/folder"
headers = {"Authorization": api_token}
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.get(url, headers=headers)
if resp.status_code == 200:
data = resp.json()
return {
"folders": data.get("folders", []),
"total": len(data.get("folders", [])),
}
error_text = resp.text[:500]
raise RuntimeError(f"获取文件夹列表失败 (HTTP {resp.status_code}): {error_text}")
def _clean_task_id(task_id: str) -> str:
return task_id.lstrip("#")