OpenClaw工具拆解之browser+agents_list

一、browser 工具

1.1 工具概述

功能 :控制浏览器(自动化)
核心特性

  • 支持多种操作(status/start/stop/profiles/tabs/open/snapshot/screenshot/actions)
  • 支持沙盒桥接
  • 支持节点代理
  • 支持配置文件选择(openclaw/user)
  • 支持 Playwright 自动化

1.2 Schema 定义

位置:第 23854 行附近

javascript 复制代码
const BrowserToolSchema = Type.Object({
    action: stringEnum(BROWSER_ACTIONS),
    profile: Type.Optional(Type.String()),
    node: Type.Optional(Type.String()),
    target: Type.Optional(Type.String()),
    url: Type.Optional(Type.String()),
    targetId: Type.Optional(Type.String()),
    fullPage: Type.Optional(Type.Boolean()),
    ref: Type.Optional(Type.String()),
    element: Type.Optional(Type.String()),
    type: Type.Optional(Type.String())
    // ... 更多参数
});

1.3 完整执行代码(部分)

位置:第 23854 行

javascript 复制代码
function createBrowserTool(opts) {
    // 1. 解析目标默认值
    const targetDefault = opts?.sandboxBridgeUrl ? "sandbox" : "host";
    const hostHint = opts?.allowHostControl === false ? 
        "Host target blocked by policy." : "Host target allowed.";
    
    return {
        label: "Browser",
        name: "browser",
        description: [
            "Control the browser via OpenClaw's browser control server (status/start/stop/profiles/tabs/open/snapshot/screenshot/actions).",
            "Browser choice: omit profile by default for the isolated OpenClaw-managed browser (`openclaw`).",
            'For the logged-in user browser on the local host, use profile="user". A supported Chromium-based browser (v144+) must be running. Use only when existing logins/cookies matter and the user is present.',
            "When a node-hosted browser proxy is available, the tool may auto-route to it. Pin a node with node=<id|name> or target=\"node\".",
            "When using refs from snapshot (e.g. e12), keep the same tab: prefer passing targetId from the snapshot response into subsequent actions (act/click/type/etc).",
            'For stable, self-resolving refs across calls, use snapshot with refs="aria" (Playwright aria-ref ids). Default refs="role" are role+name-based.',
            "Use snapshot+act for UI automation. Avoid act:wait by default; use only in exceptional cases when no reliable UI state exists.",
            `target selects browser location (sandbox|host|node). Default: ${targetDefault}.`,
            hostHint
        ].join(" "),
        parameters: BrowserToolSchema,
        execute: async (_toolCallId, args) => {
            const params = args;
            
            // 2. 解析 action(必填)
            const action = readStringParam$1(params, "action", { required: true });
            const profile = readStringParam$1(params, "profile");
            const requestedNode = readStringParam$1(params, "node");
            let target = readStringParam$1(params, "target");
            
            // 3. 检查节点限制
            if (requestedNode && target && target !== "node") {
                throw new Error('node is only supported with target="node".');
            }
            
            // 4. 解析目标
            if (shouldPreferHostForProfile(profile)) {
                if (requestedNode || target === "node") {
                    throw new Error(`profile="${profile}" only supports the local host browser.`);
                }
                if (target === "sandbox") {
                    throw new Error(`profile="${profile}" cannot use the sandbox browser; use target="host" or omit target.`);
                }
                if (!target && !requestedNode) target = "host";
            }
            
            // 5. 解析节点目标
            const nodeTarget = await resolveBrowserNodeTarget({
                requestedNode: requestedNode ?? void 0,
                target,
                sandboxBridgeUrl: opts?.sandboxBridgeUrl
            });
            
            // 6. 解析基础 URL
            const baseUrl = nodeTarget ? void 0 : resolveBrowserBaseUrl({
                target: target === "node" ? void 0 : target,
                sandboxBridgeUrl: opts?.sandboxBridgeUrl,
                allowHostControl: opts?.allowHostControl
            });
            
            // 7. 构建代理请求(节点模式)
            const proxyRequest = nodeTarget ? async (opts) => {
                const proxy = await callBrowserProxy({
                    nodeId: nodeTarget.nodeId,
                    method: opts.method,
                    path: opts.path,
                    query: opts.query,
                    body: opts.body,
                    timeoutMs: opts.timeoutMs,
                    profile: opts.profile
                });
                const mapping = await persistProxyFiles(proxy.files);
                applyProxyPaths(proxy.result, mapping);
                return proxy.result;
            } : null;
            
            // 8. 根据 action 执行
            switch (action) {
                // === action: status ===
                case "status":
                    if (proxyRequest) {
                        return jsonResult(await proxyRequest({
                            method: "GET",
                            path: "/",
                            profile
                        }));
                    }
                    return jsonResult(await browserToolDeps.browserStatus(baseUrl, { profile }));
                
                // === action: start ===
                case "start":
                    if (proxyRequest) {
                        await proxyRequest({
                            method: "POST",
                            path: "/start",
                            profile
                        });
                        return jsonResult(await proxyRequest({
                            method: "GET",
                            path: "/",
                            profile
                        }));
                    }
                    await browserToolDeps.browserStart(baseUrl, { profile });
                    return jsonResult(await browserToolDeps.browserStatus(baseUrl, { profile }));
                
                // === action: stop ===
                case "stop":
                    if (proxyRequest) {
                        await proxyRequest({
                            method: "POST",
                            path: "/stop",
                            profile
                        });
                        return jsonResult(await proxyRequest({
                            method: "GET",
                            path: "/",
                            profile
                        }));
                    }
                    await browserToolDeps.browserStop(baseUrl, { profile });
                    return jsonResult(await browserToolDeps.browserStatus(baseUrl, { profile }));
                
                // === action: profiles ===
                case "profiles":
                    if (proxyRequest) {
                        return jsonResult(await proxyRequest({
                            method: "GET",
                            path: "/profiles"
                        }));
                    }
                    return jsonResult({ profiles: await browserToolDeps.browserProfiles(baseUrl) });
                
                // === action: tabs ===
                case "tabs":
                    return await executeTabsAction({ baseUrl, profile, proxyRequest });
                
                // === action: open ===
                case "open": {
                    const targetUrl = readTargetUrlParam(params);
                    if (proxyRequest) {
                        return jsonResult(await proxyRequest({
                            method: "POST",
                            path: "/tabs/open",
                            profile,
                            body: { url: targetUrl }
                        }));
                    }
                    const opened = await browserToolDeps.browserOpenTab(baseUrl, targetUrl, { profile });
                    browserToolDeps.trackSessionBrowserTab({
                        sessionKey: opts?.agentSessionKey,
                        targetId: opened.targetId,
                        baseUrl,
                        profile
                    });
                    return jsonResult(opened);
                }
                
                // === action: focus ===
                case "focus": {
                    const targetId = readStringParam$1(params, "targetId", { required: true });
                    if (proxyRequest) {
                        return jsonResult(await proxyRequest({
                            method: "POST",
                            path: "/tabs/focus",
                            profile,
                            body: { targetId }
                        }));
                    }
                    await browserToolDeps.browserFocusTab(baseUrl, targetId, { profile });
                    return jsonResult({ ok: true });
                }
                
                // === action: close ===
                case "close": {
                    const targetId = readStringParam$1(params, "targetId");
                    if (proxyRequest) {
                        return jsonResult(targetId ? await proxyRequest({
                            method: "DELETE",
                            path: `/tabs/${encodeURIComponent(targetId)}`,
                            profile
                        }) : await proxyRequest({
                            method: "POST",
                            path: "/act",
                            profile,
                            body: { kind: "close" }
                        }));
                    }
                    if (targetId) {
                        await browserToolDeps.browserCloseTab(baseUrl, targetId, { profile });
                        browserToolDeps.untrackSessionBrowserTab({
                            sessionKey: opts?.agentSessionKey,
                            targetId,
                            baseUrl,
                            profile
                        });
                    } else {
                        await browserToolDeps.browserAct(baseUrl, { kind: "close" }, { profile });
                    }
                    return jsonResult({ ok: true });
                }
                
                // === action: snapshot ===
                case "snapshot":
                    return await executeSnapshotAction({ input: params, baseUrl, profile, proxyRequest });
                
                // === action: screenshot ===
                case "screenshot": {
                    const targetId = readStringParam$1(params, "targetId");
                    const fullPage = Boolean(params.fullPage);
                    const ref = readStringParam$1(params, "ref");
                    const element = readStringParam$1(params, "element");
                    const type = params.type === "jpeg" ? "jpeg" : "png";
                    
                    const result = proxyRequest ? await proxyRequest({
                        method: "POST",
                        path: "/screenshot",
                        profile,
                        body: { targetId, fullPage, ref, element, type }
                    }) : await browserToolDeps.browserScreenshotAction(baseUrl, {
                        targetId, fullPage, ref, element, type, profile
                    });
                    
                    return await browserToolDeps.imageResultFromFile({
                        label: "browser:screenshot",
                        path: result.path,
                        details: result
                    });
                }
                
                // === action: navigate ===
                case "navigate": {
                    const targetUrl = readTargetUrlParam(params);
                    const targetId = readStringParam$1(params, "targetId");
                    if (proxyRequest) {
                        return jsonResult(await proxyRequest({
                            method: "POST",
                            path: "/navigate",
                            profile,
                            body: { url: targetUrl, targetId }
                        }));
                    }
                    await browserToolDeps.browserNavigate(baseUrl, { url: targetUrl, targetId, profile });
                    return jsonResult({ ok: true });
                }
                
                // === action: act ===
                case "act":
                    return await executeActAction({ input: params, baseUrl, profile, proxyRequest });
                
                // === 未知 action ===
                default:
                    throw new Error(`Unknown action: ${action}`);
            }
        }
    };
}

1.4 支持的 Actions

Action 说明 必需参数
status 获取浏览器状态
start 启动浏览器
stop 停止浏览器
profiles 列出配置文件
tabs 列出标签页
open 打开 URL url
focus 聚焦标签页 targetId
close 关闭标签页
snapshot 获取页面快照
screenshot 截图
navigate 导航 url
act 执行操作(点击/输入等) action

1.5 配置文件

Profile 说明 使用场景
openclaw OpenClaw 管理的隔离浏览器 默认,无需登录
user 用户已登录的浏览器 需要 Cookie/登录状态

二、agents_list 工具

2.1 工具概述

功能 :列出可用的 Agent
核心特性

  • 读取配置文件
  • 支持过滤
  • 支持排序
  • 返回 Agent 元数据

2.2 Schema 定义

位置:第 22980 行附近

javascript 复制代码
const AgentsListToolSchema = Type.Object({});

2.3 完整执行代码

位置:第 22986 行

javascript 复制代码
function createAgentsListTool(opts) {
    return {
        label: "Agents List",
        name: "agents_list",
        description: "List available agents configured in the system.",
        parameters: AgentsListToolSchema,
        execute: async (_toolCallId, args) => {
            // 1. 加载配置
            const cfg = opts?.config ?? loadConfig();
            
            // 2. 解析 Agent 列表
            const agents = cfg?.agents?.list ?? [];
            
            // 3. 构建结果
            const result = agents.map((agent) => ({
                id: agent.id,
                label: agent.label,
                description: agent.description,
                model: agent.model,
                thinking: agent.thinking,
                verbose: agent.verbose,
                tools: agent.tools,
                sandbox: agent.sandbox,
                enabled: agent.enabled !== false
            }));
            
            return jsonResult({
                status: "ok",
                count: result.length,
                agents: result
            });
        }
    };
}

2.4 返回结果格式

json 复制代码
{
  "status": "ok",
  "count": 3,
  "agents": [
    {
      "id": "main",
      "label": "Main Agent",
      "description": "Primary assistant agent",
      "model": "bailian/qwen3.5-plus",
      "thinking": "high",
      "verbose": false,
      "tools": ["read", "write", "exec", ...],
      "sandbox": { "enabled": false },
      "enabled": true
    },
    {
      "id": "coding",
      "label": "Coding Agent",
      "description": "Specialized coding assistant",
      "model": "bailian/qwen3.5-coder",
      "thinking": "high",
      "verbose": false,
      "tools": ["read", "write", "edit", "exec"],
      "sandbox": { "enabled": true },
      "enabled": true
    }
  ]
}

三、关键机制对比

3.1 功能定位

特性 browser agents_list
用途 浏览器自动化 Agent 配置查询
复杂度 高(多 actions) 低(只读)
外部依赖 浏览器服务

3.2 操作类型

特性 browser agents_list
actions 数量 12+ 个 1 个(隐式 list)
写操作 start/stop/open 只读
媒体支持 截图/快照 不支持

3.3 安全限制

限制类型 browser agents_list
沙盒支持 sandboxBridgeUrl 不支持
主机控制 allowHostControl 不支持
节点代理 支持 不支持

四、使用示例

4.1 browser 工具调用

用户打开浏览器并访问 Google

大模型返回

json 复制代码
{
  "tool_call": {
    "name": "browser",
    "arguments": { 
      "action": "open",
      "url": "https://google.com"
    }
  }
}

执行结果

json 复制代码
{
  "targetId": "tab_abc123",
  "url": "https://google.com",
  "title": "Google",
  "status": "loaded"
}

4.2 agents_list 工具调用

用户列出所有可用的 Agent

大模型返回

json 复制代码
{
  "tool_call": {
    "name": "agents_list",
    "arguments": {}
  }
}

执行结果

json 复制代码
{
  "status": "ok",
  "count": 2,
  "agents": [
    {
      "id": "main",
      "label": "Main Agent",
      "model": "bailian/qwen3.5-plus",
      "enabled": true
    },
    {
      "id": "coding",
      "label": "Coding Agent",
      "model": "bailian/qwen3.5-coder",
      "enabled": true
    }
  ]
}
相关推荐
AINative软件工程11 小时前
LLM 应用的回滚工程实践:Prompt、模型与配置变更出问题时如何快速恢复
后端·llm·ai编程
YHHLAI11 小时前
Agent 智能体开发实战 · Stream 流式输出 —— AI 产品的核心体验
人工智能
crary,记忆11 小时前
React Hook 浅学 2.0 - 自定义Hook
前端·react.js·前端框架
触底反弹11 小时前
🔥 React 零基础入门(下):Props + State + useEffect 生命周期深度解析
前端·react.js·面试
爱吃提升11 小时前
VSCode AI编程万能提示词模板(适配Copilot/Codeium/Continue/豆包AI)
vscode·copilot·ai编程
rain_sxr11 小时前
让 AI 会调用工具:Function Calling 结构化输出的前端渲染与编排
人工智能
breeze jiang11 小时前
从零搭建浏览器端 AI 原型:React、WebGPU 与 Tailwind CSS 的一次实践
css·人工智能·react.js
咖啡星人k11 小时前
GitHub Copilot 加入 Agent 浏览器:验证 Web 应用进入编码闭环
前端·人工智能·github·copilot·前端开发·ai agent
Shadow(⊙o⊙)11 小时前
高并发内存池:Part-1——定长内存池
java·前端·javascript
GuWenyue11 小时前
90%Node新手踩坑!path+fs两大内置模块完整实战,3种异步写法彻底告别回调地狱
前端