Skip to content
FunCoding

Search

Search docs, agents, posts, Skills and MCP servers

Python SDK 参考:工具输入输出、示例与沙盒

Agent SDK Python 里内置工具(Agent、Bash、Read、Edit、Grep、Task* 等)的输入输出结构、持续对话界面示例、错误处理示例,以及 SandboxSettings、SandboxNetworkConfig、SandboxIgnoreViolations 与对未沙盒命令的权限回退。

本页是 Python Agent SDK 参考的最后一部分:内置工具的输入输出结构、两个完整示例,以及沙盒配置。

工具输入输出类型

Python SDK 不把这些作为类型导出,但它们代表消息里工具输入和输出的结构。下面每个输出都是你从该工具的 UserMessage.tool_use_result 读到的值,键名与 Claude Code 发出的完全一致;标注为 | None 并带"present when"或"optional"注释的键在不适用时被省略。

Agent

工具名: Agent。旧名 Task 仍作为别名接受,init SystemMessage 里的 tools 列表为向后兼容目前仍把该工具报告为 Task。

输入:

{
    "description": str,  # 任务的简短(3-5 个词)描述
    "prompt": str,  # 智能体要执行的任务
    "subagent_type": str | None,  # 要使用的专用智能体类型
    "model": "sonnet" | "opus" | "haiku" | "fable" | None,  # 该智能体的模型覆盖
    "run_in_background": bool | None,  # 智能体默认在后台运行;设为 False 同步运行
    "name": str | None,  # 派生智能体的名字
    "team_name": str | None,  # 已弃用;被忽略
    "mode": "acceptEdits" | "auto" | "bypassPermissions" | "default" | "dontAsk" | "plan" | None,  # 已弃用;被忽略。子智能体的权限模式由子智能体继承规则决定
    "isolation": "worktree" | "remote" | None,  # 智能体改动的隔离模式
}

输出(status:"completed"):

{
    "status": "completed",
    "agentId": str,  # 运行的智能体 ID
    "agentType": str | None,  # 处理该任务的子智能体类型
    "content": [  # 结果内容块
        {
            "type": "text",
            "text": str,
            "citations": list | None,
        }
    ],
    "resolvedModel": str | None,  # 子智能体起始使用的模型
    "modelsUsed": list[str] | None,  # 按顺序使用的模型,连续重复项被折叠
    "totalToolUseCount": int,  # 智能体发出的工具调用数
    "totalDurationMs": int,  # 执行时长(毫秒)
    "totalTokens": int,  # 来自最后一个 API 请求的 token 数,不是整个运行
    "usage": {  # token 用量统计
        "input_tokens": int,
        "output_tokens": int,
        "cache_creation_input_tokens": int | None,
        "cache_read_input_tokens": int | None,
        "server_tool_use": {"web_search_requests": int, "web_fetch_requests": int} | None,
        "service_tier": str | None,
        "cache_creation": {"ephemeral_1h_input_tokens": int, "ephemeral_5m_input_tokens": int} | None,
        "inference_geo": str | None,
        "speed": str | None,
        "iterations": Any | None,
        "output_tokens_details": {"thinking_tokens": int | None} | None,
    },
    "toolStats": {  # 运行的聚合工具活动
        "readCount": int,
        "searchCount": int,
        "bashCount": int,
        "editFileCount": int,
        "linesAdded": int,
        "linesRemoved": int,
        "otherToolCount": int,
        "frameCount": int | None,
    } | None,
    "prompt": str,  # 智能体运行的提示
    "worktreePath": str | None,  # Claude Code 保留了子智能体的 worktree 时存在
    "worktreeBranch": str | None,  # Claude Code 用 git 创建了该 worktree 时存在
}

输出(status:"async_launched"):

{
    "status": "async_launched",
    "isAsync": bool | None,  # 后台启动时为 True
    "agentId": str,  # 启动的智能体 ID
    "description": str,  # 任务描述
    "resolvedModel": str | None,  # 转入后台时使用的模型
    "modelsUsed": list[str] | None,  # 转入后台之前按顺序使用的模型,连续重复项被折叠
    "prompt": str,  # 智能体运行的提示
    "outputFile": str,  # 智能体输出写入的文件路径
    "canReadOutputFile": bool | None,  # 输出文件能否被直接读取
}

输出(status:"remote_launched"):

{
    "status": "remote_launched",
    "taskId": str,  # 分发的任务 ID
    "sessionUrl": str,  # 云端会话链接
    "description": str,  # 任务描述
    "prompt": str,  # 智能体运行的提示
    "outputFile": str,  # 智能体输出写入的文件路径
}

返回子智能体的结果,按 status 字段区分:"completed" 表示已完成的任务,"async_launched" 表示后台任务,"remote_launched" 表示 Claude Code 分发到云端会话的任务(sessionUrl 链接到该会话,taskId 标识它)。在 completed 变体上,resolvedModel 点名子智能体起始使用的模型,当 availableModels 或其他覆盖生效时它可能与请求的 model 输入不同(需要 Claude Code v2.1.174 及以上)。Claude Code 从子智能体的最后一个 API 请求而不是整次运行填充 usage 和 totalTokens;存在时,usage 里 output_tokens_details 下的 thinking_tokens 是该请求的输出 token 中属于思考 token 的数量(output_tokens_details 键需要 Python SDK v0.2.136 及以上,它捆绑 Claude Code v2.1.228)。

AskUserQuestion

工具名: AskUserQuestion。在执行期间向用户提出澄清问题(用法见「处理批准和用户输入」)。

输入:

{
    "questions": [  # 要问用户的问题(1-4 个)
        {
            "question": str,  # 要问用户的完整问题
            "header": str,  # 显示为标签的很短的标签(最多 12 字符)
            "options": [  # 可用的选项(2-4 个)
                {
                    "label": str,  # 该选项的显示文本(1-5 个词)
                    "description": str,  # 该选项含义的说明
                    "preview": str | None,  # 选项获得焦点时渲染的预览内容
                }
            ],
            "multiSelect": bool,  # 设为 true 允许多选
        }
    ],
    "answers": dict[str, str] | None,
    # 由权限系统填充的用户回答。多选回答是所选标签以逗号连接的字符串;
    # 输入时接受标签列表并强制转换成这种形式
    "annotations": dict[str, dict] | None,
    # 用户的按问题注解,以问题文本为键。每个值可携带 "preview"
    # (所选选项的预览内容)和 "notes"(对所选项的自由文本备注)
    "metadata": dict | None,  # 分析元数据,如 {"source": "remember"};不向用户显示
}

输出:

{
    "questions": [  # 所问的问题
        {
            "question": str,
            "header": str,
            "options": [{"label": str, "description": str, "preview": str | None}],
            "multiSelect": bool,
        }
    ],
    "answers": dict[str, str],  # 把问题文本映射到回答字符串
    # 多选回答以逗号分隔
    "response": str | None,
    # 用户键入的自由回复而不是回答问题;设置时,
    # Claude 收到 "The user responded: ..." 来代替回答列表
    "annotations": dict[str, dict] | None,  # 来自用户所选项的按问题 "preview" 和 "notes"
    "afkTimeoutMs": int | None,  # 对话框在用户闲置这么多毫秒后自动解决时设置;用户回答了则不存在
}

Bash

工具名: Bash。前台上限由什么决定,见「超时和输出限制」;后台时间限制见「后台命令的时间限制」。

输入:

{
    "command": str,  # 要执行的命令
    "timeout": int | None,  # 毫秒。前台:默认上限 600000,更高的值被钳到上限。带 run_in_background(Claude Code v2.1.285 及以上):后台时限,省略时 1800000,上限 7200000(除非调高)
    "description": str | None,  # 清晰简洁的描述(5-10 个词)
    "run_in_background": bool | None,  # 设为 true 在后台运行
}

输出:

{
    "stdout": str,  # 命令的输出;stdout 和 stderr 合并成这一个交错的流
    "stderr": str,  # 工具自己添加的通知,不是命令的 stderr
    "interrupted": bool,  # 命令是否被中断
    "isImage": bool | None,  # stdout 是否含图片数据
    "backgroundTaskId": str | None,  # 命令在后台运行时的后台任务 ID
}

Monitor

工具名: Monitor。运行后台来源并把每个事件交给 Claude,使它无需轮询就能作出反应:command 运行脚本并对每行 stdout 发出一个事件,ws 打开 WebSocket 并对每个文本帧发出一个事件;command 和 ws 要恰好提供一个。Monitor 运行命令时遵循与 Bash 相同的权限规则;WebSocket 监视单独提示批准;ws 来源需要 Claude Code v2.1.195 及以上。

输入:

{
    "command": str | None,  # shell 脚本;每行 stdout 是一个事件,退出即结束监视
    "ws": dict | None,  # WebSocket 来源:{"url": str, "protocols": list[str] | None};每个文本帧是一个事件
    "description": str,  # 通知里显示的简短描述
    "timeout_ms": int | None,  # 截止时间(毫秒)(默认 300000,最大 3600000;有效截止时间最多 1800000)
}

输出:

{
    "taskId": str,  # 后台监视任务的 ID
    "timeoutMs": int,  # 监视的有效截止时间(毫秒)
    "persistent": bool | None,  # False:每个监视都有截止时间
}

Edit

工具名: Edit

输入:

{
    "file_path": str,  # 要修改的文件的绝对路径
    "old_string": str,  # 要替换的文本
    "new_string": str,  # 用来替换的文本
    "replace_all": bool | None,  # 替换所有出现(默认 False)
}

输出:

{
    "filePath": str,  # 被编辑的文件
    "oldString": str,  # 被替换的文本
    "newString": str,  # 替换它的文本
    "originalFile": str | None,  # 编辑之前的文件内容
    "structuredPatch": [  # 该改动的 diff 块
        {
            "oldStart": int,
            "oldLines": int,
            "newStart": int,
            "newLines": int,
            "lines": list[str],
        }
    ],
    "userModified": bool,  # 用户是否在接受之前修改了提议的编辑
    "replaceAll": bool,  # 是否替换了所有出现
    "gitDiff": {  # 该文件可选的 git diff 摘要
        "filename": str,
        "status": "modified" | "added",
        "additions": int,
        "deletions": int,
        "changes": int,
        "patch": str,
        "repository": str | None,  # 可用时的 GitHub owner/repo
    } | None,
}

Read

工具名: Read

输入:

{
    "file_path": str,  # 要读取的文件的绝对路径
    "offset": int | None,  # 开始读取的行号
    "limit": int | None,  # 要读取的行数
}

输出取决于 Claude 读了什么,取下列形状之一,检查 type 键来区分。

# type: "text"
{
    "type": "text",
    "file": {
        "filePath": str,  # 被读取的文件
        "content": str,  # 返回的内容
        "numLines": int,  # 返回内容的行数
        "startLine": int,  # 内容起始的行号
        "totalLines": int,  # 文件的总行数
        "truncatedByTokenCap": bool | None,  # 整文件读取超过 token 上限、content 是第一页时存在且为 True
    },
}

# type: "image"
{
    "type": "image",
    "file": {
        "base64": str,  # base64 编码的图片数据
        "type": "image/jpeg" | "image/png" | "image/gif" | "image/webp",  # 图片 MIME 类型
        "originalSize": int,  # 原始文件字节数
        "dimensions": {  # 用于坐标映射的可选尺寸信息
            "originalWidth": int | None,  # 可选;原始宽度(像素)
            "originalHeight": int | None,  # 可选;原始高度(像素)
            "displayWidth": int | None,  # 可选;缩放后的宽度
            "displayHeight": int | None,  # 可选;缩放后的高度
        } | None,
    },
}

# type: "notebook"
{
    "type": "notebook",
    "file": {
        "filePath": str,  # 被读取的 notebook
        "cells": list,  # notebook 单元格
    },
}

# type: "pdf"
{
    "type": "pdf",
    "file": {
        "filePath": str,  # 被读取的 PDF
        "base64": str,  # base64 编码的 PDF 数据
        "originalSize": int,  # 文件字节数
    },
}

# type: "parts"
{
    "type": "parts",
    "file": {
        "filePath": str,  # 被读取的 PDF
        "originalSize": int,  # 文件字节数
        "count": int,  # 提取为图片的页数
        "outputDir": str,  # 含提取页面图片的目录
    },
    "firstPage": int | None,  # 可选:第一个提取页的文档页码
}

# type: "file_unchanged"
{
    "type": "file_unchanged",  # 文件自 Claude 在该会话里上次读取以来没变,所以内容不重复
    "file": {
        "filePath": str,
    },
    "source": "seeded" | None,  # 较早的副本来自启动时加载的 CLAUDE.md 或记忆文件而不是 Read 调用时存在
}

Write

工具名: Write

输入:

{
    "file_path": str,  # 要写入的文件的绝对路径
    "content": str,  # 要写入文件的内容
}

输出:

{
    "type": "create" | "update",  # 写入是创建了新文件还是覆盖了现有文件
    "filePath": str,  # 被写入的文件
    "content": str,  # 被写入的内容
    "structuredPatch": [  # diff 块;新文件、没有变化、或 Claude Code 跳过了 diff 时为空
        {
            "oldStart": int,
            "oldLines": int,
            "newStart": int,
            "newLines": int,
            "lines": list[str],
        }
    ],
    "originalFile": str | None,  # 先前内容;新文件或先前内容太大无法包含时为 None
    "gitDiff": {  # 该文件可选的 git diff 摘要
        "filename": str,
        "status": "modified" | "added",
        "additions": int,
        "deletions": int,
        "changes": int,
        "patch": str,
        "repository": str | None,  # 可用时的 GitHub owner/repo
    } | None,
    "userModified": bool | None,  # 可选;用户是否在接受之前编辑了提议的内容
}

Glob

工具名: Glob

输入:

{
    "pattern": str,  # 用来匹配文件的 glob 模式
    "path": str | None,  # 搜索的目录(默认 cwd)
}

输出:

{
    "durationMs": int,  # 运行搜索花的时间(毫秒)
    "numFiles": int,  # 返回的路径数(截断之后)
    "filenames": list[str],  # 匹配的文件路径
    "truncated": bool,  # 结果是否在 100 个文件的限制处被截断
    "totalMatches": int | None,  # 可选:截断前匹配文件的总数;countIsComplete 为 False 时是下限
    "countIsComplete": bool | None,  # 可选;totalMatches 是否精确
}

totalMatches 和 countIsComplete 需要 Claude Code v2.1.191 及以上。

Grep

工具名: Grep

输入:

{
    "pattern": str,  # 正则表达式模式
    "path": str | None,  # 搜索的文件或目录
    "glob": str | None,  # 过滤文件的 glob 模式
    "type": str | None,  # 要搜索的文件类型
    "output_mode": str | None,  # "content"、"files_with_matches" 或 "count"
    "-i": bool | None,  # 不区分大小写的搜索
    "-n": bool | None,  # 显示行号
    "-B": int | None,  # 每个匹配前显示的行数
    "-A": int | None,  # 每个匹配后显示的行数
    "-C": int | None,  # 前后都显示的行数
    "context": int | None,  # 前后显示的行数;-C 是它的别名
    "-o": bool | None,  # 只打印每行的匹配部分
    "head_limit": int | None,  # 把输出限制在前 N 行/条
    "offset": int | None,  # 在应用 head_limit 之前跳过前 N 行/条
    "multiline": bool | None,  # 启用多行模式
}

输出:

{
    "mode": "content" | "files_with_matches" | "count" | None,  # 使用的输出模式
    "numFiles": int,  # 结果里的文件数;content 模式下总是 0
    "filenames": list[str],  # files_with_matches 模式下匹配的文件;其他模式为空
    "content": str | None,  # content 模式下的匹配行,或 count 模式下每文件的计数
    "numLines": int | None,  # content 里的行数,content 模式下存在
    "numMatches": int | None,  # 匹配总数,count 模式下存在
    "totalFiles": int | None,  # 可选:files_with_matches 模式下 head_limit 和 offset 之前的总数
    "totalLines": int | None,  # 可选:content 模式下 head_limit 和 offset 之前的总数
    "appliedLimit": int | None,  # head_limit 截断了结果时存在
    "appliedOffset": int | None,  # 应用了 offset 时存在
}

Grep 在每种输出模式下都返回这个字典形状,哪些可选键存在取决于 output_mode;totalFiles 需要 Claude Code v2.1.208 及以上,totalLines 需要 v2.1.210 及以上。

NotebookEdit

工具名: NotebookEdit

输入:

{
    "notebook_path": str,  # Jupyter notebook 的绝对路径
    "cell_id": str | None,  # 要编辑的单元格的 ID
    "new_source": str,  # 该单元格的新源码
    "cell_type": "code" | "markdown" | None,  # 单元格类型
    "edit_mode": "replace" | "insert" | "delete" | None,  # 编辑操作类型
}

输出:

{
    "new_source": str,  # 写入单元格的源码
    "old_source": str | None,  # 先前的单元格源码,replace 和 delete 时存在
    "cell_id": str | None,  # 被编辑单元格的 ID(可用时)
    "cell_type": "code" | "markdown",  # 单元格类型
    "language": str,  # notebook 的编程语言
    "edit_mode": str,  # 使用的编辑模式
    "error": str | None,  # 操作失败时的错误消息
    "notebook_path": str,  # notebook 文件
    "original_file": str,  # 编辑之前的 notebook 内容
    "updated_file": str,  # 编辑之后的 notebook 内容
}

WebFetch 与 WebSearch

WebFetch 输入: {"url": str, "prompt": str}(要获取内容的 URL 和对获取内容运行的提示)。输出:

{
    "bytes": int,  # 获取内容的字节数
    "code": int,  # HTTP 响应码
    "codeText": str,  # HTTP 响应码文本
    "result": str,  # 把提示应用到内容上得到的处理结果
    "durationMs": int,  # 获取并处理内容的时间(毫秒)
    "url": str,  # 被获取的 URL
}

WebSearch 输入:

{
    "query": str,  # 要使用的搜索查询
    "allowed_domains": list[str] | None,  # 只包含来自这些域名的结果
    "blocked_domains": list[str] | None,  # 绝不包含来自这些域名的结果
}

输出:

{
    "query": str,  # 搜索查询
    "results": list[str | {"tool_use_id": str, "content": list[{"title": str, "url": str}]}],
    "durationSeconds": float,  # 搜索时长(秒)
}

TodoWrite 与 Task 工具

下列工具默认只在 Claude 3.x 模型、Opus 4 到 4.7、Sonnet 4 到 4.6 和 Haiku 4.5 上可用;在其他每个模型上(包括 Claude Code 不认识的模型 ID),除非选择加入否则不可用:TodoWrite、TaskCreate、TaskGet、TaskUpdate、TaskList。这些工具可用的地方,Claude Code 提供四个 Task 工具,或在你设了 CLAUDE_CODE_ENABLE_TASKS=0 时改为提供 TodoWrite;这个默认集合适用于 Claude Code v2.1.268 及以上。

# TodoWrite 输入
{
    "todos": [
        {
            "content": str,  # 任务描述
            "status": "pending" | "in_progress" | "completed",  # 任务状态
            "activeForm": str,  # 描述的进行时形式
        }
    ]
}

# TodoWrite 输出
{
    "oldTodos": [  # 更新之前的待办列表
        {"content": str, "status": "pending" | "in_progress" | "completed", "activeForm": str}
    ],
    "newTodos": [  # 更新之后的待办列表
        {"content": str, "status": "pending" | "in_progress" | "completed", "activeForm": str}
    ],
}

# TaskCreate 输入 / 输出
{
    "subject": str,  # 简短的任务标题
    "description": str,  # 详细的任务正文
    "activeForm": str | None,  # 进行中时显示的现在时标签
    "metadata": dict | None,  # 任意的调用方元数据
}
{
    "task": {"id": str, "subject": str},  # 带分配 ID 的已创建任务
}

# TaskUpdate 输入 / 输出
{
    "taskId": str,  # 要修补的任务的 ID
    "status": Literal["pending", "in_progress", "completed", "deleted"] | None,
    "subject": str | None,
    "description": str | None,
    "activeForm": str | None,
    "addBlocks": list[str] | None,  # 该任务现在阻塞的任务 ID
    "addBlockedBy": list[str] | None,  # 现在阻塞该任务的任务 ID
    "owner": str | None,
    "metadata": dict | None,
}
{
    "success": bool,
    "taskId": str,
    "updatedFields": list[str],  # 改变了的字段名
    "error": str | None,
    "statusChange": {"from": str, "to": str} | None,
}

# TaskGet 输入 / 输出
{"taskId": str}  # 要读取的任务的 ID
{
    "task": {
        "id": str,
        "subject": str,
        "description": str,
        "status": Literal["pending", "in_progress", "completed"],
        "blocks": list[str],
        "blockedBy": list[str],
    } | None,  # ID 找不到时为 None
}

# TaskList 输入 / 输出
{}
{
    "tasks": [
        {
            "id": str,
            "subject": str,
            "status": Literal["pending", "in_progress", "completed"],
            "owner": str | None,
            "blockedBy": list[str],
        }
    ],
}

TaskOutput 与 TaskStop

TaskOutput 在 Claude Code v2.1.277 中被移除(别名 BashOutput 也一并移除):它之前用于取得运行中或已完成后台任务的输出,现在 Claude 改用 Read 读取后台任务的输出文件;仍然点名这两个名字的 disallowed_tools 条目或拒绝规则会被静默忽略。TaskStop(旧名 KillShell 和 KillBash 仍作为别名接受):

# 输入
{
    "task_id": str | None,  # 要停止的后台任务的 ID
    "shell_id": str | None,  # 已弃用:改用 task_id
}

# 输出
{
    "message": str,  # 关于该操作的状态消息
    "task_id": str,  # 被停止的任务的 ID
    "task_type": str,  # 被停止的任务的类型
    "command": str | None,  # 被停止任务的命令或描述
}

ExitPlanMode

# 输入
{
    "plan": str  # 由用户运行以供批准的计划
}

# 输出
{
    "plan": str | None,  # 呈现给用户的计划
    "isAgent": bool,  # 子智能体调用该工具时为 True
    "filePath": str | None,  # 计划被保存到文件时存在
    "hasTaskTool": bool | None,  # 可选;当前上下文里 Agent 工具是否可用
    "planWasEdited": bool | None,  # 用户在批准之前编辑了计划时存在且为 True
    "awaitingLeaderApproval": bool | None,  # 队友把计划发给团队负责人批准时存在且为 True
    "requestId": str | None,  # 该批准请求的可选 ID
}

ListMcpResources 与 ReadMcpResource

ListMcpResourcesTool 的结果是列表而不是字典,所以该工具的 tool_use_result 持有 list。

# ListMcpResourcesTool 输入
{
    "server": str | None  # 按其过滤资源的可选服务器名
}

# 输出(每个资源一条)
[
    {
        "uri": str,  # 资源 URI
        "name": str,  # 资源名
        "mimeType": str | None,  # 可选的 MIME 类型
        "description": str | None,  # 可选的描述
        "server": str,  # 提供该资源的服务器
    }
]

# ReadMcpResourceTool 输入
{
    "server": str,  # MCP 服务器名
    "uri": str,  # 要读取的资源 URI
}

# 输出
{
    "contents": [
        {
            "uri": str,  # 资源 URI
            "mimeType": str | None,  # 可选的 MIME 类型
            "text": str | None,  # 文本内容,或关于二进制内容的说明
            "blobSavedTo": str | None,  # Claude Code 把二进制内容保存到磁盘时存在;保存文件的路径
        }
    ],
    "error": str | None,  # 服务器无法读取该资源时存在
}

示例:构建持续对话界面

下面的例子让一个 ClaudeSDKClient 跨轮次保持连接,使 Claude 记住先前的消息。键入 new 断开并重新连接以获得全新会话,键入 exit 结束对话。

from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    AssistantMessage,
    TextBlock,
)
import asyncio


class ConversationSession:
    """与 Claude 维持单个对话会话。"""

    def __init__(self, options: ClaudeAgentOptions | None = None):
        self.client = ClaudeSDKClient(options)
        self.turn_count = 0

    async def start(self):
        await self.client.connect()
        print("Starting conversation session. Claude will remember context.")
        print(
            "Commands: 'exit' to quit, 'interrupt' to stop current task, 'new' for new session"
        )

        while True:
            user_input = input(f"\n[Turn {self.turn_count + 1}] You: ")

            if user_input.lower() == "exit":
                break
            elif user_input.lower() == "interrupt":
                await self.client.interrupt()
                print("Task interrupted!")
                continue
            elif user_input.lower() == "new":
                # 断开并重新连接以获得全新会话
                await self.client.disconnect()
                await self.client.connect()
                self.turn_count = 0
                print("Started new conversation session (previous context cleared)")
                continue

            # 发送消息——会话保留所有先前的消息
            await self.client.query(user_input)
            self.turn_count += 1

            # 处理响应
            print(f"[Turn {self.turn_count}] Claude: ", end="")
            async for message in self.client.receive_response():
                if isinstance(message, AssistantMessage):
                    for block in message.content:
                        if isinstance(block, TextBlock):
                            print(block.text, end="")
            print()  # 响应之后换行

        await self.client.disconnect()
        print(f"Conversation ended after {self.turn_count} turns.")


async def main():
    options = ClaudeAgentOptions(
        allowed_tools=["Read", "Write", "Bash"], permission_mode="acceptEdits"
    )
    session = ConversationSession(options)
    await session.start()


asyncio.run(main())

示例:错误处理

下面的例子把一个 query() 调用包进 SDK 抛出的四种错误类型的处理程序里。该例捕获 ResultError,它需要 Python Agent SDK 0.2.140 及以上。

import asyncio
from claude_agent_sdk import (
    query,
    CLINotFoundError,
    ProcessError,
    ResultError,
    CLIJSONDecodeError,
)


async def main():
    try:
        async for message in query(prompt="Hello"):
            print(message)
    except CLINotFoundError:
        print(
            "Claude Code CLI not found. Try reinstalling: pip install --force-reinstall claude-agent-sdk"
        )
    # 要在它所继承的 ProcessError 之前捕获 ResultError。它的消息
    # 携带错误文本。失败的最终请求(如 API 错误)以 subtype "success"
    # 到达,所以要先按 terminal_reason 分支。
    except ResultError as e:
        if e.terminal_reason == "api_error":
            print(f"API request failed: {e}")
        else:
            print(f"Query ended with an error result ({e.terminal_reason or e.subtype}): {e}")
    except ProcessError as e:
        print(f"Process failed with exit code: {e.exit_code}")
    except CLIJSONDecodeError as e:
        print(f"Failed to parse response: {e}")


asyncio.run(main())

沙盒配置

SandboxSettings

沙盒行为的配置。用它以编程方式启用命令沙盒并配置网络限制。

class SandboxSettings(TypedDict, total=False):
    enabled: bool
    autoAllowBashIfSandboxed: bool
    excludedCommands: list[str]
    allowUnsandboxedCommands: bool
    network: SandboxNetworkConfig
    ignoreViolations: SandboxIgnoreViolations
    enableWeakerNestedSandbox: bool
属性类型默认说明
enabledboolFalse为命令执行启用沙盒模式
autoAllowBashIfSandboxedboolTrue沙盒启用时自动批准 bash 命令
excludedCommandslist[str][]绕过沙盒限制的命令,如 ["docker *"];这些命令无需模型参与自动不带沙盒运行
allowUnsandboxedCommandsboolTrue允许模型请求在沙盒之外运行命令;为 True 时模型可以在工具输入里设 dangerouslyDisableSandbox,这会回落到权限系统
networkSandboxNetworkConfigNone沙盒的网络配置
ignoreViolationsSandboxIgnoreViolationsNone配置要忽略哪些沙盒违规
enableWeakerNestedSandboxboolFalse为兼容性启用较弱的嵌套沙盒

注意:沙盒依赖平台支持,在 Linux 上还依赖 bubblewrap 和 socat 这类工具。默认情况下,enabled 为 True 但沙盒无法启动时,命令不带沙盒运行,并在 stderr 上给出警告;这个默认值与 TypeScript SDK 不同,那里 failIfUnavailable 默认为 true。要改为停止,在沙盒设置里设 "failIfUnavailable": True:该键尚未在 SandboxSettings 上声明,但 SDK 会把它转发给 Claude Code,后者会遵从;此时 query() 报告 subtype="error_during_execution" 的 ResultMessage,原因在 errors 里;因为这是单次 query() 调用,SDK 在产出该错误结果之后抛出异常。

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

sandbox_settings = {
    "enabled": True,
    "autoAllowBashIfSandboxed": True,
    "failIfUnavailable": True,
    "network": {"allowLocalBinding": True},
}


async def main():
    try:
        async for message in query(
            prompt="Build and test my project",
            options=ClaudeAgentOptions(sandbox=sandbox_settings),
        ):
            print(message)
    except Exception as error:
        # 单次 query() 在产出错误结果之后抛出,
        # 例如设了 failIfUnavailable 而沙盒无法启动时。
        print(f"Session ended with an error: {error}")


asyncio.run(main())

Unix socket 安全:allowUnixSockets 选项可以授予对延伸到沙盒之外的系统服务的访问。例如允许 /var/run/docker.sock 实际上通过 Docker API 授予完全的宿主系统访问,绕过沙盒隔离。只允许严格必需的 Unix socket,并理解每个的安全含义。

SandboxNetworkConfig

沙盒模式的网络配置。当父级 SandboxSettings 里 enabled 为 True 时,这些设置适用于沙盒里的 Bash 命令;它们不限制 WebFetch 工具,后者改用权限规则。

class SandboxNetworkConfig(TypedDict, total=False):
    allowedDomains: list[str]
    deniedDomains: list[str]
    allowManagedDomainsOnly: bool
    allowUnixSockets: list[str]
    allowAllUnixSockets: bool
    allowLocalBinding: bool
    allowMachLookup: list[str]
    httpProxyPort: int
    socksProxyPort: int
属性类型默认说明
allowedDomainslist[str][]沙盒进程可访问的域名
deniedDomainslist[str][]沙盒进程不可访问的域名,优先于 allowedDomains
allowManagedDomainsOnlyboolFalse仅限托管设置:在托管设置里设置时,忽略来自非托管设置来源的 allowedDomains 和 WebFetch(domain:...) 允许规则;经 SDK 选项设置时无效
allowUnixSocketslist[str][]仅限 macOS:进程可访问的 Unix socket 路径,如 Docker socket;在 Linux 上被忽略
allowAllUnixSocketsboolFalse允许访问所有 Unix socket
allowLocalBindingboolFalse允许进程绑定本地端口(例如用于开发服务器)
allowMachLookuplist[str][]仅限 macOS:允许的 XPC/Mach 服务名,支持结尾通配符
httpProxyPortintNone网络请求的 HTTP 代理端口
socksProxyPortintNone网络请求的 SOCKS 代理端口

注意:内置的沙盒代理按请求的主机名强制网络允许列表,不终止也不检查 TLS 流量,所以域前置之类的技术可能绕过它(细节见沙盒安全限制;配置终止 TLS 的代理见「安全部署」)。

SandboxIgnoreViolations

忽略特定沙盒违规的配置。

class SandboxIgnoreViolations(TypedDict, total=False):
    file: list[str]
    network: list[str]

file(list[str],默认 [])是要忽略违规的文件路径模式;network(list[str],默认 [])是要忽略违规的网络模式。

对不带沙盒命令的权限回退

启用 allowUnsandboxedCommands 时,模型可以通过在工具输入里设 dangerouslyDisableSandbox: True 请求在沙盒之外运行命令。这些请求回落到现有的权限系统,也就是会调用你的 can_use_tool 处理程序,让你能实现自定义的授权逻辑。你的 excludedCommands 条目则无需模型参与就把调用移出沙盒。下面的例子记录每个不带沙盒的请求,并拒绝它,除非你自己的授权逻辑允许:

import asyncio
from claude_agent_sdk import (
    query,
    ClaudeAgentOptions,
    HookMatcher,
    PermissionResultAllow,
    PermissionResultDeny,
    ToolPermissionContext,
)


def is_command_authorized(command: str | None) -> bool:
    # 换成你自己的授权逻辑
    return False


async def can_use_tool(
    tool: str, input: dict, context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
    # 检查模型是否在请求绕过沙盒
    if tool == "Bash" and input.get("dangerouslyDisableSandbox"):
        # 模型请求在沙盒之外运行这条命令
        print(f"Unsandboxed command requested: {input.get('command')}")

        if is_command_authorized(input.get("command")):
            return PermissionResultAllow()
        return PermissionResultDeny(
            message="Command not authorized for unsandboxed execution"
        )
    return PermissionResultAllow()


# 必需:用一个假 hook 让流为 can_use_tool 保持打开
async def dummy_hook(input_data, tool_use_id, context):
    return {"continue_": True}


async def prompt_stream():
    yield {
        "type": "user",
        "message": {"role": "user", "content": "Deploy my application"},
    }


async def main():
    async for message in query(
        prompt=prompt_stream(),
        options=ClaudeAgentOptions(
            sandbox={
                "enabled": True,
                "allowUnsandboxedCommands": True,  # 模型可以请求不带沙盒执行
            },
            permission_mode="default",
            can_use_tool=can_use_tool,
            hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
        ),
    ):
        print(message)


asyncio.run(main())

注意:带 dangerouslyDisableSandbox: True 运行的命令拥有完整的系统访问,要确保你的 can_use_tool 处理程序仔细验证这些请求。如果 permission_mode 设为 bypassPermissions 且启用了 allowUnsandboxedCommands,模型可以不经批准提示自主执行沙盒之外的命令(没有任何模式自动批准的动作除外),这种组合实际上让模型静默逃出沙盒隔离。

另见

  • SDK 概览:通用的 SDK 概念
  • TypeScript SDK 参考:TypeScript SDK 文档
  • 自定义工具:定义供 Claude 调用的进程内 MCP 工具
  • CLI 参考:命令行界面
  • 常见工作流:逐步指南