Back to Qwen Code

UniFFI imported-SDK implementation journal

packages/cua-driver/docs/uniffi-sdk-implementation-journal.md

0.22.29.3 KB
Original Source

UniFFI imported-SDK implementation journal

Status: historical baseline plus RFC 2447 migration log

The original sections below record the daemon-client SDK shipped in 0.11.0. RFC 2447 supersedes that topology: the public typed SDK now owns the platform runtime, and daemon/MCP hosting is migrating downstream of it.

Definition of done

Deliver an experimental imported-SDK vertical slice in pull request #2341 where:

  • Rust request types used by the live daemon are also the types exported through UniFFI.
  • A Rust CuaDriver object invokes the existing daemon protocol without routing through MCP.
  • Python and Node/TypeScript bindings are generated deterministically from the compiled Rust library.
  • Host-native loader tests exercise calls across both FFI boundaries.
  • CI checks Rust parity, generated-file drift, packaging inputs, and both loaders.
  • MCP/CLI remains the supported agent integration boundary; UniFFI is for applications importing Cua as an SDK.

Frozen baseline

  • Branch: codex/experimental-cua-driver-contract-sdk
  • Starting head: 07cec13ec1ecf2336d842b156993a83d4f713bfe
  • Upstream main at start: d5a5e33d6d0b5dc4998c4985bc0704d6df2b0a1d
  • UniFFI: 0.31.0, matching the repository's Fleet SDK precedent.
  • Node generator/runtime: uniffi-bindgen-react-native / @ubjs/* 0.31.0-3.

Historical architecture decision

Version 0.11.0 initially shipped the FFI library as a daemon client. RFC 2447 supersedes that topology: CuaDriver.create() now owns the platform runtime in the importing process, while connect() remains the explicit compatibility path for an external daemon.

The public integration split remains:

  • Agent MCP/CLI: language-neutral protocol boundary, no generated client required.
  • Imported SDK: generated Python and Node/TypeScript bindings backed by one Rust implementation.

The product boundary is reflected directly in packaging:

  • Python client applications import the Rust-backed SDK from cua_driver.
  • TypeScript client applications import it from @qwen-code/cua-sdk.
  • Agents configure cua-driver mcp through their runtime's existing MCP client and do not import either language package.
  • The language-native MCP facades and their contract generator were removed.
  • No public .sdk, /sdk, .mcp, /mcp, .native, or /native alias exists; native remains only an internal loader/artifact term.

Initial audit

  • The live session handlers and desktop backends already deserialize cua-driver-contract request types.
  • Successful structured responses are validated against output types from that same crate.
  • The daemon socket protocol is currently defined inside the binary crate; it must be moved into shared Rust code so the binary, proxy, CLI, and UniFFI library use the same wire implementation.
  • Python UniFFI generation and colocated library loading are supported upstream.
  • Node N-API generation is available through ubrn generate napi bindings; it loads the same cdylib through @ubjs/node and currently requires a separately orchestrated Rust build.

Verification log

RFC 2447 first implementation slice (2026-07-22)

  • CuaDriver.create() constructs the platform runtime in the importing process; it neither launches cua-driver nor opens daemon IPC.
  • cua-driver serve constructs that same SDK-owned runtime and temporarily exposes its private registry through the existing compatibility transport.
  • Python and TypeScript package roots select their binding client kind for both create() and the transitional connect() path.
  • SDK operations are generated as asynchronous Python and TypeScript methods.
  • Rust SDK tests passed: 11 tests, including typed inventory parity, same-process PID identity, idempotent shutdown, and daemon compatibility.
  • Python loader tests passed: 3 tests across in-process and daemon modes.
  • Node loader tests passed: 4 tests across in-process and daemon modes; the Electron lifecycle fixture remains a separate release-matrix check.
  • Local macOS tests required a test-copy rpath for the Command Line Tools swift-5.5 runtime. No rpath mutation is checked in or applied to release artifacts.

RFC 2447 versioned C ABI slice (2026-07-22)

  • A generated, checked-in public C header defines version negotiation, opaque driver and operation handles, caller-owned buffers, status/error mapping, asynchronous completion, cancellation, and idempotent release.
  • cua-driver-abi-header uses pinned cbindgen over the Rust ABI exports, and CI rejects header drift. UniFFI still independently generates the language-facing Python and TypeScript bindings from the safe Rust SDK.
  • The safe Rust CuaDriver wrapper imports the exported C symbols, so Rust, Python, and TypeScript all traverse the same native seam.
  • The ABI owns a process-lifetime asynchronous executor. Foreign callers do not need Tokio or a daemon to create and use the embedded runtime.
  • Unit tests contain panics, validate cancellation, and assert that the header matches the exported v1 contract. CI compiles and runs an external C client against the release library and checks its exported symbol table.
  • Linux, macOS, and Windows release archives include the public header next to the shared library.

Rust and contract

  • cargo check --locked --workspace --all-targets passed. Existing cross-platform example warnings remain outside this change.
  • cargo test --locked -p cua-driver passed: 115 unit tests and all enabled integration suites passed; GUI/environment-dependent cases remained ignored.
  • cargo test --locked -p cua-driver-core passed: 329 unit tests, 2 contract parity tests, and 3 session lifecycle tests.
  • cargo test --locked -p cua-driver-sdk passed: 4 tests covering published method parity, typed desktop calls, typed session output, and tool discovery.
  • cargo run --locked -p cua-driver-contract --bin cua-contract-gen -- all --check passed.
  • node scripts/generate-uniffi-bindings.mjs --check rebuilt the release cdylib and reproduced every checked-in Python and TypeScript binding.

Language loaders and packages

  • Python generated-loader test: 1 passed across ctypes -> UniFFI -> Rust -> Unix socket, and asserted all 14 typed methods are exported.
  • Python release-helper tests: 3 passed, including native-library membership for every release archive target.
  • TypeScript typecheck and the actual N-API loader call passed, including all 14 methods and proof that the removed package subpaths are not exported.
  • npm audit --audit-level=high: 0 vulnerabilities.
  • A built macOS arm64 wheel was installed into a clean Python 3.12 environment; cua_driver.CuaDriver.connect(None) loaded the packaged library and returned the canonical daemon socket path.
  • A pre-rename 382.3 kB npm tarball (1.2 MB unpacked) was installed into a clean project and loaded the packaged dylib successfully. That result used the upstream npm identity and is historical only; it is not current Qwen release evidence.
  • The macOS dylib install ID is the relocatable @rpath/libcua_driver_sdk.dylib, and release CI asserts it before signing.
  • After promoting the SDK to each package root, a fresh macOS arm64 wheel and npm tarball were installed into clean Python 3.12 and Node projects. Both roots loaded the Rust library and returned the canonical socket path; Python contained no sdk or native module, Node exported no /sdk, /native, or /mcp subpath, and neither artifact contained the removed MCP facade files.
  • Package-root cleanup validation passed: Python real-loader test (1), Python packaging/wrapper tests (13 passed, 3 binary-dependent skips), TypeScript typecheck, Node real-loader test (1), and npm audit (0 vulnerabilities).

CI and release boundary

  • The PR workflow parses successfully and checks deterministic generation, Rust parity on Linux/macOS/Windows, both Linux FFI loaders, installed wheel and npm-tarball smoke tests, and package contents.
  • Rust release artifacts now carry the SDK library on Linux, macOS, and Windows. The Python release matrix installs every built wheel and imports the generated binding before PyPI publication.
  • Release CI packs one platform-neutral @qwen-code/cua-sdk artifact, clean-installs it against the verified Cua Driver release payload, and publishes it at the Rust tag version only after the GitHub Release exists. A developer's host-local tarball is never published.
  • MCP/CLI remains unchanged as the agent boundary; the language package roots are the Rust-backed application SDK.

Qwen-owned npm identity

  • The TypeScript SDK and /computer-use wrapper are one @qwen-code/cua-sdk package. There is no driver npm package and there are no platform npm packages.
  • The package version identifies the matching Qwen CUA Driver GitHub Release. Its postinstall verifies checksums.txt before caching the SDK library and Node runtime. It never imports or resolves the published upstream driver package.
  • Release dry-run clean-installs that one tarball against the just-built native payload. Production repeats the install against the public Qwen Release before npm publication.
  • The frozen 0.12.6 TypeScript compatibility fixture keeps its historical import unchanged and passes when the Qwen SDK tarball is installed under a test-only npm alias. It is not a production dependency or publication target.