For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
メインナビゲーション
Theme

コンピューターの使用

OpenAI がホストするブラウザでエージェントにタスクを実行させます。

コンピューターの使用を有効にすると、エージェントはウェブサイトを巡回し、ブラウザ画面を操作できます。ウェブサイトのテストや情報収集のほか、UI を介したアプリケーションの操作にも利用できます。

Agents API は、OpenAI がホストする環境でブラウザを実行します。アプリケーションはセッションを開始してイベントを受信し、エージェントはブラウザで確認した内容をもとに次の操作を決めます。

ブラウザタスクは次の手順で実行します。

  1. ブラウザセッションを作成し、セッション ID を保存します。
  2. セッションイベントを受信し、エージェントにタスクを送信します。
  3. ウェブサイトへのアクセスリクエストを個別に処理します。タスクにアカウントが必要な場合は、サインインを処理します。
  4. メインエージェントのターンが終了するのを待ち、結果を確認します。接続が切れた場合は、再試行する前に同じセッションを復旧します。
  5. 保存されたブラウザの操作履歴を確認し、作業が終わったらセッションを削除します。

ブラウザの設定

Agents API クイックスタートの前提条件に従って API キーを作成し、OPENAI_API_KEY をエクスポートしてから、使用する言語の OpenAI SDK をインストールします。JavaScript の例では openai と prompt-sync をインストールしてください。cURL の例には Bash と jq が必要です。

ブラウザへのアクセスを有効にするには、次のように設定します。

  • { "type": "computer_use" } を agent.tools に追加します。
  • environment.type を openai_hosted に、environment.desktop.enabled を true に設定します。

以下の JavaScript の例は一連の手順になっています。セッションを作成し、ウェブサイトへのアクセス承認を処理できるようにしてから、タスクを送信して回答を出力します。まず、スクリーンショットを有効にしたブラウザセッションを作成します。この時点ではタスクは開始されません。

ブラウザセッションの作成
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);

オリジンへのアクセスの処理

ブラウザがウェブサイトの新しいオリジンにアクセスするには、オリジンごとにユーザーの承認が必要です。公開ウェブサイトも対象です。ネットワークアクセスを有効にしても、これらのリクエストは承認されません。

承認リクエストの自動レビューを有効にしても、ブラウザが新しいオリジンにアクセスするにはユーザーの承認が必要です。サインインのリクエストも引き続き届きます。どちらのリクエストもアプリケーション側で処理してください。

オリジンへのアクセス承認は次の手順で処理します。

  1. agent.session.requires_action を受信したら、セッションを取得し、現在の required_actions を確認します。
  2. 保留中の computer_use_approval_request のうち、内部の request.type が browser_origin_access のものを探します。
  3. アクセス先の origin と、指定されていれば reason を表示し、ユーザーに approve、deny、cancel のいずれかを選んでもらいます。以下の例のように、同じ request_id を使い、ネストされた response に type: "browser_origin_access" と decision を含めて、セッションイベントのエンドポイントに選択結果を送信します。
オリジンへのアクセスを承認しても、個々の操作前の確認は必須になりません

購入や破壊的な変更など、重大な影響を及ぼす操作の前に必ずユーザーの確認を得る必要がある場合は、ホスト型ブラウザのアクセス先を、それらの操作ができないリソースに限定してください。または、自分で制御できるブラウザ実行環境を使用してください。関数ツールで確認を求める方式では、エージェントがその関数を呼び出した場合にのみ確認が行われます。

ウェブサイトのコンテンツは信頼できないものとして扱ってください。コンテンツには権限を付与したり、ユーザーの指示を上書きしたりする効力はありません。自分で制御する実行環境での確認と同意に関するガイドを参照してください。

タスクのコードの前に、次のヘルパー関数を定義します。このタスクで読み取るのは公開ページだけなので、ヘルパー関数はオリジンへのアクセス承認を処理し、サインインリクエストはキャンセルします。

オリジンへのアクセスリクエストへの応答
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}`);
  }
}

ストリームを開いたまま、保留中の承認リクエストをすべて処理します。request_id で各リクエストを追跡し、セッションの required_actions からリクエストがなくなったら、対応する承認用の UI を削除します。承認リクエストをキャンセルしても、タスクはキャンセルされません。

202 レスポンスは、ユーザーの選択が受理されたことを示します。ページへの移動が完了したことを示すものではありません。

ブラウザタスクの実行

開発者向けの公開サイトで Agents API のクイックスタートを探し、そのタイトルと URL を報告するようエージェントに依頼します。

最初の進捗イベントをアプリケーションで受信できるよう、タスクを送信する前にイベントストリームを開きます。オリジンへのアクセス承認のリクエストが届くたびに処理し、ブラウザが操作を続けられるようにします。

タスクの送信と結果の確認
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();
}

この例ではエージェントの回答が出力されます。クイックスタートのタイトルと URL が含まれていることを確認してください。

イベントストリームを閉じてもタスクは停止しません。タスクを停止するには、ターンをキャンセルします。接続に失敗した場合や処理結果が不明な場合は、復旧手順に従ってください。詳細な cURL の例には、通信障害やエラーを診断する処理も含まれています。

ブラウザの操作状況の確認

ブラウザの操作は、セッションの出力に computer_use_call アイテムとして表示されます。ストリームで受信する操作情報と、保存されたセッション履歴の操作情報は、同じアイテム形式です。

フィールド 意味
id 操作アイテムの識別子です。
turn_id 操作が行われたターンを示します。
title ブラウザ操作の説明です。null の場合もあります。
status in_progress、completed、failed、incomplete のいずれかです。
output スクリーンショットがある場合は、その出力が入ります。

タイトルとステータスを使って、アプリケーションに進捗を表示します。ブラウザの操作アイテムが表すのは、ツールによる個々の操作です。エージェントの最終回答や、ターン全体の完了状況は示しません。セッションのイベントモデルについては、イベントとアイテムを参照してください。

スクリーンショットの取得

アプリケーションにブラウザの進捗を表示するには、include_screenshots を true に設定します。この設定は computer_use ツールに指定します。デフォルトでは API の出力にスクリーンショットは含まれませんが、エージェントはスクリーンショットを確認できます。

ブラウザ操作の完了イベントでは、その操作で最後に出力されたスクリーンショットがあれば、item.output に含まれます。

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

image_url を使ってスクリーンショットを表示します。スクリーンショットを有効にしていても output: null を返す操作があるため、アプリケーションでは画像のない操作アイテムも処理できるようにしてください。

スクリーンショットには、ページやアカウントに関する機密情報が含まれることがあります。閲覧権限のあるユーザーにのみ表示し、アプリケーションのログには記録しないでください。

ストリームを受信しながら、agent.session.turn.item.done イベントに含まれるスクリーンショットを取得します。後から取得した履歴には画像データが含まれないことがあるため、後でアイテム一覧を取得するリクエストに頼らず、アプリケーション側で画像を保持してください。SDK の例では、ターンの完了後にブラウザの操作一覧を表示し、取得した最新のスクリーンショットを保存します。ほとんどの例では browser-screenshot.jpg に保存し、Java の例では一時ファイルを使用します。

ブラウザの操作履歴の読み取り
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.");
}

SDK の例では既存のファイルを上書きしません。再実行する前に browser-screenshot.jpg を移動または削除してください。

サインインの処理

GitHub のプライベートリポジトリのイシューを読み取る場合など、タスクによっては認証済みのブラウザが必要です。アプリケーション側でサインインを処理することで、ユーザーはチャットの外でログイン方法を選び、認証情報を入力できます。

サインインには複数のリクエストが必要な場合があります。たとえば、サイトがユーザーにメールでのサインインを選択するよう求め、次にメールアドレス、続いて確認コードの入力を求めることがあります。各リクエストに含まれるログイン方法とフィールドに合わせて UI を構成してください。

ブラウザ認証をリクエストできるのはメインエージェントだけです。サブエージェントはリクエストできません。このフローはメールアドレス、パスワード、確認コードに対応していますが、パスキーや QR コードによるサインインには対応していません。未対応の方法が必須のサイトでは、このフローでサインインを完了できません。

タスクのイベントストリームを開いたまま、オリジンへのアクセス承認のリクエストが届くたびに処理します。agent.session.requires_action を受信したら、セッションを取得します。現在の required_actions にある computer_use_approval_request のうち、内部の request.type が browser_authentication のものを探します。

ネストされた request を使って、サインイン用の UI を表示します。

フィールド 使用方法
reason 理由が指定されていれば、入力が必要な理由をユーザーに説明します。null の場合もあります。
credential_origin 認証情報の送信先を表示します。null の場合もあります。
fields 各フィールドの id、label、type、required の値に従って入力欄を表示します。空の場合もあります。
options 利用できるログイン方法を表示します。各選択肢には id、label、および対応する入力欄を特定する field_ids が含まれます。

リクエストにログイン方法の選択肢がある場合は、ユーザーに 1 つ選んでもらい、その方法に対応する入力欄を表示します。選択肢がない場合は、リクエストのフィールドをそのまま表示します。

認証情報の入力を求めるのは、ユーザー自身が送信先を確認できる場合だけにしてください。送信先のオリジンが指定されていない場合や見覚えがない場合に、ユーザーが別の手段でも送信先を確認できなければ、認証リクエストをキャンセルしてください。

ユーザーの入力を送信する際は、外側のアクションの request_id を使います。ユーザーがサインインを辞退した場合は、認証リクエストをキャンセルします。その後もリクエストが届くたびに処理してください。応答を送信しただけでは、サインインが成功したかどうかはわかりません。タスクが完了するまで進捗を追い、結果を確認してください。

例:サインイン方法の選択とコードの入力

メールで届くコードとパスワードのどちらでもサインインできるサイトでは、最初はフィールドへの入力を求めず、サインイン方法の選択だけを求めることがあります。

サインイン方法の選択
{
  "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": [] }
    ]
  }
}

ユーザーがメールで届くコードを選んだ場合は、selected_option: "email_code" と fields: [] を、このリクエストの request_id を使って送信します。その後、サイトからメールアドレスと確認コードの入力を別々のリクエストで求められる場合があります。新しいリクエストが届くたびに、そのリクエストのフィールドと ID を使って入力欄を表示します。selected_option を含めるのは、そのリクエストに選択肢がある場合だけです。

ユーザーの入力の送信

ユーザーがサインインリクエストへの入力を終えたら、セッションイベントのエンドポイントに応答を送信します。action を submit に設定し、保留中の承認リクエストに含まれるリクエスト ID とフィールド ID を使います。たとえば、メールアドレスを求めるリクエストへの応答は次のようになります。

{
  "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" }]
      }
    }
  ]
}

リクエストにログイン方法の選択肢がある場合は、選択した方法の ID を response.selected_option に指定します。その方法の field_ids に含まれるフィールドだけを送信し、必須フィールドにはそれぞれ空でない値を指定してください。その方法にフィールドがない場合は、fields: [] を送信します。

ログイン方法の選択肢がない場合は、selected_option を省略し、リクエストのフィールドをそのまま送信します。リクエストにフィールドが含まれている場合は、すべて任意であっても、少なくとも 1 つは送信してください。

サインインの入力値は、この専用イベントでのみ送信してください。送信した値はエージェントのモデル入力には含まれず、セッション履歴の認証応答アイテムにも記録されません。

メールアドレスを含むすべての値を機密情報として扱ってください。入力値はマスクし、ログ、分析データ、保存される UI の状態に含めず、送信後はフォームをクリアしてください。通常のメッセージや関数ツールの結果で認証情報を送信しないでください。

送信イベントでは turn_id を省略します。フィールドとペイロードの制約については、認証情報の送信制限を参照してください。

本文が空の 202 レスポンスは、送信内容が受理されたことを示します。サインインの成功を示すものではありません。引き続きセッションイベントを受信し、追加のリクエストや作業の再開を確認してください。認証情報の送信では、HTTP または SDK による自動再試行を無効にしてください。送信内容が受理されたかどうか不明な場合は、続行する前にセッションを再取得してください。

ユーザーによるキャンセル

ユーザーがサインインを辞退した場合は、保留中のリクエストに action: "cancel" で応答します。そのリクエストの request_id を使い、fields と selected_option は省略します。

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

これで認証リクエストがキャンセルされます。タスク自体を停止するには、ターンをキャンセルします。

認証済みブラウザでのタスクの実行

アプリケーションでは、セッションイベントを受信しながら、オリジンへのアクセス承認とサインインリクエストを処理します。タスクを実行する前に、以下のヘルパー関数を定義してください。この関数は認証情報の送信先を表示し、入力値をマスクした状態でユーザーの入力を受け付け、応答を送信します。ユーザーが辞退した場合は、サインインリクエストをキャンセルします。

ブラウザのアクセス承認とサインインの処理
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 }
  );
}

次の例では、GitHub のプライベートリポジトリからイシューを読み取るようエージェントに依頼します。https://github.com/acme/private-repo/issues は、自分がアクセスできるイシューのページの URL に置き換えてください。

タスクを送信する前にイベントストリームを開き、セッションで入力が必要になるたびにヘルパー関数を呼び出します。

プライベートリポジトリのイシューの読み取り
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);
}

サインインには複数のリクエストが必要な場合があるため、タスクが終了するまで承認リクエストの処理を続けてください。エージェントが返した結果が依頼内容に合っているか確認します。この例では、報告されたイシューのタイトルと URL を確認してください。

サインイン履歴の確認

認証リクエストと受理された応答は、セッション履歴と agent.session.turn.item.added イベントに表示されます。リクエストアイテムにはサインインフォームのメタデータが含まれます。応答アイテムには受理されたアクションが記録されますが、送信した認証情報の値は含まれません。

この履歴で過去のやり取りを確認できます。サインインフォームへの入力がまだ必要かどうかを判断するには、セッションの現在の required_actions を取得してください。応答が記録されていても、サインインが成功したとは限りません。

オリジンへのアクセス承認には、リクエストや応答を記録する専用の履歴アイテムがありません。required_actions を使って処理してください。

ユーザーへの質問

タスクの途中で不明点を確認したり、ユーザーに選択を求めたりするには、関数ツールを agent.tools に定義します。たとえば、request_user_response を定義して質問を表示し、回答を受け取ることができます。ツールの名前、引数のスキーマ、UI はアプリケーション側で用意します。

agent.session.requires_action を受信したら、そのツールに対する保留中の function_call を探し、arguments を使って質問を表示します。agent.session.input.tool_result で回答を返す際は、そのアクションの turn_id と call_id を使います。success: true を設定し、回答を output に格納します。構造化された回答は JSON 文字列にシリアライズしてください。ユーザーが回答を辞退した場合は、success: false と error メッセージを返します。

回答を返した後も、セッションイベントを引き続き受信してください。前述のブラウザ承認用のヘルパー関数は、オリジンへのアクセスとサインインを処理します。質問用のツールも処理できるよう、イベントハンドラーを拡張してください。

関数ツールの結果はモデルに渡され、セッション履歴にも保存されます。パスワードと確認コードの入力にはブラウザ認証を使用してください。

承認処理の復旧

アプリケーションの接続が切れた場合や承認リクエストへの応答に失敗した場合は、処理を続ける前に同じセッションを取得し、現在の required_actions を確認します。フォームを再表示するのは、まだ保留中のリクエストだけです。一覧からなくなったリクエストの操作用 UI は削除してください。

イベントストリームが切断された場合は、ストリームの復旧手順に従ってイベントの受信を再開します。再接続時にタスクや承認リクエストへの応答を自動で再送しないでください。

レスポンスのステータスに応じて、次のように処理します。

結果 アプリケーション側の対応
202 送信済みの入力値をクリアし、セッションイベントを受信して結果を確認します。
400 応答の種類、選択したオプション、フィールド ID、必須フィールドの値を保留中のリクエストと照合します。キャンセル時やオリジンへのアクセスリクエストに応答する際は、フィールドを省略します。
404 セッション ID、リクエスト ID、応答の種類を確認します。リクエストがすでに利用できなくなっている可能性があるため、セッションを再取得します。
409 セッションを再取得します。リクエストの有効期限が切れた、そのリクエストが属するターンが終了した、または別の応答がすでに受理された可能性があります。
受理を確認する前に接続が切れた場合 受理されたかどうかは不明として扱います。再接続してセッションを取得してから、再試行するかどうかを判断します。

認証リクエストの有効期限は 5 分です。ユーザーの入力中に、そのリクエストが属するターンが終了することもあります。サインインフォームを再表示する前に、セッションを再取得してください。

認証情報の送信を再試行する場合は、同じ request_id、同じ選択肢、各フィールドに対応する同じ値を使います。まったく同じ内容を再送しても、ブラウザのフォームに再入力されることはありません。送信内容が受理された後に値を変更して送ると、409 が返されます。サインインをやり直すには、エージェントからの新しいリクエストが必要です。

オリジンへのアクセス承認で応答の送信に失敗した場合は、同じ選択結果を再送してください。受理済みの選択結果を変更すると 409 が返されます。オリジンへのアクセスリクエストは、そのリクエストが属するターンが続く間は保留されます。認証リクエストに適用される 5 分のタイムアウトはありません。

ネットワークアクセスの制御

ホスト環境の network 設定を使って、ブラウザと環境内で実行するコードの両方について、外部へのアクセスを制御します。アクセス先のウェブサイトに加え、ページのリソースの読み込みやリダイレクトに必要なドメインも許可してください。

オリジンへのアクセス承認は、ネットワークポリシーとは別にユーザーが判断するもので、ポリシーを上書きすることはありません。環境の設定方法は、ネットワークアクセスの設定を参照してください。

セッションの再利用とクリーンアップ

後続のタスクでブラウザの状態を引き継ぐには、同じセッションを再利用します。ログイン Cookie の有効期限が切れることがあります。また、環境が再作成されるとブラウザの状態は消去されます。

作業が終わったら、必要な結果を取得し、セッションを削除して環境のクリーンアップをリクエストします。

ブラウザセッションの削除
await client.beta.agents.sessions.delete(session.id);

環境の有効期間と削除時の動作については、OpenAI がホストするサンドボックスを参照してください。

リクエスト処理のリファレンス

認証情報の送信制限

1 回の送信に含められるフィールド値は最大 6 個で、各フィールドを指定できるのは 1 回だけです。各値の長さは最大 16,384 文字です。シリアライズしたフィールド値と選択したオプションの合計サイズは 120 KiB 以内でなければなりません。

保留中の承認リクエストに含まれるリクエスト ID とフィールド ID を使い、各必須フィールドに空でない値を指定してください。リクエストのスキーマは、セッションイベントの API リファレンスを参照してください。

cURL によるリクエストとストリーム処理の詳細例

通信障害、HTTP エラー、セッション作成時の不正なレスポンスを区別する必要がある場合は、こちらの例を使用してください。ストリームの出力をフィルタリングし、エラーの種類とコードも報告します。最初の例と同様に、ターミナルを 2 つ使用します。承認リクエストが届くたびに、2 つ目のターミナルで処理してください。

セッションの作成とストリーム障害の確認
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."
        }]
      }]
    }]
  }'