AI

Agents API computer use入門——ブラウザ操作をFirestoreで承認管理する

OpenAI Agents APIのcomputer use追加を題材に、Next.js + Firestoreでブラウザ操作エージェントの承認・監査台帳を作る実践手順を解説します。

2026年9月30日
OpenAIAgents APIcomputer useFirestoreNext.js
Agents API computer use入門——ブラウザ操作をFirestoreで承認管理する

はじめに

AIエージェントにWebアプリの調査やUI確認を任せたい場面は増えています。ただし、ブラウザを操作できるエージェントは便利な半面、次の不安もセットで出てきます。

よくある不安放置すると起きること
どの外部サイトへアクセスしたか追えない調査範囲や情報持ち出しの説明ができない
サインイン要求をAI任せにしてしまう権限境界が曖昧になる
スクリーンショットを保存しすぎる個人情報や社内情報の保管リスクが増える
20〜30件の確認タスクを並列実行する誰が何を承認したか分からなくなる

OpenAI API Changelogでは、2026年9月29日に Agents APIへcomputer useが追加 されました。公式説明では、Agents APIのエージェントがOpenAI-hosted browserでタスクを実行でき、Webサイトへのアクセス承認やサインイン要求はアプリケーション側で扱う位置づけです。

本記事では、架空の管理SaaS「AuditNest」を題材に、Next.js App Router + Firestore + TypeScriptで、ブラウザ操作エージェントの承認・監査台帳を作ります。この記事を読み終えると、computer useを「自動で画面を触れる機能」としてではなく、「人が承認し、あとから説明できる業務ワークフロー」として設計できるようになります。

参考にした一次情報は次の通りです。

computer useで何が変わるか

公式ドキュメントでは、computer useはエージェントがWebサイトを移動し、ブラウザインターフェースを操作するための機能として説明されています。ブラウザはOpenAI-hosted environmentで動き、アプリケーション側はセッションを開始し、イベントを追い、必要な承認に応答します。

有効化に必要な設定は明確です。agent.tools に { type: "computer_use" } を追加し、environment.type を openai_hosted、environment.desktop.enabled を true にします。JavaScript SDKの例では openai パッケージと client.beta.agents.sessions.create が使われています。

ここで大事なのは、Webサイトの内容を信用しすぎないことです。公式ドキュメントでも、Webサイトの内容は信頼できない入力として扱い、ユーザーの指示や権限を上書きできないと説明されています。AIが画面を見ているから安心、ではなく、どのoriginに、なぜアクセスするのかを人間が確認できる導線が必要です。

ハンズオン1: 承認台帳を設計する

AuditNestでは、GitHub Issueや社内管理画面の目視確認をAIに頼む想定にします。ただし、外部サイトへのアクセスやサインイン要求は必ずFirestoreに残します。

export type BrowserApprovalDecision = "pending" | "approve" | "deny" | "cancel";

export type BrowserApprovalLedger = {
  provider: "openai";
  openaiSessionId: string;
  requestId: string;
  requestType: "browser_origin_access" | "browser_authentication";
  origin: string | null;
  reason: string | null;
  decision: BrowserApprovalDecision;
  decidedBy: string | null;
  decidedAt: FirebaseFirestore.Timestamp | null;
  projectId: string;
  taskKey: string;
  createdAt: FirebaseFirestore.FieldValue;
  updatedAt: FirebaseFirestore.FieldValue;
};

origin と reason を分けて保存しておくと、あとから「このエージェントはなぜ github.com へ行こうとしたのか」を説明しやすくなります。サインイン要求はさらに慎重に扱い、最初の導入では自動入力せず、担当者へ通知してキャンセルまたは手動確認に寄せるのが安全です。

ハンズオン2: computer useセッションを作る

次に、Next.jsのRoute HandlerからAgents APIセッションを作ります。ここではスクリーンショットをUIで確認したいので include_screenshots: true にしています。画面情報を保存する場合は、保存期間と閲覧権限を先に決めてください。

// src/app/api/projects/[projectId]/browser-agent-sessions/route.ts
import OpenAI from "openai";
import { FieldValue } from "firebase-admin/firestore";
import { adminDb } from "@/lib/firebase/admin";
import { requireUser } from "@/lib/auth/requireUser";
import { assertCanReadProject } from "@/lib/projects/permissions";

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

type Params = {
  params: Promise<{ projectId: string }>;
};

type RequestBody = {
  taskKey: string;
  targetUrl: string;
  checklist: string[];
};

export async function POST(request: Request, { params }: Params) {
  const user = await requireUser(request);
  const { projectId } = await params;
  const body = (await request.json()) as RequestBody;

  await assertCanReadProject(user.uid, projectId);

  const ledgerRef = await adminDb.collection("browserAgentSessions").add({
    provider: "openai",
    openaiSessionId: null,
    projectId,
    taskKey: body.taskKey,
    targetUrl: body.targetUrl,
    requestedBy: user.uid,
    status: "running",
    createdAt: FieldValue.serverTimestamp(),
    updatedAt: FieldValue.serverTimestamp(),
  });

  const session = await client.beta.agents.sessions.create({
    agent: {
      model: "gpt-6-astra",
      instructions: [
        "あなたはQA担当です。",
        "指定された画面を確認し、チェックリストに沿って事実だけを報告してください。",
        "サインインが必要な場合は要求し、勝手に資格情報を入力しないでください。",
        "フォーム送信、コメント投稿、データ更新は行わないでください。",
      ].join("\n"),
      tools: [{ type: "computer_use", include_screenshots: true }],
    },
    environment: {
      type: "openai_hosted",
      desktop: { enabled: true },
      network: { access: "enabled" },
    },
    input: [
      `Target URL: ${body.targetUrl}`,
      "Checklist:",
      ...body.checklist.map((item) => `- ${item}`),
    ].join("\n"),
  });

  await ledgerRef.update({
    openaiSessionId: session.id,
    updatedAt: FieldValue.serverTimestamp(),
  });

  return Response.json({ ledgerId: ledgerRef.id, openaiSessionId: session.id });
}

本番では targetUrl の許可リストもアプリ側で持ちます。公式ドキュメントでは、ネットワーク設定とorigin承認は別物と説明されています。つまり、ネットワークで行ける範囲を絞ったうえで、実際にアクセスするoriginを人が承認する二段構えにします。

ハンズオン3: 承認要求をFirestoreへ流す

Agents APIのイベントを監視し、agent.session.requires_action が来たらセッションをretrieveします。公式例でも、現在の required_actions から computer_use_approval_request を取り出して処理しています。ここでは必要な項目だけをFirestoreに保存します。

// src/lib/agents/storeBrowserApprovals.ts
import OpenAI from "openai";
import { FieldValue } from "firebase-admin/firestore";
import { adminDb } from "@/lib/firebase/admin";

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

type ComputerUseApprovalRequest = {
  type: string;
  request_id: string;
  request?: {
    type?: "browser_origin_access" | "browser_authentication";
    origin?: string;
    reason?: string;
  };
};

export async function syncBrowserApprovalRequests(input: {
  projectId: string;
  taskKey: string;
  openaiSessionId: string;
}) {
  const current = await client.beta.agents.sessions.retrieve(input.openaiSessionId);

  for (const action of current.required_actions as ComputerUseApprovalRequest[]) {
    if (action.type !== "computer_use_approval_request") continue;

    const request = action.request;
    if (!request?.type) continue;

    await adminDb
      .collection("browserApprovalLedgers")
      .doc(action.request_id)
      .set(
        {
          provider: "openai",
          openaiSessionId: input.openaiSessionId,
          requestId: action.request_id,
          requestType: request.type,
          origin: request.origin ?? null,
          reason: request.reason ?? null,
          decision: "pending",
          decidedBy: null,
          decidedAt: null,
          projectId: input.projectId,
          taskKey: input.taskKey,
          createdAt: FieldValue.serverTimestamp(),
          updatedAt: FieldValue.serverTimestamp(),
        },
        { merge: true },
      );
  }
}

GitHub Actionsで5分ごとに同期するなら、1回で全セッションを見に行かず、running の台帳を3〜4件ずつ処理します。ブラウザ操作は待ち時間が読みにくいので、バッチサイズを小さくして、失敗したセッションだけ再試行する方が運用しやすいです。

ハンズオン4: 人間の判断をAgents APIへ返す

承認画面では、担当者が approve、deny、cancel を選びます。公式例では、origin accessに対して browser_origin_access の decision を返し、公開ページ確認タスクでは browser_authentication を cancel しています。

// src/app/api/browser-approvals/[requestId]/route.ts
import OpenAI from "openai";
import { FieldValue } from "firebase-admin/firestore";
import { adminDb } from "@/lib/firebase/admin";
import { requireUser } from "@/lib/auth/requireUser";

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

type Params = {
  params: Promise<{ requestId: string }>;
};

type DecisionBody = {
  decision: "approve" | "deny" | "cancel";
};

export async function POST(request: Request, { params }: Params) {
  const user = await requireUser(request);
  const { requestId } = await params;
  const body = (await request.json()) as DecisionBody;

  const approvalRef = adminDb.collection("browserApprovalLedgers").doc(requestId);
  const approvalSnap = await approvalRef.get();

  if (!approvalSnap.exists) {
    return Response.json({ error: "Approval request not found" }, { status: 404 });
  }

  const approval = approvalSnap.data() as {
    openaiSessionId: string;
    requestType: "browser_origin_access" | "browser_authentication";
    decision: "pending" | "approve" | "deny" | "cancel";
  };

  if (approval.decision !== "pending") {
    return Response.json({ ok: true, skipped: "already_decided" });
  }

  const response =
    approval.requestType === "browser_origin_access"
      ? { type: "browser_origin_access" as const, decision: body.decision }
      : { type: "browser_authentication" as const, action: "cancel" as const };

  await client.beta.agents.sessions.events.create(approval.openaiSessionId, {
    events: [
      {
        type: "agent.session.input.computer_use_approval_request_result",
        request_id: requestId,
        response,
      },
    ],
  });

  await approvalRef.update({
    decision:
      approval.requestType === "browser_authentication" ? "cancel" : body.decision,
    decidedBy: user.uid,
    decidedAt: FieldValue.serverTimestamp(),
    updatedAt: FieldValue.serverTimestamp(),
  });

  return Response.json({ ok: true });
}

この例ではサインインを常にキャンセルしています。社内アプリのE2E確認に広げる場合も、まずは「サインイン要求を検知して止める」段階から始め、資格情報入力の扱いは別設計に切り出すのがおすすめです。

運用で見るべき指標

computer useは、導入直後から広い権限で動かすより、小さな確認タスクを20〜30件ためて、3〜4件ずつ回すのが現実的です。Firestoreには次の指標を残します。

指標見る理由
承認要求数タスクが想定以上のoriginへ広がっていないか確認する
deny / cancel率プロンプトや許可リストの改善余地を見る
スクリーンショット保存数保管コストと情報リスクを管理する
完了までの時間人間の承認待ちがボトルネックか判断する
手動再確認率AIの画面確認が業務品質に届いているか測る

最初のプロンプトには、更新操作を禁止する文を明示します。ブラウザを使えることと、業務データを書き換えてよいことは別です。AIには「見る」「報告する」範囲から始め、承認UIと監査ログが安定してから、対象タスクを少しずつ広げます。

まとめ

Agents APIのcomputer useは、AIエージェントをテキスト処理からブラウザ上の実務確認へ進める大きなアップデートです。一方で、Webサイトへのアクセス、サインイン、スクリーンショット、操作ログをアプリ側がどう管理するかで、安全性と説明可能性が大きく変わります。

Next.js + Firestoreで承認台帳を作っておけば、AIがどのoriginへ行こうとしたか、誰が承認したか、どのタスクで使われたかを後から追えます。まずは読み取り専用のQA確認から始め、承認待ちと監査ログをチームの通常運用に乗せるところから試してみてください。