cua-driver 兼容性夹具(Compatibility Fixtures)解析:用快照锁住跨语言 SDK、CLI 与 MCP 的公开契约
【免费下载链接】cuaScale computer-use 2.0 with open-source drivers, cross-OS fleets, and benchmarks for training, evaluation, and data generation.项目地址: https://gitcode.com/GitHub_Trending/cua/cua
导读
本篇文章围绕 cua-driver 仓库中libs/cua-driver/compat-fixtures/目录展开,剖析这一套"兼容性夹具(compatibility fixtures)"的设计思路与工程实践:它以cua-driver-rs-v0.12.6发布版为基准,把 Python / TypeScript SDK 的导出与签名、CLI 帮助与 manifest 字段、MCP 初始化与工具列表等稳定公开契约固化成 JSON 快照,并通过 Rust / Python / TypeScript 三套测试在每次构建时校验"新版本是否仍然兼容旧契约"。读完本文,你将理解这套夹具锁定了哪些契约面、如何做"语义级"而非"进程级"的对比校验、什么样的变更被允许(增量新增)、什么样的变更必须走显式兼容决策(破坏性修改),以及 RFC 3682 与 RFC 2549 两条已接受变更在夹具中的具体落地方式。
一、为什么原生驱动需要"兼容性夹具"
cua-driver 是一个跨平台 computer-use 自动化驱动,其 SDK 并非单一语言实现:底层是 Rust 原生核心与生成绑定(generated bindings),上层同时暴露 Python、TypeScript 两个 SDK 语言面,对外还提供 CLI 子命令与 MCP JSON-RPC 服务。多语言、多入口意味着同一个功能面会同时出现在__init__.py、.d.ts声明、--help输出和tools/list响应中——任何一个入口发生无意的漂移,都会在升级后静默破坏下游调用方的代码。
compat-fixtures 正是为此设计的"契约保险丝":它从**发布标签(release tag)**而非当前工作区提取快照,锁住选定字段后,任何后续开发只要触发了契约变化,CI 就会立即报告。这套机制的价值在于:
- 契约可被机器校验:快照是结构化 JSON,测试逐字段比对语义,不依赖人工 review 记忆;
- 变更可被显式决策:新增是默认允许的,删除或修改必须经过明确的兼容决策并同步更新夹具;
- 多语言同步受控:Rust、Python、TypeScript 三套测试读取同一套夹具,避免某一语言面悄悄偏离基线。
二、快照基线:从发布标签提取的 0.12.6 契约
夹具锁定的是cua-driver-rs-v0.12.6发布标签,对应提交为9eb1f481b8a12cd6ffda2ad5af21653a9e5aa9e5。快照的生成来源在 compat-fixtures/README.md 中明确列出:
- 发布标签对应的包源码(release-tagged package sources);
- 生成绑定(generated bindings);
cua-driver --help输出;cua-driver manifest输出;- MCP JSON-RPC 实际响应。
一个关键设计是:夹具锚定的是"语义字段"而非"整进程输出"。测试有意排除了可执行文件路径、socket 路径、PID、会话标识符、平台相关散文(platform-specific prose)以及其他易变值。这样做的直接好处是,快照不会因为测试环境差异(如 macOS 与 Linux 的默认 socket 路径不同)而产生虚假失败。
另一个精妙之处在于版本号字段只校验"形状"而非冻结值:夹具不会把版本号锁死为0.12.6,而是只校验其满足语义化版本(semver)格式。原因正如 README 所写——一个兼容的后续版本必须改变版本号,若把版本号冻结反而会让每次发布都触发一次无意义的契约变更。
三、四个 JSON 快照:分别锁住哪一层契约
目录下四个 JSON 文件各自负责一个契约面,下面逐一展开。
3.1python-package.json:Python 包导出与可调用签名
该快照记录三组内容(见 python-package.json):
包根导出(package_root_exports):共 51 个符号,覆盖类型与函数两类:
- 类型/常量:
CaptureScope、ClickButton、ClickInput、DriverOptions、DriverError、DriverExecutionMode、DriverMetadata、Platform、SessionStateOutput、ToolResult等; - 函数与模块级 API:
__version__、current_mac_os_permission_status、get_binary_path、open_mac_os_screen_recording_settings、request_mac_os_permissions、run_cua_driver。
构造器签名(package_constructor_signatures):锁住类方法_connect_python_sdk(cls, socket_path)与_create_python_sdk(cls, options = None)两个内部构造入口。
CuaDriver 方法集(cua_driver_methods):锁住 25 个公开方法的完整签名,包括异步动作与查询:
- 会话管理:
start_session/get_session_state/end_session/escalate_session; - 桌面操作:
click、drag、scroll、hotkey、press_key、type_text、move_cursor、get_cursor_position、get_desktop_state、get_screen_size; - 生命周期:
connect/connect_with_client_kind/create/create_with_client_kind/shutdown/socket_path/is_available/execution_mode/metadata/list_tools_json。
以click为例,其冻结签名为async click(self, input: cua_driver._native_contract.ClickInput) -> cua_driver._native_contract.ActionResult——注意返回类型是ActionResult而非通用ToolResult,这正是 RFC 3682 破坏性变更后的形态(详见第六节)。
3.2typescript-package.json:子路径导出、声明导出与 CuaDriver 声明
该快照锁住四个层面(见 typescript-package.json):
package_exports(包子路径导出):锁住三个入口的解析条件——根入口.指向dist/index.d.ts/dist/index.js,./embedded指向dist/embedded.*,./electron指向dist/electron.*。这意味着包的"三入口布局"本身就是契约的一部分,不允许在后续版本中悄然移除。
root_declaration_exports(根声明导出):约 53 个符号,除与 Python 对应的类型外,还包括 TypeScript 特有的CuaDriverInterface、CuaDriverLike、DriverError_Tags、EmbeddedCuaDriverHostInterface、EmbeddedCuaDriverHostLike、SdkClientKind等。
cua_driver_declarations(生成的 CuaDriver 声明方法):锁住静态构造器与核心方法的声明文本,例如:
static connect(socketPath: string | undefined): CuaDriverLike;static create(options: DriverOptions | undefined): CuaDriverLike;callTool(name: string, argumentsJson: string, asyncOpts_?: { signal: AbortSignal; }): Promise<ToolResult>;shutdown(asyncOpts_?: { signal: AbortSignal; }): Promise<void>;
注意asyncOpts_上的{ signal: AbortSignal }——异步取消能力也被视为契约的一部分被锁定。
entrypoint_declarations(各入口的声明内容):进一步细化到index.d.ts(export * from "./native/index.js"与 default 导出)、embedded.d.ts(必须暴露EmbeddedCuaDriverHost、EmbeddedDriverConnection、EmbeddedPermissionMode)、electron.d.ts(必须暴露 macOS 权限相关 API:MacOSPermissionStatus、requestMacOSPermissions、hasRequiredMacOSPermissions、openMacOSScreenRecordingSettings)。
3.3cli.json:CLI 帮助目录与 manifest 字段
该快照锁住 CLI 的稳定面(见 cli.json):
help_lines:锁住--help输出的头部与子命令目录,包括 "cross-platform computer-use automation driver" 描述行,以及mcp, list-tools, describe, call, serve, stop, revoke, status, config, telemetry, recording, update, check-update, doctor, diagnose, permissions, autostart, skills, manifest共 18 个子命令。
manifest:锁住cua-driver manifest输出的关键字段:
schema_version: "1"、binary_version_format: "semver";mcp_args: ["mcp"](MCP 的调用参数形式);- 各子命令的参数目录,如
mcp的--socket(string)、--grant(repeatable-string)、--claude-code-computer-use-compat(flag)、--embedded(flag)、--host-bundle-id(string); serve的--permission-mode、--capability-manifest、--approve-capability-manifest、--session-policy、--approve-session-policy、--no-permissions-gate、--dangerously-bypass-approvals等;call的两个位置参数tool(positional-string)与json-args(positional-json),以及--screenshot-out-file、--socket;manifest自身的--prettyflag。
3.4mcp.json:MCP 初始化、工具列表与错误类别
该快照锁住 MCP 服务的协议面(见 mcp.json):
- initialize:
jsonrpc: "2.0"、protocol_version: "2025-06-18"、能力键["tools"]、server_name: "cua-driver",且 server 版本号只校验 semver 形状; - tools_list:
schema_version: "1"、capability_version: "1",以及 7 个必备工具——list_apps、list_windows、get_window_state、launch_app、click、type_text、press_key。同时针对click工具锁定了其字段集合:name、description、inputSchema、annotations、capabilities、risk; - unknown_method_error:方法不存在时的错误类别被锁定为
code: -32601、message: "Unknown method: compatibility/unknown"——这里特意用一个固定方法名做基准,确保错误响应的结构(而非具体动态内容)稳定; - tool_call:工具调用结果必须包含
content、isError、structuredContent三个字段。
四、apps/:三语言基线应用,验证"不开守护进程也能用"
除了 JSON 快照,apps/目录还冻结了三份未修改的基线应用源码(见 apps/README.md),CI 会针对候选包(candidate packages)实际编译并运行它们。
三个应用刻意只使用"发布版连接构造器 + 端点访问器 + 语言专属清理操作"三件套,且不需要运行中的守护进程:构造兼容客户端并读取其选中的端点是无副作用的本地产物。
Python(apps/python/app.py):
from cua_driver import CuaDriver driver = CuaDriver.connect(None) endpoint = driver.socket_path() if not endpoint.strip(): raise RuntimeError("default endpoint must be selected") print(endpoint)Rust(apps/rust/src/main.rs),依赖指向工作区内的cua-driver-sdkcrate(见 Cargo.toml):
use cua_driver_sdk::CuaDriver; fn main() { let driver = CuaDriver::connect(None).expect("create compatibility client"); let endpoint = driver.socket_path(); assert!(!endpoint.trim().is_empty(), "default endpoint must be selected"); println!("{endpoint}"); }TypeScript(apps/typescript/app.mjs),从@trycua/cua-driver导入,并显式调用uniffiDestroy()清理:
import { CuaDriver } from "@trycua/cua-driver"; const driver = CuaDriver.connect(undefined); const endpoint = driver.socketPath(); if (!endpoint.trim()) throw new Error("default endpoint must be selected"); console.log(endpoint); driver.uniffiDestroy();三个应用验证的是同一个事实:connect(None/undefined)必须能在无守护进程的前提下返回默认端点。由于三者完全一致地断言"默认端点必须非空",任何破坏默认连接行为的改动都会在 CI 的三语言维度上同时失败。
五、测试如何消费夹具:三套"语义比对"实现
夹具本身不产生约束力,真正起作用的是消费它们的测试。仓库中三套测试分别从各自语言面的视角校验契约,这里以 Rust 测试为主线展开(compatibility_contract_test.rs)。
5.1 Rust:真实运行二进制 + JSON-RPC 会话
Rust 测试通过include_str!把cli.json与mcp.json编译进测试二进制,然后用Command真实执行cua-driver --help与cua-driver manifest:
- help 校验:遍历夹具中的
help_lines,断言帮助输出包含每一行(assert!(help.contains(line))); - manifest 校验:对比
schema_version,用semver::Version::parse验证binary_version仍是合法语义化版本,并断言mcp_invocation.args == ["mcp"]且 command 为非空可执行路径; - 子命令参数校验:对 manifest 中每个子命令的每个参数
(name, type)对,在真实输出中查找匹配项——这正是"只允许新增、不允许删除"的机械化表达; - MCP 协议校验:测试通过
RawDriver::spawn()拉起真实驱动,依次发送initialize、tools/list、compatibility/unknown、tools/call四组 JSON-RPC 请求,逐一断言协议版本、能力键、必备工具、click工具字段、-32601错误类别以及工具结果三字段(content/isError/structuredContent)全部与夹具一致。
此外还包含两个平台分支测试:
- 非 macOS 上验证
--directMCP 运行时与发布协议契约一致; - macOS 上验证显式
--direct模式下权限工具只读(direct_capture_status: "not_checked")且拒绝 overlay 工具(拒绝码facility_unavailable)。
5.2 Python:AST 静态解析,不运行进程
Python 测试(test_compatibility_contract.py)采用纯静态 AST 比对:用ast.parse解析src/cua_driver/__init__.py、wrapper.py、_native.py,把函数签名渲染成字符串后与夹具比对。这种方式的优点是零进程开销、不受运行时环境影响,且能精确锁定"形如async click(self, input: ...) -> ActionResult"的声明细节。测试同时断言__all__中的导出集合是夹具导出集的超集(set(expected) <= actual)——再一次落实"只增不减"原则。
5.3 TypeScript:读取产物声明文件
TypeScript 测试(compatibility-contract.test.mjs)直接读取构建产物:比对package.json的exports映射、dist/native/cua_driver_contract.d.ts与cua_driver_sdk.d.ts中提取的声明导出集合、规范化后的CuaDriver方法声明文本,以及index.d.ts/embedded.d.ts/electron.d.ts三个入口必须包含的导出片段。它还额外验证了新版原生窗口方法的类型化输出,例如listApps(input: ListAppsInput, asyncOpts_?: { signal: AbortSignal; }): Promise<ListAppsOutput>。
六、两条已接受的契约变更:夹具如何演进
夹具不是"一成不变的枷锁",它区分了两种变更并给予不同待遇。
6.1 RFC 3682:一次有意的破坏性 SDK 修订
RFC 3682(Typed native-window SDK flow and explicit click addressing)接受了一次有意的破坏性变更:旧的click只接受 x/y 坐标且返回通用结果,无法表达元素 token 或后台投递;新契约要求ClickInput必须包含:
ActionTarget(明确的窗口或桌面目标);ClickPosition(坐标或快照绑定的元素 token,二选一的 sum type);- 显式的
InputDeliveryMode(后台 / 前台)。
同时click直接返回ActionResult,工具拒绝时抛出DriverError.Tool。该变更落地后,Python 的click签名同步更新,TypeScript 基线则没有冻结该方法(因此不受影响);夹具中其余冻结的包签名、CLI/MCP 快照与基线应用保持不变。测试覆盖了新的签名以及类型化原生窗口的发现(list_apps、list_windows)与观察(get_window_state)方法。
这一案例说明夹具的运作逻辑:破坏性变更本身被允许,但必须以 RFC 形式显式决策,并在夹具中留下痕迹——python-package.json中click的返回类型变为ActionResult,正是该决策的冻结证据。
6.2 RFC 2549:一个纯新增的 CLI flag
与上述不同,RFC 2549 带来的cua-driver mcp --direct属于纯新增:裸mcp行为保持平台定义(Windows / Linux 直接运行,macOS 为签名应用服务),--socket继续选择显式服务。由于夹具测试采用"候选参数集合包含夹具参数集合"的语义比对,这个新增 flag不需要重写冻结的cli.json基线也能通过校验——这正是"语义比对而非整进程输出比对"的设计回报:新增是零成本兼容,删除才需要成本。
七、工程启示:把"兼容性"变成可测试的资产
回顾整套机制,可以提炼出几条可复用的工程原则:
- 从发布标签提取基线,而不是从主干提取——快照代表"用户实际在用的契约",而非"团队想要的状态";
- 锁定语义字段而非进程输出——排除路径、PID、平台散文等易变值,让快照跨平台、跨环境稳定;
- 版本号只校验形状(semver)——避免兼容性测试与版本递增相互打架;
- 新增默认放行,删除/修改必须显式决策——通过子集/超集断言(
expected <= actual)把这一策略直接写进测试; - 多语言面共享同一套夹具——Rust、Python、TypeScript 各自动态的测试读取同一批 JSON,任何语言面漂移都会立刻暴露。
对于任何维护多语言 SDK、CLI 与协议服务的项目而言,libs/cua-driver/compat-fixtures/都是一个可以直接借鉴的"契约保险"范式:它让"我们不破坏下游"从一句口头承诺,变成了 CI 中每一次构建都会自动执行的断言。
【免费下载链接】cuaScale computer-use 2.0 with open-source drivers, cross-OS fleets, and benchmarks for training, evaluation, and data generation.项目地址: https://gitcode.com/GitHub_Trending/cua/cua
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考