---
title: Uso del navegador y de la computadora con los conjuntos de herramientas del SDK
url: https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk
description: Ejecuta la herramienta de uso del navegador o la herramienta de uso de computadora desde el SDK de Python o TypeScript. El SDK ejecuta el bucle y las comprobaciones que configures, y tú proporcionas el navegador o el escritorio.
---

Con los "toolsets" (conjuntos de herramientas) de navegador y de computadora de los SDK de Python y TypeScript, puedes crear un agente que trabaje en un navegador web o en un escritorio. Puede usar aplicaciones que no tienen interfaz programática. A través de la [herramienta de uso del navegador](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-tool), Claude navega, lee e interactúa con páginas web. A través de la [herramienta de uso de computadora](https://platform.claude.com/docs/es/agents-and-tools/tool-use/computer-use-tool), Claude ve un escritorio en capturas de pantalla y usa el mouse y el teclado.

El SDK conecta a Claude con tu navegador o escritorio. Ejecuta las comprobaciones que configures, como una política de URL para `navigate` o un callback de aprobación. Envía cada llamada que las supera a tu "driver" (controlador) y construye el `tool_result` a partir de lo que devuelve el controlador. El ["tool runner" (ejecutor de herramientas)](https://platform.claude.com/docs/es/agents-and-tools/tool-use/tool-runner) puede ejecutar el bucle agéntico por ti.

Tú proporcionas el controlador, que opera el navegador o el escritorio. Empieza con el [controlador de un proveedor](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#use-a-driver-from-a-provider). Con uno, un agente requiere unas pocas líneas de código. Si ninguno se ajusta, escribe el tuyo con una biblioteca de automatización de navegadores como Playwright.

La mayor parte de esta página trata sobre el conjunto de herramientas de navegador. [Conjunto de herramientas de computadora](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#computer-toolset) cubre lo que difiere.

Escribir tu propio controlador es un caso avanzado, que se cubre en [Implementar un controlador](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#implement-a-driver). El repositorio claude-quickstarts también tiene un [controlador de navegador de ejemplo](https://github.com/anthropics/claude-quickstarts/tree/main/browser-toolset) en Python y TypeScript. Controla Chromium a través del Chrome DevTools Protocol (CDP), y no es código de producción.

<Note>
  La clase del conjunto de herramientas de navegador está en beta y disponible en el [SDK de Python](https://github.com/anthropics/anthropic-sdk-python/blob/main/browser-toolset.md) y el [SDK de TypeScript](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/browser-toolset.md). La clase del conjunto de herramientas de computadora está en beta en los mismos SDK, que tienen una [guía del conjunto de herramientas de computadora para Python](https://github.com/anthropics/anthropic-sdk-python/blob/main/computer-toolset.md) y una [guía del conjunto de herramientas de computadora para TypeScript](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/computer-toolset.md).
</Note>

## Usar un controlador de un proveedor

Con un controlador de un proveedor, como uno de los que aparecen en [Integraciones de socios](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#partner-integrations), puedes crear un agente en unas pocas líneas de código. El proveedor de este ejemplo es inventado. Reemplaza `example_browser_provider` (typescript: `example-browser-provider`) y `ExampleBrowser` por el paquete y la clase de controlador de [un proveedor real](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#partner-integrations), y configúralos como se describe en la documentación del proveedor.

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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)
  ```

  ```typescript TypeScript
  import Anthropic from "@anthropic-ai/sdk";
  import { ExampleBrowser } from "example-browser-provider"; // made-up provider

  const client = new Anthropic();
  const browser = new ExampleBrowser();
  try {
    const runner = client.beta.messages.toolRunner({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [browser],
      messages: [{ role: "user", content: "Open example.com and tell me its heading." }]
    });
    for await (const message of runner) {
      console.log(message);
    }
  } finally {
    await browser.close();
  }
  ```
</CodeGroup>

Pasa la propia instancia del controlador como entrada de `tools`. El ejecutor nunca cierra el conjunto de herramientas, así que una instancia puede servir para varias ejecuciones. Ciérrala cuando termines.

Para escribir tu propio controlador, consulta [Implementar un controlador](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#implement-a-driver).

## Integraciones de socios

Browser Use, Browserbase, Daytona y E2B publican sus propios controladores para los conjuntos de herramientas del SDK.

<CardGroup cols={2}>
  <Card id="browser-use" title="Browser Use" image="https://platform.claude.com/docs/images/partner-logos/browser-use.svg" imageDark="https://platform.claude.com/docs/images/partner-logos/browser-use-dark.svg" href="https://docs.browser-use.com/open-source/customize/integrations/toolsets-for-claude">
    Controlador de Browser Use para el conjunto de herramientas de navegador.
  </Card>

  <Card id="browserbase" title="Browserbase" image="https://platform.claude.com/docs/images/partner-logos/browserbase.svg" imageDark="https://platform.claude.com/docs/images/partner-logos/browserbase-dark.svg" href="https://docs.stagehand.dev/v4/integrations/agent-frameworks/claude-cua-toolset-quickstart">
    Controlador Stagehand de Browserbase para el conjunto de herramientas de navegador.
  </Card>

  <Card id="daytona" title="Daytona" image="https://platform.claude.com/docs/images/partner-logos/daytona.svg" imageDark="https://platform.claude.com/docs/images/partner-logos/daytona-dark.svg" href="https://www.daytona.io/docs/en/guides/claude/claude-draws-daytona-sandbox/">
    Controlador de Daytona para el conjunto de herramientas de computadora.
  </Card>

  <Card id="e2-b" title="E2B" image="https://platform.claude.com/docs/images/partner-logos/e2b.svg" imageDark="https://platform.claude.com/docs/images/partner-logos/e2b-dark.svg" href="https://docs.e2b.dev/agents/claude-toolsets">
    Controladores de E2B para los conjuntos de herramientas de navegador y de computadora.
  </Card>
</CardGroup>

Nuestros socios también publican código en GitHub: el [inicio rápido de Browser Use](https://github.com/browser-use/browser-use/tree/main/examples/integrations/toolsets-for-claude), el [conjunto de herramientas de Browserbase](https://github.com/browserbase/claude-cua-toolset) y el [ejemplo de E2B](https://github.com/e2b-dev/e2b-cookbook/tree/main/examples/anthropic-computer-use-orangehrm-js).

<Warning>
  Haz estas dos cosas antes de ejecutar un controlador contra cualquier cosa que no sea un navegador desechable:

  * [Escribe una política de URL](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#write-a-url-policy). Sin una, el SDK no comprueba ninguna URL. Para un navegador alojado por un proveedor, lee también [Navegadores remotos y alojados](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#remote-and-hosted-browsers).
  * Usa un perfil de navegador que no haya iniciado sesión en ninguna cuenta cuyos datos o acciones no le confiarías a Claude. Consulta [Aislar el host del navegador](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#isolate-the-browser-host).

  [Ejecutar el conjunto de herramientas de forma segura](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#run-the-toolset-safely) cubre el resto.
</Warning>

## Personalizar un controlador

Los ejemplos de esta sección usan `MyBrowser`, el controlador del [ejemplo avanzado](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#quick-start) que aparece más adelante en esta página.

### Habilitar o deshabilitar miembros

`configs` acepta la configuración por miembro descrita en [Configura el conjunto de herramientas](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-tool#configure-the-toolset). Enumera solo los miembros que cambies:

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  # Un MyBrowser que también implementa read_console
  browser = MyBrowser(
      backend, configs={"read_console": {"enabled": True}, "navigate": {"enabled": False}}
  )
  ```

  ```typescript TypeScript
  // Un MyBrowser que también implementa read_console
  const browser = new MyBrowser(backend, {
    configs: { read_console: { enabled: true }, navigate: { enabled: false } }
  });
  ```
</CodeGroup>

El SDK rechaza una llamada a un miembro deshabilitado antes de que se ejecute tu código. Habilitar un miembro que tu clase no implementa es un error de configuración, a menos que la clase sobrescriba `execute`.

### Agregar hooks antes y después

Sobrescribe `execute` y llama al `execute` de la clase padre. El código antes de esa llamada se ejecuta después de la [política de URL](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#write-a-url-policy), la [política de archivos](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#confine-uploads-and-downloads) y [`confirm`](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#gate-consequential-members), y puede cambiar la entrada. El SDK no vuelve a comprobar la entrada modificada. El código después de la llamada recibe el resultado y puede cambiarlo. Lanza `ToolError` (con `throw` en TypeScript) para rechazar la llamada.

Sobrescribir `execute` cambia qué miembros se le ofrecen a Claude. El SDK cuenta cada miembro como implementado, así que a Claude se le ofrecen todos los miembros que están activados por defecto. El `MyBrowser` del [ejemplo avanzado](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#quick-start) atiende tres miembros, así que el siguiente `TracedBrowser` le ofrece a Claude miembros que no puede atender. Desactiva esos miembros con `configs` antes de usarlo.

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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
  ```

  ```typescript TypeScript
  import type { BetaToolsetCallContext } from "@anthropic-ai/sdk/helpers/beta/toolsets";
  import type {
    BetaBrowserMemberInput,
    BetaBrowserMemberName
  } from "@anthropic-ai/sdk/resources/beta";

  class TracedBrowser extends MyBrowser {
    protected override async execute(
      ctx: BetaToolsetCallContext,
      name: BetaBrowserMemberName,
      input: BetaBrowserMemberInput
    ) {
      const started = performance.now();
      const result = await super.execute(ctx, name, input);
      log.info(`${ctx.toolUse?.id ?? "-"} ${name} ${Math.round(performance.now() - started)}ms`);
      return name === "get_page_text" ? redact(result as string) : result;
    }
  }
  ```
</CodeGroup>

## Implementar un controlador

Un controlador es tu subclase de `BetaAbstractBrowserToolset20260801`. En ella, sobrescribe los miembros que admite tu navegador. Un miembro que no implementas se envía a la API como deshabilitado. Si Claude lo llama de todos modos, el SDK devuelve un error y la ejecución continúa. Sobrescribir `execute` cambia qué miembros se envían como deshabilitados ([Agregar hooks antes y después](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#add-before-and-after-hooks)). Cada miembro recibe el contexto de la llamada y la entrada del miembro como un objeto tipado, como `BetaBrowserNavigateInput`. Los tipos de entrada provienen de `anthropic.types.beta` (typescript: `@anthropic-ai/sdk/resources/beta`). [Herramientas miembro](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-tool#member-tools) enumera los campos de cada entrada.

En TypeScript, escribe los miembros como métodos, no como campos de función flecha, porque el SDK los busca en el prototipo. Escribe el miembro `type` como `type_` en TypeScript. En Python, es `type`.

### Devolver resultados

Lo que devuelve un miembro determina lo que lee Claude. Un resultado exitoso termina con un bloque `browser_state` construido a partir de tu informe de estado. Un resultado de error no lleva ningún bloque.

| Miembro                                                                                 | Devuelve                                                                                                              | Claude lee                                                                                         |
| --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `screenshot`, `zoom`                                                                    | `BetaScreenshotResult`                                                                                                | Un bloque de imagen                                                                                |
| `navigate`                                                                              | `BetaBrowserNavigateResult`                                                                                           | `Navigated to {url} — {title} (HTTP {status})`                                                     |
| `new_tab`, `switch_tab`, `list_tabs`, `close_tab`                                       | Una entrada de pestaña (`new_tab`, `switch_tab`), una lista de entradas de pestaña (`list_tabs`) o nada (`close_tab`) | Solo el bloque `browser_state`                                                                     |
| `read_page`, `get_page_text`, `find`, `read_console`, `read_network`, `javascript_exec` | Una cadena                                                                                                            | La cadena, o `(empty)` para una cadena vacía                                                       |
| Cualquier otro miembro                                                                  | Nada, o una línea de texto                                                                                            | Una confirmación breve, como `Clicked.`, seguida de la línea devuelta en un bloque de texto propio |

### Informar el estado del navegador

El SDK llama a `_browser_state` (typescript: `browserState`) después de cada llamada que devuelve un resultado, incluidas las llamadas rechazadas y fallidas. Devuelve cada pestaña abierta y lo que cambió desde el último informe:

* Las pestañas que se abrieron y los eventos de descarga.
* Un `BetaNavigationRefused` por cada navegación que bloqueó tu [hook de solicitudes](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#intercept-requests-in-the-driver).
* Un `BetaDialogDismissed` por cada diálogo nativo que descartó tu controlador.

Pon todo esto en `state_changes`. En Python, `BetaNavigationRefused()` y `BetaDialogDismissed(kind=..., message=...)` provienen de `anthropic.tools.browser`. En TypeScript, son `{ type: "navigation_refused" }` y `{ type: "dialog_dismissed", kind, message }`.

Los dos últimos no son cambios de estado de la API. El SDK se los informa a Claude como texto fuera del bloque `browser_state`: una línea para todas las navegaciones rechazadas, que no incluye una URL, y una línea para cada uno de los tres primeros diálogos descartados, seguida de un recuento de los demás.

Cuando hay alguna pestaña abierta, exactamente una debe estar activa. Cada miembro que recibe un `tab_id` debe actuar sobre la pestaña que nombra. Los límites de la API sobre el informe se enumeran en [Rastrea pestañas con `browser_state`](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-tool#track-tabs-and-page-state).

Claude lee la URL de cada pestaña, la URL de cada descarga y la URL que devuelve tu `navigate`, tal como las escribió tu controlador. El SDK no analiza estas URL. Convierte los saltos de línea y otros caracteres de control en espacios, recorta los extremos y corta cada URL a 4,096 caracteres. Así que el contenido de una URL `data:`, la ruta de una URL `file:` y un nombre de usuario o contraseña en una URL llegan todos a Claude.

### Manejar errores

| Lanzado por un miembro o el SDK | Claude lee                                                                               | La ejecución |
| ------------------------------- | ---------------------------------------------------------------------------------------- | ------------ |
| `ToolError`                     | Su mensaje, como resultado de error                                                      | Continúa     |
| `ToolsetUsageError`             | Nada                                                                                     | Se detiene   |
| Cualquier otra excepción        | `ClassName: message` en Python o `Error: message` en TypeScript, como resultado de error | Continúa     |

El SDK lanza `ToolsetUsageError` ante un error de configuración, un uso indebido del SDK durante una llamada o una llamada después de `close`. También lanza uno cuando `_browser_state` (typescript: `browserState`) lanza cualquier excepción, incluso un `ToolError`. El ejecutor de herramientas y `tool_result` (typescript: `toolResult`) no lo capturan, así que llega a quien los llamó.

El SDK no oculta las rutas locales en el texto de error de un miembro, en la línea que devuelve una acción como `left_click`, en el error de una descarga fallida ni en el mensaje de un diálogo descartado.

Un `ToolError` de tu política de URL, tu política de archivos o tu callable `confirm` llega a Claude tal como está escrito, así que no incluyas rutas locales en su texto. Captura las excepciones en tus miembros y lanza `ToolError` (con `throw` en TypeScript) con tu propio texto.

### Iniciar llamadas mientras la respuesta hace streaming

Por defecto, el ejecutor de herramientas ejecuta las llamadas de un turno después de que termina la respuesta de Claude. Con el inicio anticipado, puede iniciar una llamada del navegador o de la computadora mientras la respuesta todavía está haciendo streaming.

Para activarlo, pasa `stream=True` (typescript: `stream: true`) y `run_tools_eagerly=True` (typescript: `runToolsEagerly: true`) al ejecutor de herramientas, como hace el [ejemplo avanzado](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#quick-start). Con `stream`, cada pasada de tu bucle sobre el ejecutor te da un stream en lugar de un mensaje.

El inicio anticipado cambia cuándo puede comenzar una llamada, no cómo la ejecuta el conjunto de herramientas:

* **Las comprobaciones siguen ejecutándose primero:** El SDK llama a tu política de URL, tu política de archivos y `confirm` antes de que se ejecute una llamada. Con el inicio anticipado, puede llamar a `confirm` mientras la respuesta todavía está haciendo streaming.
* **Las llamadas siguen ejecutándose de una en una:** Un conjunto de herramientas ejecuta sus llamadas en el orden en que Claude las escribió. Una llamada fallida sigue deteniendo las llamadas posteriores de ese conjunto de herramientas en el turno.
* **Una llamada que ya comenzó no se puede deshacer:** Si luego la respuesta se corta, por ejemplo en `max_tokens`, o tu bucle se detiene antes, la acción igual ocurre y Claude nunca lee su resultado.

### Ejecutar sin el ejecutor de herramientas

Pasa la instancia en `tools` (`browser.toJSON()` en TypeScript) y responde cada llamada a un miembro con `tool_result` (typescript: `toolResult`). La herramienta de uso del navegador requiere que te detengas en la primera llamada fallida ([Acciones por lotes](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-tool#batch-actions)).

Este bucle usa `MyBrowser` y la política de URL del [ejemplo avanzado](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#quick-start). Después de una llamada fallida, responde las llamadas posteriores del turno sin ejecutarlas:

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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:
                  # Tras una llamada fallida, el resto del turno se responde, no se ejecuta.
                  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})
  ```

  ```typescript TypeScript
  const NOT_EXECUTED = "Not executed: an earlier action in this turn failed.";
  const MAX_TURNS = 10;

  const browser = new MyBrowser(backend, { urlPolicy });
  try {
    const messages: Anthropic.Beta.BetaMessageParam[] = [
      { role: "user", content: "Open example.com" }
    ];
    for (let turn = 0; turn < MAX_TURNS; turn++) {
      const response = await client.beta.messages.create({
        model: "claude-opus-5-5",
        max_tokens: 1024,
        tools: [browser.toJSON()],
        messages
      });
      messages.push({ role: "assistant", content: response.content });
      const calls = response.content.filter(
        (block): block is Anthropic.Beta.BetaToolUseBlock =>
          block.type === "tool_use" && block.toolset_name === browser.toolsetName
      );
      if (calls.length === 0) {
        break;
      }
      const results: Anthropic.Beta.BetaToolResultBlockParam[] = [];
      let failed = false;
      for (const call of calls) {
        if (failed) {
          // Tras una llamada fallida, el resto del turno se responde, no se ejecuta.
          results.push({
            type: "tool_result",
            tool_use_id: call.id,
            toolset_name: call.toolset_name,
            content: NOT_EXECUTED,
            is_error: true
          });
          continue;
        }
        const result = await browser.toolResult(call);
        failed = result.is_error === true;
        results.push(result);
      }
      messages.push({ role: "user", content: results });
    }
  } finally {
    await browser.close();
  }
  ```
</CodeGroup>

Cada respuesta a una llamada omitida lleva `is_error`, el `toolset_name` de la llamada y el texto exacto que requiere [Acciones por lotes](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-tool#batch-actions). El ejecutor de herramientas envía la misma respuesta.

## Ejemplo avanzado: escribe tu propio controlador

Este ejemplo reúne las partes principales de [Implementar un controlador](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#implement-a-driver) en un solo controlador, `MyBrowser`, y lo ejecuta con el ejecutor de herramientas y el [inicio anticipado](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#start-calls-while-the-response-streams). `MyBrowser` implementa `navigate`, `screenshot` y `left_click`, además de `_browser_state` (typescript: `browserState`), el informe de estado que necesita todo controlador.

`backend` representa tu propio wrapper alrededor de una biblioteca de automatización de navegadores, como Playwright. La política de URL es un ejemplo. [Escribir una política de URL](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#write-a-url-policy) la explica.

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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 es "back", "forward", "reload" o una URL que tu política de URL
          # permitió, tal como Claude la escribió. Igual que is_allowed, backend.goto agrega https://
          # a una URL que no comienza con un esquema.
          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:
          # Nada que devolver: Claude lee "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:
      # Un ejemplo, no una política de producción. Consulta "Escribir una política de URL".
      if url.lower() == "about:blank":
          return True
      with_scheme = url if SCHEME_PREFIX.match(url) else f"https://{url}"
      # Un navegador interpreta "\" como "/" en una URL web.
      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())
  ```

  ```typescript TypeScript
  import Anthropic from "@anthropic-ai/sdk";
  import {
    BetaAbstractBrowserToolset20260801,
    type BetaBrowserNavigateResult,
    type BetaBrowserToolsetOptions,
    type BetaScreenshotResult,
    type BetaToolsetCallContext,
    type BetaURLContext,
    ToolError
  } from "@anthropic-ai/sdk/helpers/beta/toolsets";
  import type {
    BetaBrowserLeftClickInput,
    BetaBrowserNavigateInput,
    BetaBrowserScreenshotInput
  } from "@anthropic-ai/sdk/resources/beta";

  class MyBrowser extends BetaAbstractBrowserToolset20260801 {
    constructor(
      private backend: Backend,
      options: Omit<BetaBrowserToolsetOptions, "browserState"> = {}
    ) {
      super({
        ...options,
        // Obligatorio: cada pestaña abierta y lo que cambió desde el último informe.
        browserState: () => ({
          tabs: backend.tabs().map((tab) => ({
            tab_id: tab.id,
            title: tab.title,
            url: tab.url,
            active: tab.id === backend.active
          })),
          state_changes: backend.drainChanges()
        })
      });
    }

    protected override async navigate(
      ctx: BetaToolsetCallContext,
      input: BetaBrowserNavigateInput
    ): Promise<BetaBrowserNavigateResult> {
      // input.url es "back", "forward", "reload" o una URL que tu política de URL
      // permitió, tal como Claude la escribió. Igual que isAllowed, backend.goto agrega https:// a
      // una URL que no empieza con un esquema.
      const page = await this.backend.goto(input.url, input.tab_id);
      return { url: page.url, status: page.status, title: page.title };
    }

    protected override async screenshot(
      ctx: BetaToolsetCallContext,
      input: BetaBrowserScreenshotInput
    ): Promise<BetaScreenshotResult> {
      return { data: await this.backend.pngBase64(input.tab_id), mediaType: "image/png" };
    }

    protected override async left_click(
      ctx: BetaToolsetCallContext,
      input: BetaBrowserLeftClickInput
    ): Promise<void> {
      // Nada que devolver: Claude lee "Clicked."
      await this.backend.click(input.target, input.tab_id);
    }

    override async close(): Promise<void> {
      await super.close(); // first, so no call is still using the browser when it closes
      if (!this.backend.closed) {
        await this.backend.close();
      }
    }
  }

  const ALLOWED_HOSTS = ["example.com", "iana.org"];
  const SCHEME_PREFIX = /^[a-z][a-z0-9+.-]*:/i;

  // Un ejemplo, no una política para producción. Consulta "Escribir una política de URL".
  function isAllowed(url: string): boolean {
    if (url.toLowerCase() === "about:blank") {
      return true;
    }
    const withScheme = SCHEME_PREFIX.test(url) ? url : `https://${url}`;
    // Un navegador interpreta "\" como "/" en una URL web.
    const parsed = URL.parse(withScheme.replaceAll("\\", "/"));
    const host = parsed?.hostname ?? "";
    const listed = ALLOWED_HOSTS.some((name) => host === name || host.endsWith(`.${name}`));
    return (parsed?.protocol === "http:" || parsed?.protocol === "https:") && listed;
  }

  function urlPolicy(ctx: BetaURLContext, url: string): void {
    if (!isAllowed(url)) {
      throw new ToolError(`blocked: ${url} is not on an allowed host`);
    }
  }

  const client = new Anthropic();
  const browser = new MyBrowser(backend, { urlPolicy });
  try {
    const runner = client.beta.messages.toolRunner({
      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,
      runToolsEagerly: true // so a call can start before the response ends
    });
    for await (const stream of runner) {
      console.log(await stream.finalMessage());
    }
  } finally {
    await browser.close();
  }
  ```
</CodeGroup>

## Ejecutar el conjunto de herramientas de forma segura

La siguiente acción de Claude depende de las páginas que lee. Una página, o un texto inyectado en ella, puede intentar llegar a servicios internos o extraer archivos del host. También puede intentar desencadenar acciones con efectos reales. Antes de ejecutar un controlador contra cualquier cosa que no sea un navegador desechable, sigue estos seis pasos:

1. [Escribe una política de URL](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#write-a-url-policy) que rechace cada URL de `navigate` que la tarea no necesite.
2. [Intercepta las solicitudes en el controlador](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#intercept-requests-in-the-driver) y comprueba cada solicitud con las mismas reglas.
3. [Aplica una "egress policy" (política de salida) en el contenedor](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#put-egress-policy-on-the-container), para que la red bloquee lo que el controlador no puede ver.
4. [Limita las cargas y descargas](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#confine-uploads-and-downloads), o deja las cargas desactivadas.
5. [Controla los miembros con consecuencias](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#gate-consequential-members) con `confirm`.
6. [Aísla el host del navegador](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#isolate-the-browser-host) en un contenedor o VM dedicado para cada sesión.

El SDK aplica los pasos 1, 4 y 5 mediante las políticas y el callable que le pases. Los pasos 2, 3 y 6 dependen de tu controlador y tu despliegue. Las precauciones de [Consideraciones de seguridad](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-tool#security-considerations) también se aplican.

<Note>
  Los clasificadores de "prompt injection" (inyección de prompts) de Anthropic, descritos en [Consideraciones de seguridad](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-tool#security-considerations), actualmente no se ejecutan en las solicitudes del conjunto de herramientas del navegador enviadas a través de Amazon Bedrock.
</Note>

### Escribir una política de URL

<Warning>
  Sin una política de URL, el SDK no comprueba ninguna URL, y la API no filtra las URL que abre Claude. Incluso con una política, un enlace en el que Claude hace clic o una redirección puede llevar el navegador al loopback (`localhost`, `127.0.0.1`), a direcciones link-local como la dirección de metadatos de la nube `169.254.169.254` y a rangos de red privada. Las [reglas de salida en el contenedor](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#put-egress-policy-on-the-container) pueden bloquear las direcciones link-local y privadas. Las reglas aplicadas fuera del contenedor no pueden bloquear el loopback.

  El SDK no comprueba el esquema de una URL, así que tu controlador debe rechazar cada esquema que no pretenda abrir, como `javascript:`, `view-source:`, `data:` y `file:`. La mayoría de los controladores solo necesitan `http` y `https`. La política de ejemplo de abajo permite solo URL `http` y `https`, además de `about:blank`.

  Una URL `file:` le permite a Claude leer archivos en el host del navegador. Una URL `javascript:` ejecuta un script en la página actual, incluso cuando `javascript_exec` está desactivado. Las reglas de salida no detienen ninguna de las dos.
</Warning>

Una política de URL es una función que pasas como `url_policy` (typescript: `urlPolicy`). Tú escribes la política y eres responsable de prepararla para producción. Antes de que se ejecute tu `navigate`, el SDK llama a la política con el contexto de la llamada y la URL tal como la escribió Claude.

No devuelvas nada para permitir la URL. Para rechazarla, lanza `ToolError` (con `throw` en TypeScript). Claude lee el mensaje del error y `navigate` no se ejecuta. La política comprueba solo la URL de cada llamada a `navigate`. No es un control de red.

Esta es la política del [ejemplo avanzado](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#quick-start). Permite `about:blank`, además de dos sitios y sus subdominios sobre `http` y `https`:

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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:
      # Un ejemplo, no una política para producción.
      if url.lower() == "about:blank":
          return True
      with_scheme = url if SCHEME_PREFIX.match(url) else f"https://{url}"
      # Un navegador interpreta "\" como "/" en una URL web.
      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)
  ```

  ```typescript TypeScript
  import { type BetaURLContext, ToolError } from "@anthropic-ai/sdk/helpers/beta/toolsets";

  const ALLOWED_HOSTS = ["example.com", "iana.org"];
  const SCHEME_PREFIX = /^[a-z][a-z0-9+.-]*:/i;

  // Un ejemplo, no una política para producción.
  function isAllowed(url: string): boolean {
    if (url.toLowerCase() === "about:blank") {
      return true;
    }
    const withScheme = SCHEME_PREFIX.test(url) ? url : `https://${url}`;
    // Un navegador interpreta "\" como "/" en una URL web.
    const parsed = URL.parse(withScheme.replaceAll("\\", "/"));
    const host = parsed?.hostname ?? "";
    const listed = ALLOWED_HOSTS.some((name) => host === name || host.endsWith(`.${name}`));
    return (parsed?.protocol === "http:" || parsed?.protocol === "https:") && listed;
  }

  function urlPolicy(ctx: BetaURLContext, url: string): void {
    if (!isAllowed(url)) {
      throw new ToolError(`blocked: ${url} is not on an allowed host`);
    }
  }

  const browser = new MyBrowser(backend, { urlPolicy });
  ```
</CodeGroup>

El SDK llama a la política solo para `navigate`, y no para `"back"`, `"forward"` ni `"reload"`. Claude puede omitir el esquema, como en `example.com`, así que antes de leer el host, el ejemplo agrega `https://` a una URL que no empieza con un esquema. Tu `navigate` recibe la URL tal como la escribió Claude. Agrega el esquema allí con la misma prueba, para que el navegador abra la URL que comprobó tu política.

El ejemplo no es apto para producción. Una política de producción necesita mucho más, en áreas como estas:

* Enlaces en los que Claude hace clic, formularios que envía, redirecciones y las solicitudes que una página hace por su cuenta. Solo tu controlador los ve. Consulta [Interceptar solicitudes en el controlador](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#intercept-requests-in-the-driver).
* Nombres de host y URL que provienen del contenido de la página, que un atacante puede elegir.
* Analizar cada URL como lo hace el navegador, no encontrar el nombre de host con una simple división de cadenas.
* Esquemas de URL. El SDK no los comprueba, así que rechaza cada esquema que tu controlador no pretenda abrir.
* Reglas de salida en el contenedor, para lo que ninguna comprobación de URL puede ver. Consulta [Aplicar una política de salida en el contenedor](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#put-egress-policy-on-the-container).

Si pasas `url_policy=None` (typescript: `urlPolicy: null`), el SDK rechaza cada `navigate` a una URL, así que un `None` (typescript: `null`) leído de tu configuración no puede dejar URL sin comprobar por accidente. En TypeScript, `undefined` deja la opción sin establecer, igual que no pasarla.

### Interceptar solicitudes en el controlador

La política de URL evalúa solo la URL de una llamada a `navigate`. Un enlace en el que Claude hace clic, un formulario que envía, una redirección o una imagen o script en una página permitida aún pueden alcanzar un host que la política rechazaría. Usa [reglas de salida](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#put-egress-policy-on-the-container) para bloquear esos hosts.

Un controlador con intercepción de solicitudes también puede comprobar esas solicitudes en su hook de solicitudes, la función que tu biblioteca de automatización llama antes de cada solicitud. Llama a la función que usa tu política, para escribir las reglas una sola vez:

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  class MyBrowser(BetaAbstractBrowserToolset20260801):
      ...

      # Registrado en un contexto de navegador de Playwright con context.route("**/*", self._guard)
      def _guard(self, route):
          # is_allowed es la función de "Escribe una política de URL"
          if not is_allowed(route.request.url):
              return route.abort("blockedbyclient")
          route.continue_()
  ```

  ```typescript TypeScript
  // En la clase del driver, sobre un contexto de navegador de Playwright.
  // isAllowed es la función de "Escribe una política de URL".
  await context.route("**/*", (route) =>
    isAllowed(route.request().url()) ? route.continue() : route.abort("blockedbyclient")
  );
  ```
</CodeGroup>

Un hook de solicitudes como este no ve los handshakes de WebSocket, las solicitudes de service workers ni los saltos de redirección. Bloquea los service workers. Comprueba los handshakes de WebSocket y los saltos de redirección con un hook que los vea. La política de ejemplo rechaza las URL `ws://` y `wss://`, así que cambia `ws://` por `http://` y `wss://` por `https://` antes de pasar una URL de WebSocket a `is_allowed` (typescript: `isAllowed`).

### Aplicar una política de salida en el contenedor

La interceptación no puede ver todas las solicitudes que hace el navegador, y la política de URL no ve dónde se resuelve un nombre. Una política de salida que aplique la red del contenedor cubre ambos casos:

* Bloquea los rangos de direcciones link-local y privadas en IPv4 e IPv6. Eso incluye la dirección de metadatos de la nube `169.254.169.254`.
* Permite conexiones salientes solo a los hosts que la tarea necesita. Si tus reglas coinciden con direcciones IP, resuelve los nombres de host que permites cuando se inicia el contenedor.
* Permite DNS solo hacia el resolvedor del contenedor.

Las reglas aplicadas fuera del contenedor del navegador, como una NetworkPolicy de Kubernetes o un firewall en la nube, no ven el loopback dentro de él. Con solo esas reglas, una página puede alcanzar cualquier cosa que esté escuchando allí, incluido un puerto de DevTools, que muestra una lista de las pestañas abiertas del navegador y la dirección que controla cada una.

### Limitar las cargas y descargas

`file_upload` está desactivado por defecto. Sin `file_policy` (typescript: `filePolicy`), el SDK rechaza toda carga que indique una ruta o un [ID de documento](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-tool#upload-files). Para habilitar las cargas, pasa un `BetaLocalFilePolicy` (typescript: `BetaNodeFilePolicy`) con un directorio de carga que contenga solo los archivos de la tarea:

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  from anthropic.tools.browser import BetaLocalFilePolicy

  # Un MyBrowser que también implementa file_upload
  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,
      ),
  )
  ```

  ```typescript TypeScript
  import { BetaNodeFilePolicy } from "@anthropic-ai/sdk/helpers/beta/toolsets/node";

  // Un MyBrowser que también implementa file_upload
  const browser = new MyBrowser(backend, {
    configs: { file_upload: { enabled: true } },
    confirm: makeConfirm(), // required for file_upload; see Gate consequential members
    urlPolicy,
    filePolicy: new BetaNodeFilePolicy({
      uploadRoots: ["/task/uploads"],
      downloadDir: "/task/downloads",
      exposeDownloadPaths: false
    })
  });
  ```
</CodeGroup>

El SDK resuelve cada ruta de carga, siguiendo los enlaces simbólicos, y rechaza cualquier ruta fuera de las raíces de carga. La política de archivos lanza un error de configuración para un directorio de descarga que sea una raíz de carga, esté dentro de una o contenga una. Esa comprobación puede pasar por alto un enlace simbólico, así que no dejes que uno haga que el directorio de descarga sea el mismo que una raíz de carga, ni que ponga uno dentro del otro. El SDK incluye el `path` de una descarga en el informe de estado solo cuando `expose_download_paths` (typescript: `exposeDownloadPaths`) es verdadero y el archivo está dentro del directorio de descarga.

La política de archivos incluida comprueba las rutas en el sistema de archivos del proceso que ejecuta el SDK. Solo protege a un navegador que comparte ese sistema de archivos. Para un navegador remoto, sigue [Navegadores remotos y alojados](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#remote-and-hosted-browsers) en su lugar.

Configura las descargas de esta manera:

* Crea tú mismo el directorio de descarga con el modo `0700` y móntalo con `noexec,nosuid,nodev`.
* Mantén el directorio fuera del alcance de otras herramientas que Claude pueda llamar, como un shell o una herramienta de archivos.
* En un cambio de estado `download_failed`, escribe `error` como una frase fija. El texto de una excepción puede contener la ruta o la URL.
* No leas un archivo descargado en la conversación, ni lo ejecutes, hasta que una persona lo decida.

### Controlar los miembros con consecuencias

`javascript_exec` y `file_upload` están desactivados por defecto. Si habilitas cualquiera de los dos sin un callable `confirm`, el constructor lanza un error de configuración. Con un callable `confirm`, el SDK lo llama antes de cada llamada que está a punto de ejecutarse. Sin uno, no se pregunta nada. En TypeScript, `confirm: null` no lanza el error de configuración, y el SDK rechaza cada llamada a cualquier miembro.

Devuelve `True` (typescript: `true`) para ejecutar la llamada, o `False` (typescript: `false`) para rechazarla. Cuando tu callable le pregunte a una persona, muéstrale el miembro, la URL de la página y la entrada de la llamada. Primero, escapa cada carácter fuera del ASCII imprimible en la URL y la entrada, porque ambas pueden llevar texto de la página.

Este ejemplo pregunta por los dos miembros controlados mediante tu propia función `ask_user` (typescript: `askUser`), y aprueba el resto:

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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
          # Una aprobación cubre solo esta entrada exacta en esta página.
          key = (name, page, detail)
          if key not in granted and ask_user(question):
              granted.add(key)
          return key in granted

      return confirm


  # Un MyBrowser que también implementa javascript_exec y file_upload
  # file_upload también necesita la file_policy de "Confine uploads and downloads"
  browser = MyBrowser(
      backend,
      configs={"javascript_exec": {"enabled": True}, "file_upload": {"enabled": True}},
      confirm=make_confirm(),
      url_policy=url_policy,
  )
  ```

  ```typescript TypeScript
  import type { BetaConfirmContext } from "@anthropic-ai/sdk/helpers/beta/toolsets";

  const GATED = new Set(["javascript_exec", "file_upload"]);

  /** A value as JSON, with every character outside printable ASCII escaped. */
  const shown = (value: unknown): string =>
    JSON.stringify(value, null, 2).replace(
      /[^\n\x20-\x7e]/g,
      (match) => `\\u${match.charCodeAt(0).toString(16).padStart(4, "0")}`
    );

  const makeConfirm = () => {
    const granted = new Set<string>();
    return async (ctx: BetaConfirmContext): Promise<boolean> => {
      const name = ctx.member;
      if (!GATED.has(name)) {
        return true;
      }
      const detail = shown(ctx.input);
      const page = ctx.tabURL;
      const where = page ? shown(page) : "a tab with no URL";
      const question = `Allow ${name} on ${where}?\n${detail}`;
      const scheme = page === undefined ? undefined : URL.parse(page)?.protocol;
      if (scheme !== "http:" && scheme !== "https:") {
        return askUser(question); // no web origin: ask every time
      }
      // Una aprobación cubre solo esta entrada exacta en esta página.
      const key = `${name} ${page} ${detail}`;
      if (!granted.has(key) && (await askUser(question))) {
        granted.add(key);
      }
      return granted.has(key);
    };
  };

  // Un MyBrowser que también implementa javascript_exec y file_upload
  // file_upload también necesita el filePolicy de "Confine uploads and downloads"
  const browser = new MyBrowser(backend, {
    configs: { javascript_exec: { enabled: true }, file_upload: { enabled: true } },
    confirm: makeConfirm(),
    urlPolicy
  });
  ```
</CodeGroup>

Cada llamada a `make_confirm()` (typescript: `makeConfirm()`) devuelve un callable sin aprobaciones. Llámala una vez por cada conjunto de herramientas y dale a cada usuario su propio conjunto de herramientas.

Una aprobación abarca la página tal como la mostró el último informe de estado, y la página puede cambiar antes de que se ejecute la llamada. Las compras, los mensajes enviados y los términos aceptados ocurren a través de miembros ordinarios como `left_click` y `type`, por lo que `confirm` no puede identificarlos por nombre. Para que una persona los apruebe, pregunta también por esos miembros.

No habilites `javascript_exec` a menos que las [reglas de salida](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#put-egress-policy-on-the-container) limiten las conexiones salientes del navegador a los hosts que la tarea necesita. Un script puede enviar el contenido de la página a cualquier dirección que el navegador pueda alcanzar.

Una URL `javascript:` en una llamada a `navigate` también ejecuta un script en la página actual, incluso cuando `javascript_exec` está desactivado. `confirm` ve esa llamada como un `navigate`, así que el callable `confirm` del ejemplo la aprobaría. Tu controlador debe rechazar las URL `javascript:`. En el ejemplo, la [política de URL](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#write-a-url-policy) las rechaza antes de que se ejecute `confirm`.

### Aislar el host del navegador

Ejecuta el navegador en un contenedor o VM dedicado y con privilegios mínimos para cada sesión:

* Ejecútalo como un usuario que no sea root, con un sistema de archivos raíz de solo lectura donde el navegador lo permita.
* No montes nada del host más allá de los directorios de carga y descarga que configuraste, si los hay.
* Mantén las credenciales fuera del entorno y empieza con un perfil de navegador nuevo.
* No compartas ningún sistema de archivos con otras herramientas que Claude pueda llamar.

<Warning>
  El perfil del navegador no debe haber iniciado sesión en ninguna cuenta cuyos datos o acciones no le confiarías a Claude. `javascript_exec` se ejecuta con la propia autoridad de la página: sus cookies, su almacenamiento y sus sesiones iniciadas.
</Warning>

Ejecuta el código que llama a la API fuera del contenedor del navegador, porque ese código contiene tu clave de API y la conversación. El ejecutor de herramientas y `tool_result` (typescript: `toolResult`) ejecutan ambos el toolset en el proceso de ese código, así que el navegador no comparte el sistema de archivos del toolset. Trata el navegador como remoto: se aplica [Navegadores remotos y alojados](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#remote-and-hosted-browsers).

Trata todo lo que devuelve una página como no confiable, incluidos el texto de la página, las capturas de pantalla, las entradas de consola y de red, los títulos de las pestañas y los nombres de las descargas.

### Navegadores remotos y alojados

Algunos navegadores no comparten un sistema de archivos con el proceso que ejecuta el SDK. Algunos ejemplos son un navegador en otro contenedor, uno al que accedes mediante una URL de DevTools y uno de un servicio de navegador alojado. Con estos, la política de URL, la interceptación de solicitudes y `confirm` siguen ejecutándose en tu proceso.

Con un navegador alojado, el proveedor controla la salida y el aislamiento del host. Tu propia política de salida no se aplica allí, por lo que el hook de solicitudes del controlador es tu única comprobación sobre las solicitudes del navegador. Averigua a qué puede llegar la red del navegador.

Las comprobaciones de rutas incluidas no protegen a un navegador remoto. `BetaLocalFilePolicy` (typescript: `BetaNodeFilePolicy`) comprueba las rutas en el sistema de archivos del proceso que ejecuta el SDK, y el navegador lee y escribe en su propio sistema de archivos.

El SDK no puede detectar que un navegador es remoto. Así que, para un navegador remoto, mantén `file_upload` desactivado a menos que tu controlador compruebe las rutas de carga donde se ejecuta el navegador, con un `BetaFilePolicy` propio. Un `BetaFilePolicy` examina las rutas y los ID de documento de cada carga, y decide si Claude ve la ruta de una descarga.

Haz que un navegador remoto rechace las descargas, a menos que su propio host tenga la configuración de descargas de [Limitar las cargas y descargas](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#confine-uploads-and-downloads).

Mantén la clave de API del proveedor y la URL de conexión de la sesión, que puede contener una clave, fuera de los registros, los resultados de herramientas y el texto de error. Si el proveedor graba las sesiones, la grabación es otra copia de todo lo que Claude vio y escribió, y los términos de retención del proveedor se aplican a ella.

## Conjunto de herramientas de computadora

`BetaAbstractComputerToolset20260801` es la clase para la [herramienta de uso de computadora](https://platform.claude.com/docs/es/agents-and-tools/tool-use/computer-use-tool). Un controlador es una subclase con un método por herramienta, como `screenshot` o `left_click`. Comienza con el controlador de un socio en [Integraciones de socios](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#partner-integrations). Para escribir el tuyo, el repositorio claude-quickstarts tiene un [ejemplo mínimo de VNC](https://github.com/anthropics/claude-quickstarts/tree/main/computer-toolset) en Python y TypeScript. Controla un escritorio a través de VNC y no es código de producción.

La clase recibe `configs`, `confirm` y `tool_configs` (typescript: `toolConfigs`), y los pasas igual que con la clase del navegador. [Personalizar un controlador](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#customize-a-driver), [Manejar errores](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#handle-errors), [Iniciar llamadas mientras la respuesta hace streaming](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#start-calls-while-the-response-streams) y [Ejecutar sin el ejecutor de herramientas](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#run-without-the-tool-runner) también se aplican a ella, con estas diferencias:

* **Opciones exclusivas del navegador:** La clase no tiene [política de URL](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#write-a-url-policy), ni [política de archivos](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#confine-uploads-and-downloads), ni [informe de estado](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#report-browser-state).
* **`configs`:** Cada herramienta que implementes está activada por defecto. La configuración por herramienta se enumera en [Parámetros de la herramienta](https://platform.claude.com/docs/es/agents-and-tools/tool-use/computer-use-tool#tool-parameters).
* **`confirm`:** Si tu clase implementa `type`, `key` o `hold_key`, el constructor lanza un error de configuración a menos que pases un callable `confirm` o desactives esas herramientas con `configs`. Una clase que sobrescribe `execute` cuenta como si implementara las tres.
* **Llamadas omitidas:** Después de una llamada de computadora fallida, el ejecutor de herramientas responde las llamadas de computadora posteriores del turno con `Not executed: an earlier computer action in this turn failed.` Las llamadas del navegador reciben un texto diferente. En un bucle que escribas tú, responde las llamadas de computadora omitidas con ese texto exacto ([Acciones por lotes](https://platform.claude.com/docs/es/agents-and-tools/tool-use/computer-use-tool#batch-actions)).
* **`execute`:** En TypeScript, una sobrescritura de `execute` tipa `name` como `BetaComputerMemberName` e `input` como `BetaComputerMemberInput`. Ambos provienen de `@anthropic-ai/sdk/resources/beta`.
* **Texto de error:** El SDK no oculta las rutas locales en el texto de error de una herramienta de computadora ni en el texto que devuelve una herramienta. Claude lee ambos, cortados a 4,096 caracteres. Captura las excepciones en tus herramientas y lanza `ToolError` (con `throw` en TypeScript) con tu propio texto.

### Escribir un controlador de escritorio

Este controlador implementa cinco de las 17 herramientas enumeradas en [Acciones disponibles](https://platform.claude.com/docs/es/agents-and-tools/tool-use/computer-use-tool#available-actions). Una herramienta que no implementas se envía a la API como deshabilitada, como explica [Implementar un controlador](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#implement-a-driver). En el ejemplo, `display` representa tu propio wrapper alrededor de lo que controle el escritorio, como un cliente VNC o `xdotool`, y `ask_user` (typescript: `askUser`) representa tu propia función que le pregunta a una persona.

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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:
          # Las coordenadas de Claude son posiciones en píxeles en esta imagen.
          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 es [x, y], o None para hacer clic en el cursor. input.text es un
          # modificador que se mantiene pulsado, como "shift". No devuelve nada: Claude lee "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,
      # obligatorio para type y key; consulta Ejecutar el conjunto de herramientas de computadora de forma segura
      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)
  ```

  ```typescript TypeScript
  import Anthropic from "@anthropic-ai/sdk";
  import {
    BetaAbstractComputerToolset20260801,
    type BetaComputerCursorPositionResult,
    type BetaComputerToolsetOptions,
    type BetaScreenshotResult,
    type BetaToolsetCallContext
  } from "@anthropic-ai/sdk/helpers/beta/toolsets";
  import type {
    BetaComputerCursorPositionInput,
    BetaComputerKeyInput,
    BetaComputerLeftClickInput,
    BetaComputerScreenshotInput,
    BetaComputerTypeInput
  } from "@anthropic-ai/sdk/resources/beta";

  class MyDesktop extends BetaAbstractComputerToolset20260801 {
    constructor(
      private display: Display,
      options: BetaComputerToolsetOptions = {}
    ) {
      super(options);
    }

    protected override async screenshot(
      ctx: BetaToolsetCallContext,
      input: BetaComputerScreenshotInput
    ): Promise<BetaScreenshotResult> {
      // Las coordenadas de Claude son posiciones en píxeles en esta imagen.
      return { data: await this.display.pngBase64(), mediaType: "image/png" };
    }

    protected override async cursor_position(
      ctx: BetaToolsetCallContext,
      input: BetaComputerCursorPositionInput
    ): Promise<BetaComputerCursorPositionResult> {
      const { x, y } = await this.display.cursor();
      return { x, y };
    }

    protected override async left_click(
      ctx: BetaToolsetCallContext,
      input: BetaComputerLeftClickInput
    ): Promise<void> {
      // input.coordinate es [x, y], o null o undefined para hacer clic en el cursor. input.text
      // es un modificador que se mantiene presionado, como "shift". No hay nada que devolver: Claude lee "Clicked."
      await this.display.click(input.coordinate, input.text);
    }

    // La herramienta type se escribe type_ en TypeScript.
    protected override async type_(
      ctx: BetaToolsetCallContext,
      input: BetaComputerTypeInput
    ): Promise<void> {
      await this.display.type(input.text);
    }

    protected override async key(
      ctx: BetaToolsetCallContext,
      input: BetaComputerKeyInput
    ): Promise<void> {
      await this.display.press(input.text, input.repeat ?? 1);
    }

    override async close(): Promise<void> {
      await super.close(); // first, so no call is still using the desktop when it closes
      await this.display.close();
    }
  }

  const client = new Anthropic();
  const desktop = new MyDesktop(display, {
    // obligatorio para type y key; consulta Ejecutar el conjunto de herramientas de computadora de forma segura
    confirm: (ctx) => askUser(`Allow the "${ctx.member}" tool?`)
  });
  try {
    const runner = client.beta.messages.toolRunner({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [desktop],
      messages: [{ role: "user", content: "Open the calculator and compute 17 * 23." }]
    });
    for await (const message of runner) {
      console.log(message);
    }
  } finally {
    await desktop.close();
  }
  ```
</CodeGroup>

<Warning>
  El `confirm` de este ejemplo le pregunta a una persona antes de cada llamada que ejecuta el controlador. Antes de ejecutar el controlador contra cualquier cosa que no sea una máquina desechable, lee [Ejecutar el conjunto de herramientas de computadora de forma segura](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#run-the-computer-toolset-safely).
</Warning>

Cuatro herramientas no tienen campos de entrada: `screenshot`, `cursor_position`, `left_mouse_down` y `left_mouse_up`. Cada tipo de entrada, incluidos los suyos, proviene de `anthropic.types.beta` (typescript: `@anthropic-ai/sdk/resources/beta`).

En TypeScript, escribe las herramientas como métodos, no como campos con funciones flecha. [Implementar un controlador](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#implement-a-driver) explica el motivo.

### Escalar coordenadas y dimensionar imágenes

Cada punto que Claude envía es una posición en píxeles en las capturas de pantalla que devuelves: cada `coordinate` y `start_coordinate`, y ambas esquinas de una `region` de `zoom`. El SDK los pasa a tus herramientas sin cambios.

Si tus capturas de pantalla tienen un tamaño diferente al de la pantalla, convierte en ambas direcciones en tu controlador: los puntos de Claude a píxeles de la pantalla, y la posición del cursor que informas a píxeles de la captura de pantalla. Mantén el mismo tamaño de captura de pantalla durante toda la sesión y dale la relación de aspecto de la pantalla:

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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):
          # Rechaza el punto. Moverlo dentro del rango haría clic donde Claude no eligió.
          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)
  ```

  ```typescript TypeScript
  import { ToolError } from "@anthropic-ai/sdk/helpers/beta/toolsets";

  const SCREENSHOT_SIZE = { width: 1280, height: 720 }; // the size of every full screenshot
  const DISPLAY_SIZE = { width: 2560, height: 1440 };

  function toDisplay(coordinate: number[]): [number, number] {
    const [x, y] = coordinate;
    const { width, height } = SCREENSHOT_SIZE;
    if (x < 0 || x >= width || y < 0 || y >= height) {
      // Rechaza el punto. Moverlo dentro del rango haría clic donde Claude no eligió.
      throw new ToolError(`[${x}, ${y}] is outside the ${width}x${height} screenshot`);
    }
    return [
      Math.floor((x * DISPLAY_SIZE.width) / width),
      Math.floor((y * DISPLAY_SIZE.height) / height)
    ];
  }

  function toScreenshot(x: number, y: number): [number, number] {
    return [
      Math.floor((x * SCREENSHOT_SIZE.width) / DISPLAY_SIZE.width),
      Math.floor((y * SCREENSHOT_SIZE.height) / DISPLAY_SIZE.height)
    ];
  }

  toDisplay([640, 360]); // [1280, 720]
  ```
</CodeGroup>

Llama a `to_display` (typescript: `toDisplay`) sobre cada `coordinate` y `start_coordinate` antes de actuar sobre ellos, y a `to_screenshot` (typescript: `toScreenshot`) sobre la posición que informa `cursor_position`. Un clic o desplazamiento sin `coordinate` actúa en el cursor, así que no hay nada que convertir. Escala una `region` de `zoom` por el mismo factor. Una imagen de `zoom` no cambia esto: los siguientes puntos que Claude envía siguen siendo posiciones en píxeles en la captura de pantalla completa.

El SDK nunca lee ni redimensiona una imagen. La API rechaza una imagen que supere los límites del modelo, lo que termina la ejecución. Escala cada captura de pantalla e imagen de `zoom` en el controlador. Consulta [Dimensiona las capturas de pantalla para ajustarse a los límites de imagen](https://platform.claude.com/docs/es/agents-and-tools/tool-use/computer-use-tool#handle-coordinate-scaling-for-higher-resolutions). En una ejecución larga, una solicitud puede contener más de 20 imágenes, y entonces la API somete cada una a un límite más estricto. Consulta [Gestionar el historial de capturas de pantalla](https://platform.claude.com/docs/es/agents-and-tools/tool-use/computer-use-tool#manage-screenshot-history).

### Comprobaciones de entrada y resultados

No dependas del SDK para comprobar la entrada de una llamada. Haz que tu controlador compruebe los campos que usa y rechace una llamada que no pueda ejecutar: lanza `ToolError` (con `throw` en TypeScript). Claude lee el mensaje y puede enviar una llamada corregida.

Ningún SDK limita una `duration`, un recuento de `repeat` ni un `scroll_amount`. El paso 4 de [Ejecutar el conjunto de herramientas de computadora de forma segura](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#run-the-computer-toolset-safely) los cubre.

Lo que devuelve una herramienta determina lo que Claude lee:

| Herramienta                | Devuelve                           | Claude lee                                                                                      |
| -------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------- |
| `screenshot`, `zoom`       | `BetaScreenshotResult`             | Un bloque de imagen                                                                             |
| `cursor_position`          | `BetaComputerCursorPositionResult` | `X={x},Y={y}`                                                                                   |
| Cualquier otra herramienta | Nada, o una línea de texto         | Una confirmación breve, como `Clicked.`, y luego la línea devuelta en un bloque de texto propio |

### Ejecutar el conjunto de herramientas de computadora de forma segura

Lo que hay en la pantalla dirige la siguiente acción de Claude. Una página o una notificación puede intentar que Claude escriba en la ventana equivocada, o desencadenar una acción con efectos reales.

<Warning>
  Un controlador que implementa `type`, `key` o `hold_key` necesita un callable `confirm`. Sin uno, el constructor lanza un error de configuración, a menos que `configs` desactive esas herramientas. El SDK en sí no le pregunta a nadie: ejecuta cada llamada que tu `confirm` aprueba y, sin `confirm`, ejecuta todas las llamadas, incluido cada clic. Cada herramienta de computadora que implementas está activada de forma predeterminada.
</Warning>

Antes de ejecutar un controlador contra cualquier cosa que no sea una máquina desechable, sigue estos cinco pasos:

1. Aísla el escritorio. Ejecútalo en un contenedor o VM dedicado y con privilegios mínimos para cada sesión, sin credenciales y sin montajes del host. Instala solo las aplicaciones que la tarea necesita. Ejecuta fuera de él el código que llama a la API.
2. Controla con `confirm` las acciones que tienen efectos reales. Una compra o un mensaje enviado es un `left_click`, `type` o `key` ordinario, y `confirm` recibe la herramienta y su entrada, no la pantalla. Así que elige qué herramientas necesitan aprobación según las aplicaciones a las que Claude puede acceder en el escritorio.
3. Mantén las terminales, los diálogos de ejecución y los lanzadores fuera del escritorio a menos que la tarea necesite uno. Cuando uno tiene el foco del teclado, `type`, `key` y `hold_key` ejecutan lo que Claude escriba, y un clic puede iniciar un programa. Si la tarea necesita uno, pregunta a una persona antes de la entrada de teclado y los clics, y aísla el escritorio para que un comando no pueda llegar más allá de lo que la tarea necesita.
4. Rechaza la entrada que tu escritorio no debería aceptar, como un punto fuera de la captura de pantalla, un `wait` de varios minutos, o un `repeat` o `scroll_amount` muy grande. Lanza `ToolError` (con `throw` en TypeScript) en lugar de ajustar el valor al rango.
5. Trata todo lo que hay en la pantalla como no confiable, incluidos los títulos de ventana y el texto del portapapeles. No lo ejecutes ni lo transmitas sin comprobarlo.

El SDK aplica el paso 2 mediante el callable que pasas. Los demás pasos dependen de tu controlador y tu despliegue. También se aplican las precauciones de [Consideraciones de seguridad](https://platform.claude.com/docs/es/agents-and-tools/tool-use/computer-use-tool#security-considerations).

El siguiente `confirm` pregunta a una persona antes de cualquier entrada de teclado, mediante tu propia función `ask_user` (typescript: `askUser`), y aprueba todas las demás llamadas. `context.member` (typescript: `ctx.member`) es el nombre de la herramienta. Para que una persona apruebe también los clics, establece `ASK_FIRST` en `KEYBOARD | CLICKS` (typescript: `new Set([...KEYBOARD, ...CLICKS])`).

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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
      # se muestra la función de "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)
  ```

  ```typescript TypeScript
  import type { BetaComputerConfirmContext } from "@anthropic-ai/sdk/helpers/beta/toolsets";

  const KEYBOARD = ["type", "key", "hold_key"];
  const CLICKS = [
    "left_click",
    "right_click",
    "middle_click",
    "double_click",
    "triple_click",
    "left_click_drag",
    "left_mouse_down",
    "left_mouse_up"
  ];
  const ASK_FIRST = new Set(KEYBOARD);

  async function confirm(ctx: BetaComputerConfirmContext): Promise<boolean> {
    if (!ASK_FIRST.has(ctx.member)) {
      return true;
    }
    // se muestra la función de "Gate consequential members".
    return askUser(`Allow the "${ctx.member}" tool with this input?\n${shown(ctx.input)}`);
  }

  const desktop = new MyDesktop(display, { confirm });
  ```
</CodeGroup>

Una llamada rechazada obtiene un resultado de error y la ejecución continúa. Para una llamada `type` rechazada, Claude lee `The user did not grant permission to run 'type'. Do not retry it unless the user asks you to.`

Este `confirm` aprueba cada herramienta que no está en `ASK_FIRST`, incluida cualquier herramienta que agregues más adelante. Para fallar de forma cerrada, aprueba solo las herramientas sin efecto en el escritorio (`screenshot`, `zoom`, `cursor_position` y `wait`) y pregunta por todas las demás.

### Usar ambos conjuntos de herramientas en una solicitud

Pasa ambas instancias en `tools`. Cada llamada lleva su `toolset_name`, `browser` o `computer`, y el ejecutor de herramientas envía la llamada a la instancia correspondiente. Después de una llamada fallida, el ejecutor de herramientas omite solo las llamadas posteriores del turno al mismo conjunto de herramientas, así que una llamada de computadora fallida no omite las llamadas de navegador del turno.

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  # MyBrowser, backend y url_policy provienen del ejemplo avanzado.
  # confirm es la función de "Ejecuta el conjunto de herramientas de computadora de forma segura".
  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())
  ```

  ```typescript TypeScript
  // MyBrowser, backend y urlPolicy provienen del ejemplo avanzado.
  // confirm es la función de "Ejecuta el conjunto de herramientas de computadora de forma segura".
  const browser = new MyBrowser(backend, { urlPolicy });
  const desktop = new MyDesktop(display, { confirm });
  try {
    const runner = client.beta.messages.toolRunner({
      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,
      runToolsEagerly: true // so a call can start before the response ends
    });
    for await (const stream of runner) {
      console.log(await stream.finalMessage());
    }
  } finally {
    await Promise.all([browser.close(), desktop.close()]);
  }
  ```
</CodeGroup>

En un bucle que escribas tú, responde cada llamada con `tool_result` (typescript: `toolResult`) en la instancia cuyo `toolset_name` (typescript: `toolsetName`) coincida con el `toolset_name` de la llamada. En cada turno, registra las llamadas fallidas de cada conjunto de herramientas por separado, y responde una llamada omitida con el texto propio de ese conjunto de herramientas. [Ejecutar sin el ejecutor de herramientas](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#run-without-the-tool-runner) tiene el texto del navegador, y la viñeta de llamadas omitidas al principio de esta sección tiene el de la computadora.

## Referencia

Las opciones del constructor tienen el mismo significado en ambos SDK:

| Python                     | TypeScript     | Establece                                                               |
| -------------------------- | -------------- | ----------------------------------------------------------------------- |
| `configs`                  | `configs`      | Qué miembros están habilitados                                          |
| `confirm`                  | `confirm`      | El callable que aprueba o rechaza cada llamada                          |
| `url_policy`               | `urlPolicy`    | Tu política de URL, que el SDK llama antes de cada `navigate` a una URL |
| `file_policy`              | `filePolicy`   | Raíces de carga y exposición de la ruta de descarga                     |
| `tool_configs`             | `toolConfigs`  | Campos para la entrada de `tools`, como `cache_control`                 |
| El método `_browser_state` | `browserState` | El informe de estado                                                    |

La clase de navegador acepta todas las opciones de la tabla. La clase de computadora acepta `configs`, `confirm` y `tool_configs` (typescript: `toolConfigs`).

No puedes cambiar una opción después de la construcción. La [guía del conjunto de herramientas de navegador para Python](https://github.com/anthropics/anthropic-sdk-python/blob/main/browser-toolset.md), la [guía del conjunto de herramientas de navegador para TypeScript](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/browser-toolset.md), la [guía del conjunto de herramientas de computadora para Python](https://github.com/anthropics/anthropic-sdk-python/blob/main/computer-toolset.md) y la [guía del conjunto de herramientas de computadora para TypeScript](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/computer-toolset.md) describen cada opción. Las guías de Python también cubren las clases asíncronas, `BetaAsyncAbstractBrowserToolset20260801` y `BetaAsyncAbstractComputerToolset20260801`.

## Limitaciones

* **El SDK no incluye un navegador, un escritorio, un controlador listo para usar ni una política de URL:** Tú los proporcionas. Consulta [Integraciones de socios](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#partner-integrations), [Implementar un controlador](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#implement-a-driver) y [Escribir una política de URL](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#write-a-url-policy).
* **La política de URL solo ve las llamadas `navigate`:** No ve las solicitudes que hace una página, ni las páginas que se abren de cualquier otra forma, como desde un enlace en el que Claude hace clic o una redirección. Consulta [Interceptar solicitudes en el controlador](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#intercept-requests-in-the-driver).
* **Una aprobación se basa en el último informe de estado:** La página puede cambiar después de ese informe. El SDK no vuelve a comprobar la página antes de que se ejecute la llamada.
* **Las llamadas en un mismo conjunto de herramientas se ejecutan de una en una:** No puedes desactivar esto.
* **El SDK no comprueba que los ID de pestaña sean únicos ni cuántas pestañas hay, y no siempre comprueba que haya una pestaña activa:** La API rechaza un informe que incumpla esas reglas.
* **Para una llamada de computadora, `confirm` recibe la herramienta y su entrada, no la pantalla:** No puedes saber a partir de ellas qué hace un clic en un punto determinado.
* **El SDK no redimensiona las imágenes de un conjunto de herramientas de computadora:** La API rechaza una imagen que supere los límites del modelo. Consulta [Escalar coordenadas y dimensionar imágenes](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-sdk#scale-coordinates-and-size-images).

## Próximos pasos

<CardGroup cols={2}>
  <Card title="Herramienta de uso del navegador" icon="browser" href="https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-tool">
    Las herramientas miembro, el bloque `browser_state` y las consideraciones de seguridad de la herramienta.
  </Card>

  <Card title="Herramienta de uso de computadora" icon="computer" href="https://platform.claude.com/docs/es/agents-and-tools/tool-use/computer-use-tool">
    Las 17 herramientas, las coordenadas y los límites de imagen, y las consideraciones de seguridad de la herramienta.
  </Card>

  <Card title="Ejecutor de herramientas (SDK)" icon="arrows-clockwise" href="https://platform.claude.com/docs/es/agents-and-tools/tool-use/tool-runner">
    Cómo el SDK ejecuta el bucle y cómo cambiar los mensajes que envía.
  </Card>

  <Card title="Mitigar jailbreaks e inyecciones de prompts" icon="shield" href="https://platform.claude.com/docs/es/test-and-evaluate/strengthen-guardrails/mitigate-jailbreaks">
    Barreras de protección para cualquier aplicación que lea contenido no confiable.
  </Card>
</CardGroup>
