OpenAI 计算机使用

2026-11-06 14:59 更新

计算机使用允许智能体浏览网站、操作浏览器界面,以测试网站、收集信息或使用应用。

Agents API在OpenAI托管环境运行浏览器。应用启动会话、跟踪事件,智能体依据浏览器观察决定下一步。

浏览器任务流程:

  1. 创建浏览器会话,保存ID。
  2. 先订阅事件,再提交任务。
  3. 处理每个网站源访问请求;需要账户时处理登录。
  4. 等主智能体轮次结束并验证结果。断线后先恢复同一会话,再决定是否重试。
  5. 检查保存的浏览器活动,结束后删除会话。

配置浏览器

按快速开始前置条件创建密钥并导出 OPENAI_API_KEY,安装对应语言SDK。JavaScript示例安装 openai、prompt-sync;cURL示例需要Bash和jq。

  • 在 agent.tools 添加 { "type": "computer_use" }。
  • 设 environment.type: "openai_hosted" 与 environment.desktop.enabled: true。

JavaScript示例组成一套连续操作:创建会话、处理网站批准、提交任务和打印答案。先创建启用截图的浏览器会话,此步骤尚未启动任务:

来源示例 1(bash):

程序文字说明18:在浏览器阅读公开文档;不登录、不修改网站数据,报告所找到网页的标题和URL。

程序文字说明19:此拼接指令限定浏览器只读公开文档、不登录及不改数据,并报告标题和URL。

## [中文] 示例 1:配置浏览器。
## [中文] 本段涉及的请求或服务地址:https://api.openai.com/v1/agents/sessions;示例占位地址需替换为实际资源。
## Requires Bash and jq. Keep the session ID for follow-up requests.
set -o pipefail
if ! session_id=$(curl --silent --show-error --fail 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" }
    }
  }' | jq --exit-status --raw-output '.id // empty'); then
  echo "Session creation failed or its outcome is unknown. Do not retry automatically." >&2
  exit 1
fi
printf 'Session ID: %s\n' "$session_id"

来源示例 2(javascript):

沿用程序文字说明18。

沿用程序文字说明19。

// [中文] 示例 2:配置浏览器。
// [中文] 创建会话,保存返回会话 ID;设置 stream 时继续读取事件,创建请求本身不保证任务成功。
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);

来源示例 3(python):

沿用程序文字说明18。

沿用程序文字说明19。

## [中文] 示例 3:配置浏览器。
## [中文] 创建会话,保存返回会话 ID;设置 stream 时继续读取事件,创建请求本身不保证任务成功。
import base64
import os
from pathlib import Path

from openai import OpenAI

client = OpenAI()
session = 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"},
    },
)
print("Session ID:", session.id, flush=True)
ready_to_delete = False

来源示例 4(go):

沿用程序文字说明18。

沿用程序文字说明19。

// [中文] 示例 4:配置浏览器。
// [中文] 创建会话,保存返回会话 ID;设置 stream 时继续读取事件,创建请求本身不保证任务成功。
import (
    "bufio"
    "context"
    "encoding/base64"
    "fmt"
    "os"
    "strings"

    "github.com/openai/openai-go/v3"
    "github.com/openai/openai-go/v3/option"
)

ctx := context.Background()
client := openai.NewClient()
session, err := client.Beta.Agents.Sessions.New(ctx, openai.BetaAgentSessionNewParams{
    Agent: openai.BetaAgentSessionNewParamsAgent{
        Model:        openai.String("gpt-6-astra"),
        Instructions: openai.String("Read public documentation in the browser. Do not sign in or change any website data. Report the page title and URL you find."),
        Tools: []openai.AgentToolParamUnion{{
            OfParamComputerUse: &openai.AgentToolParamComputerUse{IncludeScreenshots: openai.Bool(true)},
        }},
    },
    Environment: openai.EnvironmentParamUnion{OfParamOpenAIHosted: &openai.EnvironmentParamOpenAIHosted{
        Desktop: openai.EnvironmentParamOpenAIHostedDesktop{Enabled: true},
        Network: openai.EnvironmentParamOpenAIHostedNetwork{Access: "enabled"},
    }},
})
if err != nil {
    return err
}
fmt.Println("Session ID:", session.ID)

来源示例 5(java):

沿用程序文字说明19。

// [中文] 示例 5:配置浏览器。
// [中文] 创建会话,保存返回会话 ID;设置 stream 时继续读取事件,创建请求本身不保证任务成功。
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.beta.agents.AgentBrowserAuthenticationCancelParam;
import com.openai.models.beta.agents.AgentBrowserOriginAccessParam;
import com.openai.models.beta.agents.AgentSession;
import com.openai.models.beta.agents.AgentSessionInputMessageParam;
import com.openai.models.beta.agents.AgentSessionInputParam;
import com.openai.models.beta.agents.AgentSessionInputParam.AgentSessionInputComputerUseApprovalRequestResult;
import com.openai.models.beta.agents.AgentToolParam;
import com.openai.models.beta.agents.EnvironmentParam;
import com.openai.models.beta.agents.sessions.SessionCreateParams;
import com.openai.models.beta.agents.sessions.events.EventCreateParams;
import com.openai.models.beta.agents.sessions.items.ItemListParams;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Base64;
import java.util.HashSet;
import java.util.List;

var client = OpenAIOkHttpClient.fromEnv();
var session =
    client
        .beta()
        .agents()
        .sessions()
        .create(
            SessionCreateParams.builder()
                .agent(
                    SessionCreateParams.Agent.builder()
                        .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.")
                        .addTool(
                            AgentToolParam.ComputerUse.builder()
                                .includeScreenshots(true)
                                .build())
                        .build())
                .environment(
                    EnvironmentParam.OpenAIHosted.builder()
                        .desktop(
                            EnvironmentParam.OpenAIHosted.Desktop.builder()
                                .enabled(true)
                                .build())
                        .network(
                            EnvironmentParam.OpenAIHosted.Network.builder()
                                .access(EnvironmentParam.OpenAIHosted.Network.Access.ENABLED)
                                .build())
                        .build())
                .build());
System.out.println("Session ID: " + session.id());

来源示例 6(csharp):

沿用程序文字说明18。

沿用程序文字说明19。

// [中文] 示例 6:配置浏览器。
// [中文] C# 使用 AgentClient 创建启用 desktop 的托管浏览器会话,并保存 ID;这一创建示例尚未提交浏览任务。
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Text.Json;
using OpenAI;
using OpenAI.Agents;
#pragma warning disable OPENAI001

string key = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;
OpenAIClientOptions options = new() { RetryPolicy = new ClientRetryPolicy(maxRetries: 0) };
AgentClient client = new OpenAIClient(new ApiKeyCredential(key), options).GetAgentClient();
AgentSession session = await client.CreateAgentSessionAsync(
    new AgentSessionCreationOptions
    {
        Agent = new SessionAgentConfigParam
        {
            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 = [new AgentToolConfigParamComputerUse { IncludeScreenshots = true }],
        },
        Environment = new EnvironmentParamOpenaiHosted
        {
            Desktop = new DesktopParam(true),
            Network = new NetworkPolicyParam(NetworkAccessParam.Enabled),
        },
    }
);
Console.WriteLine($"Session ID: {session.Id}");

来源示例 7(ruby):

沿用程序文字说明18。

沿用程序文字说明19。

## [中文] 示例 7:配置浏览器。
## [中文] 创建会话,保存返回会话 ID;设置 stream 时继续读取事件,创建请求本身不保证任务成功。
require "base64"
require "openai"

client = OpenAI::Client.new
session = 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" }
  }
)
puts "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。

源批准不强制每项具体动作前确认

若应用必须保证购买、破坏性变更或其他有实际后果动作先获确认,应将托管浏览器限制到无法执行这些动作的资源,或采用自己控制的浏览器运行时。通过函数工具请求确认仍依赖智能体确实调用该工具。

网站内容是不可信数据,不能授予权限或覆盖用户指令。自管运行时的设计见用户确认与同意。

先定义下列辅助函数,再运行任务。它处理源批准,并取消登录请求,因为本任务只读公开页面:

来源示例 8(bash):

## [中文] 示例 8:处理网站源访问。
## [中文] 本段涉及的请求或服务地址:https://api.openai.com/v1/agents/sessions/$session_id、https://api.openai.com/v1/agents/sessions/$session_id/events;示例占位地址需替换为实际资源。
## [中文] 以当前 request_id 关联批准回应;源访问决定与登录字段使用不同 response 类型,不用普通消息提交凭证。
## [中文] 从当前 required_actions 判断待处理请求,并保留所需调用或批准 ID。
## Run in terminal 2 after the task reports agent.session.requires_action.
## Reuse the task's session_id and OPENAI_API_KEY.
curl --silent --show-error --fail-with-body "https://api.openai.com/v1/agents/sessions/$session_id" \
  -H "OpenAI-Beta: agents=v1" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  | jq '.required_actions[] | select(.type == "computer_use_approval_request")'

## Read request.origin and request.reason before deciding.
## Only use this response for request.type == "browser_origin_access".
## Replace REQUEST_ID and choose approve, deny, or cancel.
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.computer_use_approval_request_result",
      "request_id": "REQUEST_ID",
      "response": { "type": "browser_origin_access", "decision": "approve" }
    }]
  }'

来源示例 9(javascript):

// [中文] 示例 9:处理网站源访问。
// [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
// [中文] 以当前 request_id 关联批准回应;源访问决定与登录字段使用不同 response 类型,不用普通消息提交凭证。
// [中文] 使用 Node.js readline 的异步迭代器读取批准决定;输入流结束或留空时默认拒绝。
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}`);
  }
}

来源示例 10(python):

## [中文] 示例 10:处理网站源访问。
## [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
## [中文] 以当前 request_id 关联批准回应;源访问决定与登录字段使用不同 response 类型,不用普通消息提交凭证。
def respond_to_origin_approval(client, session_id, approval):
    request = approval.request
    if request.type == "browser_origin_access":
        print(request.reason or "The browser needs access to an origin.")
        print("Origin:", request.origin)
        while True:
            decision = (
                input("Allow this origin? [approve/deny/cancel; default: deny] ")
                .strip()
                .lower()
                or "deny"
            )
            if decision in {"approve", "deny", "cancel"}:
                break
            print("Enter approve, deny, or cancel.")
        response = {"type": "browser_origin_access", "decision": decision}
    elif request.type == "browser_authentication":
        print("This public-page task does not sign in; cancelling the request.")
        response = {"type": "browser_authentication", "action": "cancel"}
    else:
        raise RuntimeError(f"Unsupported computer-use approval: {request.type}")

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

来源示例 11(go):

// [中文] 示例 11:处理网站源访问。
// [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
// [中文] 按请求嵌套类型处理源访问批准或取消不需要的认证请求;提交原 request_id 对应的响应。
func respondToOriginApproval(ctx context.Context, client *openai.Client, sessionID string, approval openai.AgentSessionRequiredActionComputerUseApprovalRequest) error {
    response := openai.AgentSessionInputParamAgentSessionInputComputerUseApprovalRequestResultResponseUnion{}
    switch approval.Request.Type {
    case "browser_origin_access":
        request := approval.Request.AsBrowserOriginAccess()
        fmt.Println("Requested origin:", request.Origin)
        if request.Reason != "" {
            fmt.Println(request.Reason)
        }
        originResponse := openai.AgentBrowserOriginAccessParam{Decision: "deny"}
        reader := bufio.NewReader(os.Stdin)
        for {
            fmt.Print("Allow this origin? [approve/deny/cancel; default: deny] ")
            choice, err := reader.ReadString('\n')
            if err != nil {
                return err
            }
            switch strings.ToLower(strings.TrimSpace(choice)) {
            case "approve":
                originResponse.Decision = "approve"
            case "", "deny":
                originResponse.Decision = "deny"
            case "cancel":
                originResponse.Decision = "cancel"
            default:
                fmt.Println("Enter approve, deny, or cancel.")
                continue
            }
            break
        }
        response.OfBrowserOriginAccess = &originResponse
    case "browser_authentication":
        fmt.Println("This public-page task does not sign in; cancelling the sign-in request.")
        response.OfBrowserAuthenticationCancel = &openai.AgentBrowserAuthenticationCancelParam{}
    default:
        return fmt.Errorf("unsupported computer-use approval: %s", approval.Request.Type)
    }
    return client.Beta.Agents.Sessions.Events.New(ctx, sessionID, openai.BetaAgentSessionEventNewParams{
        Events: []openai.AgentSessionInputParamUnion{{
            OfParamAgentSessionInputComputerUseApprovalRequestResult: &openai.AgentSessionInputParamAgentSessionInputComputerUseApprovalRequestResult{
                RequestID: approval.RequestID,
                Response:  response,
            },
        }},
    }, option.WithMaxRetries(0))
}

来源示例 12(java):

// [中文] 从同一BufferedReader读取源访问批准,空输入默认拒绝;认证请求在本示例中取消,不能默认批准。
static void respondToOriginApproval(
    OpenAIClient client,
    String sessionId,
    AgentSession.RequiredAction.ComputerUseApprovalRequest approval,
    BufferedReader approvals)
    throws IOException {
  var result =
      AgentSessionInputComputerUseApprovalRequestResult.builder().requestId(approval.requestId());
  if (approval.request().browserOriginAccess().isPresent()) {
    var request = approval.request().browserOriginAccess().get();
    System.out.printf("Origin: %s%n", request.origin());
    System.out.printf("Reason: %s%n", request.reason().orElse("Not supplied"));
    String decision;
    while (true) {
      System.out.print("Allow browser access? [approve/deny/cancel; default deny] ");
      System.out.flush();
      String input = approvals.readLine();
      if (input == null) throw new IllegalStateException("Approval input closed.");
      decision = input.strip().toLowerCase(java.util.Locale.ROOT);
      if (decision.isEmpty()) decision = "deny";
      if (List.of("approve", "deny", "cancel").contains(decision)) break;
      System.out.printf("Enter approve, deny, or cancel.%n");
    }
    result.response(
        AgentBrowserOriginAccessParam.builder()
            .decision(AgentBrowserOriginAccessParam.Decision.of(decision))
            .build());
  } else if (approval.request().browserAuthentication().isPresent()) {
    System.out.printf("Sign-in is outside this public-page task; cancelling the request.%n");
    result.response(AgentBrowserAuthenticationCancelParam.builder().build());
  } else {
    throw new IllegalStateException("Unsupported computer-use approval request.");
  }
  client
      .withOptions(options -> options.maxRetries(0))
      .beta()
      .agents()
      .sessions()
      .events()
      .create(EventCreateParams.builder().sessionId(sessionId).addEvent(result.build()).build());
}

来源示例 13(csharp):

// [中文] 示例 13:处理网站源访问。
// [中文] C# 从控制台取得源访问决定并响应当前请求;没有交互输入时不能默认批准。
static async Task RespondToOriginApprovalAsync(
    AgentClient client, string sessionId,
    SessionRequiredActionResourceComputerUseApprovalRequest approval)
{
    if (Console.IsInputRedirected)
    {
        throw new InvalidOperationException("Run this example in an interactive terminal.");
    }
    ComputerUseApprovalResponseParam response;
    if (approval.Request is ComputerUseApprovalRequestKindResourceBrowserOriginAccess origin)
    {
        Console.WriteLine($"Origin: {origin.Origin}");
        Console.WriteLine($"Reason: {origin.Reason ?? "Not supplied"}");
        string choice;
        while (true)
        {
            Console.Write("Allow browser access? [approve/deny/cancel; default deny] ");
            choice = (Console.ReadLine() ?? throw new EndOfStreamException("Approval input closed.")).Trim().ToLowerInvariant();
            if (choice.Length == 0) choice = "deny";
            if (choice is "approve" or "deny" or "cancel") break;
            Console.WriteLine("Enter approve, deny, or cancel.");
        }
        BrowserOriginAccessDecisionParam decision = choice switch
        {
            "approve" => BrowserOriginAccessDecisionParam.Approve,
            "cancel" => BrowserOriginAccessDecisionParam.Cancel,
            _ => BrowserOriginAccessDecisionParam.Deny,
        };
        response = new ComputerUseApprovalResponseParamBrowserOriginAccess(decision);
    }
    else if (approval.Request is ComputerUseApprovalRequestKindResourceBrowserAuthentication)
    {
        Console.WriteLine("Sign-in is outside this public-page task; cancelling the request.");
        response = new ComputerUseApprovalResponseParamBrowserAuthenticationCancel();
    }
    else
    {
        throw new InvalidOperationException("Unsupported computer-use approval request.");
    }
    await client.CreateAgentSessionEventsAsync(
        sessionId,
        new CreateSessionEventsParams(
            [new SessionInputParamAgentSessionInputComputerUseApprovalRequestResult(approval.RequestId, response)]
        )
    );
}

来源示例 14(ruby):

## [中文] 示例 14:处理网站源访问。
## [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
## [中文] 以当前 request_id 关联批准回应;源访问决定与登录字段使用不同 response 类型,不用普通消息提交凭证。
def respond_to_origin_approval(client, session_id, approval)
  request = approval.request
  case request.type.to_s
  when "browser_origin_access"
    puts request.reason || "The browser needs access to an origin."
    puts "Origin: #{request.origin}"
    decision = loop do
      print "Allow this origin? [approve/deny/cancel; default: deny] "
      input = $stdin.gets || raise(EOFError, "Input closed before an origin decision.")
      choice = input.strip.downcase
      choice = "deny" if choice.empty?
      break choice if ["approve", "deny", "cancel"].include?(choice)

      puts "Enter approve, deny, or cancel."
    end
    response = {
      type: "browser_origin_access",
      decision: decision
    }
  when "browser_authentication"
    puts "This public-page task does not sign in; cancelling the request."
    response = {
      type: "browser_authentication",
      action: "cancel"
    }
  else
    raise "Unsupported computer-use approval: #{request.type}"
  end
  client.beta.agents.sessions.events.create(
    session_id,
    events: [
      {
        type: "agent.session.input.computer_use_approval_request_result",
        request_id: approval.request_id,
        response: response
      }
    ],
    request_options: { max_retries: 0 }
  )
end

保持流打开,处理每个待批准请求。用 request_id 跟踪请求;请求从当前 required_actions 消失后,移除对应批准控件。取消批准请求不等于取消任务。

202 表示决定已接受,不表示导航已完成。

运行浏览器任务

让智能体查找官方开发者网站的Agents API快速开始,并报告标题与URL。

先打开事件流,再发送任务;到达的源访问请求需持续处理:

来源示例 15(bash):

程序文字说明20:在浏览器打开https://developers.openai.com,查找Agents API快速开始,报告网页标题和URL。

## [中文] 示例 15:运行浏览器任务。
## [中文] 本段涉及的请求或服务地址:https://api.openai.com/v1/agents/sessions/$session_id/events、https://developers.openai.com;示例占位地址需替换为实际资源。
## Terminal 1: use session_id from the creation request.
## Keep this stream open. Wait for HTTP 200 before sending input.
set -o pipefail
## Retain screenshot bytes privately even if saved history omits them.
stream_file=$(mktemp -t openai-browser-stream.XXXXXX) || exit 1
printf 'Saved event stream: %s\n' "$stream_file" >&2
curl --silent --show-error --fail --no-buffer --dump-header - \
  --suppress-connect-headers \
  "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" \
  | tee "$stream_file" \
  | jq --raw-input --unbuffered '
      if startswith("HTTP/") then .
      elif startswith("data:") then
        (ltrimstr("data:") | fromjson?) |
        if .type == "agent.session.turn.output_text.done" then {type, text}
        elif .type == "agent.session.requires_action"
          or .type == "error"
          or .type == "agent.session.failed"
          or .type == "agent.session.environment.failed"
          or (.type | test("^agent.session.turn.(completed|failed|cancelled)$"))
        then {type, turn_id: .turn.id, subagent_id: .turn.subagent_id}
        else empty end
      else empty end'

## 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."
        }]
      }]
    }]
  }'

来源示例 16(javascript):

沿用程序文字说明20。

// [中文] 示例 16:运行浏览器任务。
// [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
// [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
// [中文] 读取当前会话状态和 required_actions;历史项目不代表动作仍待处理。
// [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
// [中文] 从当前 required_actions 判断待处理请求,并保留所需调用或批准 ID。
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();
}

来源示例 17(python):

沿用程序文字说明20。

## [中文] 示例 17:运行浏览器任务。
## [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
## [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
## [中文] 读取当前会话状态和 required_actions;历史项目不代表动作仍待处理。
## [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
## [中文] 从当前 required_actions 判断待处理请求,并保留所需调用或批准 ID。
last_screenshot = None
handled_requests = set()
completed = False
with client.beta.agents.sessions.events.stream(session.id) as events:
    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 event in events:
        if event.type == "agent.session.requires_action":
            current = client.beta.agents.sessions.retrieve(session.id)
            for approval in current.required_actions:
                if (
                    approval.type == "computer_use_approval_request"
                    and approval.request_id not in handled_requests
                ):
                    respond_to_origin_approval(client, session.id, approval)
                    handled_requests.add(approval.request_id)
        elif event.type == "agent.session.turn.item.done":
            # Preserve streamed screenshots; retrieved history may omit image bytes.
            item = event.item
            if item.type == "computer_use_call":
                output = getattr(item, "output", None)
                if output is not None and output.type == "computer_screenshot":
                    prefix = "data:image/jpeg;base64,"
                    if output.image_url.startswith(prefix):
                        last_screenshot = base64.b64decode(
                            output.image_url[len(prefix) :], validate=True
                        )
        elif event.type == "agent.session.turn.output_text.done":
            print(event.text, flush=True)
        elif event.type == "agent.session.turn.completed":
            if event.turn.subagent_id is None:
                completed = True
                print()
                break
        elif event.type in {
            "agent.session.turn.failed",
            "agent.session.turn.cancelled",
        }:
            if event.turn.subagent_id is None:
                raise RuntimeError(f"Browser task ended: {event.type}")
        elif event.type == "error":
            raise RuntimeError(event.error.message)
        elif event.type in {
            "agent.session.failed",
            "agent.session.environment.failed",
        }:
            raise RuntimeError(f"Session failed: {event.type}")
    else:
        raise RuntimeError("Stream closed before the browser task finished.")

来源示例 18(go):

沿用程序文字说明20。

// [中文] 示例 18:运行浏览器任务。
// [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
// [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
// [中文] 读取当前会话状态和 required_actions;历史项目不代表动作仍待处理。
// [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
var lastScreenshot []byte
    handledRequests := map[string]bool{}
    events := client.Beta.Agents.Sessions.Events.StreamStreaming(ctx, session.ID)
    defer events.Close()
    if err := events.Err(); err != nil {
        return err
    }
    err = client.Beta.Agents.Sessions.Events.New(ctx, session.ID, openai.BetaAgentSessionEventNewParams{
        Events: []openai.AgentSessionInputParamUnion{{
            OfParamAgentSessionInputMessage: &openai.AgentSessionInputParamAgentSessionInputMessage{
                Input: []openai.AgentSessionInputMessageParam{{
                    Role: "user",
                    Content: []openai.InputContentParamUnion{{
                        OfParamInputText: &openai.InputContentParamInputText{
                            Text: "Open https://developers.openai.com in the browser. Find the Agents API quickstart, then report its page title and URL.",
                        },
                    }},
                }},
            },
        }},
    })
    if err != nil {
        return err
    }
    completed := false
eventLoop:
    for events.Next() {
        event := events.Current()
        switch event.Type {
        case "agent.session.requires_action":
            current, err := client.Beta.Agents.Sessions.Get(ctx, session.ID)
            if err != nil {
                return err
            }
            for _, action := range current.RequiredActions {
                if action.Type != "computer_use_approval_request" {
                    continue
                }
                approval := action.AsComputerUseApprovalRequest()
                if handledRequests[approval.RequestID] {
                    continue
                }
                if err := respondToOriginApproval(ctx, &client, session.ID, approval); err != nil {
                    return err
                }
                handledRequests[approval.RequestID] = true
            }
        case "agent.session.turn.item.done":
            // Preserve streamed screenshots; retrieved history may omit image bytes.
            item := event.Item
            if item.Type == "computer_use_call" && item.Output.Type == "computer_screenshot" {
                const prefix = "data:image/jpeg;base64,"
                if strings.HasPrefix(item.Output.ImageURL, prefix) {
                    lastScreenshot, err = base64.StdEncoding.DecodeString(strings.TrimPrefix(item.Output.ImageURL, prefix))
                    if err != nil {
                        return err
                    }
                }
            }
        case "agent.session.turn.output_text.done":
            fmt.Println(event.Text)
        case "agent.session.turn.completed":
            if event.Turn.SubagentID == "" {
                completed = true
                break eventLoop
            }
        case "agent.session.turn.failed", "agent.session.turn.cancelled":
            if event.Turn.SubagentID == "" {
                return fmt.Errorf("browser task ended: %s", event.Type)
            }
        case "error":
            return fmt.Errorf("agent error: %s", event.Error.Message)
        case "agent.session.failed", "agent.session.environment.failed":
            return fmt.Errorf("session failed: %s", event.Type)
        }
    }
    if err := events.Err(); err != nil {
        return err
    }
    if !completed {
        return fmt.Errorf("stream closed before the browser task finished")
    }

来源示例 19(java):

// [中文] 示例 19:运行浏览器任务。
// [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
// [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
// [中文] 读取当前会话状态和 required_actions;历史项目不代表动作仍待处理。
var approvals = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
byte[] lastScreenshot = null;
var handledRequests = new HashSet<String>();
try (var events = client.beta().agents().sessions().events().streamStreaming(session.id())) {
  client
      .beta()
      .agents()
      .sessions()
      .events()
      .create(
          EventCreateParams.builder()
              .sessionId(session.id())
              .addEvent(
                  AgentSessionInputParam.AgentSessionInputMessage.builder()
                      .addInput(
                          AgentSessionInputMessageParam.builder()
                              .addInputTextContent(
                                  "Open https://developers.openai.com in the browser. Find"
                                      + " the Agents API quickstart, then report its page"
                                      + " title and URL.")
                              .build())
                      .build())
              .build());
  boolean completed = false;
  var iterator = events.stream().iterator();
  while (iterator.hasNext()) {
    var event = iterator.next();
    if (event.requiresAction().isPresent()) {
      var current = client.beta().agents().sessions().retrieve(session.id());
      for (var action : current.requiredActions()) {
        if (action.computerUseApprovalRequest().isEmpty()) continue;
        var approval = action.computerUseApprovalRequest().get();
        if (!handledRequests.contains(approval.requestId())) {
          respondToOriginApproval(client, session.id(), approval, approvals);
          handledRequests.add(approval.requestId());
        }
      }
    }
    // Preserve streamed screenshots; retrieved history may omit image bytes.
    if (event.turnItemDone().isPresent()
        && event.turnItemDone().get().item().computerUseCall().isPresent()) {
      var activity = event.turnItemDone().get().item().computerUseCall().get();
      var output = activity.output();
      if (output.isPresent()) {
        String imageUrl = output.get().imageUrl();
        String prefix = "data:image/jpeg;base64,";
        if (imageUrl.startsWith(prefix)) {
          lastScreenshot = Base64.getDecoder().decode(imageUrl.substring(prefix.length()));
        }
      }
    }
    event.turnOutputTextDone().ifPresent(text -> System.out.println(text.text()));
    if (event.turnCompleted().filter(e -> e.turn().subagentId().isEmpty()).isPresent()) {
      completed = true;
      break;
    }
    if (event.turnFailed().filter(e -> e.turn().subagentId().isEmpty()).isPresent()
        || event.turnCancelled().filter(e -> e.turn().subagentId().isEmpty()).isPresent()) {
      throw new IllegalStateException("Browser task failed or was cancelled.");
    }
    if (event.error().isPresent()) {
      throw new IllegalStateException(event.error().get().error().message());
    }
    if (event.failed().isPresent() || event.environmentFailed().isPresent()) {
      throw new IllegalStateException("The browser session failed.");
    }
  }
  if (!completed) {
    throw new IllegalStateException("Stream closed before the browser task finished.");
  }
}

来源示例 20(csharp):

沿用程序文字说明20。

// [中文] 示例 20:运行浏览器任务。
// [中文] 本段涉及的请求或服务地址:https://developers.openai.com;示例占位地址需替换为实际资源。
// [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
byte[]? lastScreenshot = null;
HashSet<string> handledRequests = new(StringComparer.Ordinal);
await using var events = await client.GetAgentSessionEventsAsync(session.Id);
await client.CreateAgentSessionEventsAsync(
    session.Id,
    new CreateSessionEventsParams(
        [
            new SessionInputParamAgentSessionInputMessage(
                [
                    new InputMessageParam(
                        [new InputContentParamInputText("Open https://developers.openai.com in the browser. Find the Agents API quickstart, then report its page title and URL.")]
                    ),
                ]
            ),
        ]
    )
);
bool completed = false;
await foreach (var message in events)
{
    using JsonDocument document = JsonDocument.Parse(message.Data.ToMemory());
    JsonElement current = document.RootElement;
    string? type = current.GetProperty("type").GetString();
    if (type == "agent.session.requires_action")
    {
        AgentSession latest = await client.RetrieveAgentSessionAsync(session.Id);
        foreach (SessionRequiredActionResource action in latest.RequiredActions)
        {
            if (action is SessionRequiredActionResourceComputerUseApprovalRequest approval
                && !handledRequests.Contains(approval.RequestId))
            {
                await RespondToOriginApprovalAsync(client, session.Id, approval);
                handledRequests.Add(approval.RequestId);
            }
        }
    }
    else if (type == "agent.session.turn.item.done")
    {
        // Preserve streamed screenshots; retrieved history may omit image bytes.
        JsonElement item = current.GetProperty("item");
        if (item.GetProperty("type").GetString() == "computer_use_call"
            && item.TryGetProperty("output", out JsonElement output)
            && output.ValueKind == JsonValueKind.Object
            && output.GetProperty("type").GetString() == "computer_screenshot")
        {
            const string prefix = "data:image/jpeg;base64,";
            string imageUrl = output.GetProperty("image_url").GetString()!;
            if (imageUrl.StartsWith(prefix, StringComparison.Ordinal))
            {
                lastScreenshot = Convert.FromBase64String(imageUrl[prefix.Length..]);
            }
        }
    }
    else if (type == "agent.session.turn.output_text.done")
    {
        Console.WriteLine(current.GetProperty("text").GetString());
    }
    else if (type is "agent.session.turn.completed" or "agent.session.turn.failed" or "agent.session.turn.cancelled")
    {
        JsonElement turn = current.GetProperty("turn");
        if (turn.TryGetProperty("subagent_id", out JsonElement subagent)
            && subagent.ValueKind != JsonValueKind.Null)
        {
            continue;
        }
        if (type != "agent.session.turn.completed")
        {
            throw new InvalidOperationException($"Browser task ended: {type}");
        }
        completed = true;
        break;
    }
    else if (type == "error")
    {
        throw new InvalidOperationException(current.GetProperty("error").GetProperty("message").GetString());
    }
    else if (type is "agent.session.failed" or "agent.session.environment.failed")
    {
        throw new InvalidOperationException($"Session failed: {type}");
    }
}
if (!completed)
{
    throw new InvalidOperationException("Stream closed before the browser task finished.");
}

来源示例 21(ruby):

沿用程序文字说明20。

## [中文] 示例 21:运行浏览器任务。
## [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
## [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
## [中文] 读取当前会话状态和 required_actions;历史项目不代表动作仍待处理。
## [中文] 从当前 required_actions 判断待处理请求,并保留所需调用或批准 ID。
last_screenshot = String.new(encoding: Encoding::BINARY)
handled_requests = Set.new
events = client.beta.agents.sessions.events.stream_streaming(session.id)
begin
  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."
              }
            ]
          }
        ]
      }
    ]
  )
  completed = events.any? do |event|
    case event
    when OpenAI::Beta::AgentSessionRequiresActionEvent
      current = client.beta.agents.sessions.retrieve(session.id)
      current.required_actions.each do |approval|
        next unless approval.is_a?(OpenAI::Beta::AgentSession::RequiredAction::ComputerUseApprovalRequest)
        next if handled_requests.include?(approval.request_id)

        respond_to_origin_approval(client, session.id, approval)
        handled_requests.add(approval.request_id)
      end
      false
    when OpenAI::Beta::AgentSessionTurnItemDoneEvent
      # Preserve streamed screenshots; retrieved history may omit image bytes.
      item = event.item
      if item.is_a?(OpenAI::Beta::AgentOutputItem::ComputerUseCall)
        output = item.output
        if output&.type.to_s == "computer_screenshot"
          prefix = "data:image/jpeg;base64,"
          if output.image_url.start_with?(prefix)
            last_screenshot.replace(Base64.strict_decode64(output.image_url.delete_prefix(prefix)))
          end
        end
      end
      false
    when OpenAI::Beta::AgentSessionTurnOutputTextDoneEvent
      puts event.text
    when OpenAI::Beta::AgentSessionTurnCompletedEvent
      event.turn.subagent_id.nil?
    when OpenAI::Beta::AgentSessionTurnFailedEvent, OpenAI::Beta::AgentSessionTurnCancelledEvent
      raise "Browser task ended: #{event.type}" if event.turn.subagent_id.nil?
    when OpenAI::Beta::AgentSessionErrorEvent
      raise event.error.message
    when OpenAI::Beta::AgentSessionFailedEvent, OpenAI::Beta::AgentSessionEnvironmentFailedEvent
      raise "Session failed: #{event.type}"
    else
      false
    end
  end
  raise "Stream closed before the browser task finished." unless completed
ensure
  events.close
end

示例打印答案,检查其中是否包含快速开始标题和URL。

关闭事件流不会停止任务,要停止需取消轮次。连接失败或结果不确定,按恢复指南处理;展开的cURL示例还包含传输与错误诊断。

跟踪浏览器活动

浏览器操作以 computer_use_call 项目出现在会话输出。流活动与保存历史使用相同结构:

字段 含义
id 活动项目标识
turn_id 产生该活动的轮次
title 操作描述,或null
status in_progress、completed、failed或incomplete
output 可用时的截图输出

用标题与状态显示进度。活动项目描述单次工具操作,不是最终回答,也不是整轮状态。会话事件模型见事件与项目。

包含截图

在 computer_use 工具设置 include_screenshots: true,可在应用展示浏览器进度。默认API输出不含截图,但智能体仍可观察截图。

已完成浏览器活动会在可用时,将最后一次发出的截图放入 item.output:

来源示例 22(json):

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

通过 image_url 渲染。即使启用截图,有些操作仍返回 output: null,应用须兼容无图活动。

截图可能含敏感网页或账户数据,只向授权用户展示,不写入应用日志。

消费事件流时,从 agent.session.turn.item.done 捕获可用截图。历史读取可能省略图像字节,应在应用保存,不依赖之后的项目列表。轮次结束后,SDK示例列出浏览器活动并保存最近捕获截图,多数使用 browser-screenshot.jpg,Java使用临时文件:

来源示例 23(bash):

## [中文] 示例 23:包含截图。
## [中文] 本段涉及的请求或服务地址:https://api.openai.com/v1/agents/sessions/$session_id/items;示例占位地址需替换为实际资源。
## [中文] 有后续页时将当前 last_id 作为下一页 after;会话列表和追踪列表游标不能混用。
## Screenshot bytes are retained in the private event stream file printed in terminal 1.
## Keep that file: saved history may omit image bytes.
## Save the first page privately; image data stays out of terminal output.
activity_file=$(mktemp)
curl --silent --show-error --fail-with-body --get \
  "https://api.openai.com/v1/agents/sessions/$session_id/items" \
  -H "OpenAI-Beta: agents=v1" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  --data-urlencode "order=asc" --data-urlencode "limit=100" \
  --output "$activity_file"

jq '.data[] | select(.type == "computer_use_call") |
  {id, title: (.title // "Browser activity"), status,
   has_screenshot: (.output != null)}' "$activity_file"
jq '{has_more, last_id}' "$activity_file"
printf 'Saved activity JSON: %s\n' "$activity_file"

## If has_more is true, repeat the GET with --data-urlencode "after=LAST_ID".
## Use the returned last_id and keep order=asc on every page.

来源示例 24(javascript):

// [中文] 示例 24:包含截图。
// [中文] 读取已保存会话项目;它用于恢复历史,不是实时事件重放。
// [中文] 保存可用截图时避免覆盖已有文件;历史读取未必包含图像字节,截图可含敏感信息。
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.");
}

来源示例 25(python):

## [中文] 示例 25:包含截图。
## [中文] 读取已保存会话项目;它用于恢复历史,不是实时事件重放。
## [中文] 保存可用截图时避免覆盖已有文件;历史读取未必包含图像字节,截图可含敏感信息。
for item in client.beta.agents.sessions.items.list(
    session.id, order="asc", limit=100
):
    if item.type != "computer_use_call":
        continue
    print(item.title or "Browser activity", item.status)

if last_screenshot is not None:
    screenshot_path = Path("browser-screenshot.jpg")
    descriptor = os.open(
        screenshot_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600
    )
    with os.fdopen(descriptor, "wb") as screenshot_file:
        screenshot_file.write(last_screenshot)
    print("Saved screenshot:", screenshot_path)
else:
    print("No screenshot was returned.")
ready_to_delete = completed

来源示例 26(go):

// [中文] 示例 26:包含截图。
// [中文] 读取已保存会话项目;它用于恢复历史,不是实时事件重放。
// [中文] 保存可用截图时避免覆盖已有文件;历史读取未必包含图像字节,截图可含敏感信息。
items := client.Beta.Agents.Sessions.Items.ListAutoPaging(ctx, session.ID, openai.BetaAgentSessionItemListParams{
    Order: "asc", Limit: openai.Int(100),
})
for items.Next() {
    item := items.Current()
    if item.Type != "computer_use_call" {
        continue
    }
    activity := item.AsComputerUseCall()
    title := activity.Title
    if title == "" {
        title = "Browser activity"
    }
    fmt.Println(title, activity.Status)
}
if err := items.Err(); err != nil {
    return err
}
if lastScreenshot != nil {
    file, err := os.OpenFile("browser-screenshot.jpg", os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0o600)
    if err != nil {
        return err
    }
    defer file.Close()
    if err := file.Chmod(0o600); err != nil {
        return err
    }
    if _, err := file.Write(lastScreenshot); err != nil {
        return err
    }
    fmt.Println("Saved screenshot: browser-screenshot.jpg")
} else {
    fmt.Println("No screenshot was returned.")
}
readyToDelete = true

来源示例 27(java):

// [中文] 示例 27:包含截图。
// [中文] 读取已保存会话项目;它用于恢复历史,不是实时事件重放。
// [中文] 保存可用截图时避免覆盖已有文件;历史读取未必包含图像字节,截图可含敏感信息。
var items =
    client
        .beta()
        .agents()
        .sessions()
        .items()
        .list(
            ItemListParams.builder()
                .sessionId(session.id())
                .order(ItemListParams.Order.ASC)
                .limit(100L)
                .build());
for (var item : items.autoPager()) {
  if (item.computerUseCall().isEmpty()) continue;
  var activity = item.computerUseCall().get();
  System.out.println(activity.title().orElse("Browser activity") + " " + activity.status());
}
if (lastScreenshot != null) {
  var screenshotPath = Files.createTempFile("browser-screenshot-", ".jpg");
  Files.write(screenshotPath, lastScreenshot);
  System.out.println("Saved screenshot: " + screenshotPath);
} else {
  System.out.println("No screenshot was returned.");
}

来源示例 28(csharp):

// [中文] 示例 28:包含截图。
// [中文] 保存可用截图时避免覆盖已有文件;历史读取未必包含图像字节,截图可含敏感信息。
await foreach (AgentSessionItem item in client.GetAgentSessionItemsAsync(
    session.Id, limit: 100, order: AgentSessionItemCollectionOrder.Ascending))
{
    if (item is not ComputerUseCallItemResource activity)
    {
        continue;
    }
    Console.WriteLine($"{activity.Title ?? "Browser activity"}: {activity.Status}");

}
if (lastScreenshot is not null)
{
    FileStreamOptions fileOptions = new()
    {
        Mode = FileMode.CreateNew,
        Access = FileAccess.Write,
        Share = FileShare.None,
    };
    if (!OperatingSystem.IsWindows())
    {
        fileOptions.UnixCreateMode = UnixFileMode.UserRead | UnixFileMode.UserWrite;
    }
    await using FileStream file = new("browser-screenshot.jpg", fileOptions);
    if (!OperatingSystem.IsWindows())
    {
        File.SetUnixFileMode(file.SafeFileHandle, UnixFileMode.UserRead | UnixFileMode.UserWrite);
    }
    await file.WriteAsync(lastScreenshot);
    Console.WriteLine("Saved screenshot: browser-screenshot.jpg");
}
else
{
    Console.WriteLine("No screenshot was returned.");
}

来源示例 29(ruby):

## [中文] 示例 29:包含截图。
## [中文] 读取已保存会话项目;它用于恢复历史,不是实时事件重放。
## [中文] 保存可用截图时避免覆盖已有文件;历史读取未必包含图像字节,截图可含敏感信息。
items = client.beta.agents.sessions.items.list(
  session.id,
  order: "asc",
  limit: 100
)
items.auto_paging_each do |item|
  next unless item.is_a?(OpenAI::Beta::AgentSessionItem::ComputerUseCall)

  puts "#{item.title || "Browser activity"}: #{item.status}"
end

if last_screenshot.empty?
  puts "No screenshot was returned."
else
  screenshot_path = "browser-screenshot.jpg"
  File.open(screenshot_path, File::WRONLY | File::CREAT | File::EXCL, 0o600) do |file|
    file.chmod(0o600)
    file.binmode
    file.write(last_screenshot)
  end
  puts "Saved screenshot: #{screenshot_path}"
end

SDK示例不覆盖现有文件,再次运行前先移动或移除已有 browser-screenshot.jpg。

处理登录

读取私有GitHub问题等任务需要已登录浏览器。由应用处理登录,用户可在聊天之外选择方式并填写凭证。

登录可能有多个请求:先选邮箱登录,再填邮箱,再填验证码。界面须依据每次请求的登录选项和字段生成。

只有主智能体可以请求浏览器认证,子智能体不能。支持邮箱、密码、验证码,不支持通行密钥或扫码;强制使用不受支持方式的网站无法通过此流程登录。

保持任务事件流打开,并处理源批准。收到 agent.session.requires_action,读取当前动作,找出嵌套 request.type: "browser_authentication" 的 computer_use_approval_request。

按嵌套 request 构建界面:

字段 用途
reason 解释为何需要输入,可为null
credential_origin 显示凭证目标,可为null
fields 根据各字段id、label、type、required生成输入框,可为空
options 登录方式,每项有id、label及关联输入field_ids

有登录方式则让用户选择,再显示关联字段;没有选项时直接显示请求字段。

用户只能向可验证目标填写凭证。目标缺失或陌生且无法独立验证时,取消认证请求。

用动作外层 request_id 提交用户输入,或在拒绝时取消。继续处理后续请求。提交回应不证明登录成功,应跟到任务完成并核对结果。

示例:先选方式,再填验证码

支持邮箱验证码和密码的网站,可能先只要求选择方式而不要求字段:

来源示例 30(json):

程序文字说明21:选择登录问题跟踪系统的方式。

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

选择邮箱验证码时,使用当前 request_id 发送 selected_option: "email_code" 与 fields: []。之后网站可能分别请求邮箱和验证码。每次都按新请求字段和ID渲染,只在当前请求有选项时提供 selected_option。

返回用户输入

通过会话事件端点发送回应。action 为 submit,请求ID和字段ID必须来自当前批准请求。邮箱输入示例:

来源示例 31(json):

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

有登录选项时,response.selected_option 使用所选ID,只提交其 field_ids 内字段,每个必填值非空。该方式无字段时发 fields: []。

没有登录选项则省略 selected_option,直接提交请求字段。只要请求列出字段,即使全为可选,也至少提供一个。

登录值只通过此专用事件发送,既不进入模型输入,也不会出现在历史中的认证响应项目里。

包括邮箱在内,每个值都视为敏感数据:输入时遮蔽,不进入日志、分析或保存的界面状态,提交后清空表单。不要通过普通消息或函数结果传凭证。

提交事件不带 turn_id。字段与载荷限制见认证提交限制。

空体 202 只说明提交被接受,不说明登录成功;继续跟随事件。关闭凭证提交的HTTP或SDK自动重试,接受与否不明时先刷新会话。

允许用户取消

用户拒绝登录时,以原 request_id 发送 action: "cancel",省略 fields 和 selected_option:

来源示例 32(json):

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

这只取消认证请求。要停止任务,另行取消轮次。

运行需要认证的浏览器任务

应用在跟随事件时处理源批准与登录。下列辅助函数先展示目标,再隐藏输入值进行收集与提交;用户拒绝则取消:

来源示例 33(bash):

## [中文] 示例 33:运行需要认证的浏览器任务。
## [中文] 本段涉及的请求或服务地址:https://api.openai.com/v1/agents/sessions/$session_id、https://api.openai.com/v1/agents/sessions/$session_id/events;示例占位地址需替换为实际资源。
## [中文] 从当前 required_actions 判断待处理请求,并保留所需调用或批准 ID。
## Run in a second terminal when the private task requires input.
## Set session_id to that task's actual session ID first.
curl --silent --show-error --fail-with-body "https://api.openai.com/v1/agents/sessions/$session_id" \
  -H "OpenAI-Beta: agents=v1" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  | jq '.required_actions[] | select(.type == "computer_use_approval_request")'

## Save a submission or cancellation payload from the preceding sections
## to browser-auth-response.json using the pending request and field IDs.
## Restrict file access to your user; remove it after submission.
curl --fail-with-body --retry 0 "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" \
  --data-binary @browser-auth-response.json

来源示例 34(javascript):

// [中文] 示例 34:运行需要认证的浏览器任务。
// [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
// [中文] 以当前 request_id 关联批准回应;源访问决定与登录字段使用不同 response 类型,不用普通消息提交凭证。
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 }
  );
}

来源示例 35(python):

程序文字说明22:智能体需要你登录。

## [中文] 示例 35:运行需要认证的浏览器任务。
## [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
## [中文] 以当前 request_id 关联批准回应;源访问决定与登录字段使用不同 response 类型,不用普通消息提交凭证。
from getpass import getpass


def read_authentication_response(request):
    cancel = {"type": "browser_authentication", "action": "cancel"}
    print(request.reason or "The agent needs you to sign in.")
    print("Credential origin:", request.credential_origin or "Not supplied")
    consent = input("Have you verified the sign-in destination? [y/N] ")
    if consent.strip().lower() not in {"y", "yes"}:
        return cancel

    selected_option = None
    active_fields = request.fields
    if request.options:
        for index, option in enumerate(request.options, start=1):
            print(f"{index}. {option.label}")
        while True:
            choice = input("Choose a sign-in method, or enter cancel: ").strip().lower()
            if choice == "cancel":
                return cancel
            if choice.isdigit() and 1 <= int(choice) <= len(request.options):
                option = request.options[int(choice) - 1]
                break
            print("Enter a method number from the list, or cancel.")
        selected_option = option.id
        fields_by_id = {field.id: field for field in request.fields}
        active_fields = [fields_by_id[field_id] for field_id in option.field_ids]

    values = []
    while True:
        for field in active_fields:
            while True:
                label = field.label if field.required else f"{field.label} (optional)"
                value = getpass(f"{label} (leave blank for options): ")
                if value:
                    values.append({"field_id": field.id, "value": value})
                    break
                choices = {"", "enter", "cancel"}
                if field.required:
                    question = "Enter a value or cancel sign-in? [enter/cancel, default enter] "
                else:
                    choices.add("skip")
                    question = "Skip this field, enter a value, or cancel sign-in? [skip/enter/cancel, default skip] "
                while True:
                    action = input(question).strip().lower()
                    if action in choices:
                        break
                if action == "cancel":
                    return cancel
                if not field.required and action in {"", "skip"}:
                    break
        if selected_option is not None or not active_fields or values:
            break
        print("This form requires at least one field. Enter a value or cancel sign-in.")

    response = {
        "type": "browser_authentication",
        "action": "submit",
        "fields": values,
    }
    if selected_option is not None:
        response["selected_option"] = selected_option
    return response


def respond_to_computer_use_approval(client, session_id, approval):
    request = approval.request
    approval_client = client
    if request.type == "browser_origin_access":
        print(request.reason or "The browser needs access to an origin.")
        print("Origin:", request.origin)
        while True:
            decision = (
                input("Allow this origin? [approve/deny/cancel; default: deny] ")
                .strip()
                .lower()
                or "deny"
            )
            if decision in {"approve", "deny", "cancel"}:
                break
            print("Enter approve, deny, or cancel.")
        response = {"type": "browser_origin_access", "decision": decision}
    elif request.type == "browser_authentication":
        response = read_authentication_response(request)
        approval_client = client.with_options(max_retries=0)
    else:
        raise RuntimeError(f"Unsupported computer-use approval: {request.type}")

    approval_client.beta.agents.sessions.events.create(
        session_id,
        events=[
            {
                "type": "agent.session.input.computer_use_approval_request_result",
                "request_id": approval.request_id,
                "response": response,
            }
        ],
    )
    # Admission does not establish sign-in or navigation success; keep reading events.

来源示例 36(go):

沿用程序文字说明22。

// [中文] 示例 36:运行需要认证的浏览器任务。
// [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
// [中文] Go 辅助函数逐次处理源批准和登录表单,隐藏凭证输入,按当前请求的字段与选项提交,并关闭凭证自动重试。
import (
    "context"
    "fmt"
    "io"
    "os"
    "strconv"
    "strings"

    "github.com/openai/openai-go/v3"
    "github.com/openai/openai-go/v3/option"
    "golang.org/x/term"
)

func respondToComputerUseApproval(ctx context.Context, client *openai.Client, sessionID string, approval openai.AgentSessionRequiredActionComputerUseApprovalRequest) error {
    send := func(response openai.AgentSessionInputParamAgentSessionInputComputerUseApprovalRequestResultResponseUnion) error {
        return client.Beta.Agents.Sessions.Events.New(ctx, sessionID, openai.BetaAgentSessionEventNewParams{
            Events: []openai.AgentSessionInputParamUnion{{
                OfParamAgentSessionInputComputerUseApprovalRequestResult: &openai.AgentSessionInputParamAgentSessionInputComputerUseApprovalRequestResult{
                    RequestID: approval.RequestID,
                    Response:  response,
                },
            }},
        }, option.WithMaxRetries(0))
    }
    readLine := func(prompt string) (string, error) {
        fmt.Print(prompt)
        var value strings.Builder
        var input [1]byte
        for {
            if _, err := io.ReadFull(os.Stdin, input[:]); err != nil {
                return "", err
            }
            if input[0] == '\n' {
                return strings.TrimSpace(value.String()), nil
            }
            value.WriteByte(input[0])
        }
    }
    cancelAuthentication := func() error {
        return send(openai.AgentSessionInputParamAgentSessionInputComputerUseApprovalRequestResultResponseUnion{
            OfBrowserAuthenticationCancel: &openai.AgentBrowserAuthenticationCancelParam{},
        })
    }
    switch approval.Request.Type {
    case "browser_origin_access":
        request := approval.Request.AsBrowserOriginAccess()
        fmt.Println("Requested origin:", request.Origin)
        if request.Reason != "" {
            fmt.Println(request.Reason)
        }
        response := openai.AgentBrowserOriginAccessParam{Decision: "deny"}
        for {
            choice, err := readLine("Allow this origin? [approve/deny/cancel; default: deny] ")
            if err != nil {
                return err
            }
            switch strings.ToLower(choice) {
            case "approve":
                response.Decision = "approve"
            case "", "deny":
                response.Decision = "deny"
            case "cancel":
                response.Decision = "cancel"
            default:
                fmt.Println("Enter approve, deny, or cancel.")
                continue
            }
            break
        }
        return send(openai.AgentSessionInputParamAgentSessionInputComputerUseApprovalRequestResultResponseUnion{OfBrowserOriginAccess: &response})
    case "browser_authentication":
        // Collect credentials only for an authentication request.
    default:
        return fmt.Errorf("unsupported computer-use approval: %s", approval.Request.Type)
    }
    challenge := approval.Request.AsBrowserAuthentication()
    reason := challenge.Reason
    if reason == "" {
        reason = "The agent needs you to sign in."
    }
    origin := challenge.CredentialOrigin
    if origin == "" {
        origin = "Not supplied"
    }
    fmt.Println(reason)
    fmt.Println("Credential origin:", origin)
    consent, err := readLine("Have you verified the sign-in destination? [y/N] ")
    if err != nil {
        return err
    }
    if strings.ToLower(consent) != "y" && strings.ToLower(consent) != "yes" {
        return cancelAuthentication()
    }

    response := openai.AgentBrowserAuthenticationSubmitParam{
        Action: "submit",
        Fields: []openai.AgentBrowserAuthenticationSubmitParamField{},
    }
    activeFields := challenge.Fields
    if len(challenge.Options) > 0 {
        for index, option := range challenge.Options {
            fmt.Printf("%d. %s\n", index+1, option.Label)
        }
        for {
            choice, err := readLine("Choose a sign-in method [number/cancel]: ")
            if err != nil {
                return err
            }
            if strings.EqualFold(choice, "cancel") {
                return cancelAuthentication()
            }
            index, err := strconv.Atoi(choice)
            if err != nil || index < 1 || index > len(challenge.Options) {
                fmt.Println("Enter a method number from the list, or enter cancel.")
                continue
            }
            option := challenge.Options[index-1]
            response.SelectedOption = openai.String(option.ID)
            activeFields = nil
            for _, fieldID := range option.FieldIDs {
                found := false
                for _, field := range challenge.Fields {
                    if field.ID == fieldID {
                        activeFields = append(activeFields, field)
                        found = true
                        break
                    }
                }
                if !found {
                    return fmt.Errorf("sign-in method references an unknown field: %s", fieldID)
                }
            }
            break
        }
    }

collectFields:
    for {
        response.Fields = response.Fields[:0]
        for _, field := range activeFields {
        fieldInput:
            for {
                choices := "enter/cancel; default: enter"
                if !field.Required {
                    choices = "enter/skip/cancel; default: enter"
                }
                choice, err := readLine(fmt.Sprintf("%s [%s]: ", field.Label, choices))
                if err != nil {
                    return err
                }
                switch strings.ToLower(choice) {
                case "cancel":
                    return cancelAuthentication()
                case "skip":
                    if field.Required {
                        fmt.Println("This field is required. Enter a value or cancel sign-in.")
                        continue
                    }
                    break fieldInput
                case "", "enter":
                    fmt.Printf("%s (hidden): ", field.Label)
                    value, err := term.ReadPassword(int(os.Stdin.Fd()))
                    fmt.Println()
                    if err != nil {
                        return err
                    }
                    if len(value) == 0 {
                        fmt.Println("No value entered. Choose an action for this field.")
                        continue
                    }
                    response.Fields = append(response.Fields, openai.AgentBrowserAuthenticationSubmitParamField{
                        FieldID: field.ID, Value: string(value),
                    })
                    clear(value)
                    break fieldInput
                default:
                    fmt.Println("Choose one of the listed actions.")
                }
            }
        }
        if len(challenge.Options) > 0 || len(challenge.Fields) == 0 || len(response.Fields) > 0 {
            break
        }
        for {
            choice, err := readLine("Enter at least one field or cancel sign-in [retry/cancel; default: cancel]: ")
            if err != nil {
                return err
            }
            switch strings.ToLower(choice) {
            case "retry":
                continue collectFields
            case "", "cancel":
                return cancelAuthentication()
            default:
                fmt.Println("Enter retry or cancel.")
            }
        }
    }
    // Admission does not establish login success; keep following the session.
    return send(openai.AgentSessionInputParamAgentSessionInputComputerUseApprovalRequestResultResponseUnion{
        OfBrowserAuthenticationSubmit: &response,
    })
}

来源示例 37(java):

沿用程序文字说明22。

// [中文] 示例 37:运行需要认证的浏览器任务。
// [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.beta.agents.AgentBrowserAuthenticationCancelParam;
import com.openai.models.beta.agents.AgentBrowserAuthenticationSubmitParam;
import com.openai.models.beta.agents.AgentBrowserOriginAccessParam;
import com.openai.models.beta.agents.AgentSession;
import com.openai.models.beta.agents.AgentSessionInputMessageParam;
import com.openai.models.beta.agents.AgentSessionInputParam;
import com.openai.models.beta.agents.AgentSessionInputParam.AgentSessionInputComputerUseApprovalRequestResult;
import com.openai.models.beta.agents.AgentToolParam;
import com.openai.models.beta.agents.EnvironmentParam;
import com.openai.models.beta.agents.sessions.SessionCreateParams;
import com.openai.models.beta.agents.sessions.events.EventCreateParams;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

static void cancelAuthentication(OpenAIClient client, String sessionId, String requestId) {
  var cancellation =
      AgentSessionInputComputerUseApprovalRequestResult.builder()
          .requestId(requestId)
          .response(AgentBrowserAuthenticationCancelParam.builder().build())
          .build();
  client
      .withOptions(options -> options.maxRetries(0))
      .beta()
      .agents()
      .sessions()
      .events()
      .create(EventCreateParams.builder().sessionId(sessionId).addEvent(cancellation).build());
}

static void respondToComputerUseApproval(
    OpenAIClient client,
    String sessionId,
    AgentSession.RequiredAction.ComputerUseApprovalRequest approval) {
  var console = System.console();
  if (console == null)
    throw new IllegalStateException("Run this example in an interactive terminal.");
  var result =
      AgentSessionInputComputerUseApprovalRequestResult.builder().requestId(approval.requestId());
  if (approval.request().browserOriginAccess().isPresent()) {
    var request = approval.request().browserOriginAccess().get();
    console.printf("Origin: %s%n", request.origin());
    console.printf("Reason: %s%n", request.reason().orElse("Not supplied"));
    String decision;
    while (true) {
      String input =
          console.readLine("Allow browser access? [approve/deny/cancel; default deny] ");
      if (input == null) throw new IllegalStateException("Approval input closed.");
      decision = input.strip().toLowerCase(java.util.Locale.ROOT);
      if (decision.isEmpty()) decision = "deny";
      if (List.of("approve", "deny", "cancel").contains(decision)) break;
      console.printf("Enter approve, deny, or cancel.%n");
    }
    result.response(
        AgentBrowserOriginAccessParam.builder()
            .decision(AgentBrowserOriginAccessParam.Decision.of(decision))
            .build());
  } else if (approval.request().browserAuthentication().isPresent()) {
    var challenge = approval.request().browserAuthentication().get();
    console.printf("%s%n", challenge.reason().orElse("The agent needs you to sign in."));
    console.printf(
        "Credential origin: %s%n", challenge.credentialOrigin().orElse("Not supplied"));
    String consent = console.readLine("Have you verified the sign-in destination? [y/N] ");
    if (consent == null
        || !List.of("y", "yes").contains(consent.strip().toLowerCase(java.util.Locale.ROOT))) {
      cancelAuthentication(client, sessionId, approval.requestId());
      return;
    }
    var response = AgentBrowserAuthenticationSubmitParam.builder().fields(List.of());
    var activeFields = challenge.fields();
    if (!challenge.options().isEmpty()) {
      for (int i = 0; i < challenge.options().size(); i++) {
        console.printf("%d. %s%n", i + 1, challenge.options().get(i).label());
      }
      while (true) {
        String choice = console.readLine("Choose a sign-in method, or enter cancel: ");
        if (choice == null || choice.strip().equalsIgnoreCase("cancel")) {
          cancelAuthentication(client, sessionId, approval.requestId());
          return;
        }
        int index;
        try {
          index = Integer.parseInt(choice.strip()) - 1;
        } catch (NumberFormatException e) {
          console.printf("Enter a method number from the list, or cancel.%n");
          continue;
        }
        if (index < 0 || index >= challenge.options().size()) {
          console.printf("Enter a method number from the list, or cancel.%n");
          continue;
        }
        var option = challenge.options().get(index);
        response.selectedOption(option.id());
        activeFields =
            challenge.fields().stream()
                .filter(field -> option.fieldIds().contains(field.id()))
                .toList();
        break;
      }
    }
    while (true) {
      int fieldsSubmitted = 0;
      for (var field : activeFields) {
        while (true) {
          String choice =
              console.readLine(
                  field.required()
                      ? "%s [enter/cancel; default enter]: "
                      : "%s [enter/skip/cancel; default skip]: ",
                  field.label());
          if (choice == null || choice.strip().equalsIgnoreCase("cancel")) {
            cancelAuthentication(client, sessionId, approval.requestId());
            return;
          }
          choice = choice.strip().toLowerCase(java.util.Locale.ROOT);
          if (choice.isEmpty()) choice = field.required() ? "enter" : "skip";
          if (choice.equals("skip") && !field.required()) break;
          if (!choice.equals("enter")) {
            console.printf("Choose one of the displayed options.%n");
            continue;
          }
          char[] characters = console.readPassword("%s: ", field.label());
          if (characters == null) {
            cancelAuthentication(client, sessionId, approval.requestId());
            return;
          }
          String value = new String(characters);
          Arrays.fill(characters, '\0');
          if (value.isEmpty()) {
            if (!field.required()) break;
            console.printf("This field is required.%n");
            continue;
          }
          response.addField(
              AgentBrowserAuthenticationSubmitParam.Field.builder()
                  .fieldId(field.id())
                  .value(value)
                  .build());
          fieldsSubmitted++;
          break;
        }
      }
      if (!challenge.options().isEmpty() || activeFields.isEmpty() || fieldsSubmitted > 0) break;
      console.printf("Enter at least one field to submit this form, or cancel.%n");
    }
    result.response(response.build());
  } else {
    throw new IllegalStateException("Unsupported computer-use approval request.");
  }
  // An uncertain approval must not resend credentials automatically.
  client
      .withOptions(options -> options.maxRetries(0))
      .beta()
      .agents()
      .sessions()
      .events()
      .create(EventCreateParams.builder().sessionId(sessionId).addEvent(result.build()).build());
  // Admission does not establish sign-in or navigation success; keep following the session.
}

来源示例 38(csharp):

沿用程序文字说明22。

// [中文] 示例 38:运行需要认证的浏览器任务。
// [中文] C# 辅助函数显示凭证目标、读取隐藏字段输入并提交专用认证回应;仍需跟随事件确认登录及任务结果。
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Globalization;
using System.Text;
using System.Text.Json;
using OpenAI;
using OpenAI.Agents;
#pragma warning disable OPENAI001

static string ReadLine(string prompt)
{
    Console.Write(prompt);
    return Console.ReadLine() ?? "cancel";
}

static string ReadHidden(string prompt)
{
    if (Console.IsInputRedirected)
    {
        throw new InvalidOperationException("Run this sign-in example in a terminal.");
    }
    Console.Write(prompt);
    StringBuilder value = new();
    while (true)
    {
        ConsoleKeyInfo key = Console.ReadKey(intercept: true);
        if (key.Key == ConsoleKey.Enter)
        {
            Console.WriteLine();
            return value.ToString();
        }
        if (key.Key == ConsoleKey.Backspace)
        {
            if (value.Length > 0)
            {
                value.Length--;
            }
        }
        else if (!char.IsControl(key.KeyChar))
        {
            value.Append(key.KeyChar);
        }
    }
}

static async Task RespondToComputerUseApprovalAsync(
    AgentClient client, string sessionId,
    SessionRequiredActionResourceComputerUseApprovalRequest approval)
{
    if (Console.IsInputRedirected)
    {
        throw new InvalidOperationException("Run this example in an interactive terminal.");
    }
    ComputerUseApprovalResponseParam response;
    if (approval.Request is ComputerUseApprovalRequestKindResourceBrowserOriginAccess origin)
    {
        Console.WriteLine($"Origin: {origin.Origin}");
        Console.WriteLine($"Reason: {origin.Reason ?? "Not supplied"}");
        string choice;
        while (true)
        {
            Console.Write("Allow browser access? [approve/deny/cancel; default deny] ");
            choice = (Console.ReadLine() ?? throw new EndOfStreamException("Approval input closed.")).Trim().ToLowerInvariant();
            if (choice.Length == 0) choice = "deny";
            if (choice is "approve" or "deny" or "cancel") break;
            Console.WriteLine("Enter approve, deny, or cancel.");
        }
        BrowserOriginAccessDecisionParam decision = choice switch
        {
            "approve" => BrowserOriginAccessDecisionParam.Approve,
            "cancel" => BrowserOriginAccessDecisionParam.Cancel,
            _ => BrowserOriginAccessDecisionParam.Deny,
        };
        response = new ComputerUseApprovalResponseParamBrowserOriginAccess(decision);
    }
    else if (approval.Request is ComputerUseApprovalRequestKindResourceBrowserAuthentication challenge)
    {
        async Task CancelAuthenticationAsync()
        {
            await client.CreateAgentSessionEventsAsync(
                sessionId,
                new CreateSessionEventsParams(
                    [new SessionInputParamAgentSessionInputComputerUseApprovalRequestResult(
                        approval.RequestId, new ComputerUseApprovalResponseParamBrowserAuthenticationCancel())]
                )
            );
        }

        Console.WriteLine(challenge.Reason ?? "The agent needs you to sign in.");
        Console.WriteLine($"Credential origin: {challenge.CredentialOrigin ?? "Not supplied"}");
        string consent = ReadLine("Have you verified the sign-in destination? [y/N] ").Trim();
        if (!consent.Equals("y", StringComparison.OrdinalIgnoreCase)
            && !consent.Equals("yes", StringComparison.OrdinalIgnoreCase))
        {
            await CancelAuthenticationAsync();
            return;
        }

        ComputerUseApprovalResponseParamBrowserAuthenticationSubmit submission = new([]);
        var activeFields = challenge.Fields.ToList();
        if (challenge.Options.Count > 0)
        {
            for (int index = 0; index < challenge.Options.Count; index++)
            {
                Console.WriteLine($"{index + 1}. {challenge.Options[index].Label}");
            }
            while (true)
            {
                string choice = ReadLine("Choose a sign-in method, or enter cancel: ").Trim();
                if (choice.Equals("cancel", StringComparison.OrdinalIgnoreCase))
                {
                    await CancelAuthenticationAsync();
                    return;
                }
                if (!int.TryParse(choice, NumberStyles.None, CultureInfo.InvariantCulture, out int index)
                    || index < 1 || index > challenge.Options.Count)
                {
                    Console.WriteLine("Enter a method number from the list, or cancel.");
                    continue;
                }
                var option = challenge.Options[index - 1];
                submission.SelectedOption = option.Id;
                var fieldsById = challenge.Fields.ToDictionary(field => field.Id, StringComparer.Ordinal);
                activeFields = option.FieldIds.Select(id => fieldsById[id]).ToList();
                break;
            }
        }
        while (true)
        {
            foreach (var field in activeFields)
            {
                while (true)
                {
                    string choices = field.Required
                        ? "enter/cancel; default enter" : "enter/skip/cancel; default skip";
                    string choice = ReadLine($"{field.Label} [{choices}]: ").Trim().ToLowerInvariant();
                    if (choice == "cancel")
                    {
                        await CancelAuthenticationAsync();
                        return;
                    }
                    if (choice.Length == 0) choice = field.Required ? "enter" : "skip";
                    if (choice == "skip" && !field.Required) break;
                    if (choice != "enter")
                    {
                        Console.WriteLine("Choose one of the displayed options.");
                        continue;
                    }
                    string value = ReadHidden($"{field.Label}: ");
                    if (value.Length == 0)
                    {
                        if (!field.Required) break;
                        Console.WriteLine("This field is required.");
                        continue;
                    }
                    submission.Fields.Add(new BrowserAuthenticationFieldValueParam(field.Id, value));
                    break;
                }
            }
            if (challenge.Options.Count > 0 || activeFields.Count == 0 || submission.Fields.Count > 0) break;
            Console.WriteLine("Enter at least one field to submit this form, or cancel.");
        }
        response = submission;
    }
    else
    {
        throw new InvalidOperationException("Unsupported computer-use approval request.");
    }
    await client.CreateAgentSessionEventsAsync(
        sessionId,
        new CreateSessionEventsParams(
            [new SessionInputParamAgentSessionInputComputerUseApprovalRequestResult(approval.RequestId, response)]
        )
    );
    // Admission does not establish sign-in or navigation success; keep following the session.
}

来源示例 39(ruby):

沿用程序文字说明22。

## [中文] 示例 39:运行需要认证的浏览器任务。
## [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
## [中文] 以当前 request_id 关联批准回应;源访问决定与登录字段使用不同 response 类型,不用普通消息提交凭证。
require "io/console"

def computer_use_sign_in_choice(prompt)
  print prompt
  input = $stdin.gets || raise(EOFError, "Input closed before a sign-in choice.")
  input.strip.downcase
end

def computer_use_authentication_response(request)
  cancel = {
    type: "browser_authentication",
    action: "cancel"
  }
  puts request.reason || "The agent needs you to sign in."
  puts "Credential origin: #{request.credential_origin || "Not supplied"}"
  consent = computer_use_sign_in_choice("Have you verified the sign-in destination? [y/N] ")
  return cancel unless ["y", "yes"].include?(consent)

  selected_option = nil
  active_fields = request.fields
  unless request.options.empty?
    request.options.each_with_index do |option, index|
      puts "#{index + 1}. #{option.label}"
    end
    option = loop do
      choice = computer_use_sign_in_choice("Choose a sign-in method [number/cancel]: ")
      return cancel if choice == "cancel"
      if choice.match?(/\A\d+\z/) && choice.to_i.between?(1, request.options.length)
        break request.options[choice.to_i - 1]
      end

      puts "Enter a method number from the list, or enter cancel."
    end
    selected_option = option.id
    fields_by_id = request.fields.to_h { |field| [field.id, field] }
    active_fields = option.field_ids.map { |field_id| fields_by_id.fetch(field_id) }
  end

  values = []
  loop do
    values.clear
    active_fields.each do |field|
      loop do
        choices = field.required ? "enter/cancel; default: enter" : "enter/skip/cancel; default: enter"
        choice = computer_use_sign_in_choice("#{field.label} [#{choices}]: ")
        case choice
        when "cancel"
          return cancel
        when "skip"
          break unless field.required

          puts "This field is required. Enter a value or cancel sign-in."
        when "", "enter"
          value = $stdin.getpass("#{field.label} (hidden): ")
          if value.empty?
            puts "No value entered. Choose an action for this field."
            next
          end
          values << {
            field_id: field.id,
            value: value
          }
          break
        else
          puts "Choose one of the listed actions."
        end
      end
    end
    break unless request.options.empty? && !request.fields.empty? && values.empty?

    loop do
      choice = computer_use_sign_in_choice("Enter at least one field or cancel sign-in [retry/cancel; default: cancel]: ")
      return cancel if ["", "cancel"].include?(choice)
      break if choice == "retry"

      puts "Enter retry or cancel."
    end
  end

  response = {
    type: "browser_authentication",
    action: "submit",
    fields: values
  }
  response[:selected_option] = selected_option unless selected_option.nil?
  response
end

def respond_to_computer_use_approval(client, session_id, approval)
  request = approval.request
  case request.type.to_s
  when "browser_origin_access"
    puts request.reason || "The browser needs access to an origin."
    puts "Origin: #{request.origin}"
    decision = loop do
      print "Allow this origin? [approve/deny/cancel; default: deny] "
      input = $stdin.gets || raise(EOFError, "Input closed before an origin decision.")
      choice = input.strip.downcase
      choice = "deny" if choice.empty?
      break choice if ["approve", "deny", "cancel"].include?(choice)

      puts "Enter approve, deny, or cancel."
    end
    response = {
      type: "browser_origin_access",
      decision: decision
    }
  when "browser_authentication"
    response = computer_use_authentication_response(request)
  else
    raise "Unsupported computer-use approval: #{request.type}"
  end
  client.beta.agents.sessions.events.create(
    session_id,
    events: [
      {
        type: "agent.session.input.computer_use_approval_request_result",
        request_id: approval.request_id,
        response: response
      }
    ],
    request_options: { max_retries: 0 }
  )
  # Admission does not establish sign-in or navigation success; keep reading events.
end

下例读取私有GitHub仓库问题。将 https://github.com/acme/private-repo/issues 换为自己可访问的问题页。先开流再提交任务,需要输入时调用辅助函数:

来源示例 40(bash):

程序文字说明23:在浏览器读取指定GitHub问题列表,需要时请求登录;不创建、编辑、评论或关闭问题。

程序文字说明24:打开示例私有仓库问题页,需要时登录,报告最近更新的未关闭问题的标题和URL,不作修改。

程序文字说明25:此拼接指令要求读取GitHub问题列表、必要时请求登录,并禁止创建、编辑、评论和关闭问题。

## [中文] 示例 40:运行需要认证的浏览器任务。
## [中文] 本段涉及的请求或服务地址:https://api.openai.com/v1/agents/sessions、https://github.com/acme/private-repo/issues;示例占位地址需替换为实际资源。
## Terminal 1: create a new session and stream its first task.
## Replace the illustrative repository URL with one you can access.
curl --no-buffer --fail-with-body https://api.openai.com/v1/agents/sessions \
  -H "OpenAI-Beta: agents=v1" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{
    "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" }
    },
    "input": "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.",
    "stream": true
  }'

## Copy session.id from agent.session.created into terminal 2.
## Keep this stream open while responding to origin and sign-in approvals there.

来源示例 41(javascript):

沿用程序文字说明23。

沿用程序文字说明24。

沿用程序文字说明25。

// [中文] 示例 41:运行需要认证的浏览器任务。
// [中文] 创建会话,保存返回会话 ID;设置 stream 时继续读取事件,创建请求本身不保证任务成功。
// [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
// [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
// [中文] 删除指定会话;自托管提供商计算资源还需独立清理。
// [中文] 读取当前会话状态和 required_actions;历史项目不代表动作仍待处理。
// [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
// [中文] 从当前 required_actions 判断待处理请求,并保留所需调用或批准 ID。
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);
}

来源示例 42(python):

沿用程序文字说明23。

沿用程序文字说明24。

沿用程序文字说明25。

## [中文] 示例 42:运行需要认证的浏览器任务。
## [中文] 创建会话,保存返回会话 ID;设置 stream 时继续读取事件,创建请求本身不保证任务成功。
## [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
## [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
## [中文] 删除指定会话;自托管提供商计算资源还需独立清理。
## [中文] 读取当前会话状态和 required_actions;历史项目不代表动作仍待处理。
## [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
## [中文] 从当前 required_actions 判断待处理请求,并保留所需调用或批准 ID。
from openai import OpenAI

client = OpenAI()
session = 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"},
    },
)
print("Session ID:", session.id, flush=True)
handled_requests = set()
completed = False
ready_to_delete = False

try:
    with client.beta.agents.sessions.events.stream(session.id) as events:
        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 event in events:
            if event.type == "agent.session.requires_action":
                current = client.beta.agents.sessions.retrieve(session.id)
                for approval in current.required_actions:
                    if (
                        approval.type == "computer_use_approval_request"
                        and approval.request_id not in handled_requests
                    ):
                        respond_to_computer_use_approval(client, session.id, approval)
                        handled_requests.add(approval.request_id)
            elif event.type == "agent.session.turn.output_text.done":
                print(event.text, flush=True)
            elif event.type == "agent.session.turn.completed":
                if event.turn.subagent_id is None:
                    completed = True
                    print()
                    break
            elif event.type in {
                "agent.session.turn.failed",
                "agent.session.turn.cancelled",
            }:
                if event.turn.subagent_id is None:
                    raise RuntimeError(f"Browser task ended: {event.type}")
            elif event.type == "error":
                raise RuntimeError(event.error.message)
            elif event.type in {
                "agent.session.failed",
                "agent.session.environment.failed",
            }:
                raise RuntimeError(f"Session failed: {event.type}")
        else:
            raise RuntimeError("Stream closed before the browser task finished.")
    ready_to_delete = completed
except (Exception, KeyboardInterrupt):
    print(
        f"Session {session.id} was kept. Use the same ID to check its status before trying again.",
        flush=True,
    )
    raise
finally:
    if ready_to_delete:
        client.beta.agents.sessions.delete(session.id)
    client.close()

来源示例 43(go):

沿用程序文字说明23。

沿用程序文字说明24。

沿用程序文字说明25。

// [中文] 示例 43:运行需要认证的浏览器任务。
// [中文] 创建会话,保存返回会话 ID;设置 stream 时继续读取事件,创建请求本身不保证任务成功。
// [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
// [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
// [中文] 删除指定会话;自托管提供商计算资源还需独立清理。
// [中文] 读取当前会话状态和 required_actions;历史项目不代表动作仍待处理。
// [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
// [中文] 从当前 required_actions 判断待处理请求,并保留所需调用或批准 ID。
ctx := context.Background()
client := openai.NewClient()
session, err := client.Beta.Agents.Sessions.New(ctx, openai.BetaAgentSessionNewParams{
    Agent: openai.BetaAgentSessionNewParamsAgent{
        Model:        openai.String("gpt-6-astra"),
        Instructions: openai.String("Read the requested GitHub issue list in the browser. Request sign-in when needed. Do not create, edit, comment on, or close issues."),
        Tools: []openai.AgentToolParamUnion{{
            OfParamComputerUse: &openai.AgentToolParamComputerUse{IncludeScreenshots: openai.Bool(false)},
        }},
    },
    Environment: openai.EnvironmentParamUnion{OfParamOpenAIHosted: &openai.EnvironmentParamOpenAIHosted{
        Desktop: openai.EnvironmentParamOpenAIHostedDesktop{Enabled: true},
        Network: openai.EnvironmentParamOpenAIHostedNetwork{Access: "enabled"},
    }},
})
if err != nil {
    return err
}
fmt.Println("Session ID:", session.ID)
completed := false
defer func() {
    if !completed {
        fmt.Fprintf(os.Stderr, "Session %s was not deleted. Retrieve it and check required_actions before attempting recovery.\n", session.ID)
        return
    }
    if _, err := client.Beta.Agents.Sessions.Delete(ctx, session.ID); err != nil {
        fmt.Fprintf(os.Stderr, "Could not delete completed session %s: %v\n", session.ID, err)
    }
}()
handledRequests := map[string]bool{}
events := client.Beta.Agents.Sessions.Events.StreamStreaming(ctx, session.ID)
defer events.Close()
if err := events.Err(); err != nil {
    return err
}
err = client.Beta.Agents.Sessions.Events.New(ctx, session.ID, openai.BetaAgentSessionEventNewParams{
    Events: []openai.AgentSessionInputParamUnion{{
        OfParamAgentSessionInputMessage: &openai.AgentSessionInputParamAgentSessionInputMessage{
            Input: []openai.AgentSessionInputMessageParam{{
                Role: "user",
                Content: []openai.InputContentParamUnion{{
                    OfParamInputText: &openai.InputContentParamInputText{
                        // 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.",
                    },
                }},
            }},
        },
    }},
})
if err != nil {
    return err
}
for events.Next() {
    event := events.Current()
    switch event.Type {
    case "agent.session.requires_action":
        current, err := client.Beta.Agents.Sessions.Get(ctx, session.ID)
        if err != nil {
            return err
        }
        for _, action := range current.RequiredActions {
            if action.Type != "computer_use_approval_request" {
                continue
            }
            approval := action.AsComputerUseApprovalRequest()
            if handledRequests[approval.RequestID] {
                continue
            }
            if err := respondToComputerUseApproval(ctx, &client, session.ID, approval); err != nil {
                return err
            }
            handledRequests[approval.RequestID] = true
        }
    case "agent.session.turn.output_text.done":
        fmt.Println(event.Text)
    case "agent.session.turn.completed":
        if event.Turn.SubagentID == "" {
            completed = true
            return nil
        }
    case "agent.session.turn.failed", "agent.session.turn.cancelled":
        if event.Turn.SubagentID == "" {
            return fmt.Errorf("browser task ended: %s", event.Type)
        }
    case "error":
        return fmt.Errorf("agent error: %s", event.Error.Message)
    case "agent.session.failed", "agent.session.environment.failed":
        return fmt.Errorf("session failed: %s", event.Type)
    }
}
if err := events.Err(); err != nil {
    return err
}
return fmt.Errorf("stream closed before the browser task finished")

来源示例 44(java):

沿用程序文字说明25。

// [中文] 示例 44:运行需要认证的浏览器任务。
// [中文] 创建会话,保存返回会话 ID;设置 stream 时继续读取事件,创建请求本身不保证任务成功。
// [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
// [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
// [中文] 删除指定会话;自托管提供商计算资源还需独立清理。
// [中文] 读取当前会话状态和 required_actions;历史项目不代表动作仍待处理。
var client = OpenAIOkHttpClient.fromEnv();
var session =
    client
        .beta()
        .agents()
        .sessions()
        .create(
            SessionCreateParams.builder()
                .agent(
                    SessionCreateParams.Agent.builder()
                        .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.")
                        .addTool(
                            AgentToolParam.ComputerUse.builder()
                                .includeScreenshots(false)
                                .build())
                        .build())
                .environment(
                    EnvironmentParam.OpenAIHosted.builder()
                        .desktop(
                            EnvironmentParam.OpenAIHosted.Desktop.builder()
                                .enabled(true)
                                .build())
                        .network(
                            EnvironmentParam.OpenAIHosted.Network.builder()
                                .access(EnvironmentParam.OpenAIHosted.Network.Access.ENABLED)
                                .build())
                        .build())
                .build());
System.out.println("Session ID: " + session.id());
var handledRequests = new HashSet<String>();
try {
  try (var events = client.beta().agents().sessions().events().streamStreaming(session.id())) {
    client
        .beta()
        .agents()
        .sessions()
        .events()
        .create(
            EventCreateParams.builder()
                .sessionId(session.id())
                .addEvent(
                    AgentSessionInputParam.AgentSessionInputMessage.builder()
                        .addInput(
                            AgentSessionInputMessageParam.builder()
                                // Replace this illustrative URL with your private repository.
                                .addInputTextContent(
                                    "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.")
                                .build())
                        .build())
                .build());
    boolean completed = false;
    var iterator = events.stream().iterator();
    while (iterator.hasNext()) {
      var event = iterator.next();
      if (event.requiresAction().isPresent()) {
        var current = client.beta().agents().sessions().retrieve(session.id());
        for (var action : current.requiredActions()) {
          if (action.computerUseApprovalRequest().isEmpty()) continue;
          var approval = action.computerUseApprovalRequest().get();
          if (!handledRequests.contains(approval.requestId())) {
            respondToComputerUseApproval(client, session.id(), approval);
            handledRequests.add(approval.requestId());
          }
        }
      }
      event.turnOutputTextDone().ifPresent(text -> System.out.println(text.text()));
      if (event.turnCompleted().filter(e -> e.turn().subagentId().isEmpty()).isPresent()) {
        completed = true;
        break;
      }
      if (event.turnFailed().filter(e -> e.turn().subagentId().isEmpty()).isPresent()
          || event.turnCancelled().filter(e -> e.turn().subagentId().isEmpty()).isPresent()) {
        throw new IllegalStateException("Browser task failed or was cancelled.");
      }
      if (event.error().isPresent()) {
        throw new IllegalStateException(event.error().get().error().message());
      }
      if (event.failed().isPresent() || event.environmentFailed().isPresent()) {
        throw new IllegalStateException("The browser session failed.");
      }
    }
    if (!completed) {
      throw new IllegalStateException("Stream closed before the browser task finished.");
    }
  } catch (Exception error) {
    System.err.printf(
        "Session %s was kept. Retrieve it and check pending actions before sending anything"
            + " again.%n",
        session.id());
    throw error;
  }
  client.beta().agents().sessions().delete(session.id());
} finally {
  client.close();
}

来源示例 45(csharp):

沿用程序文字说明23。

沿用程序文字说明24。

沿用程序文字说明25。

// [中文] 示例 45:运行需要认证的浏览器任务。
// [中文] 本段涉及的请求或服务地址:https://github.com/acme/private-repo/issues;示例占位地址需替换为实际资源。
// [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
string key = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;
// An uncertain approval must not resend credentials automatically.
OpenAIClientOptions options = new() { RetryPolicy = new ClientRetryPolicy(maxRetries: 0) };
AgentClient client = new OpenAIClient(new ApiKeyCredential(key), options).GetAgentClient();
AgentSession session = await client.CreateAgentSessionAsync(
    new AgentSessionCreationOptions
    {
        Agent = new SessionAgentConfigParam
        {
            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 = [new AgentToolConfigParamComputerUse { IncludeScreenshots = false }],
        },
        Environment = new EnvironmentParamOpenaiHosted
        {
            Desktop = new DesktopParam(true),
            Network = new NetworkPolicyParam(NetworkAccessParam.Enabled),
        },
    }
);
Console.WriteLine($"Session ID: {session.Id}");
HashSet<string> handledRequests = new(StringComparer.Ordinal);
try
{
    await using var events = await client.GetAgentSessionEventsAsync(session.Id);
    await client.CreateAgentSessionEventsAsync(
        session.Id,
        new CreateSessionEventsParams(
            [
                new SessionInputParamAgentSessionInputMessage(
                    [
                        new InputMessageParam(
                            // Replace this illustrative URL with your private repository.
                            [new InputContentParamInputText("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.")]
                        ),
                    ]
                ),
            ]
        )
    );
    bool completed = false;
    await foreach (var message in events)
    {
        using JsonDocument document = JsonDocument.Parse(message.Data.ToMemory());
        JsonElement current = document.RootElement;
        string? type = current.GetProperty("type").GetString();
        if (type == "agent.session.requires_action")
        {
            AgentSession latest = await client.RetrieveAgentSessionAsync(session.Id);
            foreach (SessionRequiredActionResource action in latest.RequiredActions)
            {
                if (action is SessionRequiredActionResourceComputerUseApprovalRequest approval
                    && !handledRequests.Contains(approval.RequestId))
                {
                    await RespondToComputerUseApprovalAsync(client, session.Id, approval);
                    handledRequests.Add(approval.RequestId);
                }
            }
        }
        else if (type == "agent.session.turn.output_text.done")
        {
            Console.WriteLine(current.GetProperty("text").GetString());
        }
        else if (type is "agent.session.turn.completed" or "agent.session.turn.failed" or "agent.session.turn.cancelled")
        {
            JsonElement turn = current.GetProperty("turn");
            if (turn.TryGetProperty("subagent_id", out JsonElement subagent)
                && subagent.ValueKind != JsonValueKind.Null)
            {
                continue;
            }
            if (type != "agent.session.turn.completed")
            {
                throw new InvalidOperationException($"Browser task ended: {type}");
            }
            completed = true;
            break;
        }
        else if (type == "error")
        {
            throw new InvalidOperationException(current.GetProperty("error").GetProperty("message").GetString());
        }
        else if (type is "agent.session.failed" or "agent.session.environment.failed")
        {
            throw new InvalidOperationException($"Session failed: {type}");
        }
    }
    if (!completed)
    {
        throw new InvalidOperationException("Stream closed before the browser task finished.");
    }
}
catch
{
    Console.Error.WriteLine($"Session {session.Id} was kept. Retrieve it and check pending actions before sending anything again.");
    throw;
}
await client.DeleteAgentSessionAsync(session.Id);

来源示例 46(ruby):

沿用程序文字说明23。

沿用程序文字说明24。

沿用程序文字说明25。

## [中文] 示例 46:运行需要认证的浏览器任务。
## [中文] 创建会话,保存返回会话 ID;设置 stream 时继续读取事件,创建请求本身不保证任务成功。
## [中文] 向会话事件端点提交输入;各事件的关联标识按载荷保留。
## [中文] 订阅会话事件流;在提交后续任务前建立订阅,避免遗漏早期事件。
## [中文] 删除指定会话;自托管提供商计算资源还需独立清理。
## [中文] 读取当前会话状态和 required_actions;历史项目不代表动作仍待处理。
## [中文] 从当前 required_actions 判断待处理请求,并保留所需调用或批准 ID。
require "openai"

client = OpenAI::Client.new
session = 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" }
  }
)
puts "Session ID: #{session.id}"
handled_requests = Set.new

begin
  events = client.beta.agents.sessions.events.stream_streaming(session.id)
  begin
    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."
                }
              ]
            }
          ]
        }
      ]
    )
    completed = events.any? do |event|
      case event
      when OpenAI::Beta::AgentSessionRequiresActionEvent
        current = client.beta.agents.sessions.retrieve(session.id)
        current.required_actions.each do |approval|
          next unless approval.is_a?(OpenAI::Beta::AgentSession::RequiredAction::ComputerUseApprovalRequest)
          next if handled_requests.include?(approval.request_id)

          respond_to_computer_use_approval(client, session.id, approval)
          handled_requests.add(approval.request_id)
        end
        false
      when OpenAI::Beta::AgentSessionTurnOutputTextDoneEvent
        puts event.text
      when OpenAI::Beta::AgentSessionTurnCompletedEvent
        event.turn.subagent_id.nil?
      when OpenAI::Beta::AgentSessionTurnFailedEvent, OpenAI::Beta::AgentSessionTurnCancelledEvent
        raise "Browser task ended: #{event.type}" if event.turn.subagent_id.nil?
      when OpenAI::Beta::AgentSessionErrorEvent
        raise event.error.message
      when OpenAI::Beta::AgentSessionFailedEvent, OpenAI::Beta::AgentSessionEnvironmentFailedEvent
        raise "Session failed: #{event.type}"
      else
        false
      end
    end
    raise "Stream closed before the browser task finished." unless completed
  ensure
    events.close
  end
rescue StandardError, Interrupt
  warn "Session #{session.id} was not deleted. Retrieve it and check required_actions before attempting recovery."
  raise
else
  begin
    client.beta.agents.sessions.delete(session.id)
  rescue => error
    warn "Could not delete completed session #{session.id}: #{error.message}"
    raise
  end
end

可能有多次登录请求,持续处理至任务结束,并核对问题标题和URL等结果。

读取登录历史

认证请求及已接受回应出现在会话历史和 agent.session.turn.item.added 中。请求项目含表单元数据,响应只记录已接受动作,不含凭证值。

历史用于审计交互;判断是否仍需输入,必须读取当前 required_actions。历史响应也不证明登录成功。

源批准没有专门的请求或响应历史项目,通过 required_actions 处理。

向用户提问

需要澄清或选择时,在 agent.tools 定义函数工具,例如 request_user_response,由应用提供名称、参数结构和界面。

收到 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。只恢复仍待处理表单,移除已不存在请求的控件。

事件流按断流恢复重连,不能自动重发任务或批准回应。

结果 处理方式
202 清空已提交值,继续跟随结果事件。
400 对照待处理请求检查响应类型、选项、字段ID和必填值;取消及源访问响应不包含字段。
404 检查会话ID、请求ID和响应类型,再读取会话;请求可能已不可用。
409 刷新会话,可能请求过期、轮次结束或另一回应已被接受。
确认前断连 接受状态未知,先重连读取会话再决定重试。

认证请求五分钟过期,用户输入期间所属轮次也可能结束,恢复表单前先刷新会话。

需重试认证提交时,使用相同 request_id、选项与字段值映射。相同重试不会再次填写浏览器表单;接受后更改值返回 409,新登录尝试需智能体发新请求。

源批准投递失败时重试相同决定。接受后改变决定返回 409;源请求在所属轮次期间待处理,没有认证请求的五分钟超时。

控制网络访问

托管环境 network 同时控制浏览器和代码的出站访问,需允许目标网站及资源、重定向所需域名。

源批准是独立用户决定,不覆盖网络策略,见网络设置。

继续与清理

依赖浏览器状态的后续任务复用同一会话。登录Cookie可能过期,环境回收会清空浏览器状态。

结束后先取回结果,再删除会话请求环境清理:

来源示例 47(bash):

## [中文] 示例 47:继续与清理。
## [中文] 本段涉及的请求或服务地址:https://api.openai.com/v1/agents/sessions/$session_id;示例占位地址需替换为实际资源。
## Run after the root turn completes, fails, or is cancelled.
curl --fail-with-body -X DELETE "https://api.openai.com/v1/agents/sessions/$session_id" \
  -H "OpenAI-Beta: agents=v1" \
  -H "Authorization: Bearer $OPENAI_API_KEY"

来源示例 48(javascript):

// [中文] 示例 48:继续与清理。
// [中文] 删除指定会话;自托管提供商计算资源还需独立清理。
await client.beta.agents.sessions.delete(session.id);

来源示例 49(python):

## [中文] 示例 49:继续与清理。
## [中文] 删除指定会话;自托管提供商计算资源还需独立清理。
if ready_to_delete:
    client.beta.agents.sessions.delete(session.id)

来源示例 50(go):

// [中文] 示例 50:继续与清理。
// [中文] 删除指定会话;自托管提供商计算资源还需独立清理。
// [中文] 从当前 required_actions 判断待处理请求,并保留所需调用或批准 ID。
readyToDelete := false
defer func() {
    if !readyToDelete {
        fmt.Fprintf(os.Stderr, "Session %s was not deleted. Retrieve it and check required_actions before attempting recovery.\n", session.ID)
        return
    }
    if _, err := client.Beta.Agents.Sessions.Delete(ctx, session.ID); err != nil {
        fmt.Fprintf(os.Stderr, "Could not delete completed session %s: %v\n", session.ID, err)
    }
}()

来源示例 51(java):

// [中文] 示例 51:继续与清理。
// [中文] 删除指定会话;自托管提供商计算资源还需独立清理。
client.beta().agents().sessions().delete(session.id());

来源示例 52(csharp):

// [中文] 示例 52:继续与清理。
// [中文] 本段清理指定资源;会话与提供商沙箱是独立资源,按本节要求分别处理。
await client.DeleteAgentSessionAsync(session.Id);

来源示例 53(ruby):

## [中文] 示例 53:继续与清理。
## [中文] 删除指定会话;自托管提供商计算资源还需独立清理。
begin
  client.beta.agents.sessions.delete(session.id) if completed
rescue => error
  warn "Could not delete completed session #{session.id}: #{error.message}"
  raise
end

寿命与删除行为见托管沙箱。

请求处理参考

认证提交限制

每次最多六个字段值,每字段只出现一次;单值最多16,384字符,序列化字段值与选项合计不超过120 KiB。

使用待处理请求中的请求及字段ID,必填值非空。结构见会话事件API参考。

展开的cURL请求与事件流处理

此版本区分传输失败、HTTP错误及无效创建响应,筛选流输出并报告错误类型和代码。与首例同样使用两个终端,第二个终端随到随处理批准:

来源示例 54(bash):

沿用程序文字说明18。

沿用程序文字说明19。

沿用程序文字说明20。

## [中文] 示例 54:认证提交限制。
## [中文] 本段涉及的请求或服务地址:https://api.openai.com/v1/agents/sessions、https://api.openai.com/v1/agents/sessions/$session_id/events;示例占位地址需替换为实际资源。
## [中文] 按主智能体轮次的终结事件判断结果,不把 idle 或子智能体完成当成整轮成功。
## [中文] 这个双终端 Bash 示例先检查创建响应和传输错误,再筛选流事件;批准在第二终端处理,不自动重发登录值。
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."
        }]
      }]
    }]
  }'
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号