2> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt2> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
3> Use this file to discover all available pages before exploring further.3> Use this file to discover all available pages before exploring further.
4 4
5# 待办事项列表5# 跟踪待办事项
6 6
7> 使用 Claude Agent SDK 跟踪和显示待办事项,实现有组织的任务管理7> 在 Agent SDK 会话中跟踪待办事项,并从结构化工具调用中呈现 Claude 的进度
8 8
9待办事项跟踪提供了一种结构化的方式来管理任务并向用户显示进度。Claude Agent SDK 包含内置的待办事项功能,可帮助组织复杂的工作流程并让用户了解任务进度。9在[模型可用性](#model-availability)下列出的模型上,Claude 无需书面待办事项列表即可跟踪多步骤工作,Claude Code 默认会从会话中排除[任务跟踪工具](/docs/zh-CN/tools-reference#task-tool-availability)。对于这些模型上的多步骤任务,您不需要本页面上的任何内容即可让 Claude 完成工作。
10
11在具有任务跟踪工具的会话中,Claude 保持书面待办事项列表,在工作时更新每个项目的状态。您在消息流中看到每个更改作为结构化工具调用。仅当您的应用程序读取这些工具调用时才选择加入会话,无论是记录任务活动还是呈现自己的进度显示。
12
13<h2 id="model-availability">
14 模型可用性
15</h2>
10 16
11<Note>17<Note>
12 截至 TypeScript Agent SDK 0.3.142 和 Claude Code v2.1.142,会话使用结构化的 Task 工具 `TaskCreate`、`TaskUpdate`、`TaskGet` 和 `TaskList`,而不是 `TodoWrite`。Python SDK 从它启动的 Claude Code CLI 获得此更改,而不是从 Python 包版本获得:一旦该 CLI(pip 包内捆绑的副本,或您使用 `cli_path` 指向的副本)为 v2.1.142 或更高版本,该切换就会应用。请参阅[迁移到 Task 工具](#migrate-to-task-tools)了解监控代码如何变化。本页面上的示例设置 `CLAUDE_CODE_ENABLE_TASKS=0` 以继续为尚未迁移的会话显示 `TodoWrite`。18 在 TypeScript Agent SDK 0.3.233 及更高版本或 Python Agent SDK 0.2.139 及更高版本上,以下限制适用。
19
20 The following tools are available by default only on Claude 3.x models, Opus 4 through 4.7, Sonnet 4 through 4.6, and Haiku 4.5. On every other model, including model IDs Claude Code doesn't recognize, they aren't available unless you opt in:
21
22 * `TodoWrite`
23 * `TaskCreate`
24 * `TaskGet`
25 * `TaskUpdate`
26 * `TaskList`
27
28 Wherever the tools are available, Claude Code provides the four Task tools, or `TodoWrite` instead when you set `CLAUDE_CODE_ENABLE_TASKS=0`.
29
30 This default set applies in Claude Code v2.1.268 and later, which the TypeScript Agent SDK bundles from v0.3.268.
13</Note>31</Note>
14 32
15<h3 id="todo-lifecycle">33在列出的模型上,除非您选择加入会话,否则您在消息流中看不到这些工具的 `tool_use` 块。Agent SDK 通过它捆绑的 Claude Code 二进制文件应用这些默认值。如果您将 `pathToClaudeCodeExecutable`(TypeScript)或 `cli_path`(Python)指向您自己的 Claude Code 安装,您将获得该安装提供的任何工具,在其自己的默认值下。要查看运行中会话中的确切集合,请[检查哪些工具可用](/docs/zh-CN/tools-reference#check-which-tools-are-available)。要选择加入会话,请执行以下操作之一:
34
35* 在 [`allowedTools`](/docs/zh-CN/agent-sdk/permissions#allow-and-deny-rules)(TypeScript)或 `allowed_tools`(Python)选项中命名其中一个工具
36* 在 `tools` 选项中列出工具,该选项将会话的内置工具限制为它命名的工具。将您想要的工具与您使用的其他内置工具一起包括
37* 在 `env` 选项中设置 `CLAUDE_CODE_ENABLE_TODO_TOOLS=1`,如本页面上的示例所做的那样。在 TypeScript 中,`env` 替换子进程环境,因此展开 `...process.env` 以保持继承的变量。在 Python 中,`env` 合并在继承的环境之上
38
39<h2 id="todo-lifecycle">
16 待办事项生命周期40 待办事项生命周期
17</h3>41</h2>
18 42
19待办事项遵循可预测的生命周期:43Claude 将每个待办事项移动通过可预测的生命周期:
20 44
211. **创建**为 `pending` 状态,当任务被识别时451. **创建**:当 Claude 识别任务时,将待办事项添加为 `pending`
222. **激活**为 `in_progress` 状态,当工作开始时462. **激活**:当 Claude 开始工作时,将待办事项设置为 `in_progress`
233. **完成**当任务成功完成时473. **完成**:当任务成功完成时,Claude 将其标记为已完成
244. **移除**当组中的所有任务都完成时484. **移除**:Claude 通过在 `TaskUpdate` 调用中设置 `status: "deleted"` 来删除不再需要的待办事项
25 49
26<h3 id="when-todos-are-used">50<h2 id="when-claude-creates-todos">
27 何时使用待办事项51 何时 Claude 创建待办事项
28</h3>52</h2>
29 53
30SDK 会自动为以下情况创建待办事项:54在[具有任务跟踪工具的会话](#model-availability)中,Claude 为大多数多步骤工作创建待办事项,例如:
31 55
32* **复杂的多步骤任务**需要 3 个或更多不同的操作56* **复杂的多步骤任务**需要三个或更多不同的操作
33* **用户提供的任务列表**当提到多个项目时57* **用户提供的任务列表**当提到多个项目时
34* **非平凡的操作**受益于进度跟踪58* **较长的操作**受益于进度跟踪
35* **明确的请求**当用户要求组织待办事项时59* **明确的请求**当用户要求待办事项组织时
60
61Claude 可能会跳过非常短或单步骤请求的待办事项。
36 62
37<h2 id="examples">63<h2 id="examples">
38 示例64 示例
39</h2>65</h2>
40 66
41在运行这些示例之前,请按照[快速入门](/docs/zh-CN/agent-sdk/quickstart)安装 Claude Agent SDK。67在运行这些示例之前,请按照[快速入门](/docs/zh-CN/agent-sdk/quickstart)安装 Claude Agent SDK。本页面上的每个示例都共享相同的权限设置和退出行为:
42 68
43每个示例运行到代理完成并产生其最终结果消息为止。如果会话首先达到其轮次限制,该结果消息将具有 `error_max_turns` 子类型。检查 `subtype` 以检测该结束。69* **权限模式**:示例提示要求 Claude 对项目进行真实工作,因此每个示例都设置 `permissionMode: "acceptEdits"`(TypeScript)或 `permission_mode="acceptEdits"`(Python)以自动批准工作产生的文件编辑。有关替代方案,请参阅[权限模式](/docs/zh-CN/agent-sdk/permissions#permission-modes)。
70* **轮次限制**:每个示例运行直到代理完成并产生其最终结果消息。如果会话首先达到其轮次限制,该结果消息具有 `error_max_turns` 子类型。检查 `subtype` 以检测该结束。
71* **错误处理**:这些示例使用单次 `query()` 调用。在产生 `error_max_turns` 结果后,`query()` 会抛出一个包含 `Reached maximum number of turns` 的错误。每个示例都将其循环包装在 try 块中,以便在发生这种情况时干净地退出。有关结果子类型,请参阅[处理结果](/docs/zh-CN/agent-sdk/agent-loop#handle-the-result)。
44 72
45这些示例使用单次 `query()` 调用。在产生 `error_max_turns` 结果后,`query()` 会抛出一个包含 `Reached maximum number of turns` 的错误。每个示例都将其循环包装在 try 块中,以便在发生这种情况时干净地退出。73<Note>
46 74 任务系统消息,[`SDKTaskNotificationMessage`](/docs/zh-CN/agent-sdk/typescript#sdktasknotificationmessage)(TypeScript)或 [`TaskNotificationMessage`](/docs/zh-CN/agent-sdk/python#tasknotificationmessage)(Python)等,报告后台任务,例如后台命令和子代理。在消息流中,您看到待办事项活动作为助手消息中的 `tool_use` 块。
47有关结果子类型,请参阅[处理结果](/docs/zh-CN/agent-sdk/agent-loop#handle-the-result)。75</Note>
48 76
49<h3 id="monitoring-todo-changes">77<h3 id="monitor-todo-changes">
50 监控待办事项变化78 监控待办事项变化
51</h3>79</h3>
52 80
81以下示例监视助手流中的 `TaskCreate` 和 `TaskUpdate` `tool_use` 块,并为每个新任务的主题打印一条 `+` 行,为每个状态更改的任务 ID 和新状态打印一条更新行。当您想要任务活动的日志而不是呈现的显示时,请使用此形状。`+` 行不包括分配的 ID,因此此日志无法将更新与其创建相匹配。要保持该关联,请按照[实时显示进度](#display-progress-in-real-time)所做的那样捕获 ID。
82
83流式传输的 `tool_use` 输入是模型发出的原始形状。Claude Code 在执行前修复一些接近但不正确的键名,将 `id` 或 `task_id` 映射到 `taskId`,将 `active_form` 映射到 `activeForm`,但该修复不会反映在流中。防御性地读取 `TaskUpdate` 输入字段,如本页面上的两个示例所做的那样,而不是假设规范名称始终存在。
84
53<CodeGroup>85<CodeGroup>
54 ```typescript TypeScript theme={null}86 ```typescript TypeScript theme={null}
55 import { query } from "@anthropic-ai/claude-agent-sdk";87 import { query } from "@anthropic-ai/claude-agent-sdk";
56 88
57 try {89 try {
58 for await (const message of query({90 for await (const message of query({
59 prompt: "Optimize my React app performance and track progress with todos",91 prompt: "Create a static website with a home page, an about page, and a shared stylesheet, and track progress with todos",
60 // Re-enable TodoWrite, which this example monitors. Without it, the SDK uses92 // Keeps the Task tools on models where Claude Code otherwise doesn't provide them.
61 // Task tools instead and these tool_use blocks never appear.93 options: { maxTurns: 15, permissionMode: "acceptEdits", env: { ...process.env, CLAUDE_CODE_ENABLE_TODO_TOOLS: "1" } },
62 options: { maxTurns: 15, env: { ...process.env, CLAUDE_CODE_ENABLE_TASKS: "0" } }
63 })) {94 })) {
64 // Todo updates are reflected in the message stream95 if (message.type !== "assistant") continue;
65 if (message.type === "assistant") {
66 for (const block of message.message.content) {96 for (const block of message.message.content) {
67 if (block.type === "tool_use" && block.name === "TodoWrite") {97 if (block.type !== "tool_use") continue;
68 const todos = block.input.todos;98 if (block.name === "TaskCreate") {
69 99 const input = block.input as { subject: string };
70 console.log("Todo Status Update:");100 console.log(`+ ${input.subject}`);
71 todos.forEach((todo, index) => {101 } else if (block.name === "TaskUpdate") {
72 const status =102 const input = block.input as {
73 todo.status === "completed" ? "✅" : todo.status === "in_progress" ? "🔧" : "❌";103 taskId?: string;
74 console.log(`${index + 1}. ${status} ${todo.content}`);104 id?: string;
75 });105 task_id?: string;
76 }106 status?: string;
107 };
108 const taskId = input.taskId ?? input.id ?? input.task_id;
109 if (taskId && input.status) console.log(` ${taskId} -> ${input.status}`);
77 }110 }
78 }111 }
79 }112 }
80 } catch (error) {113 } catch (error) {
81 // A single-shot query() throws after yielding an error result,114 // A single-shot query() throws after yielding an error result.
82 // such as when the maxTurns limit is hit.
83 console.log(`Session ended with an error: ${error}`);115 console.log(`Session ended with an error: ${error}`);
84 }116 }
85 ```117 ```
93 async def main():124 async def main():
94 try:125 try:
95 async for message in query(126 async for message in query(
96 prompt="Optimize my React app performance and track progress with todos",127 prompt="Create a static website with a home page, an about page, and a shared stylesheet, and track progress with todos",
97 # Re-enable TodoWrite, which this example monitors. Without it, the SDK uses128 # Keeps the Task tools on models where Claude Code otherwise doesn't provide them.
98 # Task tools instead and these tool_use blocks never appear.129 options=ClaudeAgentOptions(max_turns=15, permission_mode="acceptEdits", env={"CLAUDE_CODE_ENABLE_TODO_TOOLS": "1"}),
99 options=ClaudeAgentOptions(max_turns=15, env={"CLAUDE_CODE_ENABLE_TASKS": "0"}),
100 ):130 ):
101 # Todo updates are reflected in the message stream131 if not isinstance(message, AssistantMessage):
102 if isinstance(message, AssistantMessage):132 continue
103 for block in message.content:133 for block in message.content:
104 if isinstance(block, ToolUseBlock) and block.name == "TodoWrite":134 if not isinstance(block, ToolUseBlock):
105 todos = block.input["todos"]135 continue
106 136 if block.name == "TaskCreate":
107 print("Todo Status Update:")137 print(f"+ {block.input.get('subject', '')}")
108 for i, todo in enumerate(todos):138 elif block.name == "TaskUpdate" and block.input.get("status"):
109 status = (139 task_id = (
110 "✅"140 block.input.get("taskId")
111 if todo["status"] == "completed"141 or block.input.get("id")
112 else "🔧"142 or block.input.get("task_id")
113 if todo["status"] == "in_progress"
114 else "❌"
115 )143 )
116 print(f"{i + 1}. {status} {todo['content']}")144 if task_id:
145 print(f" {task_id} -> {block.input['status']}")
117 except Exception as error:146 except Exception as error:
118 # A single-shot query() raises after yielding an error result,147 # A single-shot query() raises after yielding an error result.
119 # such as when the max_turns limit is hit.
120 print(f"Session ended with an error: {error}")148 print(f"Session ended with an error: {error}")
121 149
122 150
124 ```152 ```
125</CodeGroup>153</CodeGroup>
126 154
127<h3 id="real-time-progress-display">155<h3 id="display-progress-in-real-time">
128 实时进度显示156 实时显示进度
129</h3>157</h3>
130 158
159以下示例监视助手流中的 `TaskCreate` 和 `TaskUpdate` `tool_use` 块,并在 `TaskTracker` 类中保持由任务 ID 键入的任务映射,在每次更改时重新呈现进度摘要。摘要计算已完成和进行中的任务,并显示每个活跃项目的 `activeForm` 标签代替其 `subject`。当您的应用程序维护进度显示而不是记录每个事件时,请使用此形状。
160
161分配的任务 ID 不在 `TaskCreate` 输入中。Claude Code 在携带其 `tool_result` 块的用户消息上传递每个工具的结构化输出,在 `tool_use_result` 字段中。对于 `TaskCreate`,该对象在 TypeScript 中记录为[工具输出类型](/docs/zh-CN/agent-sdk/typescript#tool-output-types)下的 `TaskCreateOutput`,在 Python 中该字段是相同形状的普通字典。跟踪器通过 `tool_use_id` 将每个 `tool_result` 块与其 `tool_use` 调用配对,并从配对消息的 `tool_use_result` 中读取 `task.id`。Claude 可以使用 `TaskList` 读取列表,使用 `TaskGet` 读取一个任务的完整详细信息。
162
131<CodeGroup>163<CodeGroup>
132 ```typescript TypeScript theme={null}164 ```typescript TypeScript theme={null}
133 import { query } from "@anthropic-ai/claude-agent-sdk";165 import { query } from "@anthropic-ai/claude-agent-sdk";
134 166
135 class TodoTracker {167 type Task = { subject: string; activeForm?: string; status: string };
136 private todos: any[] = [];168
169 class TaskTracker {
170 private tasks = new Map<string, Task>();
171 private pendingCreates = new Map<string, { subject: string; activeForm?: string }>();
137 172
138 displayProgress() {173 displayProgress() {
139 if (this.todos.length === 0) return;174 if (this.tasks.size === 0) {
175 console.log("\nProgress: no open tasks\n");
176 return;
177 }
140 178
141 const completed = this.todos.filter((t) => t.status === "completed").length;179 const items = [...this.tasks.values()];
142 const inProgress = this.todos.filter((t) => t.status === "in_progress").length;180 const completed = items.filter((t) => t.status === "completed").length;
143 const total = this.todos.length;181 const inProgress = items.filter((t) => t.status === "in_progress").length;
144 182
145 console.log(`\nProgress: ${completed}/${total} completed`);183 console.log(`\nProgress: ${completed}/${this.tasks.size} completed`);
146 console.log(`Currently working on: ${inProgress} task(s)\n`);184 console.log(`Currently working on: ${inProgress} task(s)\n`);
147 185
148 this.todos.forEach((todo, index) => {186 for (const [id, task] of this.tasks) {
149 const icon =187 const icon =
150 todo.status === "completed" ? "✅" : todo.status === "in_progress" ? "🔧" : "❌";188 task.status === "completed" ? "✅" : task.status === "in_progress" ? "🔧" : "❌";
151 const text = todo.status === "in_progress" ? todo.activeForm : todo.content;189 const text = task.status === "in_progress" && task.activeForm ? task.activeForm : task.subject;
152 console.log(`${index + 1}. ${icon} ${text}`);190 console.log(`${id}. ${icon} ${text}`);
191 }
192 }
193
194 handleToolUse(block: { id: string; name: string; input: unknown }) {
195 if (block.name === "TaskCreate") {
196 const input = block.input as { subject: string; activeForm?: string; active_form?: string };
197 this.pendingCreates.set(block.id, {
198 subject: input.subject,
199 activeForm: input.activeForm ?? input.active_form,
153 });200 });
201 } else if (block.name === "TaskUpdate") {
202 const input = block.input as {
203 taskId?: string;
204 id?: string;
205 task_id?: string;
206 status?: string;
207 activeForm?: string;
208 active_form?: string;
209 };
210 const taskId = input.taskId ?? input.id ?? input.task_id;
211 if (!taskId) return;
212 if (input.status === "deleted") {
213 this.tasks.delete(taskId);
214 this.displayProgress();
215 return;
216 }
217 const task = this.tasks.get(taskId);
218 if (!task) return;
219 if (input.status) task.status = input.status;
220 const active = input.activeForm ?? input.active_form;
221 if (active) task.activeForm = active;
222 this.displayProgress();
223 }
224 }
225
226 handleToolResult(block: { tool_use_id: string; is_error?: boolean }, result: unknown) {
227 const create = this.pendingCreates.get(block.tool_use_id);
228 if (!create) return;
229 this.pendingCreates.delete(block.tool_use_id);
230 if (block.is_error) return;
231 // The result's user message carries the tool's structured output as
232 // tool_use_result; for TaskCreate that's TaskCreateOutput,
233 // { task: { id, subject } }.
234 const out = result as { task?: { id: string } };
235 if (!out?.task?.id) return;
236 this.tasks.set(out.task.id, { ...create, status: "pending" });
237 this.displayProgress();
154 }238 }
155 239
156 async trackQuery(prompt: string) {240 async trackQuery(prompt: string) {
157 try {241 try {
158 for await (const message of query({242 for await (const message of query({
159 prompt,243 prompt,
160 // Re-enable TodoWrite, which this tracker watches for.244 options: { maxTurns: 20, permissionMode: "acceptEdits", env: { ...process.env, CLAUDE_CODE_ENABLE_TODO_TOOLS: "1" } },
161 options: { maxTurns: 20, env: { ...process.env, CLAUDE_CODE_ENABLE_TASKS: "0" } }
162 })) {245 })) {
163 if (message.type === "assistant") {246 if (message.type === "assistant") {
164 for (const block of message.message.content) {247 for (const block of message.message.content) {
165 if (block.type === "tool_use" && block.name === "TodoWrite") {248 if (block.type === "tool_use") this.handleToolUse(block);
166 this.todos = block.input.todos;249 }
167 this.displayProgress();
168 }250 }
251 if (message.type === "user" && Array.isArray(message.message.content)) {
252 for (const block of message.message.content) {
253 if (block.type === "tool_result") this.handleToolResult(block, message.tool_use_result);
169 }254 }
170 }255 }
171 }256 }
178 }263 }
179 264
180 // Usage265 // Usage
181 const tracker = new TodoTracker();266 const tracker = new TaskTracker();
182 await tracker.trackQuery("Build a complete authentication system with todos");267 await tracker.trackQuery("Build a complete authentication system with todos");
183 ```268 ```
184 269
185 ```python Python theme={null}270 ```python Python theme={null}
186 import asyncio271 import asyncio
187 272
188 from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlock273 from claude_agent_sdk import (
189 from typing import List, Dict274 query,
275 ClaudeAgentOptions,
276 AssistantMessage,
277 UserMessage,
278 ToolUseBlock,
279 ToolResultBlock,
280 )
190 281
191 282
192 class TodoTracker:283 class TaskTracker:
193 def __init__(self):284 def __init__(self):
194 self.todos: List[Dict] = []285 self.tasks: dict[str, dict] = {}
286 self.pending_creates: dict[str, dict] = {}
195 287
196 def display_progress(self):288 def display_progress(self):
197 if not self.todos:289 if not self.tasks:
290 print("\nProgress: no open tasks\n")
198 return291 return
199 292
200 completed = len([t for t in self.todos if t["status"] == "completed"])293 completed = len([t for t in self.tasks.values() if t["status"] == "completed"])
201 in_progress = len([t for t in self.todos if t["status"] == "in_progress"])294 in_progress = len([t for t in self.tasks.values() if t["status"] == "in_progress"])
202 total = len(self.todos)
203 295
204 print(f"\nProgress: {completed}/{total} completed")296 print(f"\nProgress: {completed}/{len(self.tasks)} completed")
205 print(f"Currently working on: {in_progress} task(s)\n")297 print(f"Currently working on: {in_progress} task(s)\n")
206 298
207 for i, todo in enumerate(self.todos):299 for task_id, task in self.tasks.items():
208 icon = (300 icon = (
209 "✅"301 "✅"
210 if todo["status"] == "completed"302 if task["status"] == "completed"
211 else "🔧"303 else "🔧"
212 if todo["status"] == "in_progress"304 if task["status"] == "in_progress"
213 else "❌"305 else "❌"
214 )306 )
215 text = (307 text = (
216 todo["activeForm"]308 task["activeForm"]
217 if todo["status"] == "in_progress"309 if task["status"] == "in_progress" and task.get("activeForm")
218 else todo["content"]310 else task["subject"]
219 )311 )
220 print(f"{i + 1}. {icon} {text}")312 print(f"{task_id}. {icon} {text}")
313
314 def handle_tool_use(self, block: ToolUseBlock):
315 if block.name == "TaskCreate":
316 self.pending_creates[block.id] = {
317 "subject": block.input.get("subject", ""),
318 "activeForm": block.input.get("activeForm") or block.input.get("active_form"),
319 }
320 elif block.name == "TaskUpdate":
321 task_id = (
322 block.input.get("taskId")
323 or block.input.get("id")
324 or block.input.get("task_id")
325 )
326 if not task_id:
327 return
328 if block.input.get("status") == "deleted":
329 self.tasks.pop(task_id, None)
330 self.display_progress()
331 return
332 task = self.tasks.get(task_id)
333 if not task:
334 return
335 if block.input.get("status"):
336 task["status"] = block.input["status"]
337 active = block.input.get("activeForm") or block.input.get("active_form")
338 if active:
339 task["activeForm"] = active
340 self.display_progress()
341
342 def handle_tool_result(self, block: ToolResultBlock, tool_use_result):
343 create = self.pending_creates.pop(block.tool_use_id, None)
344 if create is None or block.is_error:
345 return
346 # The result's user message carries the tool's structured output as
347 # tool_use_result; for TaskCreate that's {"task": {"id": ..., "subject": ...}}.
348 task = (tool_use_result or {}).get("task") or {}
349 if not task.get("id"):
350 return
351 self.tasks[task["id"]] = {**create, "status": "pending"}
352 self.display_progress()
221 353
222 async def track_query(self, prompt: str):354 async def track_query(self, prompt: str):
223 try:355 try:
224 async for message in query(356 async for message in query(
225 prompt=prompt,357 prompt=prompt,
226 # Re-enable TodoWrite, which this tracker watches for.358 options=ClaudeAgentOptions(
227 options=ClaudeAgentOptions(max_turns=20, env={"CLAUDE_CODE_ENABLE_TASKS": "0"}),359 max_turns=20,
360 permission_mode="acceptEdits",
361 env={"CLAUDE_CODE_ENABLE_TODO_TOOLS": "1"},
362 ),
228 ):363 ):
229 if isinstance(message, AssistantMessage):364 if isinstance(message, AssistantMessage):
230 for block in message.content:365 for block in message.content:
231 if isinstance(block, ToolUseBlock) and block.name == "TodoWrite":366 if isinstance(block, ToolUseBlock):
232 self.todos = block.input["todos"]367 self.handle_tool_use(block)
233 self.display_progress()368 if isinstance(message, UserMessage) and isinstance(message.content, list):
369 for block in message.content:
370 if isinstance(block, ToolResultBlock):
371 self.handle_tool_result(block, message.tool_use_result)
234 except Exception as error:372 except Exception as error:
235 # A single-shot query() raises after yielding an error result,373 # A single-shot query() raises after yielding an error result,
236 # such as when the max_turns limit is hit.374 # such as when the max_turns limit is hit.
239 377
240 # Usage378 # Usage
241 async def main():379 async def main():
242 tracker = TodoTracker()380 tracker = TaskTracker()
243 await tracker.track_query("Build a complete authentication system with todos")381 await tracker.track_query("Build a complete authentication system with todos")
244 382
245 383
247 ```385 ```
248</CodeGroup>386</CodeGroup>
249 387
250<h2 id="migrate-to-task-tools">
251 迁移到 Task 工具
252</h2>
253
254Task 工具将单个 `TodoWrite` 调用分为 `TaskCreate`(用于每个新项目)和 `TaskUpdate`(用于每个状态更改),`TaskList` 和 `TaskGet` 可供模型读取当前列表。您的监控代码仍然检查助手流中的 `tool_use` 块,但维护一个由任务 ID 键入的映射,而不是在每次调用时替换整个列表。Task 工具是 TypeScript Agent SDK 0.3.142 和 Claude Code v2.1.142 的默认工具,因此不需要更改 `options.env`。
255
256| 使用 `TodoWrite` | 使用 Task 工具 |
257| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
258| 一个工具调用重写完整的 `todos` 数组 | `TaskCreate` 添加一个项目,`TaskUpdate` 按 `taskId` 修补一个项目 |
259| 匹配 `block.name === "TodoWrite"` | 匹配 `block.name === "TaskCreate"` 或 `"TaskUpdate"` |
260| 项目形状:`{ content, status, activeForm }` | `TaskCreate` 输入:`{ subject, description, activeForm?, metadata? }`。`TaskUpdate` 输入:`{ taskId, status?, subject?, description?, activeForm?, addBlocks?, addBlockedBy?, owner?, metadata? }`。`status` 是 `"pending"`、`"in_progress"` 或 `"completed"`;设置 `status: "deleted"` 以删除 |
261| 直接渲染 `block.input.todos` | 跨调用累积项目,或从 `TaskList` 工具结果读取快照 |
262
263分配的任务 ID 不在 `TaskCreate` 输入中。它在匹配的 `tool_result` 中返回为 `{ task: { id, subject } }`,因此从结果块捕获它以键入您的映射。以下示例显示了对[监控待办事项变化](#monitoring-todo-changes)循环的最小更改。它仅读取 `tool_use` 输入并跳过从 `tool_result` 块捕获 ID。要渲染完整列表,请在流中监视 `TaskList` 工具结果或将 `TaskCreate` 结果和 `TaskUpdate` 输入累积到映射中。
264
265流式传输的 `tool_use` 输入是模型发出的原始形状。Claude Code 在执行前修复一些接近但不正确的键名,将 `id` 或 `task_id` 映射到 `taskId`,将 `active_form` 映射到 `activeForm`,但该修复不会反映在流中。防御性地读取 `TaskUpdate` 输入字段,如下面的示例所示,而不是假设规范名称始终存在。
266
267<CodeGroup>
268 ```typescript TypeScript theme={null}
269 import { query } from "@anthropic-ai/claude-agent-sdk";
270
271 try {
272 for await (const message of query({
273 prompt: "Optimize my React app performance and track progress with todos",
274 options: { maxTurns: 15 },
275 })) {
276 if (message.type !== "assistant") continue;
277 for (const block of message.message.content) {
278 if (block.type !== "tool_use") continue;
279 if (block.name === "TaskCreate") {
280 const input = block.input as { subject: string };
281 console.log(`+ ${input.subject}`);
282 } else if (block.name === "TaskUpdate") {
283 const input = block.input as {
284 taskId?: string;
285 id?: string;
286 task_id?: string;
287 status?: string;
288 };
289 const taskId = input.taskId ?? input.id ?? input.task_id;
290 if (taskId && input.status) console.log(` ${taskId} -> ${input.status}`);
291 }
292 }
293 }
294 } catch (error) {
295 // A single-shot query() throws after yielding an error result.
296 console.log(`Session ended with an error: ${error}`);
297 }
298 ```
299
300 ```python Python theme={null}
301 import asyncio
302
303 from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlock
304
305 async def main():
306 try:
307 async for message in query(
308 prompt="Optimize my React app performance and track progress with todos",
309 options=ClaudeAgentOptions(max_turns=15),
310 ):
311 if not isinstance(message, AssistantMessage):
312 continue
313 for block in message.content:
314 if not isinstance(block, ToolUseBlock):
315 continue
316 if block.name == "TaskCreate":
317 print(f"+ {block.input['subject']}")
318 elif block.name == "TaskUpdate" and block.input.get("status"):
319 task_id = (
320 block.input.get("taskId")
321 or block.input.get("id")
322 or block.input.get("task_id")
323 )
324 if task_id:
325 print(f" {task_id} -> {block.input['status']}")
326 except Exception as error:
327 # A single-shot query() raises after yielding an error result.
328 print(f"Session ended with an error: {error}")
329
330
331 asyncio.run(main())
332 ```
333</CodeGroup>
334
335<h2 id="related-documentation">388<h2 id="related-documentation">
336 相关文档389 相关文档
337</h2>390</h2>
338 391
339* [TypeScript SDK 参考](/docs/zh-CN/agent-sdk/typescript)392* [Agent SDK 参考 - TypeScript](/docs/zh-CN/agent-sdk/typescript):TypeScript SDK 的选项、类型和工具架构,包括 Task 工具输入和输出类型
340* [Python SDK 参考](/docs/zh-CN/agent-sdk/python)393* [Agent SDK 参考 - Python](/docs/zh-CN/agent-sdk/python):Python SDK 的选项、类型和工具文档
341* [流式模式与单一模式](/docs/zh-CN/agent-sdk/streaming-vs-single-mode)394* [流式输入](/docs/zh-CN/agent-sdk/streaming-vs-single-mode):两种输入模式,以及何时使用流式输入而不是这些示例使用的单次调用
342* [自定义工具](/docs/zh-CN/agent-sdk/custom-tools)395* [为 Claude 提供自定义工具](/docs/zh-CN/agent-sdk/custom-tools):使用 SDK 的进程内 MCP 服务器定义您自己的工具