Claude Platform Docs
Messages工具

使用 SDK 工具集进行浏览器和计算机使用

从 Python 或 TypeScript SDK 运行浏览器使用工具或计算机使用工具。SDK 负责运行循环和您配置的检查,您负责提供浏览器或桌面。

借助 Python 和 TypeScript SDK 中的浏览器和计算机 "toolset"(工具集),您可以构建在 Web 浏览器或桌面上工作的智能体。它可以使用没有编程接口的应用程序。通过浏览器使用工具,Claude 可以导航、阅读网页并与之交互。通过计算机使用工具,Claude 通过截图查看桌面,并使用鼠标和键盘。

SDK 将 Claude 连接到您的浏览器或桌面。它会运行您配置的检查,例如针对 navigate 的 URL 策略或批准回调。它将每个通过检查的调用发送给您的 "driver"(驱动程序),并根据驱动程序返回的内容构建 tool_result。工具运行器可以为您运行智能体循环。

您需要提供驱动程序,由它来操作浏览器或桌面。可以从提供商的驱动程序开始。使用现成的驱动程序,只需几行代码即可构建智能体。如果没有合适的,可以使用 Playwright 等浏览器自动化库编写您自己的驱动程序。

本页大部分内容介绍浏览器工具集。计算机工具集介绍两者的不同之处。

编写您自己的驱动程序属于高级用法,详见实现驱动程序。claude-quickstarts 仓库中还提供了 Python 和 TypeScript 版本的浏览器驱动程序示例。它通过 "Chrome DevTools Protocol"(Chrome 开发者工具协议),即 CDP 控制 Chromium,并且不是生产代码。

使用提供商的驱动程序

借助提供商的驱动程序(例如合作伙伴集成中列出的驱动程序),您只需几行代码即可构建智能体。本示例中的提供商是虚构的。请将 example_browser_provider 和 ExampleBrowser 替换为真实提供商的包和驱动程序类,并按照该提供商文档的说明进行设置。

from anthropic import Anthropic
from example_browser_provider import ExampleBrowser  # made-up provider

client = Anthropic()
with ExampleBrowser() as browser:
    runner = client.beta.messages.tool_runner(
        model="claude-opus-5-5",
        max_tokens=1024,
        tools=[browser],
        messages=[
            {"role": "user", "content": "Open example.com and tell me its heading."}
        ],
    )
    for message in runner:
        print(message)

将驱动程序实例本身作为 tools 条目传入。运行器从不关闭工具集,因此一个实例可以服务多次运行。使用完毕后请将其关闭。

要编写您自己的驱动程序,请参阅实现驱动程序。

合作伙伴集成

Browser Use、Browserbase、Daytona 和 E2B 为 SDK 工具集发布了各自的驱动程序。

适用于浏览器工具集的 Browser Use 驱动程序。

适用于浏览器工具集的 Browserbase Stagehand 驱动程序。

适用于计算机工具集的 Daytona 驱动程序。

E2B

适用于浏览器和计算机工具集的 E2B 驱动程序。

我们的合作伙伴还在 GitHub 上发布了代码:Browser Use 快速入门、Browserbase 工具集和 E2B 示例。

自定义驱动程序

本节中的示例使用 MyBrowser,即本页后面高级示例中的驱动程序。

启用或禁用成员

configs 接受配置工具集中描述的各成员设置。只需列出您要更改的成员:

# 同时实现了 read_console 的 MyBrowser
browser = MyBrowser(
    backend, configs={"read_console": {"enabled": True}, "navigate": {"enabled": False}}
)

SDK 会在您的代码运行之前拒绝对已禁用成员的调用。启用您的类未实现的成员属于配置错误,除非该类重写了 execute。

添加前置和后置钩子

重写 execute 并调用父类的 execute。该调用之前的代码在 URL 策略、文件策略和 confirm 之后运行,并且可以修改输入。SDK 不会再次检查修改后的输入。该调用之后的代码会接收结果,并且可以修改结果。抛出 ToolError(在 TypeScript 中使用 throw)即可拒绝该调用。

重写 execute 会改变向 Claude 提供哪些成员。SDK 会将每个成员都视为已实现,因此 Claude 会获得所有默认开启的成员。高级示例中的 MyBrowser 只支持三个成员,因此下面的 TracedBrowser 会向 Claude 提供它无法支持的成员。使用之前,请通过 configs 关闭这些成员。

import time


class TracedBrowser(MyBrowser):
    def execute(self, context, name, input):
        started = time.monotonic()
        result = super().execute(context, name, input)
        elapsed_ms = (time.monotonic() - started) * 1000
        call_id = context.tool_use.id if context.tool_use else "-"
        log.info("%s %s %.0fms", call_id, name, elapsed_ms)
        return redact(result) if name == "get_page_text" else result

实现驱动程序

驱动程序是您编写的 BetaAbstractBrowserToolset20260801 子类。在其中重写您的浏览器所支持的成员。您未实现的成员会以禁用状态发送给 API。如果 Claude 仍然调用它,SDK 会返回错误,运行继续进行。重写 execute 会改变哪些成员以禁用状态发送(添加前置和后置钩子)。每个成员都会接收调用上下文以及该成员的输入(一个类型化对象,例如 BetaBrowserNavigateInput)。输入类型来自 anthropic.types.beta。成员工具列出了每种输入的字段。

在 TypeScript 中,请将成员编写为方法,而不是箭头函数字段,因为 SDK 是在原型上查找它们的。在 TypeScript 中,type 成员应写作 type_。在 Python 中则为 type。

返回结果

成员返回的内容决定了 Claude 读取的内容。成功的结果以根据您的状态报告构建的 browser_state 块结尾。错误结果不带该块。

成员返回Claude 读取的内容
screenshot、zoomBetaScreenshotResult一个图像块
navigateBetaBrowserNavigateResultNavigated to {url} — {title} (HTTP {status})
new_tab、switch_tab、list_tabs、close_tab一个标签页条目(new_tab、switch_tab)、一个标签页条目列表(list_tabs),或不返回任何内容(close_tab)仅 browser_state 块
read_page、get_page_text、find、read_console、read_network、javascript_exec一个字符串该字符串;如果是空字符串,则为 (empty)
其他所有成员不返回任何内容,或一行文本一条简短的确认信息(例如 Clicked.),然后是位于单独文本块中的返回行

报告浏览器状态

SDK 会在每个返回结果的调用之后调用 _browser_state,包括被拒绝和失败的调用。返回每个打开的标签页,以及自上次报告以来发生的变化:

  • 已打开的标签页和下载事件。
  • 针对您的请求钩子阻止的每次导航,返回一个 BetaNavigationRefused。
  • 针对您的驱动程序关闭的每个原生对话框,返回一个 BetaDialogDismissed。

将所有这些放入 state_changes。在 Python 中,BetaNavigationRefused() 和 BetaDialogDismissed(kind=..., message=...) 来自 anthropic.tools.browser。在 TypeScript 中,它们是 { type: "navigation_refused" } 和 { type: "dialog_dismissed", kind, message }。

后两者不是 API 状态变化。SDK 会以 browser_state 块之外的文本形式将它们报告给 Claude:所有被拒绝的导航合并为一行(不包含 URL),前三个被关闭的对话框各占一行,其余的则给出数量。

当有任何标签页打开时,必须恰好有一个处于活动状态。每个接受 tab_id 的成员都必须作用于它所指定的标签页。API 对报告的限制列在使用 browser_state 跟踪标签页中。

Claude 会按照您的驱动程序写入的原样读取每个标签页的 URL、每个下载的 URL,以及您的 navigate 返回的 URL。SDK 不会解析这些 URL。它会将换行符和其他控制字符转换为空格,去除首尾空白,并将每个 URL 截断为 4,096 个字符。因此,data: URL 的内容、file: URL 的路径,以及 URL 中的用户名或密码都会传达给 Claude。

处理错误

由成员或 SDK 抛出Claude 读取到的内容运行
ToolError其消息,作为错误结果继续
ToolsetUsageError无停止
任何其他异常Python 中为 ClassName: message,TypeScript 中为 Error: message,作为错误结果继续

SDK 会在出现配置错误、调用期间误用 SDK,或在 close 之后进行调用时抛出 ToolsetUsageError。当 _browser_state 抛出任何异常(即使是 ToolError)时,它也会抛出该错误。工具运行器和 tool_result 不会捕获它,因此它会传递到它们的调用方。

SDK 不会隐藏以下内容中的本地路径:成员的错误文本、left_click 等操作返回的行、失败下载的错误,以及被关闭对话框的消息。

来自您的 URL 策略、文件策略或 confirm 可调用对象的 ToolError 会按原样传达给 Claude,因此请不要在其文本中包含本地路径。请在您的成员中捕获异常,并使用您自己的文本抛出 ToolError(在 TypeScript 中使用 throw)。

在响应流式传输时启动调用

默认情况下,工具运行器会在 Claude 的响应结束后运行该轮次的调用。启用提前启动后,它可以在响应仍在流式传输时启动浏览器或计算机调用。

要启用此功能,请向工具运行器传入 stream=True 和 run_tools_eagerly=True,如高级示例所示。使用 stream 时,您对运行器的循环每次迭代得到的是一个流,而不是一条消息。

提前启动改变的是调用何时可以开始,而不是工具集如何运行它:

  • 检查仍然先运行: SDK 会在调用运行之前调用您的 URL 策略、文件策略和 confirm。启用提前启动后,它可能会在响应仍在流式传输时调用 confirm。
  • 调用仍然逐个运行: 工具集按照 Claude 编写的顺序运行其调用。失败的调用仍会阻止该工具集在本轮次中的后续调用。
  • 已开始的调用无法撤回: 如果响应随后被截断(例如在 max_tokens 处),或者您的循环提前停止,该操作仍会发生,而 Claude 永远不会读取到其结果。

不使用工具运行器运行

在 tools 中传入实例(在 TypeScript 中为 browser.toJSON()),并使用 tool_result 响应每个成员调用。浏览器使用工具要求您在第一个失败的调用处停止(批量操作)。

此循环使用高级示例中的 MyBrowser 和 URL 策略。在某个调用失败后,它会响应该轮次的后续调用而不运行它们:

from anthropic.types.beta import BetaMessageParam, BetaToolResultBlockParam

NOT_EXECUTED = "Not executed: an earlier action in this turn failed."
MAX_TURNS = 10

with MyBrowser(backend, url_policy=url_policy) as browser:
    messages: list[BetaMessageParam] = [{"role": "user", "content": "Open example.com"}]
    for _ in range(MAX_TURNS):
        response = client.beta.messages.create(
            model="claude-opus-5-5",
            max_tokens=1024,
            tools=[browser],
            messages=messages,
        )
        messages.append({"role": "assistant", "content": response.content})
        calls = [
            block
            for block in response.content
            if block.type == "tool_use" and block.toolset_name == browser.toolset_name
        ]
        if not calls:
            break
        results: list[BetaToolResultBlockParam] = []
        failed = False
        for call in calls:
            if failed:
                # 某次调用失败后,本轮其余调用只返回应答,不再实际执行。
                results.append(
                    {
                        "type": "tool_result",
                        "tool_use_id": call.id,
                        "toolset_name": call.toolset_name,
                        "content": NOT_EXECUTED,
                        "is_error": True,
                    }
                )
                continue
            result = browser.tool_result(call)
            failed = bool(result.get("is_error"))
            results.append(result)
        messages.append({"role": "user", "content": results})

对每个被跳过调用的响应都带有 is_error、该调用的 toolset_name,以及批量操作所要求的确切文本。工具运行器会发送相同的响应。

高级示例:编写您自己的驱动程序

此示例将实现驱动程序中的主要部分整合到一个驱动程序 MyBrowser 中,并使用工具运行器和提前启动来运行它。MyBrowser 实现了 navigate、screenshot 和 left_click,以及每个驱动程序都需要的状态报告 _browser_state。

backend 代表您自己对浏览器自动化库(例如 Playwright)的封装。URL 策略只是一个示例。编写 URL 策略对其进行了说明。

import re
from urllib.parse import urlsplit

from anthropic import Anthropic
from anthropic.tools import ToolError
from anthropic.tools.browser import (
    BetaAbstractBrowserToolset20260801,
    BetaBrowserNavigateResult,
    BetaBrowserState,
    BetaScreenshotResult,
    BetaToolsetCallContext,
    BetaURLContext,
)
from anthropic.types.beta import (
    BetaBrowserLeftClickInput,
    BetaBrowserNavigateInput,
    BetaBrowserScreenshotInput,
    BetaBrowserStateTabEntryParam,
)


class MyBrowser(BetaAbstractBrowserToolset20260801):
    def __init__(self, backend, **options):
        super().__init__(**options)
        self.backend = backend

    def _browser_state(self, context: BetaToolsetCallContext) -> BetaBrowserState:
        return BetaBrowserState(
            tabs=[
                BetaBrowserStateTabEntryParam(
                    tab_id=tab.id,
                    title=tab.title,
                    url=tab.url,
                    active=tab.id == self.backend.active,
                )
                for tab in self.backend.tabs()
            ],
            state_changes=self.backend.drain_changes(),
        )

    def navigate(
        self, context: BetaToolsetCallContext, input: BetaBrowserNavigateInput
    ) -> BetaBrowserNavigateResult:
        # input.url 为 "back"、"forward"、"reload",或是您的 URL 策略
        # 已允许的 URL(保持 Claude 所写的原样)。与 is_allowed 一样,backend.goto 会为
        # 不以协议开头的 URL 添加 https://。
        page = self.backend.goto(input.url, input.tab_id)
        return BetaBrowserNavigateResult(
            url=page.url, status=page.status, title=page.title
        )

    def screenshot(
        self, context: BetaToolsetCallContext, input: BetaBrowserScreenshotInput
    ) -> BetaScreenshotResult:
        data = self.backend.png_base64(input.tab_id)
        return BetaScreenshotResult(data=data, media_type="image/png")

    def left_click(
        self, context: BetaToolsetCallContext, input: BetaBrowserLeftClickInput
    ) -> None:
        # 无需返回内容:Claude 读取到的是 "Clicked."
        self.backend.click(input.target, input.tab_id)

    def close(self) -> None:
        super().close()  # first, so no call is still using the browser when it closes
        if not self.backend.closed:
            self.backend.close()


ALLOWED_HOSTS = ("example.com", "iana.org")
SCHEME_PREFIX = re.compile(r"[a-z][a-z0-9+.-]*:", re.IGNORECASE)


def is_allowed(url: str) -> bool:
    # 仅为示例,并非生产环境策略。请参阅 "Write a URL policy"。
    if url.lower() == "about:blank":
        return True
    with_scheme = url if SCHEME_PREFIX.match(url) else f"https://{url}"
    # 在 Web URL 中,浏览器会将 "\" 视为 "/"。
    try:
        parts = urlsplit(with_scheme.replace("\\", "/"))
    except ValueError:
        return False
    host = parts.hostname or ""
    listed = any(host == name or host.endswith(f".{name}") for name in ALLOWED_HOSTS)
    return parts.scheme in ("http", "https") and listed


def url_policy(context: BetaURLContext, url: str) -> None:
    if not is_allowed(url):
        raise ToolError(f"blocked: {url} is not on an allowed host")


client = Anthropic()
with MyBrowser(backend, url_policy=url_policy) as browser:
    runner = client.beta.messages.tool_runner(
        model="claude-opus-5-5",
        max_tokens=1024,
        tools=[browser],
        messages=[
            {
                "role": "user",
                "content": "Open example.com and tell me the page heading.",
            }
        ],
        stream=True,
        run_tools_eagerly=True,  # so a call can start before the response ends
    )
    for stream in runner:
        print(stream.get_final_message())

安全地运行工具集

Claude 的下一步操作取决于它读取的页面。页面或注入页面的文本可能会尝试访问内部服务或从主机上提取文件,也可能尝试触发具有实际影响的操作。在针对一次性浏览器以外的任何浏览器运行驱动程序之前,请采取以下六个步骤:

  1. 编写 URL 策略,拒绝任务不需要的每个 navigate URL。
  2. 在驱动程序中拦截请求,并使用相同的规则检查每个请求。
  3. 在容器上设置出站策略,让网络阻止驱动程序无法看到的内容。
  4. 限制上传和下载,或保持上传关闭。
  5. 使用 confirm 对有重大影响的成员进行把关。
  6. 为每个会话在专用容器或虚拟机中隔离浏览器主机。

SDK 通过您传入的策略和可调用对象来执行第 1、4 和 5 步。第 2、3 和 6 步由您的驱动程序和部署负责。安全注意事项中的预防措施同样适用。

编写 URL 策略

URL 策略是一个您作为 url_policy 传入的函数。策略由您编写,您负责使其达到生产就绪状态。在您的 navigate 运行之前,SDK 会使用调用的上下文和 Claude 编写的原始 URL 调用该策略。

不返回任何内容即表示允许该 URL。要拒绝它,请抛出 ToolError(在 TypeScript 中使用 throw)。Claude 会读取该错误的消息,且 navigate 不会运行。该策略只检查每个 navigate 调用中的 URL。它不是网络控制手段。

以下是高级示例中的策略。它允许 about:blank,以及通过 http 和 https 访问的两个站点及其子域名:

import re
from urllib.parse import urlsplit

from anthropic.tools import ToolError
from anthropic.tools.browser import BetaURLContext

ALLOWED_HOSTS = ("example.com", "iana.org")
SCHEME_PREFIX = re.compile(r"[a-z][a-z0-9+.-]*:", re.IGNORECASE)


def is_allowed(url: str) -> bool:
    # 仅为示例,并非生产环境策略。
    if url.lower() == "about:blank":
        return True
    with_scheme = url if SCHEME_PREFIX.match(url) else f"https://{url}"
    # 在 Web URL 中,浏览器会将 "\" 视为 "/"。
    try:
        parts = urlsplit(with_scheme.replace("\\", "/"))
    except ValueError:
        return False
    host = parts.hostname or ""
    listed = any(host == name or host.endswith(f".{name}") for name in ALLOWED_HOSTS)
    return parts.scheme in ("http", "https") and listed


def url_policy(context: BetaURLContext, url: str) -> None:
    if not is_allowed(url):
        raise ToolError(f"blocked: {url} is not on an allowed host")


browser = MyBrowser(backend, url_policy=url_policy)

SDK 只对 navigate 调用该策略,而不会对 "back"、"forward" 或 "reload" 调用。Claude 可能会省略协议,例如 example.com,因此在读取主机之前,示例会为不以协议开头的 URL 添加 https://。您的 navigate 收到的是 Claude 编写的原始 URL。请在那里使用相同的判断添加协议,以便浏览器打开的正是您的策略检查过的 URL。

该示例并非生产级别。生产策略需要更多内容,涉及以下方面:

  • Claude 点击的链接、提交的表单、重定向,以及页面自行发出的请求。只有您的驱动程序能看到它们。请参阅在驱动程序中拦截请求。
  • 来自页面内容的主机名和 URL,攻击者可以选择这些内容。
  • 以浏览器的方式解析每个 URL,而不是通过简单的字符串分割来查找主机名。
  • URL 协议。SDK 不会检查它们,因此请拒绝您的驱动程序不打算打开的所有协议。
  • 容器上的出站规则,用于处理任何 URL 检查都看不到的内容。请参阅在容器上设置出站策略。

如果您传入 url_policy=None,SDK 会拒绝每个导航到 URL 的 navigate,因此从您的配置中读取到的 None 不会意外地让 URL 不受检查。在 TypeScript 中,undefined 会使该选项保持未设置状态,与不传入它相同。

在驱动程序中拦截请求

URL 策略只判断 navigate 调用中的 URL。Claude 点击的链接、提交的表单、重定向,或允许页面上的图像或脚本,仍然可能访问策略会拒绝的主机。请使用出站规则阻止这些主机。

支持请求拦截的驱动程序还可以在其请求钩子中检查这些请求,请求钩子是您的自动化库在每个请求之前调用的函数。调用您的策略所使用的函数,这样规则只需编写一次:

class MyBrowser(BetaAbstractBrowserToolset20260801):
    ...

    # 通过 context.route("**/*", self._guard) 注册到 Playwright 浏览器上下文
    def _guard(self, route):
        # is_allowed 是“编写 URL 策略”一节中的函数
        if not is_allowed(route.request.url):
            return route.abort("blockedbyclient")
        route.continue_()

像这样的请求钩子看不到 WebSocket 握手、service worker 请求或重定向跳转。请阻止 service worker。使用能够看到 WebSocket 握手和重定向跳转的钩子来检查它们。示例策略会拒绝 ws:// 和 wss:// URL,因此在将 WebSocket URL 传给 is_allowed 之前,请将 ws:// 改为 http://,将 wss:// 改为 https://。

在容器上设置出站策略

拦截无法看到浏览器发出的每个请求,而 URL 策略看不到名称解析到的位置。由容器网络强制执行的 "egress policy"(出站策略)可以同时覆盖这两种情况:

  • 阻止 IPv4 和 IPv6 中的链路本地地址段和私有地址段。这包括云元数据地址 169.254.169.254。
  • 只允许到任务所需主机的出站连接。如果您的规则匹配 IP 地址,请在容器启动时解析您允许的主机名。
  • 只允许向容器的解析器发送 DNS 请求。

在浏览器容器外部执行的规则(例如 Kubernetes NetworkPolicy 或云防火墙)看不到容器内部的回环地址。如果只有这些规则,页面就可以访问在那里监听的任何服务,包括 DevTools 端口,该端口会显示浏览器已打开标签页的列表以及控制每个标签页的地址。

限制上传和下载

file_upload 默认处于关闭状态。如果没有 file_policy,SDK 会拒绝每个提供路径或文档 ID 的上传。要启用上传,请传入一个 BetaLocalFilePolicy,并指定一个只包含任务文件的上传目录:

from anthropic.tools.browser import BetaLocalFilePolicy

# 同时实现了 file_upload 的 MyBrowser
browser = MyBrowser(
    backend,
    configs={"file_upload": {"enabled": True}},
    confirm=make_confirm(),  # required for file_upload; see Gate consequential members
    url_policy=url_policy,
    file_policy=BetaLocalFilePolicy(
        upload_roots=["/task/uploads"],
        download_dir="/task/downloads",
        expose_download_paths=False,
    ),
)

SDK 会解析每个上传路径(跟随符号链接),并拒绝上传根目录之外的任何路径。如果下载目录是某个上传根目录、位于某个上传根目录内部,或包含某个上传根目录,文件策略会抛出配置错误。该检查可能会遗漏符号链接,因此不要让符号链接使下载目录与上传根目录相同,或使其中一个位于另一个内部。只有当 expose_download_paths 为 true 且文件位于下载目录内时,SDK 才会在状态报告中包含下载的 path。

随附的文件策略检查的是运行 SDK 的进程所在文件系统上的路径。它只保护与该文件系统共享的浏览器。对于远程浏览器,请改为遵循远程和托管浏览器。

请按以下方式设置下载:

  • 自行创建模式为 0700 的下载目录,并以 noexec,nosuid,nodev 方式挂载。
  • 确保 Claude 可以调用的其他工具(例如 shell 或文件工具)无法访问该目录。
  • 在 download_failed 状态变更中,将 error 写为固定短语。异常的文本可能包含路径或 URL。
  • 在有人做出决定之前,不要将下载的文件读入对话,也不要运行它。

对有重大影响的成员进行把关

javascript_exec 和 file_upload 默认关闭。如果您在没有 confirm 可调用对象的情况下启用其中任何一个,构造函数会抛出配置错误。有 confirm 可调用对象时,SDK 会在每个即将运行的调用之前调用它。没有时,则不会询问任何内容。在 TypeScript 中,confirm: null 不会引发配置错误,而 SDK 会拒绝对任何成员的每个调用。

返回 True 以运行该调用,或返回 False 以拒绝它。当您的可调用对象询问某个人时,请向其展示成员、页面的 URL 以及调用的输入。首先,对 URL 和输入中所有可打印 ASCII 之外的字符进行转义,因为两者都可能携带来自页面的文本。

此示例通过您自己的 ask_user 函数询问两个受把关的成员,并批准其余成员:

import json
from collections.abc import Callable
from urllib.parse import urlsplit

from anthropic.tools.browser import BetaConfirmContext

GATED = {"javascript_exec", "file_upload"}


def shown(value: object) -> str:
    """A value as JSON, with every character outside printable ASCII escaped."""
    return json.dumps(value, ensure_ascii=True, indent=2)


def make_confirm() -> Callable[[BetaConfirmContext], bool]:
    granted: set[tuple[str, str, str]] = set()

    def confirm(context: BetaConfirmContext) -> bool:
        name = context.member
        if name not in GATED:
            return True
        detail = shown(context.input.to_dict())
        page = context.tab_url
        where = shown(page) if page else "a tab with no URL"
        question = f"Allow {name} on {where}?\n{detail}"
        if page is None or urlsplit(page).scheme not in ("http", "https"):
            return ask_user(question)  # no web origin: ask every time
        # 一次批准仅适用于此页面上的这一确切输入。
        key = (name, page, detail)
        if key not in granted and ask_user(question):
            granted.add(key)
        return key in granted

    return confirm


# 同时实现了 javascript_exec 和 file_upload 的 MyBrowser
# file_upload 还需要“限制上传和下载”中的 file_policy
browser = MyBrowser(
    backend,
    configs={"javascript_exec": {"enabled": True}, "file_upload": {"enabled": True}},
    confirm=make_confirm(),
    url_policy=url_policy,
)

每次调用 make_confirm() 都会返回一个没有任何批准记录的可调用对象。为每个工具集调用一次,并为每个用户提供各自的工具集。

批准针对的是上一次状态报告所显示的页面,而页面可能在调用运行之前发生变化。购买、发送消息和接受条款都是通过 left_click 和 type 等普通成员进行的,因此 confirm 无法按名称将它们挑出来。要让人来批准这些操作,请同样询问这些成员。

除非出站规则将浏览器的出站连接限制在任务所需的主机上,否则不要启用 javascript_exec。脚本可以将页面内容发送到浏览器能够访问的任何地址。

navigate 调用中的 javascript: URL 也会在当前页面中运行脚本,即使 javascript_exec 处于关闭状态。confirm 将该调用视为 navigate,因此示例中的 confirm 可调用对象会批准它。您的驱动程序必须拒绝 javascript: URL。在示例中,URL 策略会在 confirm 运行之前拒绝它们。

隔离浏览器主机

为每个会话在专用的、最小权限的容器或虚拟机中运行浏览器:

  • 以非 root 用户身份运行,并在浏览器允许的情况下使用只读根文件系统。
  • 除了您配置的上传和下载目录(如果有)之外,不要从主机挂载任何内容。
  • 不要在环境中放置凭据,并从全新的浏览器配置文件开始。
  • 不与 Claude 可以调用的其他工具共享任何文件系统。

在浏览器容器之外运行调用 API 的代码,因为该代码持有您的 API 密钥和对话内容。工具运行器和 tool_result 都在该代码的进程中运行工具集,因此浏览器不与工具集共享文件系统。请将浏览器视为远程浏览器:远程和托管浏览器适用。

将页面返回的所有内容都视为不可信内容,包括页面文本、屏幕截图、控制台和网络条目、标签页标题以及下载名称。

远程和托管浏览器

有些浏览器不与运行 SDK 的进程共享文件系统。例如,位于另一个容器中的浏览器、通过 DevTools URL 访问的浏览器,以及来自托管浏览器服务的浏览器。对于这些浏览器,URL 策略、请求拦截和 confirm 仍在您的进程中运行。

对于托管浏览器,由提供商控制出站流量和主机隔离。您自己的出站策略在那里不适用,因此驱动程序的请求钩子是您对浏览器请求的唯一检查。请了解浏览器的网络可以访问哪些内容。

随附的路径检查无法保护远程浏览器。BetaLocalFilePolicy 检查的是运行 SDK 的进程所在文件系统上的路径,而浏览器读写的是它自己的文件系统。

SDK 无法检测浏览器是否为远程浏览器。因此对于远程浏览器,请保持 file_upload 关闭,除非您的驱动程序使用自己的 BetaFilePolicy 在浏览器运行的位置检查上传路径。BetaFilePolicy 会审查每个上传的路径和文档 ID,并决定 Claude 是否能看到下载的路径。

让远程浏览器拒绝下载,除非其自身主机具有限制上传和下载中所述的下载设置。

不要让提供商的 API 密钥和会话的连接 URL(其中可能包含密钥)出现在日志、工具结果和错误文本中。如果提供商会录制会话,则录制内容是 Claude 所见和所输入的一切内容的另一份副本,并且适用提供商的保留条款。

计算机工具集

BetaAbstractComputerToolset20260801 是用于计算机使用工具的类。驱动程序是一个子类,每个工具对应一个方法,例如 screenshot 或 left_click。您可以从合作伙伴集成下的合作伙伴驱动程序入手。如果要自己编写,claude-quickstarts 仓库中有一个使用 Python 和 TypeScript 编写的最小 VNC 示例。它通过 VNC 控制桌面,并非生产代码。

该类接受 configs、confirm 和 tool_configs,传入方式与浏览器类相同。自定义驱动程序、处理错误、在响应流式传输时启动调用和不使用工具运行器运行同样适用于它,但有以下不同之处:

  • 仅限浏览器的选项: 该类没有 URL 策略、文件策略和状态报告。
  • configs: 您实现的每个工具默认都是开启的。各工具的设置列在工具参数中。
  • confirm: 如果您的类实现了 type、key 或 hold_key,构造函数会抛出配置错误,除非您传入 confirm 可调用对象或通过 configs 关闭这些工具。重写了 execute 的类被视为实现了全部三个工具。
  • 被跳过的调用: 在计算机调用失败后,工具运行器会用 Not executed: an earlier computer action in this turn failed. 响应该轮次的后续计算机调用。浏览器调用得到的是不同的文本。在您自己编写的循环中,请使用该确切文本响应被跳过的计算机调用(批量操作)。
  • execute: 在 TypeScript 中,execute 重写将 name 的类型定为 BetaComputerMemberName,将 input 的类型定为 BetaComputerMemberInput。两者都来自 @anthropic-ai/sdk/resources/beta。
  • 错误文本: SDK 不会隐藏计算机工具的错误文本或工具返回的文本中的本地路径。Claude 会读取这两者,截断为 4,096 个字符。请在您的工具中捕获异常,并使用您自己的文本抛出 ToolError(在 TypeScript 中使用 throw)。

编写桌面驱动程序

此驱动程序实现了可用操作下列出的 17 个工具中的 5 个。您未实现的工具会以禁用状态发送给 API,如实现驱动程序中所述。在示例中,display 代表您自己对控制桌面的任何工具(例如 VNC 客户端或 xdotool)的封装,ask_user 代表您自己用于询问某个人的函数。

from anthropic import Anthropic
from anthropic.tools.computer import (
    BetaAbstractComputerToolset20260801,
    BetaComputerCursorPositionResult,
    BetaScreenshotResult,
    BetaToolsetCallContext,
)
from anthropic.types.beta import (
    BetaComputerCursorPositionInput,
    BetaComputerKeyInput,
    BetaComputerLeftClickInput,
    BetaComputerScreenshotInput,
    BetaComputerTypeInput,
)


class MyDesktop(BetaAbstractComputerToolset20260801):
    def __init__(self, display, **options):
        super().__init__(**options)
        self.display = display

    def screenshot(
        self, context: BetaToolsetCallContext, input: BetaComputerScreenshotInput
    ) -> BetaScreenshotResult:
        # Claude 的坐标是此图像中的像素位置。
        data = self.display.png_base64()
        return BetaScreenshotResult(data=data, media_type="image/png")

    def cursor_position(
        self, context: BetaToolsetCallContext, input: BetaComputerCursorPositionInput
    ) -> BetaComputerCursorPositionResult:
        x, y = self.display.cursor()
        return BetaComputerCursorPositionResult(x=x, y=y)

    def left_click(
        self, context: BetaToolsetCallContext, input: BetaComputerLeftClickInput
    ) -> None:
        # input.coordinate 为 [x, y],或为 None 表示在光标处点击。input.text 是
        # 要按住的修饰键,例如 "shift"。无需返回内容:Claude 读取 "Clicked."
        self.display.click(input.coordinate, input.text)

    def type(
        self, context: BetaToolsetCallContext, input: BetaComputerTypeInput
    ) -> None:
        self.display.type(input.text)

    def key(self, context: BetaToolsetCallContext, input: BetaComputerKeyInput) -> None:
        self.display.press(input.text, input.repeat or 1)

    def close(self) -> None:
        super().close()  # first, so no call is still using the desktop when it closes
        self.display.close()


client = Anthropic()
with MyDesktop(
    display,
    # type 和 key 操作必需;请参阅“安全运行计算机工具集”
    confirm=lambda context: ask_user(f'Allow the "{context.member}" tool?'),
) as desktop:
    runner = client.beta.messages.tool_runner(
        model="claude-opus-5-5",
        max_tokens=1024,
        tools=[desktop],
        messages=[
            {"role": "user", "content": "Open the calculator and compute 17 * 23."}
        ],
    )
    for message in runner:
        print(message)

有四个工具没有输入字段:screenshot、cursor_position、left_mouse_down 和 left_mouse_up。每种输入类型(包括它们的)都来自 anthropic.types.beta。

在 TypeScript 中,请将工具写成方法,而不是箭头函数字段。实现驱动程序给出了原因。

缩放坐标并调整图像尺寸

Claude 发送的每个点都是您返回的屏幕截图中的像素位置:每个 coordinate 和 start_coordinate,以及 zoom region 的两个角。SDK 会将它们原样传递给您的工具。

如果您的屏幕截图尺寸与显示器不同,请在驱动程序中进行双向转换:将 Claude 的点转换为显示器像素,并将您报告的光标位置转换为屏幕截图像素。在整个会话中保持屏幕截图尺寸不变,并使其与显示器的宽高比一致:

from anthropic.tools import ToolError

SCREENSHOT_SIZE = (1280, 720)  # the size of every full screenshot the driver returns
DISPLAY_SIZE = (2560, 1440)


def to_display(coordinate: list[int]) -> tuple[int, int]:
    x, y = coordinate
    width, height = SCREENSHOT_SIZE
    if not (0 <= x < width and 0 <= y < height):
        # 拒绝该坐标点。若将其移入范围内,点击位置将并非 Claude 所选。
        raise ToolError(f"[{x}, {y}] is outside the {width}x{height} screenshot")
    return x * DISPLAY_SIZE[0] // width, y * DISPLAY_SIZE[1] // height


def to_screenshot(x: int, y: int) -> tuple[int, int]:
    width, height = DISPLAY_SIZE
    return x * SCREENSHOT_SIZE[0] // width, y * SCREENSHOT_SIZE[1] // height


to_display([640, 360])  # (1280, 720)

在对每个 coordinate 和 start_coordinate 执行操作之前,先对其调用 to_display;对 cursor_position 报告的位置调用 to_screenshot。没有 coordinate 的点击或滚动会在光标处执行,因此无需转换。按相同比例缩放 zoom region。zoom 图像不会改变这一点:Claude 接下来发送的点仍然是完整屏幕截图中的像素位置。

SDK 从不读取或调整图像尺寸。API 会拒绝超出模型限制的图像,这会终止运行。请在驱动程序中缩放每张屏幕截图和 zoom 图像。请参阅调整截图大小以符合图像限制。在长时间运行中,一个请求可能包含超过 20 张图像,此时 API 会对每张图像施加更严格的限制。请参阅管理截图历史。

输入检查与结果

不要依赖 SDK 来检查调用的输入。让您的驱动程序检查它使用的字段,并拒绝无法执行的调用:抛出 ToolError(在 TypeScript 中使用 throw)。Claude 会读取该消息,并可以发送更正后的调用。

两个 SDK 都不限制 duration、repeat 次数或 scroll_amount。安全地运行计算机工具集下的第 4 步涵盖了这些内容。

工具返回的内容决定了 Claude 读取的内容:

工具返回Claude 读取
screenshot、zoomBetaScreenshotResult一个图像块
cursor_positionBetaComputerCursorPositionResultX={x},Y={y}
其他所有工具无,或一行文本一条简短确认,例如 Clicked.,然后是单独文本块中的返回行

安全地运行计算机工具集

屏幕上的内容会引导 Claude 的下一步操作。页面或通知可能试图让 Claude 在错误的窗口中输入,或触发具有实际影响的操作。

在将驱动程序用于一次性机器以外的任何环境之前,请采取以下五个步骤:

  1. 隔离桌面。为每个会话在专用的、最小权限的容器或虚拟机中运行它,不包含凭据,也不挂载主机目录。只安装任务所需的应用程序。在其外部运行调用 API 的代码。
  2. 使用 confirm 对具有实际影响的操作进行把关。购买或发送消息只是普通的 left_click、type 或 key,而 confirm 接收的是工具及其输入,而不是屏幕。因此,请根据 Claude 在桌面上可以访问的应用程序来选择哪些工具需要批准。
  3. 除非任务需要,否则不要在桌面上放置终端、运行对话框和启动器。当其中之一获得键盘焦点时,type、key 和 hold_key 会运行 Claude 输入的任何内容,而一次点击就可能启动程序。如果任务需要其中之一,请在键盘输入和点击之前询问人员,并隔离桌面,使命令的影响范围不超出任务所需。
  4. 拒绝桌面不应执行的输入,例如屏幕截图之外的点、长达数分钟的 wait,或非常大的 repeat 或 scroll_amount。抛出 ToolError(在 TypeScript 中使用 throw),而不是将值调整到范围内。
  5. 将屏幕上的所有内容视为不可信,包括窗口标题和剪贴板文本。不要在未经检查的情况下运行或传递它们。

SDK 通过您传入的可调用对象来实施第 2 步。其他步骤取决于您的驱动程序和部署。安全注意事项下的预防措施同样适用。

以下 confirm 会在任何键盘输入之前通过您自己的 ask_user 函数询问人员,并批准其他所有调用。context.member 是工具的名称。若要让人员也批准点击,请将 ASK_FIRST 设置为 KEYBOARD | CLICKS。

from anthropic.tools.computer import BetaComputerConfirmContext

KEYBOARD = {"type", "key", "hold_key"}
CLICKS = {
    "left_click",
    "right_click",
    "middle_click",
    "double_click",
    "triple_click",
    "left_click_drag",
    "left_mouse_down",
    "left_mouse_up",
}
ASK_FIRST = KEYBOARD


def confirm(context: BetaComputerConfirmContext) -> bool:
    if context.member not in ASK_FIRST:
        return True
    # 此处所示为“Gate consequential members”中的函数。
    detail = shown(context.input.to_dict())
    return ask_user(f'Allow the "{context.member}" tool with this input?\n{detail}')


desktop = MyDesktop(display, confirm=confirm)

被拒绝的调用会得到一个错误结果,运行会继续。对于被拒绝的 type 调用,Claude 会读取 The user did not grant permission to run 'type'. Do not retry it unless the user asks you to.

此 confirm 会批准所有不在 ASK_FIRST 中的工具,包括您以后添加的任何工具。若要采用失败即关闭(fail closed)的方式,请只批准对桌面没有影响的工具(screenshot、zoom、cursor_position 和 wait),并对其他所有工具进行询问。

在一个请求中使用两个工具集

在 tools 中传入两个实例。每个调用都带有其 toolset_name(browser 或 computer),工具运行器会将调用发送到匹配的实例。调用失败后,工具运行器只会跳过该轮中对同一工具集的后续调用,因此失败的计算机调用不会跳过该轮的浏览器调用。

# MyBrowser、backend 和 url_policy 来自高级示例。
# confirm 是“安全运行计算机工具集”中的函数。
with (
    MyBrowser(backend, url_policy=url_policy) as browser,
    MyDesktop(display, confirm=confirm) as desktop,
):
    runner = client.beta.messages.tool_runner(
        model="claude-opus-5-5",
        max_tokens=1024,
        tools=[browser, desktop],
        messages=[
            {
                "role": "user",
                "content": "Copy the heading on example.com into the open text editor.",
            }
        ],
        stream=True,
        run_tools_eagerly=True,  # so a call can start before the response ends
    )
    for stream in runner:
        print(stream.get_final_message())

在您自己编写的循环中,使用 toolset_name 与调用的 toolset_name 匹配的实例上的 tool_result 来响应每个调用。在每一轮中,分别跟踪每个工具集的失败调用,并使用该工具集自己的文本来响应被跳过的调用。不使用工具运行器运行中提供了浏览器的文本,本节顶部的"被跳过的调用"要点中提供了计算机的文本。

参考

构造函数选项在两个 SDK 中的含义相同:

PythonTypeScript设置
configsconfigs启用哪些成员
confirmconfirm批准或拒绝每个调用的可调用对象
url_policyurlPolicy您的 URL 策略,SDK 会在每次 navigate 到某个 URL 之前调用它
file_policyfilePolicy上传根目录和下载路径暴露
tool_configstoolConfigstools 条目的字段,例如 cache_control
_browser_state 方法browserState状态报告

浏览器类接受表中的所有选项。计算机类接受 configs、confirm 和 tool_configs。

构造之后无法更改选项。Python 浏览器工具集指南、TypeScript 浏览器工具集指南、Python 计算机工具集指南和 TypeScript 计算机工具集指南介绍了每个选项。Python 指南还涵盖了异步类 BetaAsyncAbstractBrowserToolset20260801 和 BetaAsyncAbstractComputerToolset20260801。

限制

  • SDK 不包含浏览器、桌面、现成的驱动程序或 URL 策略: 这些由您提供。请参阅合作伙伴集成、实现驱动程序和编写 URL 策略。
  • URL 策略只能看到 navigate 调用:它看不到页面发出的请求,也看不到以其他方式打开的页面,例如通过 Claude 点击的链接或重定向打开的页面。请参阅在驱动程序中拦截请求。
  • 批准基于上一次状态报告: 页面可能在该报告之后发生变化。SDK 不会在调用运行之前再次检查页面。
  • 同一工具集上的调用一次只运行一个: 您无法关闭此行为。
  • SDK 不检查标签页 ID 是否唯一或标签页数量,也不总是检查是否只有一个标签页处于活动状态:API 会拒绝违反这些规则的报告。
  • 对于计算机调用,confirm 接收的是工具及其输入,而不是屏幕:您无法从中判断在给定点的点击会产生什么效果。
  • SDK 不会调整计算机工具集图像的尺寸:API 会拒绝超出模型限制的图像。请参阅缩放坐标并调整图像尺寸。

后续步骤

成员工具、browser_state 块以及该工具的安全注意事项。

17 个工具、坐标和图像限制,以及该工具的安全注意事项。

SDK 如何运行循环,以及如何更改它发送的消息。

适用于任何读取不可信内容的应用程序的防护措施。

Was this page helpful?