【免费下载链接】agent-orchestrator
Run and supervise teams of coding agents from planning to merge. Any harness (Claude code, codex, +25 more). Desktop, web, mobile, and cloud agents.
导读
@aoagents/cloud-client是 Agent Orchestrator 仓库中面向 AO Cloud 公开 API 的运行时中立 TypeScript 契约包,同时内置一个零依赖的 fetch 客户端实现。它定义了客户端与云端之间的调用边界:既为桌面、Web、移动端提供面向组织、项目、会话的CloudClient,也为沙箱 Worker 提供覆盖 bootstrap、心跳、fenced turn、凭据、checkout-grant、子编排、工作区与终端传输的WorkerClient。读完本文,你将掌握该包的配置方式、认证模型、两类客户端的完整 API 面、SSE 事件流重连机制、幂等与分页约定,以及如何基于仓库内的 OpenAPI 契约自行生成类型。
一、包定位:只定义客户端边界,不实现服务端路由
从 packages/cloud-client/README.md 的定位描述可以明确:本包是"runtime-neutral TypeScript contracts and a small fetch-based client for AO Cloud's public API"。它只负责定义"客户端边界"(client boundary),当前仓库并不实现 Cloud 的服务端路由,服务端由独立的 AO Cloud 实现提供。
这一点在 contracts/cloud/openapi.yaml 的info.description中同样被强调:这是 "Client-facing contract for a separate AO Cloud implementation. This repository does not serve these routes",其中 Worker 部分文档化了沙箱 Worker 使用的精确出站 HTTP 协议——一次性 bootstrap、轮换凭据、fenced turn 执行、编排,以及持久化的工作区与终端传输。
包结构非常精简(见 packages/cloud-client):
| 文件 | 作用 |
|---|---|
src/client.ts | CloudClient、WorkerClient两个类的完整实现与工厂函数 |
src/index.ts | 包入口,重新导出客户端类、工厂函数与全部类型 |
src/types.ts | 从生成 schema 映射出来的领域类型别名与请求选项接口 |
src/schema.ts | 由 OpenAPI 契约生成的 TypeScript 类型(提交进仓库) |
test/client.test.ts | 基于 vitest 的客户端行为测试 |
package.json | 包元信息与generate、build、typecheck、test脚本 |
从 package.json 可以看出,它同时导出了主入口.和./schema子路径,type: "module"、sideEffects: false,打包产物只包含dist与 README,publishConfig.access为public。
二、快速上手:两个客户端的最小用法
2.1 CloudClient:面向用户/组织资源的客户端
import { createCloudClient } from "@aoagents/cloud-client"; const cloud = createCloudClient({ baseUrl: "https://cloud.example.com", getAccessToken: () => authSession.getAccessToken(), fetch, }); const sessions = await cloud.listSessions(orgId, { limit: 50 });配置项说明(对应 client.ts 中的CloudClientConfig):
| 配置项 | 类型 | 说明 |
|---|---|---|
baseUrl | string | Cloud 服务根地址。构造时会被new URL()校验,若包含查询串或片段(query/fragment)会抛出TypeError,尾部的/会被去除 |
getAccessToken | () => MaybePromise<string \| null \| undefined> | 每个请求发起前立即调用的取 token 回调,由调用方负责认证与刷新 |
fetch | typeof globalThis.fetch(可选) | 注入自定义 fetch 实现,便于测试(如 Node/测试环境 mock)或自定义传输 |
2.2 WorkerClient:面向沙箱 Worker 的客户端
import { createWorkerClient } from "@aoagents/cloud-client"; let workerToken: string | null = null; const worker = createWorkerClient({ baseUrl: "https://cloud.example.com", getWorkerToken: () => workerToken, fetch, }); const bootstrap = await worker.bootstrap({ bootstrapToken: oneTimeTicket, version: workerVersion, capabilities, }); workerToken = bootstrap.workerToken; const heartbeat = await worker.heartbeat({ version: workerVersion, capabilities }); workerToken = heartbeat.workerToken;WorkerClientConfig(client.ts)与CloudClientConfig结构相同,只是 token 回调是getWorkerToken。
三、认证模型:调用方持有 token,客户端按请求即时取用
该包的核心设计原则是"The caller owns authentication and token refresh":
createCloudClient会在每次用户请求之前立即调用getAccessToken()获取最新 token;createWorkerClient对每个已认证的 Worker 请求做同样的事。
这一点在源码里有明确体现:CloudClient.authorizedFetch(client.ts)每次请求前调用getAccessToken(),token 缺失时抛出 401 的CloudApiError,随后以Authorization: Bearer <token>注入请求头;WorkerClient.authorizedFetch(client.ts)则以Authorization: Worker <token>前缀注入。
测试 client.test.ts 专门验证了这一行为:连续两次listProviderConnections调用会触发getAccessToken两次,且两次请求头分别为Bearer first-token与Bearer second-token,证明客户端不会缓存 token,永远取最新值——这为 token 刷新场景提供了天然支持。
Worker 侧的认证细节由契约定义。在 openapi.yaml 的securitySchemes中:
bearerAuth:HTTP Bearer 认证(JWT),用于用户侧 API;workerAuth:header 中的 apiKey,格式为Worker <token>,要求 token 携带当前 worker ID 与 epoch,沙箱被替换时未过期 token 会被STALE_WORKER_TOKEN围栏(fence)掉;- 路由级作用域包括
worker:connect(心跳)、worker:event(事件)、worker:turn:claim/worker:turn:poll/worker:turn:complete(turn 执行)、worker:credential:read(agent 密钥)、worker:git(checkout grant)、worker:orchestrate(子编排)、worker:transport(工作区与终端传输)。
四、Worker 生命周期:bootstrap → 心跳 → 轮换凭据
4.1 一次性 bootstrap 交换
WorkerClient.bootstrap(client.ts)是唯一的免认证请求,走unauthenticatedRequest,即不会附加Authorization头(测试 client.test.ts 明确断言 bootstrap 请求没有 Authorization 头)。
契约 openapi.yaml 说明:bootstrap token 是一次性(one-time)票据,原子消费、不可重放;成功响应会分配 worker epoch 并返回首个短生命周期 worker token,该 token 携带票据的 scopes,禁止被记录日志或持久化。测试中的响应体{ workerToken, workerId, epoch, expiresIn, sessionId, launch }(client.test.ts)展示了 bootstrap 成功后的完整返回结构,launch内含 sessionId、projectId、harness、displayName、branch、repositoryUrl、defaultBranch 等启动上下文。
4.2 心跳与 token 轮换
WorkerClient.heartbeat(client.ts)在每次心跳时都会返回一个新签发的 token(同一 worker 身份、epoch 与 scopes),调用方需要把返回值中的workerToken写回内存供下次使用——这正是 README 示例中workerToken = heartbeat.workerToken;的意义。
契约 openapi.yaml 补充了底层语义:心跳记录 worker 存活,将"引导中"的沙箱提升为"运行中",且旧 token 不会被吊销,在过期前仍有效,除非 worker epoch 被替换。WorkerClient.bootstrap与heartbeat都使用cache: "no-store",测试断言了这一点(client.test.ts)。
4.3 凭据与 checkout-grant 的安全处理
README 明确规定:bootstrap、worker、agent-credential、checkout-grant 四类秘密只能保存在内存中,绝不写入日志。携带秘密的请求统一使用cache: "no-store",且 credential 与 checkout-grant 的响应还要求服务端返回Cache-Control: no-store。
对应源码:getCredential(client.ts)与createCheckoutGrant(client.ts)均带cache: "no-store";契约侧 openapi.yaml 与 openapi.yaml 在响应头中要求Cache-Control: no-store。测试 client.test.ts 同样覆盖了这两个请求的 no-store 断言。
五、CloudClient 全 API 面:账户、GitHub、项目、会话、工作区与 Provider
CloudClient(client.ts)的方法围绕组织作用域资源组织,路由统一以/api/cloud/v1/orgs/{orgId}/...拼接(orgPath,client.ts),并会对orgId、sessionId、projectId等做encodeURIComponent编码(测试 client.test.ts 验证了含空格、/、?等特殊字符的编码行为)。
按功能域划分:
- 账户:
getCurrentAccount()→/api/cloud/v1/me,返回当前用户与组织成员关系; - Agents:
listAgents(orgId)→/orgs/{orgId}/agents,返回运行时与组织宿主提供的 agent profile(含 capabilities 与 availability 状态,见测试 client.test.ts); - Projects:
listProjects(支持 cursor/limit 分页)、createProject、updateProject(PATCH)、deleteProject(返回 202 持久化删除); - GitHub 集成:用户级
getGitHubUserConnection、startGitHubUserAuthorization、disconnectGitHubUser;组织级listGitHubInstallations、startGitHubInstallation、syncGitHubInstallation、disconnectGitHubInstallation、listGitHubRepositories、createProjectFromGitHub、createGitHubScratchProject; - Sessions:
listSessions(可按projectId过滤)、getSession、createSession、deleteSession、sendMessage、cancelTurn、listSessionPullRequests、getSessionReviewState; - 事件流:
replayEvents(游标回放)与streamEvents(实时 SSE 流); - Terminal:
createTerminalTicket与terminalUrl(构造wss://WebSocket URL,见 client.ts); - Workspace:
listWorkspaceFiles、readWorkspaceFile、writeWorkspaceFile(PUT)、getWorkspaceDiff; - Provider 连接:
listProviderConnections、putAgentProviderConnection(claude-code/codex/cursor三选一)、deleteAgentProviderConnection,用于管理 coding-agent 的凭据连接(测试见 client.test.ts)。
六、SSE 事件流:断线重连与游标续传
streamEvents(client.ts)是客户端最复杂也最值得研究的方法,它以AsyncGenerator形式消费text/event-stream:
- 以
Accept: text/event-stream请求/sessions/{sessionId}/events?after=<sequence>; - 使用
ReadableStreamreader 按块解码,将\r\n归一为\n,按\n\n边界解析 SSE 块(parseSSEBlock只提取data:行); - 每个事件带单调递增的
sequence序号,仅当event.sequence > after时才 yield,天然实现去重与续传; - 流中断或可重试错误时(
isRetryableStreamError判定 408/425/429/5xx,client.ts),用已消费的最大 sequence作为新的after参数重新连接; - 重试退避采用指数退避加抖动(
waitForRetry,client.ts),上限 4 秒; - 支持
AbortSignal优雅取消。
测试 client.test.ts 展示了重连语义:第一次连接收到 sequence 8,断开后用after=8重连,即使服务端重发 sequence 8 也会被跳过,最终只 yield 8、9 两个事件;而 401 这类不可重试错误(client.test.ts)不会触发重连。
七、幂等与分页:防重复与游标约定
7.1 幂等键(Idempotency-Key)
所有变更类操作(创建项目、发送消息、取消 turn、创建子会话等)都接受IdempotentRequestOptions,其中idempotencyKey为必填字符串,通过Idempotency-Key请求头传递。validateIdempotencyKey(client.ts)强制1~200 个字符。
契约侧语义(openapi.yaml):同一 key 用于相同命令时返回原始结果;用于不同命令时返回IDEMPOTENCY_CONFLICT。测试 client.test.ts 验证了冲突场景下CloudApiError携带status: 409、code: "IDEMPOTENCY_CONFLICT"、requestId与details的完整错误信封。
7.2 分页与事件游标
分页统一使用PaginationOptions { cursor?, limit?, signal? }(types.ts),limit默认 50、最大 100(契约Limit参数,openapi.yaml)。事件回放使用EventReplayOptions { after?, limit?, signal? },after为 int64 序号,默认 0,limit默认 100、最大 500(契约EventLimit/After参数,openapi.yaml)。withQuery辅助方法(client.ts)保证未提供的查询参数不会被序列化。
八、错误处理:统一错误信封CloudApiError
客户端把所有失败响应统一归一为CloudApiError(client.ts),其字段:
| 字段 | 来源 |
|---|---|
status | HTTP 状态码 |
code | 错误信封中的机器可读 code(如AUTH_REQUIRED、IDEMPOTENCY_CONFLICT) |
requestId | 服务端错误信封或x-request-id响应头 |
details | 可选的附加结构化信息 |
envelope | 完整错误信封ErrorEnvelope |
非 JSON 的失败响应会被包装为INVALID_RESPONSE错误;本地缺少 token 时抛出AUTH_REQUIRED/WORKER_AUTH_REQUIRED。toErrorEnvelope(client.ts)负责从响应体或响应头中尽力恢复错误结构。
九、契约驱动开发:从 OpenAPI 生成类型
README 明确了契约驱动工作流:
The source contract is
contracts/cloud/openapi.yaml. Runnpm run generatefrom this directory after changing it. The generatedsrc/schema.tsfile is committed so consumers do not need an OpenAPI toolchain.
即:
- 契约唯一事实来源是仓库根目录的 contracts/cloud/openapi.yaml(约 3700 行,OpenAPI 3.1.0,覆盖 Account/Agents/Projects/Sessions/GitHub/Pull Requests/Reviews/Events/Terminal/Workspace/Providers/Worker Lifecycle/Worker Execution/Worker Orchestration/Worker Transport 等标签);
- 修改契约后在
packages/cloud-client目录下执行npm run generate(脚本为openapi-typescript ../../contracts/cloud/openapi.yaml -o src/schema.ts,见 package.json); - 生成的 src/schema.ts直接提交进仓库,消费者无需安装 OpenAPI 工具链即可获得完整类型;
- src/types.ts 再从 schema 的
components["schemas"]映射出全部领域类型(Session、Project、ClientEvent、WorkerTurn、WorkerTransportRequest等),并定义PaginationOptions、EventReplayOptions、IdempotentRequestOptions、RequestOptions四个请求选项接口。
测试与类型检查脚本:npm run typecheck(同时检查主 tsconfig 与测试 tsconfig)、npm test(vitest 运行 test/client.test.ts)。
十、Worker 路由覆盖范围:明确包含与明确排除
README 对 Worker 客户端的边界做了精确声明:
明确覆盖(与 openapi.yaml 的 worker 路由一一对应):
- bootstrap / heartbeat:
POST /worker/bootstrap、POST /worker/heartbeat; - event:
POST /worker/events(worker.ready、agent.activity、chat.assistant_delta等 allowlist 事件); - fenced turn:
POST /worker/turns/claim、GET /worker/turns/{turnId}/cancellation?attempt=、POST /worker/turns/{turnId}/complete、POST /worker/turns/{turnId}/fail; - credential:
GET /worker/credential; - checkout-grant:
POST /worker/checkout-grant; - child orchestration:
GET/POST /worker/children、DELETE /worker/children/{sessionId}、POST /worker/children/{sessionId}/messages(另有契约中的父会话上报POST /worker/parent/messages); - workspace transport:
POST /worker/transport/claim、POST /worker/transport/{requestId}/complete、POST /worker/transport/{requestId}/fail(承载 workspace list/read/write/diff 等操作); - terminal transport:
POST /worker/terminals/agent、POST /worker/terminals/{terminalId}/output、POST /worker/terminals/{terminalId}/exit。
明确排除:worker provisioning(沙箱供应)、数据库细节、秘密存储、本地 daemon 路由——这些不属于公开客户端契约,消费者不应期待在包内找到对应方法。
claimTurn与claimTransport在无任务可领时返回null(服务端返回{ turn: null }/{ request: null }),测试 client.test.ts 专门验证了这一"空领取"语义;完整的 19 个路由调用链端到端断言见 client.test.ts,可作为理解 Worker 客户端全生命周期的最佳参考。
结语
@aoagents/cloud-client是一个小而精的契约型客户端:通过getAccessToken/getWorkerToken回调把认证与刷新完全交给调用方,通过 OpenAPI 契约生成提交进仓库的完整类型,通过CloudApiError统一错误信封、Idempotency-Key保证幂等、SSE 游标实现可续传事件流,并严格划分用户侧CloudClient与沙箱侧WorkerClient的边界。无论你要为桌面/移动端接入 AO Cloud 的组织、项目与会话 API,还是为沙箱 Worker 实现 bootstrap、fenced turn 与传输协议,本包都是开箱即用的 TypeScript 边界层。深入阅读 src/client.ts、test/client.test.ts 与 contracts/cloud/openapi.yaml 三份文件,即可完整掌握其实现细节。
【免费下载链接】agent-orchestrator
Run and supervise teams of coding agents from planning to merge. Any harness (Claude code, codex, +25 more). Desktop, web, mobile, and cloud agents.
相关推荐
SpacetimeDB 客户端连接实战指南:从 `DbConnection` 建立到生命周期管理
SpacetimeDB 客户端连接实战指南:从 DbConnection 建立到生命周期管理 本篇技术指南围绕 SpacetimeDB 1.12.0 客户端的核
数据库关系型数据库后端python-sdk 的 MCP 客户端 `Client`:连接、生命周期与全部协议操作实战指南
python sdk 的 MCP 客户端 Client :连接、生命周期与全部协议操作实战指南 本篇指南以 Model Context Protocol(MCP
人工智能MCP 服务MCP Clients5分钟上手 Mermaid Live Editor:免费实时预览与图表分享的在线编辑器
5分钟上手 Mermaid Live Editor:免费实时预览与图表分享的在线编辑器 Mermaid Live Editor(Mermaid 在线编辑器)是一
前端开发者工具数据可视化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考