For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navigation principale
Theme

Utilisation de l’ordinateur

Confiez des tâches à un agent dans un navigateur hébergé par OpenAI.

La fonctionnalité Utilisation de l’ordinateur permet à un agent de naviguer sur des sites web et d’interagir avec les interfaces affichées dans le navigateur pour tester un site, recueillir des informations ou utiliser une application via son interface utilisateur.

L’API Agents exécute le navigateur dans un environnement hébergé par OpenAI. Votre application démarre la session et en suit les événements. L’agent choisit sa prochaine action en fonction de ce qu’il observe dans le navigateur.

Pour exécuter une tâche dans le navigateur :

  1. Créez une session de navigateur et enregistrez son identifiant.
  2. Suivez les événements de la session et envoyez une tâche à l’agent.
  3. Traitez chaque demande d’accès à un site. Si la tâche nécessite un compte, gérez la connexion au site.
  4. Attendez la fin du tour de l’agent principal et vérifiez son résultat. En cas de perte de connexion, reprenez la même session avant de réessayer.
  5. Consultez l’activité enregistrée du navigateur, puis supprimez la session lorsque vous avez terminé.

Configurer le navigateur

Suivez les étapes préalables du guide de démarrage rapide de l’API Agents pour créer une clé API et exporter OPENAI_API_KEY, puis installez le SDK OpenAI pour votre langage. Pour les exemples JavaScript, installez openai et prompt-sync. Les exemples cURL nécessitent Bash et jq.

Pour activer l’accès au navigateur :

  • Ajoutez { "type": "computer_use" } à agent.tools.
  • Définissez environment.type sur openai_hosted et environment.desktop.enabled sur true.

Les exemples JavaScript ci-dessous se suivent : créez une session, traitez les demandes d’approbation d’accès aux sites, puis envoyez une tâche et affichez la réponse. Commencez par créer une session de navigateur en activant les captures d’écran. La création de la session ne lance aucune tâche.

Créer une session de navigateur
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);

Gérer l’accès aux origines

Le navigateur doit obtenir l’approbation de l’utilisateur avant d’accéder à chaque nouvelle origine web, y compris pour les sites publics. Activer l’accès réseau ne suffit pas à approuver ces demandes.

Même si vous activez la révision automatique, le navigateur continue de demander l’approbation de l’utilisateur pour accéder aux origines et d’émettre des demandes de connexion. Continuez à traiter ces deux types de demandes dans votre application.

Pour traiter une demande d’approbation d’accès à une origine :

  1. À la réception de agent.session.requires_action, récupérez la session et examinez le contenu actuel de required_actions.
  2. Repérez les entrées computer_use_approval_request en attente dont le champ imbriqué request.type vaut browser_origin_access.
  3. Affichez l’origine demandée (origin) et le motif (reason), s’il est fourni, puis recueillez la décision de l’utilisateur : approve, deny ou cancel. Envoyez cette décision au point de terminaison des événements de session avec le même request_id et un objet response imbriqué contenant type: "browser_origin_access" et decision, comme dans l’exemple ci-dessous.
L’approbation d’une origine n’impose pas de confirmation avant chaque action

Si votre application doit garantir une confirmation avant tout achat, toute modification destructrice ou toute autre action lourde de conséquences, limitez l’accès du navigateur hébergé aux ressources qui ne permettent pas ces actions, ou utilisez un environnement d’exécution du navigateur que vous contrôlez. Si vous utilisez un outil de type fonction pour demander confirmation, la demande dépend de l’appel de cette fonction par l’agent.

Considérez le contenu des sites web comme non fiable. Il ne peut ni accorder d’autorisation ni prévaloir sur les instructions de l’utilisateur. Consultez les consignes sur la confirmation et le consentement dans un environnement d’exécution que vous contrôlez.

Définissez la fonction auxiliaire suivante avant le code de la tâche. Elle traite les demandes d’approbation d’accès aux origines et annule les demandes de connexion, car cette tâche consulte uniquement des pages publiques.

Répondre aux demandes d’accès aux origines
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}`);
  }
}

Gardez le flux ouvert et traitez chaque demande d’approbation en attente. Utilisez request_id pour suivre les demandes et retirez les commandes d’approbation de l’interface dès qu’une demande ne figure plus dans required_actions pour la session. L’annulation d’une demande d’approbation n’annule pas la tâche.

Une réponse 202 indique que la décision a été acceptée. Elle ne garantit pas que le navigateur a fini de charger la page.

Exécuter une tâche dans le navigateur

Demandez à l’agent de trouver le guide de démarrage rapide de l’API Agents sur le site public destiné aux développeurs et d’en indiquer le titre et l’URL.

Ouvrez le flux d’événements avant d’envoyer la tâche pour que votre application reçoive les premiers événements de progression. Traitez les demandes d’approbation d’accès aux origines à mesure qu’elles arrivent pour permettre au navigateur de poursuivre la tâche.

Envoyer la tâche et suivre son exécution jusqu’au résultat
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();
}

L’exemple affiche la réponse de l’agent. Vérifiez qu’elle contient le titre et l’URL du guide de démarrage rapide.

Fermer le flux d’événements n’arrête pas la tâche. Pour l’arrêter, annulez le tour. En cas de problème de connexion ou de doute sur le résultat, suivez la procédure de reprise. L’exemple cURL détaillé fournit des informations de diagnostic sur le transport et les erreurs.

Suivre l’activité du navigateur

Les opérations du navigateur apparaissent sous forme d’éléments computer_use_call dans la sortie de la session. Ces éléments ont la même structure dans le flux d’événements et dans l’historique enregistré de la session.

Champ Signification
id Identifiant de l’élément d’activité.
turn_id Tour au cours duquel l’activité a eu lieu.
title Description de l’activité du navigateur, ou null.
status in_progress, completed, failed ou incomplete.
output Capture d’écran, si elle est disponible.

Utilisez le titre et le statut pour afficher la progression dans votre application. Un élément d’activité du navigateur décrit une opération de l’outil. Il ne contient ni la réponse finale de l’agent ni le statut du tour dans son ensemble. Consultez la section Événements et éléments pour en savoir plus sur le modèle d’événements de session.

Inclure des captures d’écran

Pour afficher la progression du navigateur dans votre application, définissez include_screenshots sur true pour l’outil computer_use. Par défaut, l’API n’inclut pas les captures d’écran dans sa sortie, mais l’agent peut quand même les voir.

Lorsqu’une activité du navigateur se termine, l’événement correspondant contient, dans item.output, la dernière capture d’écran émise si elle est disponible :

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

Utilisez image_url pour afficher la capture d’écran. Certaines opérations renvoient output: null, même lorsque les captures d’écran sont activées. Votre application doit donc pouvoir traiter les éléments d’activité sans image.

Les captures d’écran peuvent contenir des données sensibles provenant d’une page ou d’un compte. Affichez-les uniquement pour les utilisateurs autorisés et excluez-les des journaux de l’application.

Récupérez les captures d’écran disponibles dans les événements agent.session.turn.item.done à mesure que vous lisez le flux. L’historique récupéré peut ne pas contenir les octets de l’image. Conservez donc l’image dans votre application sans attendre une requête ultérieure pour lister les éléments. Une fois le tour terminé, les exemples utilisant un SDK listent les activités du navigateur et enregistrent la dernière capture d’écran récupérée. La plupart utilisent browser-screenshot.jpg. L’exemple Java utilise un fichier temporaire.

Consulter l’activité du navigateur
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.");
}

Les exemples utilisant un SDK n’écrasent pas les fichiers existants. Déplacez ou supprimez browser-screenshot.jpg avant de les exécuter à nouveau.

Gérer la connexion aux sites

Certaines tâches, comme la consultation des issues d’un dépôt GitHub privé, nécessitent une session authentifiée dans le navigateur. Votre application gère la connexion pour que les utilisateurs puissent choisir une méthode de connexion et saisir leurs identifiants en dehors de la discussion.

La connexion peut nécessiter plusieurs demandes successives. Par exemple, un site peut demander à l’utilisateur de choisir la connexion par e-mail, de saisir une adresse e-mail, puis un code de vérification. Construisez votre interface à partir des méthodes de connexion et des champs de chaque demande.

Seul l’agent principal peut demander une authentification dans le navigateur. Les sous-agents ne le peuvent pas. Ce parcours prend en charge les adresses e-mail, les mots de passe et les codes de vérification, mais pas les clés d’accès ni la connexion par code QR. Si un site exige une méthode non prise en charge, l’utilisateur ne pourra pas s’y connecter via ce parcours.

Gardez le flux d’événements de la tâche ouvert et traitez les demandes d’approbation d’accès aux origines à mesure qu’elles arrivent. À la réception de agent.session.requires_action, récupérez la session et recherchez dans son champ required_actions les entrées computer_use_approval_request dont le champ imbriqué request.type vaut browser_authentication.

Utilisez l’objet request imbriqué pour afficher votre interface de connexion :

Champ Utilisation
reason Si un motif est fourni, indiquez à l’utilisateur pourquoi une saisie est nécessaire. Ce champ peut valoir null.
credential_origin Affichez la destination à laquelle les identifiants sont destinés. Ce champ peut valoir null.
fields Affichez les champs de saisie à partir des valeurs id, label, type et required de chaque champ. La liste peut être vide.
options Affichez les méthodes de connexion disponibles. Chaque option possède un identifiant id, un libellé label et une liste field_ids qui identifie ses champs de saisie.

Si la demande propose des méthodes de connexion, laissez l’utilisateur en choisir une et affichez les champs associés. Sinon, affichez directement les champs de la demande.

Demandez à l’utilisateur de saisir ses identifiants uniquement pour une destination qu’il peut vérifier. Si l’origine à laquelle les identifiants sont destinés est absente ou inconnue et que l’utilisateur ne peut pas la vérifier par un autre moyen, annulez la demande d’authentification.

Envoyez les données saisies par l’utilisateur avec le request_id de l’action qui contient la demande, ou annulez la demande d’authentification si l’utilisateur refuse. Continuez à traiter les demandes à mesure qu’elles arrivent. L’envoi d’une réponse ne garantit pas que la connexion a réussi. Suivez la tâche jusqu’à son terme et vérifiez son résultat.

Exemple : choisir une méthode, puis saisir un code

Un site qui propose la connexion par code envoyé par e-mail ou par mot de passe peut d’abord demander à l’utilisateur de choisir une méthode, sans lui présenter de champs à remplir :

Choisir une méthode de connexion
{
  "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 l’utilisateur choisit la connexion par code envoyé par e-mail, envoyez selected_option: "email_code" avec fields: [] en utilisant le request_id de cette demande. Le site peut ensuite demander une adresse e-mail et un code de vérification dans deux demandes distinctes. Affichez chaque nouvelle demande avec ses propres champs et identifiants, et incluez selected_option uniquement si cette demande propose des options.

Renvoyer les données saisies par l’utilisateur

Lorsque l’utilisateur a rempli le formulaire de connexion, envoyez sa réponse au point de terminaison des événements de session. Définissez action sur submit et utilisez les identifiants de la demande et des champs qui figurent dans la demande d’approbation en attente. Voici un exemple de réponse à une demande d’adresse e-mail :

{
  "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 demande propose des méthodes de connexion, incluez l’identifiant de la méthode choisie dans response.selected_option. Envoyez uniquement les champs répertoriés dans field_ids pour cette méthode, avec une valeur non vide pour chaque champ obligatoire. Si la méthode ne comporte aucun champ, envoyez fields: [].

Si aucune méthode de connexion n’est proposée, omettez selected_option et envoyez directement les champs de la demande. Lorsque la demande comporte des champs, incluez-en au moins un, même s’ils sont tous facultatifs.

Envoyez les données de connexion uniquement au moyen de cet événement dédié. Les valeurs envoyées ne sont pas transmises au modèle de l’agent et ne figurent pas dans les éléments de réponse aux demandes d’authentification dans l’historique de la session.

Traitez toutes les valeurs comme des données sensibles, y compris les adresses e-mail. Masquez les valeurs saisies, excluez-les des journaux, des données d’analyse et de l’état enregistré de l’interface, puis effacez le contenu du formulaire après l’envoi. N’envoyez pas d’identifiants dans des messages ordinaires ni dans les résultats d’outils de type fonction.

Omettez turn_id dans l’événement d’envoi. Consultez les limites d’envoi des données d’authentification pour connaître les contraintes applicables aux champs et à la charge utile.

Une réponse 202 au corps vide confirme que l’envoi a été accepté. Elle ne garantit pas que la connexion a réussi. Continuez à suivre les événements de session pour repérer de nouvelles demandes ou la reprise de la tâche. Désactivez les nouvelles tentatives automatiques du client HTTP ou du SDK pour l’envoi d’identifiants. En cas de doute sur l’acceptation d’un envoi, actualisez la session avant de continuer.

Permettre à l’utilisateur d’annuler

Si l’utilisateur refuse de se connecter, répondez à la demande en attente avec action: "cancel". Utilisez son request_id et omettez fields et selected_option :

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

Cette réponse annule la demande d’authentification. Pour arrêter la tâche elle-même, annulez le tour.

Exécuter une tâche dans un navigateur authentifié

Votre application traite les demandes d’approbation d’accès aux origines et les demandes de connexion tout en suivant les événements de session. Définissez la fonction auxiliaire ci-dessous avant d’exécuter la tâche. Elle affiche la destination, recueille les données en masquant les valeurs saisies, puis envoie la réponse. Si l’utilisateur refuse, elle annule la demande de connexion.

Gérer les demandes d’approbation et de connexion du navigateur
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 }
  );
}

L’exemple suivant demande à l’agent de consulter les issues d’un dépôt GitHub privé. Remplacez https://github.com/acme/private-repo/issues par l’URL d’une page d’issues à laquelle vous avez accès.

Ouvrez le flux d’événements avant d’envoyer la tâche et appelez la fonction auxiliaire chaque fois que la session attend une saisie.

Consulter les issues d’un dépôt privé
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);
}

La connexion peut nécessiter plusieurs demandes. Continuez donc à traiter les demandes d’approbation jusqu’à la fin de la tâche. Vérifiez que le résultat de l’agent correspond à la demande. Dans cet exemple, vérifiez le titre et l’URL de l’issue indiqués par l’agent.

Consulter l’historique des connexions

Les demandes d’authentification et les réponses acceptées figurent dans l’historique de la session et dans les événements agent.session.turn.item.added. Les éléments de demande contiennent les métadonnées du formulaire de connexion. Les éléments de réponse indiquent l’action acceptée sans inclure les identifiants saisis.

Utilisez cet historique pour consulter les interactions passées. Pour savoir si un formulaire de connexion attend toujours une saisie, récupérez le contenu actuel de required_actions pour la session. La présence d’une réponse dans l’historique ne garantit pas que la connexion a réussi.

L’historique ne comporte pas d’éléments de demande ou de réponse propres aux approbations d’accès aux origines. Traitez ces approbations à partir de required_actions.

Poser une question à l’utilisateur

Pour demander une précision ou permettre à l’utilisateur de faire un choix pendant une tâche, définissez un outil de type fonction dans agent.tools. Par exemple, vous pouvez définir request_user_response pour afficher une question et recueillir la réponse. Votre application fournit le nom de l’outil, le schéma de ses arguments et son interface utilisateur.

À la réception de agent.session.requires_action, repérez l’élément function_call en attente qui correspond à votre outil et utilisez ses arguments pour afficher la question. Renvoyez la réponse via agent.session.input.tool_result, avec le turn_id et le call_id de l’action. Définissez success: true et placez la réponse dans output. Si la réponse est structurée, sérialisez-la sous forme de chaîne JSON. Si l’utilisateur refuse de répondre, renvoyez success: false avec un message dans error.

Continuez à suivre les événements de session après avoir renvoyé la réponse. Les fonctions auxiliaires présentées plus haut gèrent les demandes d’accès aux origines et de connexion. Étendez votre gestionnaire d’événements pour qu’il traite également les appels à l’outil qui pose la question.

Le modèle peut voir les résultats des outils de type fonction, et ces résultats sont enregistrés dans l’historique de la session. Recueillez les mots de passe et les codes de vérification via le parcours d’authentification du navigateur.

Reprendre le traitement des demandes d’approbation

Si votre application perd la connexion ou si l’envoi d’une réponse à une demande d’approbation échoue, récupérez la même session et examinez le contenu actuel de required_actions avant de continuer. Rétablissez les formulaires uniquement pour les demandes toujours en attente et retirez de l’interface les commandes associées aux demandes qui n’y figurent plus.

Si le flux d’événements est interrompu, suivez la procédure de reprise du flux pour recevoir à nouveau les événements. La reconnexion ne doit pas déclencher le renvoi automatique de la tâche ou d’une réponse à une demande d’approbation.

Utilisez le statut de la réponse pour décider de la suite :

Résultat Action à effectuer dans votre application
202 Effacez les valeurs envoyées et suivez les événements de session pour connaître le résultat.
400 Vérifiez que le type de réponse, l’option sélectionnée, les identifiants de champ et les valeurs obligatoires correspondent à la demande en attente. Omettez les champs de saisie dans les réponses d’annulation et les réponses aux demandes d’accès aux origines.
404 Vérifiez l’identifiant de session, l’identifiant de demande et le type de réponse. Récupérez à nouveau la session, car la demande peut ne plus être disponible.
409 Actualisez la session. Il est possible que la demande ait expiré, que le tour correspondant soit terminé ou qu’une autre réponse ait déjà été acceptée.
Connexion perdue avant l’accusé de réception Considérez que vous ignorez si la réponse a été acceptée. Rétablissez la connexion et récupérez la session avant de décider de réessayer.

Les demandes d’authentification expirent au bout de cinq minutes, et le tour auquel elles sont rattachées peut se terminer pendant la saisie. Actualisez la session avant de rétablir un formulaire de connexion.

Si vous renvoyez une réponse à une demande d’authentification, utilisez le même request_id, la même option et les mêmes valeurs pour les mêmes champs. Un envoi à l’identique ne remplit pas une seconde fois le formulaire dans le navigateur. Si vous changez les valeurs après l’acceptation d’une réponse, l’API renvoie 409. Pour tenter à nouveau de se connecter, l’agent doit émettre une nouvelle demande.

Pour les approbations d’accès aux origines, renvoyez la même décision si l’envoi échoue. Si vous modifiez une décision déjà acceptée, l’API renvoie 409. Les demandes d’accès aux origines restent en attente pendant le tour auquel elles sont rattachées. Le délai de cinq minutes applicable à l’authentification ne s’applique pas à ces demandes.

Contrôler l’accès réseau

Utilisez la configuration network de l’environnement hébergé pour contrôler l’accès réseau sortant du navigateur et du code exécuté dans cet environnement. Autorisez le site de destination ainsi que les domaines nécessaires au chargement des ressources de la page ou aux redirections.

L’approbation d’accès à une origine relève d’une décision distincte de l’utilisateur et ne prévaut pas sur la politique réseau. Consultez les paramètres d’accès réseau pour configurer l’environnement.

Poursuivre les tâches et libérer les ressources

Réutilisez la même session pour les tâches suivantes si elles nécessitent de conserver l’état du navigateur. Les cookies de connexion peuvent expirer et le recyclage de l’environnement efface l’état du navigateur.

Lorsque vous avez terminé, récupérez les résultats dont vous avez besoin et supprimez la session pour demander le nettoyage de l’environnement :

Supprimer la session de navigateur
await client.beta.agents.sessions.delete(session.id);

Consultez la section Bacs à sable hébergés par OpenAI pour en savoir plus sur la durée de vie de l’environnement et les modalités de sa suppression.

Référence sur le traitement des demandes

Limites d’envoi des données d’authentification

Chaque envoi peut contenir jusqu’à six valeurs de champ. Chaque champ ne peut figurer qu’une seule fois. Chaque valeur peut contenir jusqu’à 16 384 caractères. Une fois sérialisées, les valeurs des champs et l’option sélectionnée ne doivent pas dépasser 120 Kio au total.

Utilisez les identifiants de la demande et des champs qui figurent dans la demande d’approbation en attente, et fournissez une valeur non vide pour chaque champ obligatoire. Consultez la référence de l’API des événements de session pour connaître le schéma de la requête.

Traitement détaillé des requêtes et du flux avec cURL

Utilisez cette version pour distinguer les problèmes de transport, les erreurs HTTP et les réponses non valides à la création d’une session. Elle filtre également les données reçues dans le flux et indique les types et les codes d’erreur. Comme le premier exemple, elle utilise deux terminaux. Traitez les demandes d’approbation dans le second terminal à mesure qu’elles arrivent.

Créer une session et diagnostiquer les erreurs du flux
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."
        }]
      }]
    }]
  }'