Cua Driver SDK 的 Rust 单一事实源与 UniFFI 评估:一份完整的技术决策与落地复盘
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
导读
本文以 Cua Driver 仓库中的 sdk-rust-source-of-truth-and-uniffi-evaluation-plan.md 为核心,系统梳理这条贯穿"契约单一事实源 → 类型化运行时 → 语言 SDK 生成 → UniFFI 绑定评估"的完整技术决策链。你将看到:为什么 Rust 被选为唯一的事实源、如何用可验证的子集检查关闭契约与运行时之间的漂移、MCP 与 UniFFI 分别解决哪两个正交问题,以及 Python 与 TypeScript 为何最终各自保留类型化 MCP 客户端而非切换到 UniFFI。读完本文,你可以复现这套"先证明、后重构、再测量、最后分语言做决定"的评估方法论。
说明:本文所述方案在 PR #2341 及后续落地中已执行并产生了最终绑定决策(2026-07-21)。文中保留了完整的评估证据链,并标注哪些内容已被后续决策取代,方便读者对照当前仓库代码理解。
一、背景:为什么要做"Rust 单一事实源"
1.1 问题:手动维护契约与运行时的双份账本
在发布 Python 与 TypeScript SDK 之前,Cua Driver 面临一个典型的跨语言工程问题:同一份工具契约(工具名称、输入/输出类型、能力声明、平台可用性)需要在Rust 运行时、Python 包、TypeScript 包三个地方各自维护。任何一处的字段、枚举值或边界条件漂移,都会导致"客户端调用了运行时不接受的参数"这类难以排查的线上故障。
文档给出的首要目标非常明确:在发布语言 SDK 之前,消除手动维护的契约/运行时账本(manual contract/runtime bookkeeping)。
1.2 决策前提:Rust 是唯一的执行引擎
方案有一组不可动摇的约束前提,它们决定了后续所有架构选择的边界:
- Rust 是 GUI 执行、权限、策略、会话状态与平台集成的唯一所有者;
- Python 与 TypeScript不重新实现任何 OS 自动化行为;
- 每个对外发布的类型化方法,都必须映射到被线上实现实际消费的 Rust 输入/结果类型;
- MCP 始终可用,无论绑定方案如何演进;
- 实验性与运行时专用工具仍可通过通用调用(generic call)触达;
- 传输层不得自动重发任何改变状态(state-changing)的工具调用;
- 生成文件必须确定性、所有权清晰、原子替换、并在 CI 中做漂移检查;
- 平台可用性必须显式声明到工具、变体与字段级别;
- 能力与风险元数据只有一个权威声明点;
- 混合 MCP 内容只是传输信封:类型化结构化结果不能抹掉文本、图片、拒绝(refusal)、降级结果或诊断信息。
这些不变量在源码中可以直接对应到契约 crate 的设计。例如 cua-driver-contract/src/lib.rs 的模块注释明确写道:本 crate刻意不含任何传输或平台实现,原生 driver 仍是唯一执行引擎,本包只拥有类型化输入/结果与版本化声明。
1.3 起点状态:PR #2341 与十四工具契约面
文档确认的起点事实(对应当时的 PR #2341):
cua-driver-contract声明了14 个类型化工具:4 个会话生命周期工具 + 10 个可移植桌面循环(desktop-loop)工具;- manifest、Python 的参数类/方法、TypeScript 的接口/方法,均由该 Rust 契约 crate生成;
- 两个语言 SDK 通过 stdio 与
cua-driver mcp通信; - 两个 SDK 都保留通用调用与
tools/list访问,以覆盖运行时发现与平台专有工具; - 生成文件有所有权清单(ownership inventory)和真实的
--check漂移模式; - 语言包的公开类为 Python 的
CuaDriver/AsyncCuaDriver与 TypeScript 的CuaDriver。
在当前仓库中可以看到这份契约的最终形态:
- 契约 crate 的
CONTRACT_VERSION = "0.7.0"、TOOLS_LIST_SCHEMA_VERSION = "1"、CAPABILITY_VERSION = "1"、MCP_PROTOCOL_VERSION = "2025-06-18"(见 lib.rs); - 生成的 manifest 位于 contract/manifest.json,其头部的
generated_notice明确写着 "Generated by cua-contract-gen; do not edit by hand.",并带有experimental: true、transport: "mcp_stdio"等元数据; - 会话契约在 session.rs 中定义,桌面契约在 desktop.rs 中定义。
1.4 当时的"平价"(parity)现状
文档实事求是地指出,当时存在两类平价:
- 元数据平价已成立:4 个会话工具通过
ToolDef::from_contract从规范契约构建线上ToolDef元数据。源码ToolDef::from_contract(见 tool.rs)断言只有CanonicalRuntime模式的契约才能替换线上运行时 schema,可移植子集契约不能直接顶替。 - 行为/输出平价未成立:10 个桌面契约是单独编写的
portable_subset声明,平台 crate 并未消费这些输入类型,而是各自持有更丰富的ToolDefschema 与逐字段ArgsExt解析。当时没有任何测试证明每个可移植输入 schema 都能被对应平台 schema 接受。
能力声明当时也有两个声明点:运行时能力映射 + 每个ToolContract。已有测试比较两者,但并未消除重复所有权。
1.5 平台名册与运行时形态
文档给出了当时基于注册的近似工具数(且强调必须在各 OS 上用线上tools/list核验后才能作为发布证据,不得固化为全局断言):
| 平台 | 注册工具数(约) | 平台专有例子 |
|---|---|---|
| macOS | 48 | —— |
| Windows | 49 | debug_window_info |
| Linux | 52 | 四个额外的指针原语 |
这些名册刻意不同且持续演进,因此不能写死。
运行时形态的关键约束:daemon 通过一个共享注册表与工具状态服务并发连接;核心会话钩子、活动、元素 token 等使用进程级全局单例;macOS 上 AppKit 表面要求主线程、权限身份绑定可执行文件。这些约束正是传输层决策的核心变量——直接嵌入 GUI 引擎会破坏这些假设。
二、Fleet 先例:UniFFI 能做什么、不能做什么
方案引入 Fleet 作为 UniFFI 的参照系,但结论是审慎的:
Fleet 能证明:官方 UniFFI 目标(Python、Kotlin、Swift、Ruby)有强先例——其签入的生成器使用钉死的 Rust 依赖并支持再生成检查。
Fleet 的 TypeScript 路径只能证明"可行"而非"生产可用":
- 存在 Node N-API 路径(
uniffi-bindgen-react-native+@ubjs/core+@ubjs/node加载原生cdylib)与浏览器/WASM 路径; - 但在被检视的树中:TypeScript 不在 Fleet 主生成/检查脚本内、React Native 生成器未钉死在工作区、示例
@ubjs/*依赖用的是latest、浏览器 WASM 胶水依赖未签入的配置与生成。
由此导出三条纪律,并被最终决策继承:
- 不得把 Fleet 的 TypeScript 路径描述为可复现模板;
- Cua Driver 自己的确定性
--check流程应作为 TypeScript UniFFI spike 必须达到的标准; - Python 与 TypeScript 的 UniFFI 采用必须独立评估。
三、目标架构与公开面
文档给出了清晰的最终目标架构(摘要如下):
Transport-free Rust ToolSpec + typed inputs/results | +--> live ToolDef 与 tools/list 元数据 +--> 运行时类型化输入解析 +--> 结构化结果校验 +--> 可移植与平台富化投影 +--> 契约 manifest +--> 可选:类型化 MCP 客户端生成 +--> 可选:UniFFI 实现导出 Agent 面: Codex / Claude / 其他 agent ---- MCP stdio ---- 原生 daemon ---- OS APIs Shell 自动化: shell ---- CLI 调用 ---- 原生 daemon ---- OS APIs 导入型应用 SDK 面: Python/TS 类型化客户端 ---- MCP stdio ---- 原生 daemon ---- OS APIs 或 语言绑定 ---- UniFFI ---- Rust daemon client ---- daemon 或(需另行设计宿主运行时后) 语言绑定 ---- UniFFI ---- 内嵌 Rust 引擎 ---- OS APIs无传输规范应放在现有cua-driver-contractcrate 或一个窄范围的兄弟rlib中;所有平台 crate 必须能依赖这些类型而不导入 MCP framing 或公开 SDK 运行时。
类型化层必须区分四类东西:
- 类型化命令输入;
- 类型化成功
structuredContent载荷(存在时); - 稳定的类型化拒绝/错误;
- 可包含文本、图片、诊断的 MCP 内容信封。
关键约束:不得用一把大而全的适配器假装"所有工具结果都是一个可序列化的成功对象"。Agent 示例必须直接用现有 agent SDK 的 MCP 客户端,否则会掩盖本方案赖以成立的区分。
四、Workstream A0:先关闭 PR #2341 的即时平价缺口
这是整个方案的第一步也是先决条件:在更大规模重构之前,为 10 个桌面契约增加一个机械式子集-存在性测试。对每个声明平台与可移植桌面工具,测试必须证明:
- 线上注册表包含该工具;
- 每个可移植属性存在于线上输入 schema;
- 可移植必填字段在线上保持必填;
- 可移植的类型、枚举/const 值、边界、封闭对象行为不比线上 schema 允许的范围更宽;
- 可移植注解与能力 token 与线上工具兼容;
- 刻意引入一个不兼容字段变更时,测试必须失败。
实现策略:为 10 个契约用到的 schema 构造写一个小的 schema 子集检查器,不声称通用 JSON Schema 蕴含(implication);遇到不支持的 keyword 就显式失败,防止测试静默接受未被证明的情况。
A0 验收门:
- 10 个可移植契约在 macOS / Windows / Linux 三套注册表上全部通过;
- 契约、平台
ToolDef、注册表或能力映射任一变化都会触发工作流; - PR #2341 的描述准确表述为"证明可移植输入/元数据兼容",而非"完整运行时行为平价"。
这在当前仓库的测试中得到印证:契约 crate 内有大量断言可移植子集语义的单元测试(例如desktop_contracts_are_explicit_portable_subsets断言 click、drag、get_cursor_position、get_desktop_state、get_screen_size、hotkey、invoke_menu、move_cursor、press_key、scroll、set_window_frame、type_text 的schema_mode都是PortableSubset,见 lib.rs 测试),而 cua-driver-core/tests/contract_parity.rs 则把CanonicalRuntime会话契约与线上注册表逐项比对(描述、inputSchema、readOnlyHint、destructiveHint、idempotentHint、openWorldHint、capabilities)。
五、Workstream A:让 Rust 成为真正的单一事实源
A1. 建立无传输规范模型(ToolSpec)
ToolSpec模型必须包含:
- 规范名称与描述;
- 类型化输入;
- 类型化成功结构化载荷(如适用);
- 稳定拒绝/错误形态;
- 注解与风险元数据;
- 唯一的能力声明;
- 逐平台工具存在性;
- 可移植 vs 富化暴露元数据;
- schema 与契约版本。
随后给 macOS、Windows、Linux 平台 crate 增加指向规范 crate 的新依赖,保持依赖方向无环且无传输。
对 schema 方言,文档明确不预先承诺schemars:先在 1 个会话输入与 1 个复杂桌面输入上跑 schema 方言 spike,必须复现现有 required/optional、封闭对象、enum/const、可空性、默认值与边界语义,且覆盖范围受限;若做不到,保留现有 schema builder,同时让类型化 Rust 输入保持权威。
当前仓库中,输入类型定义于 inputs.rs:ToolInputtrait 通过schemars的 draft2020_12 设置生成 schema(inline_subschemas = true),并经过normalize_schema后处理(移除 title、补齐 properties/required/additionalProperties),确保输出形态与线上 wire shape 一致。这里可以看到契约 crate 在 Cargo.toml 中同时依赖schemars = "1.2.1"与uniffi,正是"契约即生成源"的具体体现。
A2. 增加类型化运行时适配
适配器需要依次完成:把 MCP 参数反序列化为共享类型化输入 → 用该输入调用平台实现 → 在声明了结构化输出时校验/序列化稳定输出 → 保留现有 MCP 文本/图片/诊断信封 → 规范化稳定错误而不抹掉平台诊断。
对已迁移工具,线上ToolDef必须由其 spec 派生,平台实现不得保留第二个 schema 字面量或逐字段ArgsExt解析器。起点是 4 个会话工具——它们的线上元数据已经来自规范契约,风险最低。
A3. 一次建模平台存在性与富化变体
模型必须同时覆盖字段级富化与工具集分叉:
- 可移植桌面坐标 vs 窗口/元素定位;
- 平台专有字段与枚举变体;
- Windows 专有工具(如
debug_window_info); - Linux 专有低层指针原语。
可移植 SDK 输入必须是更丰富类型化声明的派生投影,而不是平行的手写 schema。若一个公共 union 会产生误导,就定义DesktopClickInput与WindowClickInput等独立类型化命令,但路由到同一实现族。
这一点在契约 crate 中有直接印证:inputs.rs 导出ClickInput、WindowClickInput、DragInput、WindowDragInput、HotkeyInput、WindowHotkeyInput等成对类型;而 desktop.rs 中paste与select_text明确标记为macOS only(platforms = vec![Platform::Macos]、SchemaMode::CanonicalRuntime),list_windows的成功输出 schema 则刻意保持窄(只承诺z_index的可移植语义,additionalProperties: true允许平台富化字段)。
A4. 证明一条分叉垂直切片
迁移目标集合(五类代表性工具):
start_session/end_session:生命周期与结构化输出;get_desktop_state:图片 + 结构化元数据;click:可移植 vs 富化定位 + 破坏性注解;get_window_state:窗口聚焦富化;- 至少一个在部分平台缺失的工具,以证明存在性建模。
切片完成的判定标准:所有参与平台实现消费共享输入;线上tools/list元数据由 spec 派生;代表性结构化结果按声明 schema 校验通过;MCP 信封保留文本与图片;生成的 Python / TypeScript 方法通过可执行 fixture 测试。
A5. 迁移当前 14 工具 SDK 面
切片稳定后,迁移 PR #2341 当前生成的 14 个方法,删除这些工具的重复 schema 与 ad hoc 解析;兼容别名只保留在 dispatch 层,对tools/list与 SDK 隐藏。
A6. 按工具族扩展
按可独立评审的族迁移稳定工具:剩余感知与桌面输入 → 窗口与应用生命周期 → 浏览器 → 录制与回放 → 光标/配置/诊断 → 剩余平台专有稳定工具。每个运行时名册条目必须归类为:稳定类型化、实验通用-only、兼容别名、内部/隐藏——不编码一个全局工具总数。
六、Workstream B:把 Agent 集成与 SDK 分发分开
6.1 Agent 集成(基线推荐):直接 MCP/CLI
用各 agent 运行时现有的 MCP 客户端配置cua-driver mcp,或让 shell 类 agent 用cua-driver call。此路径不需要生成的 Cua 语言客户端,但要维护可运行的 Python Claude Agent SDK 与 TypeScript Codex SDK 示例作为证明。
这是 O(1) 的 agent 协议面:一个 MCP 服务器可被 N 个 MCP 运行时消费;生成的客户端只是为应用增加 ergonomics,不增加这些 agent 的互操作性。
6.2 SDK 选项 1:薄生成的 MCP 客户端(最终被采纳)
继续生成 Python / TypeScript 类型与方法,小原生传输直连 MCP stdio。它保留:daemon 的权限身份与主线程所有权、进程与崩溃隔离、通用tools/list/tools/call逃生口、无额外客户端cdylib的小语言包、以及已有的可复现 TypeScript 生成/检查流水线。剩余手写语言代码只是少量传输、结果与门面模块。这是类型化远程客户端 SDK,不是原生实现绑定。
6.3 SDK 选项 2:UniFFI 门面覆盖 Rust daemon client(门控 spike)
构建平台中立 Rust 客户端:拥有 MCP framing 与结果规范化,仍启动/连接原生 driver daemon,只通过 UniFFI 导出垂直切片 API。跑两个独立实验:Python 走官方 UniFFI 支持;Node TypeScript 走uniffi-bindgen-react-native+@ubjs/node。
TypeScript 实验从绿地可复现假设出发(不沿用 Fleet 手写产物),必须补齐:精确钉死的生成器与运行时版本;仓库内生成命令;确定性自有输出与--check模式;lockfile 与可发布包定义;从 Cargo 编译产物解析宿主cdylib;每个受支持平台/架构的隔离包测试。此 spike 不做本地桌面自动化的浏览器/WASM 打包。
本选项的意义判据:在 Rust 中实现一次应用客户端行为并分发到 N 个运行时。仅仅把 JSON-RPC framing 挪进 Rust 收益不足;spike 必须识别出它真正拥有的共享生命周期、规范化、策略或服务器组合行为。
6.4 SDK 选项 3:UniFFI 内嵌/服务器实现(独立提案)
面向想创建或内嵌 Cua 服务器的应用开发者。不得当作传输层的回退方案。单独提案必须先消除或显式托管:进程级会话与元素 token 状态;单注册表假设与并发会话清理风险;macOS AppKit 主线程所有权与权限身份;Windows 交互式会话/UIAccess/前台/覆盖行为;Linux 合成器、X11/Wayland 与会话总线集成;宿主崩溃/运行时/取消耦合。
6.5 B1:测量 SDK 候选
对选项 1 与每个选项 2 的语言 spike 记录:生成与手写维护的源码行数;按平台/架构的 wheel/npm 产物大小;冷启动、首次调用与稳态只读调用延迟;Python 同步/异步与 TypeScript 异步行为;错误/拒绝/图片/结构化结果保真度;通用运行时-only 工具访问;安装与原生加载失败模式;发布矩阵与 CI 时长变化;当前超时/截止行为(不暗示工具取消)。
6.6 B2:分语言 SDK 决策门
Python 与 TypeScript独立决策(UniFFI 可能对 Python 通过、对 TypeScript 失败)。采用选项 2 的条件:
- 实质性减少维护的 SDK 行为;
- 生成输出可复现且仓库内漂移检查;
- 所有生成器/运行时依赖钉死;
- 安装、启动、通用访问、错误保真不回归;
- 完整受支持产物矩阵通过;
- MCP 仍作为公开边界可用。
对 TypeScript 而言:无法提供钉死的确定性生成与 CI 检查 =自动拒绝,无论运行时基准如何。否则保留选项 1。无论结果如何,单一事实源工作都是完整且有价值的;且两种结果都不改变"直接 MCP/CLI 是推荐 Agent 集成"这一结论。
七、Workstream D:取消与超时语义
当时取消并非运行时能力:dispatch 直接 await 工具;serve 层截止时间不取消正在执行的动作;单个工具各自含有限制重试/轮询行为。
原则:不要因发明取消而阻塞 A0 或初始类型化迁移。在宣称取消平价或采用 UniFFI 传输前,做一个显式决策:
- 实现调用方取消——把 token/deadline 穿过
Tooltrait 与类型化适配器,定义安全中断点与动作完成语义;或 - 文档化 best-effort 客户端截止——停止等待但不取消进行中的工具。
无论哪种,传输层永不自动重发模糊的状态变更请求;测试必须区分传输重发、调用方取消与单次工具调用内的受限重试。
八、验证计划
契约与源码测试
- A0 子集/存在性平价覆盖全部 10 个可移植桌面契约;
- 每个生成的稳定方法映射到恰好一个Rust spec;
- 每个已迁移平台工具消费共享输入类型;
- 已迁移工具无第二个
ToolDefschema 或ArgsExt解析器; - 能力只有一个声明点;
- 逐平台存在性与可移植投影机械派生;
- 生成的 manifest 与 SDK 输出确定性、零漂移。
运行时行为测试
- 最小合法输入走与 MCP 相同的类型化适配器;
- 非法类型、缺失字段、未知字段、边界、枚举产生稳定错误;
- 代表性结构化结果按成功 schema 校验;
- 文本、图片、拒绝、验证元数据、降级结果与平台诊断原样存活;
- 兼容别名调用规范实现但保持隐藏;
- 截止/取消测试只断言 Workstream D 实际实现的语义。
平台测试
- 三平台编译并跑注册表/schema 平价测试;
- 线上名册证据取自精确源码修订;
- fixture 支撑的调用覆盖会话生命周期、桌面感知、指针、键盘、窗口状态(不在 CI 中做失控操作);
- 平台专有工具与字段只出现在声明处;
- 会话捕获范围(
auto/window/desktop)在通用调用与每种生成绑定选项下强制相同策略行为。
包测试
- 构建隔离 Python wheel 与 npm tarball;
- 安装进不含仓库相对导入的空消费者;
- 验证精确原生产物来源与加载路径;
- 每个候选跑同一套公共 MCP fixture 套件;
- 断言公共类名,拒绝陈旧的
Client导出; - 断言包内容不含陈旧生成/平台产物。
九、CI 与发布接线
契约/SDK 工作流必须在以下变更时触发:规范 crate 与生成 manifest;平台ToolDef、注册表与迁移实现代码;共享结果/内容信封与能力声明;生成器与所有权清单;Python、TypeScript、原生产物与打包代码。
必需 job:
- Rust 格式化 + 契约/单元测试;
- 三平台注册表的 A0 子集/存在性检查;
- 已迁移工具的类型化运行时与结构化输出测试;
- 确定性生成检查模式;
- Python 同步/异步测试与隔离 wheel 消费;
- TypeScript typecheck/测试与隔离 npm 消费;
- UniFFI 生成/产物 job 仅在 spike 或被采纳绑定存在时运行。
比较期间不发布任一绑定选项。
十、交付序列
保持提交可独立评审:
test(cua-driver): prove portable desktop contracts match live schemasrefactor(cua-driver): collapse tool capability ownershiprefactor(cua-driver): introduce transport-free typed tool specsrefactor(cua-driver): route session tools through typed runtime adapterrefactor(cua-driver): migrate divergent desktop vertical slicerefactor(cua-driver): migrate current portable desktop SDK toolstest(cua-driver): spike UniFFI Python Rust daemon clienttest(cua-driver): evaluate reproducible UniFFI Node bindings- 决策提交:删除被拒 spike 或采纳其生产接线
- 后续 PR:按族迁移稳定工具
PR #2341 合并前必须先落地 A0 并更新其描述以陈述精确保证;完整类型化运行时迁移可随后进行,不得把当前 manifest 表述为完整运行时平价。
十一、完成标准
- PR #2341 平价缺口关闭:每个可移植契约被证明存在于各声明线上平台 schema 且被接受;CI 对每个相关契约/运行时变更重跑该证明;PR 描述区分子集兼容与行为平价。
- 当前 SDK 的类型化 Rust 单一事实源完成:14 个生成方法全部使用被线上实现消费的 Rust 输入/结果;迁移工具无重复 schema 或 ad hoc 字段解析;线上元数据与代表性结构化输出通过平价测试;能力单一所有者;三注册表通过声明支持检查;改一个 Rust 字段会在每个消费者处引起编译失败或确定性生成 diff。
- UniFFI 评估完成:目标产品显式(类型化 daemon client 或内嵌/服务器 SDK);直接 Agent MCP 示例独立于每个生成客户端;薄类型化客户端与各语言 UniFFI 原型对选定 SDK 目标通过等价行为与产物测试;记录测量与平台矩阵;Python 与 TypeScript 决策独立成档;任何被采纳的 TypeScript 流水线钉死、可复现、CI 检查;所有被拒 spike 代码删除。
- 更广的类型化 SDK 覆盖完成:每个公开运行时工具要么带显式平台支持的类型化,要么有意归类为 generic-only,任何受支持 OS 上不得存在未分类的
tools/list条目。
十二、主要风险与缓解
| 风险 | 缓解 |
|---|---|
| 可移植声明在重构完成前漂移 | 先落地 A0,并在平台/运行时变更时触发 |
| 派生 schema 方言与现有契约不同 | 用方言复现 spike 门控schemars;必要时保留有界显式 builder |
| 平台富化产生不可用的公共 union | 从同一 spec 族建模独立类型化命令与显式逐平台存在性 |
| 混合 MCP 结果放不进单一类型化输出 | 只类型化结构化载荷与稳定错误;保留内容信封 |
| 迁移范围膨胀到大平台文件 | 从会话工具起步,证明一条分叉切片,再按族迁移 |
| TypeScript UniFFI 继承不成熟工具链 | 当绿地处理,全量钉死,要求确定性生成与 CI 检查 |
| 原生客户端库使分发复杂化 | 测全矩阵;收益不实质化就保留薄 MCP |
| 宣称取消但未实现 | 完成 Workstream D 或文档化非取消截止语义 |
| 直接内嵌破坏权限或全局状态假设 | 排除在本方案外,要求单独的 OS 特定证明 |
非目标清单(同样重要):不在 Python/TypeScript 中重新实现 GUI 动作;不删除 MCP 服务器或 CLI;比较期间不发布实验包;不承诺浏览器本地桌面自动化的 WASM;不在 SDK 生成工作中直接内嵌 GUI 引擎;不在类型化地基与决策门稳定前迁移每个运行时工具。
十三、评审处置(Review disposition)
文档记录了一次独立的第三方评审(Claude Code Opus 对仓库、原计划、三平台注册表、当前 SDK 生成器与 Fleet UniFFI 产物),并纳入其 must-fix 发现:
- Fleet TypeScript 描述为可行但未可复现接线;
- 修正线上工具数与平价声明;
- A0 关闭即时可移植子集缺口;
- 能力所有权与平台 crate 依赖显式化;
- schema 推导门控而非假设;
- 类型化结构化输出与混合 MCP 内容分离;
- 取消视为缺失的运行时设计工作;
- UniFFI 采用分语言进行;直接 MCP/CLI 保持 Agent 基线,薄 MCP 与 UniFFI 仅对导入型应用 SDK 比较。
十四、最终落地与绑定决策(2026-07-21)
14.1 实现结果
实施保留 MCP/CLI 作为公开 Agent 边界,并让类型化 Rust 成为当前 14 工具类型化客户端 SDK 面的来源:
- Schemars 派生的 Rust 输入与结构化输出类型确定性生成签入的 manifest 与 Python/TypeScript 源码;
- 4 个会话工具直接消费这些类型;每个 OS 桌面分支在动作前反序列化共享可移植投影,其更丰富的 window/element 路径仍对通用 MCP 调用者开放;
- SDK 路径的成功结构化载荷通过线上注册表中的共享 Rust 输出类型反序列化;MCP 内容信封不变,包括图片、文本、诊断与错误;
- 10 个可移植 schema 在 Linux、macOS、Windows 上被检查为各线上平台 schema 的逻辑子集;未知 schema keyword 使蕴含检查器封闭失败;
- 14 个工具的能力 token 全部从契约解析;遗留运行时能力映射现在只覆盖非 SDK 工具。
14.2 实测类型化 MCP 客户端基线
数据取自本次检出、版本0.10.0、包发布之前:
| 候选 | 维护源码 | 生成源码 | 通用包 |
|---|---|---|---|
| Python MCP SDK | 676 行 | 331 行 | 10,666 字节纯 Python wheel |
| TypeScript MCP SDK | 278 行 | 275 行 | 4,923 字节 npm tarball;解压 19,506 字节 |
两个客户端都保留通用call_tool访问与完整 MCP 内容/结果信封;其可执行 fixture 套件覆盖初始化、类型化调用、错误、图片与通用运行时-only 调用。
14.3 UniFFI 仓库 spike
比较使用的是签入的 Fleet 先例(commitc2ba0b5e94d0f2c06d0c7efb0913803ca0a616af),而非再建一个相同工具链的一次性绑定实现:
- Fleet 证明官方 UniFFI Python 绑定可以生成、漂移检查并在宿主原生 cdylib 上运行;
- Fleet 也证明 Node 与浏览器 TypeScript 生成通过
uniffi-bindgen-react-native技术可行——不限于 WASM; - 签入的 Fleet TypeScript 根目录约 4,777 行生成代码(220 KiB),需要
@ubjs/core、@ubjs/node与并置的原生 cdylib,且不在 Fleet 钉死的四语言生成/检查脚本内;可运行示例把运行时/构建依赖声明为latest; - Fleet Python 生成根约 5,675 行(228 KiB),对应更大的 API。这些源码规模是工具链证据,不是同类 API 规模基准。
14.4 当前 PR 的包决策
Python:本 PR 保留类型化 MCP 客户端。UniFFI 本身通过可行性与可复现性门,但 Cua Driver 还没有带实质应用行为的共享 Rust daemon client;在今天传输上做门面,会在 10.4 KiB 通用 wheel 上增加逐平台逐架构 cdylib 与原生加载失败模式,而仍然只是与现有 daemon 对话。
TypeScript:本 PR 保留类型化 MCP 客户端。当前先例失败于自动采纳门:生成在钉死的漂移检查脚本之外、运行时包未钉死、消费者必须定位宿主原生库。技术可行尚不构成可复现的 npm 发布流水线。
此包决策不否定UniFFI 内嵌/服务器 SDK,也不声称生成客户端提升 MCP 互操作性——MCP 能力 agent 应直接连接(如 agent 示例所示)。后续 SDK 提案必须首先决定其消费者需要共享 Rust daemon client 还是内嵌/服务器实现,再针对该目标评估 UniFFI。
不保留任何被拒 UniFFI spike 代码。冷启动与稳态对比有意不声称:两个原生候选都未通过证明有必要做发布矩阵原型的打包/可复现预门。当存在可复用的 Rust daemon client 或受支持的内嵌宿主时再重新考虑 Python;当钉死生成、确定性 CI 检查与原生包加载落地到仓库后再重新考虑 TypeScript。
十五、与当前仓库代码的对照
最终落地在当前仓库中有完整闭环证据,可逐项验证:
| 方案要素 | 仓库落点 |
|---|---|
| 无传输契约 crate(工具名、schema、能力、注解、平台) | cua-driver-contract/src/lib.rs |
类型化输入(ToolInput+ schemars 生成 + 规范化) | cua-driver-contract/src/inputs.rs |
类型化输出(ToolOutput、拒绝信封、广告输出 schema) | cua-driver-contract/src/outputs.rs |
| 会话契约(start/get/list/get_state/escalate/end) | cua-driver-contract/src/session.rs |
| 桌面契约(可移植子集 + 平台富化) | cua-driver-contract/src/desktop.rs |
| 生成 manifest(勿手改) | contract/manifest.json |
契约→线上ToolDef桥(from_contract) | cua-driver-core/src/tool.rs |
| 契约/注册表平价测试 | cua-driver-core/tests/contract_parity.rs |
UniFFI 绑定配置(cdylib 名cua_driver_sdk) | cua-driver-contract/uniffi.toml、cua-driver-sdk/uniffi.toml |
Python 包(原生CuaDriver.connect/create等入口) | cua-driver/python/src/cua_driver |
TypeScript 包(@qwen-code/cua-sdk) | cua-driver/typescript/src |
版本说明:文档正文与实测数据针对
0.10.0阶段;当前仓库中 TypeScript 包的版本号已演进(package.json),且why-cua-driver-uses-mcp-instead-of-uniffi.md提到后续 RFC 2447 会把 MCP 调整为下游 SDK 消费者——即"SDK 仅是 daemon client"的表述已被取代。阅读本文时请以最新代码为准,本文聚焦的是决策方法与评估门这一可复用资产。
结语:可复用的工程方法论
回顾这条决策链,真正可迁移到其他项目的是这套纪律:
- 先证明再重构:A0 子集测试先于一切大规模迁移,把"平价"精确限定为"元数据/输入子集兼容",拒绝提前声称行为平价;
- 单一事实源 + 机械派生:类型、schema、能力、存在性全部从 Rust 一处派生,改一个字段要么编译失败要么产生确定性 diff;
- 传输与 SDK 面分离:MCP/CLI 解决 agent 互操作,UniFFI 只回答"多语言如何调用同一 Rust 实现",两者正交,各司其职;
- 分语言决策:不搞一刀切,Python 与 TypeScript 各自按钉死依赖、确定性生成、漂移 CI 检查等硬门独立裁决;
- 不越界:直接内嵌引擎、WASM 本地自动化、取消语义这些未成熟能力明确列入非目标或独立提案,不强行打包进当前版本。
这套方法论的产物,是当前仓库中可编译、可测试、可生成、可漂移检查的完整闭环——它比任何单一技术选型结论都更有参考价值。
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考