跳到主要内容

动作落地:从 click(x,y) 到沙箱里真的点下去

前四章都在 agent 侧。这一章往下走:模型说的 click 动作,是怎么穿过三层、最后在一台真实(虚拟)电脑上点下去的。这是“agent-runtime”里 runtime 的部分。

它要解决的小问题

agent 侧只想说“在 (412,380) 点一下”,不关心那台电脑是本地 Docker 容器、云端 macOS VM 还是 Android 模拟器。需要一层把“统一动作”翻译并送达到具体环境。

思路/直觉:三层适配,逐层下沉

agent 主循环 computer.click(412, 380)

┌─────▼─────────────────────────────────────────┐
│ ① AsyncComputerHandler (agent 侧适配器) │
│ cuaComputerHandler.click → interface.left_click │ 统一动作 → 接口方法
└─────┬─────────────────────────────────────────┘

┌─────▼─────────────────────────────────────────┐
│ ② Computer SDK (libs/python/computer) │
│ 管 VM 生命周期(run/stop),经 WebSocket 发命令 │ 本机 → 沙箱(跨进程/跨网络)
└─────┬─────────────────────────────────────────┘
│ ws://沙箱:port/ws
┌─────▼─────────────────────────────────────────┐
│ ③ computer-server (跑在沙箱内部) │
│ HandlerFactory 按 OS 选 macOS/Linux/Windows │ 命令 → 真实鼠标键盘
│ handler,真正调系统 API 动鼠标键盘 │
└───────────────────────────────────────────────┘

怎么读: 从上到下是一次动作的“下沉”。①把抽象动作名翻成接口方法名;②负责“出本机、进沙箱”的传输(WebSocket);③在沙箱里按操作系统挑对应实现,真正执行。

第①层:动作适配器(agent 侧)

AsyncComputerHandler 是个协议(agent/cua_agent/computers/base.py:18),定义了 agent 期望的动作空间——分两套:

  • OpenAI computer-use-preview 动作空间:click/double_click/scroll/type/keypress/drag/move/screenshot 等(computers/base.py:23-73)。
  • Anthropic 动作空间:额外的 left_mouse_down/left_mouse_up(computers/base.py:77-83),因为 Anthropic 模型会分别发“按下/抬起”。

具体实现 cuaComputerHandler 把这些翻成 Computer.interface 的调用(agent/cua_agent/computers/cua.py:13):

# agent/cua_agent/computers/cua.py:49-58 (真实源码,节选)
async def click(self, x: int, y: int, button: str = "left") -> None:
if button == "left":
await self.interface.left_click(x, y)
elif button == "right":
await self.interface.right_click(x, y)
else:
await self.interface.left_click(x, y) # 未知按钮默认左键

回到 01 章 _handle_item 里那句 getattr(computer, action_type)——computer 就是这个 handler,action_type="click" 就调到这里。

工厂选 handler: make_computer_handler(agent/cua_agent/computers/__init__.py)按传入的“电脑”类型分派:Computer 实例→cuaComputerHandlerSandboxSandboxComputerHandlerdictCustomComputerHandler(传一堆函数当电脑用,适合测试)。

第②层:Computer SDK(管沙箱 + 传输)

Computer 类(computer/computer/computer.py:72)是“一台电脑”的句柄。构造时指定 os_type(macos/linux/windows/android)和 provider_type(LUME/CLOUD/DOCKER/WINSANDBOX/...,computer.py:104)。

run()(computer/computer/computer.py:362)按 provider 启动或连接 VM,拿到 IP,再为该 OS 初始化 interface(computer.py:629-722)。这个 interface(BaseComputerInterface,computer/computer/interface/base.py:10)就是 agent 侧 handler 调用的对象,方法是 left_click/type_text/screenshot/scroll 这些(interface/base.py:40-390),底层经 WebSocket 把命令发给沙箱里的 server。

动作空间还包括文件操作(file_exists/read_text/write_text/list_dir,interface/base.py:321-390)和剪贴板——所以 agent 不止能点屏幕,也能直接读写沙箱文件。

第③层:computer-server(沙箱内部)

这是跑在沙箱里面的 FastAPI 服务(computer-server/computer_server/main.py),暴露一个 WebSocket 端点 /ws(main.py:544,websocket_endpoint)接收命令,还有 /status/commands 等 HTTP 端点。

按 OS 分派HandlerFactory(computer-server/computer_server/handlers/factory.py:40):import 时根据 OS_TYPE 加载对应实现——MacOSAutomationHandler / LinuxAutomationHandler / WindowsAutomationHandler(factory.py:29-110)。这些 handler 才真正调用系统级 API(pyautogui、辅助功能 API 等)去动鼠标键盘。

WebSocket 收到 {"command": "left_click", "x":412, "y":380}

HandlerFactory.create_handlers() ── OS_TYPE=="linux" ──► LinuxAutomationHandler


系统级:把光标移到 (412,380) 并按下左键

关键细节/坑

  • get_environment() 写死成 "linux"。 cuaComputerHandler.get_environment() 直接 return "linux",带 # TODO: detect actual environment(agent/cua_agent/computers/cua.py:28-31)——真实环境检测还没做。
  • keypress 的别名归一在两处。 agent 侧 keypress"ctrl-c" 拆成 ["ctrl","c"] 再决定单键 press_key 还是组合 hotkey(computers/cua.py:93-102);更早还有 OperatorNormalizerCallback 先把各种动作名/keys 别名找平(见 04 章)。
  • drag 支持两种入参。 既接受 path=[{x,y},...] 也接受 start_x/.../end_y,内部统一成 path 再 mouse_down→move→mouse_up(computers/cua.py:104-137)。
  • 浏览器动作走 Playwright。 playwright_execvisit_url/web_search 等浏览器命令透传给 interface(computers/cua.py:168-184),这是 tool_type="browser" 那类 loop 的落地通道。
  • provider 决定一切传输细节。 云端(CLOUD/CLOUDV2)和本地(LUME/DOCKER)在 run() 里走不同分支拿 IP、建连接(computer/computer/computer.py:431-722),但对上层 interface 完全透明。

代码地图

主题文件路径符号名
动作协议(两套动作空间)agent/cua_agent/computers/base.pyAsyncComputerHandler
动作适配器实现agent/cua_agent/computers/cua.pycuaComputerHandler
handler 工厂agent/cua_agent/computers/__init__.pymake_computer_handler, is_agent_computer
沙箱句柄 + 生命周期computer/computer/computer.pyComputer, Computer.run
底层接口协议computer/computer/interface/base.pyBaseComputerInterface
沙箱内服务器computer-server/computer_server/main.pywebsocket_endpoint
沙箱内 OS 分派computer-server/computer_server/handlers/factory.pyHandlerFactory