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": "自定义字段 ID(UUID 格式),通过 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("#")