Cyclops Fleet SDK Node.js 实战:ts-uniffi 绑定下的 Pool、Claim 与沙箱健康检查完整生命周期
发布时间:2026/9/13 15:27:50 锦皓数字建站

Cyclops Fleet SDK Node.js 实战ts-uniffi 绑定下的 Pool、Claim 与沙箱健康检查完整生命周期【免费下载链接】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本文聚焦仓库中 Cyclops Fleet SDK 的 Node.js 实时生命周期示例live lifecycle example它基于ts-uniffi生成的 TypeScript 绑定用 Node 18 内置fetch实现 SDK 的HttpClient回调完整演示“创建 Pool 与 Template → 创建 Claim → 等待沙箱绑定 → 调用沙箱mcp服务的GET /health→ 在finally中清理全部资源”的五步闭环。读完后你将掌握如何在 Node.js 中正确接入libcyclops_sdk原生库、配置 OAuth 凭证与资源规格并理解该示例背后的 UniFFI 绑定机制与客户端 API 全貌。ts-uniffi 绑定这个示例站在什么基础上该示例位于 ts-uniffi 示例目录其依赖的 TypeScript 绑定位于 ts-uniffi 根目录。要准确理解示例的运行前提先要理解这套绑定的性质生成方式按 ts-uniffi/README.md该绑定由uniffi-bindgen-react-native面向 Node.js N-API 生成。Rust 是cyclops-sdk的规范实现source of truthTypeScript 一侧只是生成产物。兼容性快照根据 sdk-bindings 总 READMEts-uniffiNode.js与go-uniffi不属于主生成管线generate-sdk-bindings.sh的产物而是由uniffi-bindgen-react-native产生的“已入库兼容性快照checked-in compatibility snapshots”由generate-compat-sdk-bindings.sh负责其确定性的仓库内规范化。这意味着该快照目前只暴露直接 record 工厂direct record factories不包含更新管线中的UniffiBuilder对象——这也是示例代码直接用字面量构造CreatePoolRequest、CreateClaimRequest等 record而不是链式 builder 的原因。运行时依赖运行时要求三样东西同时存在ubjs/core、ubjs/node两个 npm 包以及一个与生成代码同置colocated、可被 Node 加载器找到的宿主原生库libcyclops_sdkLinux 上为libcyclops_sdk.somacOS 上为libcyclops_sdk.dylib。入口装配index.ts 在模块加载时同步调用cyclops_sdk_schema与fleet_sdk两个命名空间的initialize()见 index.ts 第 8-13 行并把两个模块 re-export 出去。这正是示例文档强调“原生库必须在 Node 加载../index.ts之前就位”的原因——初始化发生在 import 阶段原生库缺失会直接导致加载失败。前置条件示例文档明确了三项前置条件Node.js 18 或更新版本——因为HttpClient实现直接使用全局fetch而fetch是 Node 18 引入的稳定全局 API。宿主原生库libcyclops_sdkcdylib 必须与生成的 UniFFI Node 运行时同置保证 Node 加载器能找到它。父级绑定 READMEsdk-bindings/README.md描述了从 Rust 工作区构建该库、以及用CYCLOPS_SDK_NATIVE_TARGET_DIR等变量在多条命令间共享同一份原生构建的流程。一个在端口3000暴露 MCP 服务、且响应GET /health的容器磁盘镜像——这个镜像通过CYCLOPS_IMAGE传入 Pool Template。环境变量配置示例通过环境变量注入控制面地址、OAuth 凭证与资源参数。完整参数表如下继承自 示例 README变量必填说明CYCLOPS_BASE_URL是Cyclops 控制面 base URLCYCLOPS_TOKEN_URL是OAuth token 端点CYCLOPS_CLIENT_ID是OAuth client IDCYCLOPS_CLIENT_SECRET是OAuth client secretCYCLOPS_NAMESPACE是资源创建所在的命名空间CYCLOPS_IMAGE是Pool Template 使用的容器磁盘镜像CYCLOPS_IMAGE_PULL_SECRET否Kubernetes image-pull secret 名称按文档给出的配置方式示例值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 cyclops-cs/sdk-bindings/ts-uniffi/examples这是上游仓库目录布局的写法在当前仓库检出中对应目录是libs/fleet/sdk-bindings/ts-uniffi/examples。在对应目录下执行npm install与npm start即可。注意该示例与sdk-bindings/examples/下各语言的live_app_controlled同类会在真实部署上创建计费资源运行前请确认凭证与配额。生命周期五步流程node.ts 逐段精读示例主程序是 examples/node.ts约 120 行结构清晰可分四块理解。1. 用 Node 18 fetch 实现 HttpClientSDK 的 HTTP 传输层被设计为“外部语言实现的回调边界”foreign HTTP client boundary。示例用一个约 15 行的类完成适配node.ts 第 20-33 行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(), }; } }这里有两个值得注意的类型契约细节请求头/响应头是HttpHeader数组{ name, value }对而非对象响应体是二进制 buffer 而非字符串——这与 Rust 侧“保留重复请求头与状态体Duplicate headers and status bodies are preserved”的语义一致参见 sdk-bindings/README.md 的 Native HTTP transport 一节。body用response.arrayBuffer()一次性读入保证 Rust 侧拿到完整响应体。2. 客户端连接CyclopsClient.connect 与轮询参数主函数通过CyclopsClient.connect建立客户端node.ts 第 61-72 行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());参数解读credentials使用 OAuth client-credentials 形式SDK 在内部向tokenUrl换取 bearer token再带着它调用baseUrl上的控制面 API。poolPollIntervalMs: 5000n/poolPollLimit: 100Pool 就绪等待采用“每 5 秒轮询一次、最多 100 次”的策略即最长约 8.3 分钟。注意间隔是bigint5000n这是 UniFFI 生成代码对u64毫秒字段的 TypeScript 映射。claimPollIntervalMs: 5000n/claimPollLimit: 120Claim 绑定沙箱的等待为“每 5 秒一次、最多 120 次”最长约 10 分钟——沙箱冷启动通常比 Pool 就绪慢因此上限更高。requiredEnv辅助函数node.ts 第 35-39 行在缺失必填环境变量时直接抛出带变量名的错误避免带着空配置继续执行。3. 资源规格Template 与 Pool 的构造示例定义了两个 spec 构造函数node.ts 第 41-59 行function templateSpec(image: string, imagePullSecret?: string): OsGymSandboxTemplateSpec { return { vmTemplate: { // The live example requests the same resources as the working SDK examples. containerDiskImage: image, imagePullSecret, cpuCores: 4, memory: 4Gi, services: [{ name: serviceName, targetPort: 3000 }], }, }; } function poolSpec(namespace: string): OsGymSandboxWarmPoolSpec { return { replicas: 1, sandboxTemplateRef: { name: ${namespace}-template }, }; }vmTemplate.containerDiskImage即CYCLOPS_IMAGEimagePullSecret为可选字段省略时保持undefined。资源规格为4 核 / 4Gi 内存注释说明这是与仓库内其他“可工作的 SDK 示例”保持一致的规格。services: [{ name: mcp, targetPort: 3000 }]声明沙箱内名为mcp、监听 3000 端口的服务——这正是第 4 步serviceRequest的寻址目标。poolSpec创建的是1 副本的 warm pool预热池通过sandboxTemplateRef引用名为{namespace}-template的 Template。Template 与 Pool 之间是“引用”关系而非嵌套因此两者必须分别创建。4. 五步主流程与 finally 清理主流程node.ts 第 61-118 行按五个阶段推进try { // [1/5] 创建 Pool 与 Template pool await client.createPool({ namespace, spec: poolSpec(namespace) }); template await client.createTemplate({ namespace, name: ${namespace}-template, spec: templateSpec(image, process.env.CYCLOPS_IMAGE_PULL_SECRET), }); // [2/5] 创建 Claim绑定到刚才的 Pool claim await client.createClaim({ pool }); // [3/5] 等待 Claim 绑定到一个沙箱 const sandbox await client.waitClaim(claim); // [4/5] 调用沙箱服务mcp 服务的 GET /health 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) }); } finally { // [cleanup] 逆序删除claim → template → pool if (claim) await client.deleteClaim(claim); if (template) await client.deleteTemplate(template); if (pool) await client.deletePool(pool); }各步骤的语义与细节创建 Pool 与 Template两个请求都是“先构造 record、再交给客户端”。注意CreatePoolRequest只有{ namespace, spec }两个字段名称由控制面生成。创建 ClaimCreateClaimRequest只带{ pool }表示“从该 Pool 中申请一个沙箱”。waitClaim 轮询绑定客户端按前面配置的轮询参数反复查询 Claim 状态直到其绑定到一个具体Sandbox对象并返回。serviceRequest 调用沙箱服务注意传入的HttpRequest.url是https://ignored.invalid/health这样的占位 URL——真正的目标地址由 SDK 根据Sandbox、服务名mcp与路径/health在内部解析控制面会为沙箱服务暴露可路由的地址调用方只需要提供 method 与相对语义。这一点从占位 URL 的域名invalidRFC 6761 保留的无效域可以确证它永远不会被真实请求。finally 保证清理无论主流程在哪一步失败finally都会逆序删除已创建的 claim、template、pool。这与 Go 版示例文档 的表述一致“示例总是尝试删除它创建的资源包括在 wait 或 service 请求失败之后”若进程被强制终止kill -9等导致 finally 未执行则需手动清理残留资源。源码级证据CyclopsClient 的完整 API 面示例用到的createPool/createTemplate/createClaim/waitClaim/serviceRequest/delete*只是客户端 API 的一小部分。生成的 fleet_sdk.ts 中CyclopsClientLike接口fleet_sdk.ts 第 2524-2563 行展示了完整能力面类别方法资源创建createClaim、createNamespace、createPool、createSignedServiceUrl、createTemplate、createUserApiKey资源查询getClaim、getNamespace、getPool、getTemplate、listClaims、listNamespaces、listPools、listSignedServiceUrls、listTemplates、listUserApiKeys资源变更updatePool、updateTemplate、reconcilePool、reconcileTemplate租约管理renewClaim推进 claim 的shutdownTime绝对到期时间——这是 Pool operator 的 claim reaper 唯一认可的活性输入服务访问serviceRequest经控制面路由到沙箱服务、listSignedServiceUrls、revokeSignedServiceUrl、createSignedServiceUrl资源删除deleteClaim、deleteNamespace、deletePool、deleteTemplate、deleteUserApiKey等待waitClaim阻塞直到 Claim 绑定沙箱几个从源码可以确认的实现事实连接重载族除示例使用的static connect(configuration, httpClient)fleet_sdk.ts 第 2583 行外绑定还生成了connectWithNativeHttpClientRust 侧 reqwest 原生传输、connectWithAccessToken*静态 token、connectWithAccessTokenProvider*token 提供器以及connectBrowserWithAccessTokenBrowser/WASM 路径等重载。原生传输按 sdk-bindings/README.md 的说明使用 reqwest Rustls/平台信任根、30 秒整请求超时、直连不走代理环境变量、不自动重定向从而避免 bearer 凭证被重放到重定向目标。本示例选择外部HttpClient回调路径是文档化的“高级路径”适用于代理、自定义 CA 信任、TLS 策略与测试场景。异步模型所有方法返回Promise并支持asyncOpts_?: { signal: AbortSignal }取消参数waitClaim这类长时间等待同样可被 AbortSignal 中断。错误类型绑定生成了SdkError、HttpError、SdkBuildError、AccessTokenProviderError等错误 record见 fleet_sdk.ts 第 1354-2131 行 一带的错误定义connect与异步方法的/*throws*/注释即对应这些可抛出类型。schema 记录类型Pool、Claim、Sandbox、Template、Namespace以及CreatePoolRequest、CreateClaimRequest、CreateTemplateRequest等 record 类型定义在fleet_sdk.ts而OsGymSandboxTemplateSpec、OsGymSandboxWarmPoolSpec、SandboxService、VmTemplate等沙箱规格类型来自 cyclops_sdk_schema.ts。示例代码中对 spec 字面量的构造方式直接字段赋值正对应总 README 所述“快照仅暴露直接 record 构造器”的现状。HttpClient 是安全边界父级 sdk-bindings/README.md 明确指出外部HttpClient回调会看到 token 请求中的 OAuth client 凭证、已认证请求上的 bearer token以及解析后的控制面/服务 URL应视为受信传输代码——不要在其中记录或导出这些值SDK 会在应用自身认证前剥离调用方提供的服务Authorization头与 hop-by-hop 头。示例中的FetchHttpClient严格做到了“只搬运、不解析、不记录”。构建与运行package.json 和 tsconfig 的约定示例的 package.json 与 tsconfig.json 体现了 TypeScript 绑定的运行约定{ 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/core/ubjs/node是 UniFFI 的 npm 运行时UniFFI Bindings for JavaScript版本固定为0.31.0-3对应 UniFFI0.31.0工具链与 sdk-bindings 总 README 中“pinned workspace wrapper around UniFFI 0.31.0”的表述一致。它们负责 N-API 加载与 Rust FFI 调用的桥接也是定位libcyclops_sdk的加载器。npm start使用tsx直接执行node.ts无需预编译npm run build则只做类型检查noEmit。tsconfig.json 启用allowImportingTsExtensions、module: ESNext、moduleResolution: Bundler、strict、target: ES2022因此node.ts才能以import ... from ../index.ts的方式带扩展名导入生成代码。运行前提再次强调先保证libcyclops_sdk处于 Node/UBRN 运行时可加载的位置即与绑定代码同置再启动脚本。父级 README 的排障一节给出了通用手段运行工作区的原生构建脚本构建宿主 cdylib导出CYCLOPS_SDK_NATIVE_TARGET_DIR等文档化的原生目标变量并使用宿主匹配的.so/.dylib文件名。跨语言对照同一生命周期在 Go 快照中的形态仓库中 go-uniffi 示例 与本 Node 示例在结构上完全同构同样要求一个“在 3000 端口暴露 MCP 服务并响应GET /health”的镜像同样使用完全一致的CYCLOPS_*环境变量表同样执行“Pool → Claim → wait →GET /health→ 清理”流程且同样强调即使中途失败也始终尝试删除所创建的资源。差异仅在语言侧的传输装配Go 用 cgo 通过CGO_LDFLAGS/LD_LIBRARY_PATH定位原生库Node 用 UBRN 运行时加载同置的 cdylib。这种跨语言一致性来自“Rust 规范实现 各语言生成绑定”的架构同一份 Rust API 语义在 Python/Kotlin/Swift/Ruby/Go/Node.js 各快照中呈现为同构的生命周期代码。小结该示例是 Cyclops Fleet SDK 在 Node.js 侧的可运行实时参考实现五步生命周期建 Pool/Template → 建 Claim → waitClaim → serviceRequestmcp:/health→ finally 逆序清理与配置表6 个必填 1 个可选环境变量构成最小完整闭环。接入要点有三个Node 18 的fetch实现HttpClient.execute保留头数组与二进制 body 语义CyclopsClient.connect的轮询参数按“Pool 5s×100、Claim 5s×120”配置原生库libcyclops_sdk必须在模块加载期之前就位。从源码层面看示例用到的方法只是 CyclopsClientLike 全 API 面的一部分renewClaim、signed service URL 系列、原生 HTTP 传输重载等能力均可在同一客户端上扩展同时需注意HttpClient回调是安全边界不得在其中泄露凭证与解析后的 URL。适用限制ts-uniffi当前为兼容性快照仅暴露直接 record 构造器该 API 处于快速演进中短期兼容破坏是有意为之使用时应以仓库内当前生成源码的签名为准。【免费下载链接】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),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。