For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navegación principal
Theme

Uso de la computadora

Permite que un agente complete tareas en un navegador alojado por OpenAI.

El uso de la computadora permite que un agente navegue por sitios web e interactúe con las interfaces del navegador para probar un sitio, recopilar información o usar una aplicación a través de su interfaz.

La API de agentes ejecuta el navegador en un entorno alojado por OpenAI. Tu aplicación inicia la sesión y sigue sus eventos. El agente decide qué hacer a continuación según lo que observa en el navegador.

Para ejecutar una tarea en el navegador:

  1. Crea una sesión de navegador y guarda el ID de la sesión.
  2. Sigue los eventos de la sesión y envíale una tarea al agente.
  3. Atiende cada solicitud de acceso a un sitio web. Si la tarea requiere una cuenta, gestiona el inicio de sesión.
  4. Espera a que termine el turno del agente principal y verifica el resultado. Si se interrumpe la conexión, recupera la misma sesión antes de volver a intentarlo.
  5. Revisa la actividad guardada del navegador y, cuando termines, elimina la sesión.

Configurar el navegador

Sigue los pasos que se indican en los requisitos previos de la guía de inicio rápido de la API de agentes para crear una clave de API y exportar OPENAI_API_KEY. Luego, instala el SDK de OpenAI para tu lenguaje de programación. Para los ejemplos de JavaScript, instala openai y prompt-sync. Los ejemplos de cURL requieren Bash y jq.

Para habilitar el acceso al navegador:

  • Agrega { "type": "computer_use" } a agent.tools.
  • Establece environment.type en openai_hosted y environment.desktop.enabled en true.

Los siguientes ejemplos de JavaScript se ejecutan en secuencia. Primero creas una sesión y gestionas las aprobaciones de acceso a sitios web. Luego envías una tarea e imprimes la respuesta. Para empezar, crea una sesión de navegador con las capturas de pantalla habilitadas. Este paso no inicia ninguna tarea.

Crear una sesión de navegador
import OpenAI from "openai";
import { open } from "node:fs/promises";

const client = new OpenAI();
const session = await client.beta.agents.sessions.create({
  agent: {
    model: "gpt-6-astra",
    instructions:
      "Read public documentation in the browser. Do not sign in or change any website data. Report the page title and URL you find.",
    tools: [{ type: "computer_use", include_screenshots: true }],
  },
  environment: {
    type: "openai_hosted",
    desktop: { enabled: true },
    network: { access: "enabled" },
  },
});
console.log("Session ID:", session.id);

Gestionar el acceso a un origen

El navegador necesita la aprobación del usuario antes de acceder a cada nuevo origen web, incluso si el sitio es público. Habilitar el acceso a la red no aprueba estas solicitudes.

Aunque habilites la revisión automática, el navegador seguirá solicitando la aprobación del usuario para acceder a nuevos orígenes y enviando solicitudes de inicio de sesión. Sigue atendiendo ambos tipos de solicitudes en tu aplicación.

Para gestionar la aprobación de acceso a un origen:

  1. Cuando recibas agent.session.requires_action, recupera la sesión y revisa sus required_actions actuales.
  2. Busca las entradas pendientes de tipo computer_use_approval_request cuyo campo anidado request.type tenga el valor browser_origin_access.
  3. Muestra el origen solicitado (origin) y el motivo (reason), si se proporcionó. Luego, pide al usuario que elija entre approve, deny y cancel. Envía la decisión a través del punto de acceso de eventos de la sesión con el mismo request_id y un objeto response anidado que contenga type: "browser_origin_access" y decision, como se muestra a continuación.
Aprobar el acceso a un origen no exige confirmar cada acción

Si tu aplicación debe garantizar que el usuario confirme las compras, los cambios destructivos u otras acciones con consecuencias importantes antes de que se lleven a cabo, limita el acceso del navegador alojado a recursos que no permitan realizar esas acciones o usa un entorno de ejecución del navegador que controles. Si solicitas confirmación mediante una herramienta de función, dependes de que el agente llame a esa función.

Trata el contenido de los sitios web como contenido no confiable. No puede conceder permisos ni anular las instrucciones del usuario. Consulta la guía sobre confirmación y consentimiento para un entorno de ejecución que controles.

Define esta función auxiliar antes del código de la tarea. Gestiona las aprobaciones de acceso a orígenes y cancela las solicitudes de inicio de sesión porque esta tarea solo lee páginas públicas.

Responder a las solicitudes de acceso a un origen
import { createInterface } from "node:readline";

const inputLines = createInterface({
  input: process.stdin,
  crlfDelay: Infinity,
});
const answers = inputLines[Symbol.asyncIterator]();

/** @param {OpenAI} client */
async function respondToOriginApproval(client, sessionId, approval) {
  const request = approval.request;
  if (request.type === "browser_origin_access") {
    console.log("Requested origin:", request.origin);
    console.log(request.reason ?? "The browser needs access to this origin.");
    let input;
    do {
      process.stdout.write(
        "Allow access? [approve/deny/cancel, default deny] "
      );
      const answer = await answers.next();
      input = (answer.done ? "" : answer.value).trim().toLowerCase() || "deny";
    } while (!["approve", "deny", "cancel"].includes(input));
    const decision =
      input === "approve" ? "approve" : input === "cancel" ? "cancel" : "deny";
    await client.beta.agents.sessions.events.create(sessionId, {
      events: [
        {
          type: "agent.session.input.computer_use_approval_request_result",
          request_id: approval.request_id,
          response: { type: "browser_origin_access", decision },
        },
      ],
    });
  } else if (request.type === "browser_authentication") {
    // This public-page task must not sign in.
    await client.beta.agents.sessions.events.create(sessionId, {
      events: [
        {
          type: "agent.session.input.computer_use_approval_request_result",
          request_id: approval.request_id,
          response: { type: "browser_authentication", action: "cancel" },
        },
      ],
    });
  } else {
    throw new Error(`Unsupported approval request: ${request.type}`);
  }
}

Mantén abierto el flujo de eventos y atiende todas las solicitudes de aprobación pendientes. Usa request_id para dar seguimiento a las solicitudes y retira los controles de aprobación cuando una solicitud deje de aparecer en required_actions de la sesión. Cancelar una solicitud de aprobación no cancela la tarea.

Una respuesta 202 indica que se aceptó la decisión. La navegación aún podría estar en curso.

Ejecutar una tarea en el navegador

Pídele al agente que busque la guía de inicio rápido de la API de agentes en el sitio público para desarrolladores e indique su título y su URL.

Abre el flujo de eventos antes de enviar la tarea para que tu aplicación reciba los primeros eventos de progreso. Atiende las solicitudes de aprobación de acceso a orígenes a medida que lleguen para que el navegador pueda continuar.

Enviar la tarea y dar seguimiento al resultado
let screenshot;
const events = await client.beta.agents.sessions.events.stream(session.id);
const handledRequests = new Set();
let completed = false;
try {
  await client.beta.agents.sessions.events.create(session.id, {
    events: [
      {
        type: "agent.session.input.message",
        input: [
          {
            role: "user",
            content: [
              {
                type: "input_text",
                text: "Open https://developers.openai.com in the browser. Find the Agents API quickstart, then report its page title and URL.",
              },
            ],
          },
        ],
      },
    ],
  });
  for await (const event of events) {
    switch (event.type) {
      case "agent.session.requires_action": {
        const current = await client.beta.agents.sessions.retrieve(
          session.id
        );
        for (const approval of current.required_actions) {
          if (
            approval.type === "computer_use_approval_request" &&
            !handledRequests.has(approval.request_id)
          ) {
            await respondToOriginApproval(client, session.id, approval);
            handledRequests.add(approval.request_id);
          }
        }
        break;
      }
      case "agent.session.turn.item.done": {
        // Preserve streamed screenshots; retrieved history may omit image bytes.
        const item = event.item;
        if (item.type === "computer_use_call") {
          const output = item.output;
          if (
            output?.type === "computer_screenshot" &&
            output.image_url.startsWith("data:image/jpeg;base64,")
          ) {
            screenshot = Buffer.from(
              output.image_url.split(",", 2)[1],
              "base64"
            );
          }
        }
        break;
      }
      case "agent.session.turn.output_text.done":
        console.log(event.text);
        break;
      case "error":
        throw new Error(event.error.message);
      case "agent.session.failed":
      case "agent.session.environment.failed":
        throw new Error(`Agent lifecycle failure: ${event.type}`);
      case "agent.session.turn.failed":
        if (event.turn.subagent_id === null) {
          throw new Error(event.turn.error?.message ?? "Browser task failed");
        }
        break;
      case "agent.session.turn.cancelled":
        if (event.turn.subagent_id === null) {
          throw new Error("Browser task was cancelled");
        }
        break;
      case "agent.session.turn.completed":
        if (event.turn.subagent_id === null) completed = true;
        break;
    }
    if (completed) break;
  }
  if (!completed) {
    throw new Error("Stream closed before the browser task finished");
  }
  console.log();
} finally {
  events.controller.abort();
}

El ejemplo imprime la respuesta del agente. Comprueba que incluya el título y la URL de la guía de inicio rápido.

Cerrar el flujo de eventos no detiene la tarea. Para detenerla, cancela el turno. Si falla la conexión o no tienes certeza del resultado, sigue las instrucciones de recuperación. El ejemplo ampliado de cURL incluye información de diagnóstico sobre el transporte y los errores.

Seguir la actividad del navegador

Las operaciones del navegador aparecen como elementos computer_use_call en la salida de la sesión. Los elementos de actividad tienen la misma estructura en el flujo de eventos y en el historial guardado de la sesión.

Campo Significado
id Identificador del elemento de actividad.
turn_id Turno en el que se produjo la actividad.
title Descripción de la actividad del navegador, o null.
status in_progress, completed, failed o incomplete.
output Captura de pantalla, si está disponible.

Usa el título y el estado para mostrar el progreso en tu aplicación. Un elemento de actividad del navegador describe una operación de la herramienta. No contiene la respuesta final del agente ni indica si el turno completo ha finalizado. Consulta Eventos y elementos para conocer el modelo de eventos de la sesión.

Incluir capturas de pantalla

Para mostrar el progreso del navegador en tu aplicación, establece include_screenshots en true en la herramienta computer_use. De forma predeterminada, la salida de la API omite las capturas de pantalla, aunque el agente puede verlas.

Cuando se completa una actividad del navegador, el evento correspondiente incluye en item.output la última captura de pantalla emitida, si está disponible:

{
  "type": "computer_screenshot",
  "image_url": "data:image/jpeg;base64,..."
}

Usa image_url para mostrar la captura de pantalla. Algunas operaciones devuelven output: null incluso si las capturas están habilitadas, por lo que tu aplicación debe poder gestionar elementos de actividad sin imagen.

Las capturas de pantalla pueden contener datos sensibles de la página o de la cuenta. Muéstralas solo a usuarios autorizados y evita que se guarden en los registros de la aplicación.

Guarda las capturas de pantalla disponibles en los eventos agent.session.turn.item.done mientras procesas el flujo. El historial que recuperes podría omitir los bytes de la imagen. Conserva la imagen en tu aplicación para no depender de una solicitud posterior de la lista de elementos. Una vez que termina el turno, los ejemplos de los SDK enumeran las operaciones del navegador y guardan la última captura obtenida. La mayoría usa browser-screenshot.jpg. El ejemplo de Java usa un archivo temporal.

Leer la actividad del navegador
for await (const item of client.beta.agents.sessions.items.list(session.id, {
  order: "asc",
  limit: 100,
})) {
  if (item.type !== "computer_use_call") continue;
  console.log(item.title ?? "Browser activity", item.status);
}
if (screenshot) {
  const file = await open("browser-screenshot.jpg", "wx", 0o600);
  try {
    await file.writeFile(screenshot);
  } finally {
    await file.close();
  }
  console.log("Saved browser-screenshot.jpg");
} else {
  console.log("No browser screenshot was returned.");
}

Los ejemplos de los SDK no sobrescriben archivos existentes. Mueve o elimina browser-screenshot.jpg antes de volver a ejecutarlos.

Gestionar el inicio de sesión

Para realizar tareas como leer los issues de un repositorio privado de GitHub, es necesario iniciar sesión en el navegador. Tu aplicación gestiona el inicio de sesión para que los usuarios puedan elegir un método e ingresar sus credenciales fuera del chat.

El inicio de sesión puede requerir varias solicitudes. Por ejemplo, un sitio puede pedirle al usuario que elija el inicio de sesión por correo electrónico, ingrese su dirección de correo y luego ingrese un código de verificación. Crea la interfaz a partir de los métodos de inicio de sesión y los campos de cada solicitud.

Solo el agente principal puede solicitar la autenticación en el navegador. Los subagentes no pueden hacerlo. Este flujo admite direcciones de correo electrónico, contraseñas y códigos de verificación. No admite llaves de acceso ni el inicio de sesión mediante códigos QR. Si un sitio requiere un método no compatible, no se podrá completar el inicio de sesión con este flujo.

Mantén abierto el flujo de eventos de la tarea y atiende las solicitudes de aprobación de acceso a orígenes a medida que lleguen. Cuando recibas agent.session.requires_action, recupera la sesión y busca en sus required_actions actuales las entradas de tipo computer_use_approval_request cuyo campo anidado request.type tenga el valor browser_authentication.

Usa el objeto request anidado para mostrar la interfaz de inicio de sesión:

Campo Cómo usarlo
reason Si se proporciona un motivo, úsalo para explicar por qué se necesitan los datos. Puede ser null.
credential_origin Muestra el destino para el que se solicitan las credenciales. Puede ser null.
fields Muestra los campos de entrada según los valores de id, label, type y required de cada campo. La lista puede estar vacía.
options Muestra los métodos de inicio de sesión disponibles. Cada opción tiene un id, un label y una lista field_ids que identifica sus campos de entrada.

Si la solicitud ofrece métodos de inicio de sesión, permite que el usuario elija uno y muestra los campos correspondientes. Si no ofrece métodos, muestra directamente los campos de la solicitud.

Pide a los usuarios que ingresen sus credenciales solo si pueden verificar el destino. Si el origen de las credenciales no se indica o les resulta desconocido, cancela la solicitud de autenticación cuando no puedan verificarlo por otros medios.

Envía los datos ingresados por el usuario con el request_id de la acción que contiene la solicitud, o cancela la solicitud de autenticación si el usuario decide no iniciar sesión. Sigue atendiendo las solicitudes a medida que lleguen. Enviar una respuesta no garantiza que el inicio de sesión se haya completado. Sigue la tarea hasta que termine y comprueba el resultado.

Ejemplo: elegir un método y luego ingresar un código

Un sitio que permita iniciar sesión con un código enviado por correo electrónico o con una contraseña podría pedirle primero al usuario que elija un método, sin solicitarle que complete ningún campo:

Elegir un método de inicio de sesión
{
  "type": "computer_use_approval_request",
  "turn_id": "turn_example",
  "request_id": "request_choose_method",
  "request": {
    "type": "browser_authentication",
    "reason": "Choose how to sign in to the issue tracker",
    "credential_origin": "https://issues.example.com",
    "fields": [],
    "options": [
      { "id": "email_code", "label": "Email me a code", "field_ids": [] },
      { "id": "password", "label": "Use a password", "field_ids": [] }
    ]
  }
}

Si el usuario elige iniciar sesión con un código enviado por correo electrónico, envía selected_option: "email_code" con fields: [] usando el request_id de esta solicitud. Luego, el sitio podría pedir la dirección de correo electrónico y el código de verificación en solicitudes separadas. Muestra cada nueva solicitud con sus propios campos e identificadores e incluye selected_option solo cuando esa solicitud ofrezca opciones.

Devolver los datos ingresados por el usuario

Cuando el usuario complete una solicitud de inicio de sesión, envía su respuesta a través del punto de acceso de eventos de la sesión. Establece action en submit y usa los identificadores de la solicitud y de los campos de la aprobación pendiente. Por ejemplo, así se responde a una solicitud de dirección de correo electrónico:

{
  "events": [
    {
      "type": "agent.session.input.computer_use_approval_request_result",
      "request_id": "REQUEST_ID",
      "response": {
        "type": "browser_authentication",
        "action": "submit",
        "fields": [{ "field_id": "email", "value": "USER_ENTERED_VALUE" }]
      }
    }
  ]
}

Si la solicitud ofrece métodos de inicio de sesión, incluye el ID del método elegido en response.selected_option. Envía únicamente los campos que aparecen en field_ids de ese método, con un valor no vacío para cada campo obligatorio. Si el método no tiene campos, envía fields: [].

Si no se ofrecen métodos de inicio de sesión, omite selected_option y envía directamente los campos de la solicitud. Cuando la solicitud incluya campos, envía al menos uno, incluso si todos son opcionales.

Envía los datos de inicio de sesión únicamente a través de este evento específico. Los valores enviados quedan fuera de los datos de entrada del modelo del agente y se omiten en los elementos de respuesta de autenticación del historial de la sesión.

Trata todos los valores como datos sensibles, incluidas las direcciones de correo electrónico. Oculta los valores ingresados, evita que se guarden en registros, sistemas de análisis o en el estado guardado de la interfaz, y vacía el formulario después del envío. No envíes credenciales en mensajes comunes ni en los resultados de herramientas de función.

Omite turn_id en el evento de envío. Consulta los límites de envío de datos de autenticación para conocer las restricciones de los campos y de la carga útil.

Una respuesta 202 con el cuerpo vacío confirma que se aceptaron los datos enviados. El inicio de sesión aún podría estar pendiente. Sigue los eventos de la sesión para ver si llegan más solicitudes o si se reanuda la tarea. Deshabilita los reintentos automáticos de HTTP o del SDK al enviar credenciales. Si no tienes certeza de que se hayan aceptado los datos, vuelve a consultar la sesión antes de continuar.

Permitir que el usuario cancele

Si el usuario decide no iniciar sesión, responde a la solicitud pendiente con action: "cancel". Usa su request_id y omite fields y selected_option:

{
  "events": [
    {
      "type": "agent.session.input.computer_use_approval_request_result",
      "request_id": "REQUEST_ID",
      "response": { "type": "browser_authentication", "action": "cancel" }
    }
  ]
}

Esto cancela la solicitud de autenticación. Para detener la tarea, cancela el turno.

Ejecutar una tarea en el navegador con autenticación

Tu aplicación gestiona las aprobaciones de acceso a orígenes y las solicitudes de inicio de sesión mientras sigue los eventos de la sesión. Define la siguiente función auxiliar antes de ejecutar la tarea. La función muestra el destino, recopila los datos sin mostrar los valores ingresados y envía la respuesta. Si el usuario decide no continuar, la función cancela la solicitud de inicio de sesión.

Gestionar las aprobaciones del navegador y el inicio de sesión
import promptSync from "prompt-sync";

const prompt = promptSync({ sigint: true });

/** @param {OpenAI} client */
async function respondToComputerUseApproval(client, sessionId, approval) {
  const request = approval.request;
  if (request.type === "browser_origin_access") {
    console.log("Requested origin:", request.origin);
    console.log(request.reason ?? "The browser needs access to this origin.");
    let input;
    do {
      input =
        prompt("Allow access? [approve/deny/cancel, default deny] ")
          .trim()
          .toLowerCase() || "deny";
    } while (!["approve", "deny", "cancel"].includes(input));
    const decision =
      input === "approve" ? "approve" : input === "cancel" ? "cancel" : "deny";
    await client.beta.agents.sessions.events.create(sessionId, {
      events: [
        {
          type: "agent.session.input.computer_use_approval_request_result",
          request_id: approval.request_id,
          response: { type: "browser_origin_access", decision },
        },
      ],
    });
    return;
  }
  if (request.type !== "browser_authentication") {
    throw new Error(`Unsupported approval request: ${request.type}`);
  }
  async function cancelSignIn() {
    await client.beta.agents.sessions.events.create(
      sessionId,
      {
        events: [
          {
            type: "agent.session.input.computer_use_approval_request_result",
            request_id: approval.request_id,
            response: { type: "browser_authentication", action: "cancel" },
          },
        ],
      },
      { maxRetries: 0 }
    );
  }
  console.log(request.reason ?? "Sign in to continue");
  console.log(
    "Credential origin:",
    request.credential_origin ?? "Not supplied"
  );
  const consent = prompt("Have you verified the sign-in destination? [y/N] ")
    .trim()
    .toLowerCase();
  if (!["y", "yes"].includes(consent)) {
    await cancelSignIn();
    return;
  }

  let selectedOption;
  let activeFields = request.fields;
  if (request.options.length > 0) {
    request.options.forEach((option, index) => {
      console.log(`${index + 1}. ${option.label}`);
    });
    let choice;
    do {
      const input = prompt("Choose a sign-in method, or enter cancel: ")
        .trim()
        .toLowerCase();
      if (input === "cancel") {
        await cancelSignIn();
        return;
      }
      choice = Number(input);
    } while (
      !Number.isInteger(choice) ||
      choice < 1 ||
      choice > request.options.length
    );
    selectedOption = request.options[choice - 1];
    activeFields = request.fields.filter((field) =>
      selectedOption.field_ids.includes(field.id)
    );
  }

  const fields = [];
  do {
    for (const field of activeFields) {
      while (true) {
        const value = prompt.hide(
          `${field.label}${field.required ? "" : " (optional)"} (leave blank for options): `
        );
        if (value.length > 0) {
          fields.push({ field_id: field.id, value });
          break;
        }
        let action;
        do {
          action = prompt(
            field.required
              ? "Enter a value or cancel sign-in? [enter/cancel, default enter] "
              : "Skip this field, enter a value, or cancel sign-in? [skip/enter/cancel, default skip] "
          )
            .trim()
            .toLowerCase();
        } while (
          ![
            "",
            "enter",
            "cancel",
            ...(field.required ? [] : ["skip"]),
          ].includes(action)
        );
        if (action === "cancel") {
          await cancelSignIn();
          return;
        }
        if (!field.required && (action === "" || action === "skip")) break;
      }
    }
    if (!selectedOption && activeFields.length > 0 && fields.length === 0) {
      console.log(
        "This form requires at least one field. Enter a value or cancel sign-in."
      );
    }
  } while (!selectedOption && activeFields.length > 0 && fields.length === 0);
  await client.beta.agents.sessions.events.create(
    sessionId,
    {
      events: [
        {
          type: "agent.session.input.computer_use_approval_request_result",
          request_id: approval.request_id,
          response: {
            type: "browser_authentication",
            action: "submit",
            fields,
            ...(selectedOption ? { selected_option: selectedOption.id } : {}),
          },
        },
      ],
    },
    { maxRetries: 0 }
  );
}

El siguiente ejemplo le pide al agente que lea los issues de un repositorio privado de GitHub. Reemplaza https://github.com/acme/private-repo/issues por la URL de una página de issues a la que tengas acceso.

Abre el flujo de eventos antes de enviar la tarea y llama a la función auxiliar cada vez que la sesión requiera datos del usuario.

Leer los issues de un repositorio privado
import OpenAI from "openai";

const client = new OpenAI();
const session = await client.beta.agents.sessions.create({
  agent: {
    model: "gpt-6-astra",
    instructions:
      "Read the requested GitHub issue list in the browser. Request sign-in when needed. Do not create, edit, comment on, or close issues.",
    tools: [{ type: "computer_use", include_screenshots: false }],
  },
  environment: {
    type: "openai_hosted",
    desktop: { enabled: true },
    network: { access: "enabled" },
  },
});
console.log("Session ID:", session.id);

let completed = false;
let readyToDelete = false;
try {
  const events = await client.beta.agents.sessions.events.stream(session.id);
  const handledRequests = new Set();
  try {
    await client.beta.agents.sessions.events.create(session.id, {
      events: [
        {
          type: "agent.session.input.message",
          input: [
            {
              role: "user",
              content: [
                {
                  type: "input_text",
                  // Replace this illustrative URL with your private repository.
                  text: "Open https://github.com/acme/private-repo/issues in the browser. Sign in if needed, then report the title and URL of the most recently updated open issue. Do not make changes.",
                },
              ],
            },
          ],
        },
      ],
    });
    for await (const event of events) {
      switch (event.type) {
        case "agent.session.requires_action": {
          const current = await client.beta.agents.sessions.retrieve(
            session.id
          );
          for (const approval of current.required_actions) {
            if (
              approval.type === "computer_use_approval_request" &&
              !handledRequests.has(approval.request_id)
            ) {
              await respondToComputerUseApproval(client, session.id, approval);
              handledRequests.add(approval.request_id);
            }
          }
          break;
        }
        case "agent.session.turn.output_text.done":
          console.log(event.text);
          break;
        case "error":
          throw new Error(event.error.message);
        case "agent.session.failed":
        case "agent.session.environment.failed":
          throw new Error(`Agent lifecycle failure: ${event.type}`);
        case "agent.session.turn.failed":
          if (event.turn.subagent_id === null) {
            throw new Error(event.turn.error?.message ?? "Browser task failed");
          }
          break;
        case "agent.session.turn.cancelled":
          if (event.turn.subagent_id === null) {
            throw new Error("Browser task was cancelled");
          }
          break;
        case "agent.session.turn.completed":
          if (event.turn.subagent_id === null) completed = true;
          break;
      }
      if (completed) break;
    }
    if (!completed) {
      throw new Error("Stream closed before the browser task finished");
    }
    console.log();
  } finally {
    events.controller.abort();
  }
  readyToDelete = completed;
} catch (error) {
  console.error(
    `Session ${session.id} was kept. Use the same ID to check its status before trying again.`
  );
  throw error;
} finally {
  if (readyToDelete) await client.beta.agents.sessions.delete(session.id);
}

El inicio de sesión puede requerir varias solicitudes. Sigue atendiendo las solicitudes de aprobación hasta que termine la tarea. Comprueba que el resultado del agente corresponda a la tarea solicitada. En este ejemplo, verifica el título y la URL del issue que indicó el agente.

Consultar el historial de inicio de sesión

Las solicitudes de autenticación y las respuestas aceptadas aparecen en el historial de la sesión y en los eventos agent.session.turn.item.added. Los elementos de solicitud contienen los metadatos del formulario de inicio de sesión. Los elementos de respuesta registran la acción aceptada y omiten los valores de las credenciales enviadas.

Usa el historial para revisar interacciones anteriores. Para saber si un formulario de inicio de sesión aún requiere datos del usuario, consulta los valores actuales de required_actions de la sesión. El registro de una respuesta no garantiza que el inicio de sesión se haya completado.

El historial no tiene elementos específicos para las solicitudes y respuestas de aprobación de acceso a orígenes. Gestiona estas aprobaciones mediante required_actions.

Hacerle una pregunta al usuario

Para pedir una aclaración o permitir que el usuario elija una opción durante una tarea, define una herramienta de función en agent.tools. Por ejemplo, puedes definir request_user_response para mostrar una pregunta y recopilar la respuesta. Tu aplicación proporciona el nombre de la herramienta, el esquema de sus argumentos y la interfaz de usuario.

Cuando recibas agent.session.requires_action, busca la llamada pendiente de tipo function_call para tu herramienta y usa sus arguments para mostrar la pregunta. Devuelve la respuesta a través de agent.session.input.tool_result con el turn_id y el call_id de la acción. Establece success: true y coloca la respuesta en output. Si la respuesta es estructurada, serialízala como una cadena JSON. Si el usuario decide no responder, devuelve success: false con un mensaje de error en error.

Sigue los eventos de la sesión después de devolver la respuesta. Las funciones auxiliares de aprobación del navegador que se mostraron antes gestionan el acceso a orígenes y el inicio de sesión. Amplía tu controlador de eventos para que también gestione la herramienta que hace las preguntas.

El modelo puede ver los resultados de las herramientas de función y estos se guardan en el historial de la sesión. Recopila las contraseñas y los códigos de verificación mediante la autenticación en el navegador.

Reanudar la gestión de aprobaciones

Si tu aplicación se desconecta o falla una respuesta a una solicitud de aprobación, recupera la misma sesión y revisa sus required_actions actuales antes de continuar. Vuelve a crear los formularios únicamente para las solicitudes que sigan pendientes y retira los controles de las solicitudes que ya no aparezcan.

Si se desconecta el flujo de eventos, sigue las instrucciones de recuperación del flujo para volver a recibir eventos. Al restablecer la conexión, la aplicación no debe reenviar automáticamente la tarea ni una respuesta de aprobación.

Usa el estado de la respuesta para decidir cómo continuar:

Resultado Qué debe hacer tu aplicación
202 Borra los valores enviados y sigue los eventos de la sesión para conocer el resultado.
400 Comprueba que el tipo de respuesta, la opción seleccionada, los ID de los campos y los valores obligatorios coincidan con la solicitud pendiente. Omite los campos en las respuestas de cancelación y de acceso a orígenes.
404 Verifica el ID de la sesión, el ID de la solicitud y el tipo de respuesta. Vuelve a recuperar la sesión, ya que la solicitud podría haber dejado de estar disponible.
409 Vuelve a consultar la sesión. Es posible que la solicitud haya vencido, que su turno haya terminado o que ya se haya aceptado otra respuesta.
Se perdió la conexión antes de recibir la confirmación Si se pierde la conexión en ese punto, se desconoce si se aceptó la respuesta. Restablece la conexión y recupera la sesión antes de decidir si vuelves a intentarlo.

Las solicitudes de autenticación vencen después de cinco minutos. El turno al que pertenecen puede terminar mientras el usuario ingresa los datos. Vuelve a consultar la sesión antes de restaurar un formulario de inicio de sesión.

Si reintentas un envío de datos de autenticación, usa el mismo request_id, la misma opción seleccionada y el mismo valor para cada campo. Un reintento idéntico no vuelve a llenar el formulario del navegador. Si cambias los valores después de que se haya aceptado el envío, recibirás 409. Para volver a intentar el inicio de sesión, el agente debe generar una nueva solicitud.

Si falla el envío de una aprobación de acceso a un origen, vuelve a enviar la misma decisión. Si cambias una decisión que ya se aceptó, recibirás 409. Las solicitudes de acceso a orígenes permanecen pendientes durante el turno al que pertenecen. El plazo de cinco minutos de las solicitudes de autenticación no se aplica a estas solicitudes.

Controlar el acceso a la red

Usa la configuración network del entorno alojado para controlar el acceso saliente tanto del navegador como del código que se ejecuta en el entorno. Permite el acceso al sitio web de destino y a los dominios necesarios para cargar los recursos de la página o seguir redirecciones.

La aprobación de acceso a un origen es una decisión independiente del usuario y no anula la política de red. Consulta la configuración de acceso a la red para configurar el entorno.

Continuar y liberar recursos

Reutiliza la misma sesión para las tareas posteriores que necesiten el estado del navegador. Las cookies de inicio de sesión pueden vencer y, al reciclar el entorno, se borra el estado del navegador.

Cuando termines, recupera los resultados que necesites y elimina la sesión para solicitar la limpieza del entorno:

Eliminar la sesión de navegador
await client.beta.agents.sessions.delete(session.id);

Consulta Sandboxes alojados por OpenAI para saber cuánto dura el entorno y qué ocurre al eliminarlo.

Referencia para la gestión de solicitudes

Límites de envío de datos de autenticación

Cada envío puede incluir hasta seis valores de campo y cada campo puede aparecer una sola vez. Cada valor admite hasta 16 384 caracteres. En conjunto, los valores de los campos serializados y la opción seleccionada no deben superar los 120 KiB.

Usa los identificadores de la solicitud y de los campos de la aprobación pendiente y proporciona un valor no vacío para cada campo obligatorio. Consulta la referencia de la API de eventos de la sesión para ver el esquema de la solicitud.

Ejemplo ampliado de gestión de solicitudes y del flujo de eventos con cURL

Usa esta versión cuando necesites distinguir entre fallas de transporte, errores HTTP y respuestas no válidas a solicitudes de creación de sesiones. También filtra la salida del flujo de eventos e indica los tipos y códigos de error. Sigue el mismo procedimiento con dos terminales que el primer ejemplo. Atiende las solicitudes de aprobación en la segunda terminal a medida que lleguen.

Crear una sesión y examinar las fallas del flujo de eventos
create_browser_session() {
  unset session_id
  local result http_status body curl_status
  if result=$(curl --silent --fail-with-body --write-out '\n%{http_code}' \
    https://api.openai.com/v1/agents/sessions \
    -H "OpenAI-Beta: agents=v1" \
    -H "Authorization: Bearer $OPENAI_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "agent": {
        "model": "gpt-6-astra",
        "instructions": "Read public documentation in the browser. Do not sign in or change any website data. Report the page title and URL you find.",
        "tools": [{ "type": "computer_use", "include_screenshots": true }]
      },
      "environment": {
        "type": "openai_hosted",
        "desktop": { "enabled": true },
        "network": { "access": "enabled" }
      }
    }'); then curl_status=0; else curl_status=$?; fi
  http_status=$(printf '%s\n' "$result" | tail -n 1)
  body=$(printf '%s\n' "$result" | sed '$d')
  [[ "$http_status" =~ ^[0-9]{3}$ ]] || http_status=000

  if [ "$curl_status" -ne 0 ] || [[ ! "$http_status" =~ ^2[0-9][0-9]$ ]]; then
    printf '%s' "$body" | jq --raw-input --slurp --compact-output \
      --arg status "$http_status" --arg curl_status "$curl_status" '
        def identifier:
          if type == "string" and test("^[A-Za-z][A-Za-z0-9_]{0,79}$") then . else null end;
        (try fromjson catch {}) as $response
        | (if ($response | type) == "object" then $response.error // $response else {} end) as $error
        | if $curl_status != "0" and $curl_status != "22" then
            {status: $status, type: "transport_error", code: ("curl_" + $curl_status)}
          else
            {status: $status, type: ((try ($error.type | identifier) catch null) // "http_error"),
             code: (try ($error.code | identifier) catch null)}
          end' >&2
    return 1
  fi
  if ! session_id=$(printf '%s' "$body" | jq --exit-status --raw-output \
    'select(type == "object") | .id | select(type == "string" and length > 0)' 2>/dev/null); then
    unset session_id
    printf '{"status":"%s","type":"invalid_response","code":"missing_session_id"}\n' "$http_status" >&2
    return 1
  fi
  printf 'Session ID: %s\n' "$session_id"
}
create_browser_session

# Terminal 1: use session_id from the creation request.
# Keep this stream open. Wait for HTTP 200 before sending input.
set -o pipefail
curl --silent --show-error --dump-header - --suppress-connect-headers \
  --no-buffer --fail-with-body \
  "https://api.openai.com/v1/agents/sessions/$session_id/events" \
  -H "OpenAI-Beta: agents=v1" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Accept: text/event-stream" \
  | jq --null-input --raw-input --compact-output --unbuffered '
      def identifier:
        if type == "string" and test("^[A-Za-z][A-Za-z0-9_]{0,79}$") then . else null end;
      def safe_error:
        if type == "object" then {type: (.type | identifier), code: (.code | identifier)} else null end;
      def public_event:
        if .type == "agent.session.turn.output_text.done" then {type, text}
        elif .type == "agent.session.turn.completed" or .type == "agent.session.turn.failed" or .type == "agent.session.turn.cancelled" then
          {type, turn_id: .turn.id, subagent_id: .turn.subagent_id, error: (.turn.error | safe_error)}
        elif .type == "error" or .type == "agent.session.failed" or .type == "agent.session.environment.failed" then
          {type, error: ((.error // .session.error // .environment.error) | safe_error)}
        elif .type == "agent.session.requires_action" then {type}
        else empty end;
      foreach inputs as $raw (
        {body: "", headers: false, failed: false, output: []};
        .output = []
        | ($raw | rtrimstr([13] | implode)) as $line
        | if ($line | startswith("HTTP/")) then
            (try ($line | capture("^(?<protocol>HTTP/[0-9.]+) (?<status>[0-9]{3})(?: |$)")) catch null) as $http
            | if $http == null then . else
                .body = "" | .headers = true | .status = $http.status
                | .failed = (($http.status | tonumber) >= 300)
                | .output = [$http.protocol + " " + $http.status]
              end
          elif .headers then
            if $line == "" then .headers = false else . end
          elif ($line | startswith("data:")) then
            (try ($line | ltrimstr("data:") | fromjson) catch null) as $event
            | .output = [$event | select(type == "object") | public_event]
          elif .failed and .body != null then
            .body += ($line + "\n")
            | (try (.body | fromjson) catch null) as $body
            | if ($body | type) == "object" then
                (($body.error // $body) | safe_error) as $error
                | .output = [{status: .status, type: ($error.type // "http_error"), code: $error.code}]
                | .body = null
              else . end
          else . end;
        .output[])'

# Terminal 2: replace sess_123 with the ID printed in terminal 1.
# Export OPENAI_API_KEY in this terminal too.
session_id="sess_123"
curl --fail-with-body "https://api.openai.com/v1/agents/sessions/$session_id/events" \
  -H "OpenAI-Beta: agents=v1" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [{
      "type": "agent.session.input.message",
      "input": [{
        "role": "user",
        "content": [{
          "type": "input_text",
          "text": "Open https://developers.openai.com in the browser. Find the Agents API quickstart, then report its page title and URL."
        }]
      }]
    }]
  }'