
Cyclops TypeScript UniFFI 绑定实战Node.js 与浏览器/WASM 双根生成管线、兼容归一化与运行时集成【免费下载链接】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/cuaCyclops SDK 以 Rust 为核心实现并通过 UniFFI 向 Python、Kotlin、Swift、Ruby、Go 与 TypeScript 等语言导出跨平台绑定。其中 TypeScript 绑定被刻意拆分为 Node.js 与浏览器/WASM 两个根root二者 API 形态不同Node.js 通过兼容归一化保留直接记录工厂浏览器/WASM 则保留生成式 Builder API。本文以 libs/fleet/sdk-bindings/ts-uniffi/README.md 为骨架结合 生成脚本、兼容归一化器 与两套可运行示例完整讲解双根的生成管线、--check漂移检测、运行时依赖ubjs/core、ubjs/node与同置的libcyclops_sdkcdylib以及从创建 Pool、Claim 到服务调用的完整生命周期实战帮助你在自己的 Node.js 或浏览器项目中正确接入并维护这套绑定。Cyclops 绑定体系中的 TypeScript 根Cyclops SDK 的官方 API 由 Rust 定义并持有唯一实现canonical implementationUniFFI 负责把这份 API 导出到多种宿主语言。仓库中 TypeScript 相关的绑定源文件统一存放在 libs/fleet/sdk-bindings 下由确定性生成与漂移检测管线统一管理共涉及两类根ts-uniffi面向 Node.js 的根生成的 TypeScript 源码直接检入仓库ts-uniffi-browser面向浏览器/WASM 的根生成 TypeScript 与 Ccpp桥接源码。从 generate-sdk-bindings.sh 的变量定义可以看到全部被管束的绑定根languagespython kotlin swift ruby binding_roots$languages go-uniffi ts-uniffi ts-uniffi-browser typescript_bindgen_packageuniffi-bindgen-react-native0.31.0-3 manifest_name.cyclops-sdk-generated-files也就是说TypeScript 的 Node 根与浏览器根和其余五个语言根一样参与同一条生成、归一化、检入与漂移检测流水线。两套生成出来的源文件都以只读形式提交在仓库中例如libs/fleet/sdk-bindings/ts-uniffi/index.tsNode 根的导出入口libs/fleet/sdk-bindings/ts-uniffi/fleet_sdk.tsSDK 主模块含CyclopsClient、Pool、Claim等高层 APIlibs/fleet/sdk-bindings/ts-uniffi/cyclops_sdk_schema.tsSchema 记录类型OsGymSandboxTemplateSpec、ClaimSpec等对应的*-ffi.ts文件承载原生 FFI 调用层。不要手工编辑这些生成文件它们由钉扎版本的生成器产出任何直接修改都会在下一次--check或重新生成时被判定为漂移而失败。需要变更 API 时应当修改 Rust 侧 schema 源libs/fleet/sdk-schema并重新运行生成管线。双根设计Node.js 直录记录 vs 浏览器/WASM Builder API这是 ts-uniffi 根 README 强调的核心设计差异同一个 Rust API在两个 TypeScript 根上暴露为两种不同形态。Node.js 根通过兼容归一化保留直接记录工厂uniffi-bindgen-react-native默认会为记录类型生成 Builder 类。但对 Node.js 使用者而言直接构造记录对象plain record更贴近既有的调用习惯也避免破坏已有的调用代码。因此生成管线对 Node 根应用了兼容归一化compatibility normalization由 normalize-compat-sdk-bindings.py 在新鲜生成的原始输出上确定性移除 Builder 相关的 ABI但绝不删除非 Builder 的记录与函数。以normalize_node()为例它按NODE_DECLARATION正则定位顶层声明interface/type/class/enum/const/function找出所有以Builder结尾的类然后执行三步裁剪remove_node_builder_checksums从nativeModule()初始化校验块中剔除与 Builder 符号相关的校验remove_node_builder_default_exports从export default Object.freeze({...})中剔除 Builder 的 FFI 转换器导出remove_node_builder_declarations删除所有 Builder 类及其衍生类型*Like、*Interface、uniffiType*ObjectFactory、FfiConverterType*的声明。裁剪完成后还会调用check_no_builders()做最终断言若归一化结果中仍出现以Builder结尾的声明立即抛错。也就是说Node 根承诺永不暴露 Builder ABI但保留了TtlSecondsAfterCreated之类的可选字段、直接记录构造函数与全部 API 方法。浏览器/WASM 根保留生成式 Builder 表面与 Node 根相反浏览器/WASM 根libs/fleet/sdk-bindings/ts-uniffi-browser保留其对外公布的生成式 Builder 表面。上游绑定体系为七个沙箱池记录生成的 BuilderVmTemplateBuilder、SandboxServiceBuilder、OSGymSandboxTemplateSpecBuilder、CreateTemplateRequestBuilder、SandboxTemplateRefBuilder、OSGymSandboxWarmPoolSpecBuilder、CreatePoolRequestBuilder在浏览器侧继续可用每个 setter 返回新的不可变 Builder 对象对应 Rust 侧self - ArcSelf接收者形态不修改接收者最后调用build()产出精确的记录类型。为什么必须分成两个根两者共享同一份 Rust 元数据与生成器家族但运行环境差异巨大Node 侧加载主机原生 cdylib 走 NAPI 路径浏览器侧则必须走 WASM。分开成根后Node 根可以放心裁剪 Builder ABI它的受众更喜欢直接构造记录而浏览器根保留 Builder互不干扰同时两者都纳入统一的--check保证各自生成内容不漂移。生成管线版本钉扎、cdylib 解析与事务式替换钉扎的生成器版本为保证确定性生成脚本对生成器版本做了严格钉扎见 generate-sdk-bindings.sh组件钉扎版本UniFFIRust 侧0.31.0uniffi-bindgen-react-nativeTypeScript 生成器0.31.0-3uniffi-bindgen-goGo 生成器同源元数据0.7.1v0.31.0Rust 工具链要求cargo、rustc在 PATH 上可用脚本启动时会校验uniffi-bindgen-go版本必须精确匹配否则直接退出exit 127。生成命令解析脚本按如下顺序工作cargo metadata --locked解析 workspace 元数据cargo build --release -p cyclops-sdk --message-formatjson-render-diagnostics构建原生 SDK通过cyclops-sdk-bindgen resolve-cdylib从构建消息 JSON 中解析出 cdylib 实际路径libs/fleet/bindgen-cli 即该生成器包的载体定位libcyclops_sdk对四个官方语言根执行cargo run -p cyclops-sdk-bindgen -- generate --library ... --language python --language kotlin --language swift --language rubyGo 根由uniffi-bindgen-go $library --library生成TypeScript 双根由npx --package uniffi-bindgen-react-native0.31.0-3 ubrn generate napi bindings ...产出ts-uniffi--lib-colocated模式与ubrn generate wasm bindings ...产出ts-uniffi-browser的ts/与cpp/两个目录生成随后进入归一化阶段Python/Ruby facade 装配、Swift modulemap 合并、Go/Node 兼容归一化、rustfmt格式化浏览器桥接代码等每个根写入清单文件.cyclops-sdk-generated-files记录目录与文件清单供后续漂移对比与增量清理使用。事务式替换与失败回滚绑定源替换不是简单的覆盖写脚本先把现有sdk-bindings目录整体备份mv到临时备份根再把新生成目录整体换入并注入可测试的事务失败点通过CYCLOPS_SDK_BINDINGS_TEST_FAIL_TRANSACTION_POINT或信号注入环境变量模拟中断。任何一步失败都会执行rollback_transaction恢复原状确保生成过程要么整体成功、要么整体回滚不会留下半新半旧的绑定源。该过程还强制拒绝符号链接reject_symlinks生成根与生成文件不得出现 symlink保留文件权限模式set_generated_modes按清单逐项chmod对齐依据旧清单最深优先删除已废弃文件并保留未列入清单的第三方文件。规范--check双根共同的漂移防线Node 根与浏览器根都参与同一套canonical--check。运行# 重新生成全部绑定根 libs/fleet/scripts/generate-sdk-bindings.sh # 仅校验将新生成结果与检入源码逐项对比不修改仓库 libs/fleet/scripts/generate-sdk-bindings.sh --check--check模式下脚本在临时目录完成全部生成与归一化然后对每个根执行compare_root逐条对比清单文件、每个文件的字节内容与权限模式任何差异都会输出generated content differs/generated mode differs并以非零码退出。这解释了为什么 README 强调不要编辑生成文件——一旦手工改动--check必然失败。值得注意的配套约束兼容归一化器从不把检入的快照当作转换输入它只处理新鲜的原始生成输出因此漂移检查始终是新鲜输出 vs 检入结果的确定性比较而不是快照间的对拍。schema 变更后应同步更新 schema 源与上游 CRD 定义而不是为绑定添加兼容性 shim唯一的例外就是上述仓库自有的 Go/Node 兼容归一化。运行时依赖ubjs/core、ubjs/node与同置 cdylibTypeScript 根的运行时依赖非常收敛。以 examples/package.json 为证{ private: true, type: module, scripts: { start: tsx node.ts, build: tsc }, dependencies: { ubjs/core: 0.31.0-3, ubjs/node: 0.31.0-3, tsx: latest, typescript: latest }, devDependencies: { types/node: latest } }ubjs/coreUniFFI 的 TypeScript 运行时核心RustBuffer、FfiConverter、UniffiRustCaller、异步调用桥等版本与生成器家族保持一致0.31.0-3ubjs/nodeNode 环境的 NAPI 适配层同置的libcyclops_sdkcdylib生成代码通过nativeModule()见 fleet_sdk.ts 的import nativeModule from ./fleet_sdk-ffi在运行时加载主机原生库因此该 cdylib 必须与生成的绑定源码同目录放置--lib-colocated生成模式的语义。Linux 上文件名为libcyclops_sdk.somacOS 上为libcyclops_sdk.dylib。入口文件 index.ts 展示了初始化契约它同时导出cyclops_sdk_schema与fleet_sdk两个模块并在首次导入时调用两者的initialize()完成原生运行时初始化同时提供同步风味的uniffiInitAsync()sync flavor 下为空操作。构建并暂存原生库的标准路径来自父级 libs/fleet/sdk-bindings/README.mdexport CYCLOPS_SDK_NATIVE_TARGET_DIR$PWD/libs/fleet/target/sdk-bindings-native libs/fleet/scripts/build-sdk-bindings-native.sh启动器会把新构建的主机 cdylib 暂存到临时语言运行时目录绝不拷贝进生成源码这也是运行时打包是独立校验步骤的原因——生成源码成功并不代表运行时打包可用。Node.js 实弹生命周期示例从 Pool 到服务调用仓库在 ts-uniffi/examples 提供可直接运行的 Node.js 实弹示例node.ts覆盖创建 Pool 与 Template → 创建 Claim → 等待沙箱绑定 → 调用沙箱服务 → 清理资源的完整闭环。环境变量实弹示例会创建可计费的真实资源运行前必须设置以下变量见 examples/README.md 的配置表变量是否必填说明CYCLOPS_BASE_URL是Cyclops 控制面control-plane基础 URLCYCLOPS_TOKEN_URL是OAuth 令牌端点CYCLOPS_CLIENT_ID是OAuth 客户端 IDCYCLOPS_CLIENT_SECRET是OAuth 客户端密钥CYCLOPS_NAMESPACE是创建资源的命名空间CYCLOPS_IMAGE是Pool 模板使用的容器磁盘镜像CYCLOPS_IMAGE_PULL_SECRET否Kubernetes 镜像拉取密钥名称export CYCLOPS_BASE_URLhttps://cyclops.example export CYCLOPS_TOKEN_URLhttps://auth.example/oauth/token export CYCLOPS_CLIENT_IDexample-client export CYCLOPS_CLIENT_SECRETreplace-me export CYCLOPS_NAMESPACEdefault export CYCLOPS_IMAGEregistry.example/cyclops-mcp:latest cd libs/fleet/sdk-bindings/ts-uniffi/examples npm install npm start要求 Node.js 18 及以上示例使用原生fetch提供 UniFFI 的HttpClient回调且镜像需暴露 3000 端口的 MCP 服务并响应GET /health。注意原生库必须在 Node 加载../index.ts之前就位。用浏览器 fetch 实现 HttpClient 回调HttpClient是一个异步、由宿主语言实现的安全边界回调SDK 会把 OAuth 客户端凭据令牌请求、Bearer 令牌鉴权请求以及解析后的控制面/服务 URL 交给它。因此实现方应把它当作可信传输代码不得记录或导出这些值在这里执行目标策略校验并保持请求/响应体语义SDK 会在应用自身鉴权前剥离调用方传入的服务Authorization头与逐跳头。示例实现class FetchHttpClient implements HttpClient { async execute(request: HttpRequest): PromiseHttpResponse { const response await fetch(request.url, { method: request.method, headers: request.headers.map(({ name, value }) [name, value]), body: request.body, }); return { status: response.status, headers: [...response.headers].map(([name, value]) ({ name, value })), body: await response.arrayBuffer(), }; } }连接与生命周期客户端通过CyclopsClient.connect建立配置项包括baseUrl、tokenUrl、credentialsnew CyclopsCredentials(clientId, clientSecret)以及轮询参数poolPollIntervalMs/poolPollLimit/claimPollIntervalMs/claimPollLimit注意 Node 侧轮询间隔为bigint毫秒值const client CyclopsClient.connect({ baseUrl: requiredEnv(CYCLOPS_BASE_URL), tokenUrl: requiredEnv(CYCLOPS_TOKEN_URL), credentials: new CyclopsCredentials( requiredEnv(CYCLOPS_CLIENT_ID), requiredEnv(CYCLOPS_CLIENT_SECRET), ), poolPollIntervalMs: 5000n, poolPollLimit: 100, claimPollIntervalMs: 5000n, claimPollLimit: 120, }, new FetchHttpClient());随后按五步走完整个生命周期并在finally中逆序清理全部资源创建 Pool 与 Templateclient.createPool({ namespace, spec: poolSpec })与client.createTemplate(createTemplate)模板规格通过直接记录构造Node 根直录形态例如{ vmTemplate: { containerDiskImage, imagePullSecret, cpuCores: 4, memory: 4Gi, services: [{ name: mcp, targetPort: 3000 }] } }创建 Claimclient.createClaim({ pool })等待绑定client.waitClaim(claim)返回绑定的沙箱调用服务client.serviceRequest(sandbox, mcp, /health, request)对沙箱上的 MCP 服务发起请求清理deleteClaim→deleteTemplate→deletePool即使中途失败也保证执行。console.log([4/5] Calling the sandbox service...); const response await client.serviceRequest(sandbox, serviceName, servicePath, { method: GET, url: https://ignored.invalid${servicePath}, headers: [], }); console.log({ status: response.status, body: new TextDecoder().decode(response.body) });对于请求体未给出的可选字段如绑定期限SDK 会套用默认值——父级文档明确说明未指定的 deadline 默认 900 秒。浏览器/WASM 根ubrn.config.yaml与 WASM 打包管线浏览器源生成在../ts-uniffi-browser即 libs/fleet/sdk-bindings/ts-uniffi-browser其 WASM crate 的打包由项目级 ubrn.config.yaml 驱动rust: directory: ../.. manifestPath: sdk/Cargo.toml bindings: cpp: cpp ts: ts web: manifestPath: rust_modules/wasm/Cargo.toml tsBindings: ts entrypoint: ts/index.web.ts关键语义rust.directory: ../..指向libs/fleetmanifestPath: sdk/Cargo.toml指向 SDK cratelibs/fleet/sdk/Cargo.tomlbindings声明生成物落位C 桥接进cpp/TypeScript 进ts/web声明 WASM 侧的 manifest、TypeScript 绑定目录与入口ts/index.web.ts。浏览器生成源会导入ts/wasm-bindgen/index.js该模块由 UBRN 的浏览器/WASM 管线在打包时产出不检入仓库ts/wasm-bindgen/、Rust 构建目录与打包出的examples/dist/均为生成产物。从仓库根构建libs/fleet/scripts/build-browser-sdk-binding.sh该脚本还会应用 UBRN WASM 模板所需的钉扎兼容补丁并把browser.ts打包为examples/dist/browser.js。浏览器侧的安全模型浏览器实弹示例ts-uniffi-browser/examples/README.md与 Node 版执行相同的生命周期但安全模型完全不同浏览器永不接触 OAuth 客户端凭据由受信任的运行方通过客户端凭据流程铸造短期访问令牌再以window.__CYCLOPS_BROWSER_CONFIG__在页面启动前注入跨源托管时控制面需为该源开启 CORS不要通过把客户端凭据暴露给浏览器代码来绕过 CORS 失败CI 中的实弹测试通过 Playwright 在临时https://run.cua.ai路径上以同源方式拦截生成包本地 Vite 服务http://127.0.0.1:4174仅用于产物与 UI 边界测试。故障排查要点针对 TypeScript 绑定最常见的三类问题仓库文档给出了明确的处置路径libcyclops_sdk无法加载先运行build-sdk-bindings-native.sh确认导出文档中要求的原生 target 变量并使用与宿主机匹配的文件名Linuxlibcyclops_sdk.so/ macOSlibcyclops_sdk.dylib同时确保 cdylib 与生成的绑定源码同目录放置绑定漂移drift运行匹配的生成命令generate-sdk-bindings.sh或--check只提交原始生成源码生成后不要再手工修补生成环境缺失脚本会直接报cargo must be available on PATH等错误请先按 libs/fleet/rust-toolchain.toml 安装对应工具链并把uniffi-bindgen-go、npx等依赖置于 PATH。小结Cyclops 的 TypeScript 绑定通过双根 统一生成管线同时服务 Node.js 与浏览器两类宿主Node 根经 兼容归一化器 去除 Builder ABI、保留直接记录工厂浏览器/WASM 根以 ubrn.config.yaml 驱动 WASM 打包并保留 Builder 表面。两个根共享同一份钉扎生成器uniffi-bindgen-react-native0.31.0-3、同一套--check漂移防线与同一个运行时前提——ubjs/core、ubjs/node加同置的libcyclops_sdkcdylib。理解这条管线后你可以放心地把 ts-uniffi/examples/node.ts 的池/认领/服务调用闭环移植进自己的 Node.js 或浏览器应用并在 schema 演进时用--check守住生成源码的确定性。【免费下载链接】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),仅供参考