6.0 KiB
6.0 KiB
客户端与服务端协议(v1)
契约本体在
contracts/schemas/(JSON Schema),TS 镜像在contracts/,漂移由scripts/check-contracts.test.mjs卡。本文是给人看的同一份东西。
1 范围与所有权
服务端与客户端之间只认这一套接口。引擎协议(Pi 的 JSONL、Codex 的 app-server)只活在 server/agent 内部,客户端与 Core 都不感知——协议的所有权比协议的兼容性重要(架构设计 5.2)。
界面只依赖本文的事件模型:换引擎、换模型,事件不变。
2 版本与兼容规则
| 规则 | 说明 |
|---|---|
| 路径版本 | 前缀 {PROTOCOL_PREFIX} = /v1;改语义就换前缀,不原地改 |
| 事件版本 | 每个事件带 v,当前 1;字段只加不改不删 |
| 删除字段 | 先废弃(保留但不再填充)→ 下一个大版本再删 |
| 未知事件 | 客户端必须忽略不认识的 type,不能崩 |
| 未知字段 | 客户端必须忽略,不能因多字段报错 |
3 认证与身份
| 客户端 | 方式 | 说明 |
|---|---|---|
| 浏览器 | Cookie: ap_session=<httpOnly> |
沿用现有 JMS 登录;服务端不把业务系统 token 下发给客户端 |
| 桌面端 | Authorization: Bearer <desktop-token> |
授权码 + 设备绑定换来的短期令牌(沿用现有授权方式) |
两者都由 server/entry 解析成同一个 Subject(用户 + 租户),再交给应用服务与 Core。首次调用的返回值就是 MeResponse。
4 端点
| 方法 | 路径 | 请求 | 响应 | 说明 |
|---|---|---|---|---|
| GET | /v1/health |
— | HealthResponse |
健康检查 |
| GET | /v1/me |
— | MeResponse |
当前身份与客户端形态 |
| GET | /v1/projects |
— | ProjectListResponse |
可见项目 |
| GET | /v1/sessions |
?projectId= |
SessionListResponse |
会话列表 |
| POST | /v1/sessions |
CreateSessionRequest |
CreateSessionResponse |
建会话;写操作必须建在项目会话(ADR-0003) |
| GET | /v1/sessions/{id} |
?limit= |
SessionDetailResponse |
会话详情与最近消息 |
| GET | /v1/sessions/{id}/events |
Last-Event-ID 或 ?since= |
SSE 事件流 | 回放 + 实时,见第 5 节 |
| POST | /v1/sessions/{id}/messages |
SendMessageRequest |
SendMessageResponse |
发消息;返回受理,结果走事件流 |
| POST | /v1/sessions/{id}/cancel |
— | CancelResponse |
取消当前运行 |
| GET | /v1/capabilities |
?sessionId= |
CapabilitiesResponse |
按身份过滤的能力清单 |
| GET | /v1/executions/{executionId} |
— | ExecutionReceipt |
执行状态与回执 |
| POST | /v1/approvals/{approvalId} |
ApprovalDecisionRequest |
ApprovalDecisionResponse |
确认卡响应 |
5 事件流(SSE)
| 规则 | 说明 |
|---|---|
| 传输 | text/event-stream;event: 填事件 type,data: 填整条事件 JSON |
| 序号 | seq 在会话内单调递增,从 1 开始;客户端按它去重与排序 |
| 断点续传 | 客户端带 Last-Event-ID(或 ?since=)重连,服务端从该序号之后回放 |
| 回放 | 回放与实时用同一格式,客户端不需要两套解析 |
| 心跳 | 无事件时按固定间隔发 heartbeat,用于探活与代理保活 |
| 顺序 | 同一会话的事件严格有序;不同会话之间不保证 |
| 敏感 | 事件里只有参数摘要(argsDigest、paramsDigest),没有凭据与参数明文 |
6 事件清单
| 事件 | 含义 | data 关键字段 |
|---|---|---|
message.user |
用户消息(多端与重连回放用) | messageId、text |
message.delta |
助手增量文本 | messageId、text |
message.completed |
一条助手消息结束 | messageId、finishReason |
tool.call |
引擎请求调用能力 | callId、capabilityId、argsDigest |
tool.result |
能力调用的回执 | callId、executionId、status |
execution.updated |
执行状态变化 | executionId、status、errorKind |
approval.requested |
需要用户确认 | approvalId、capabilityId、paramsDigest、impactSummary、revision、expiresAt |
approval.resolved |
确认结果 | approvalId、decision |
run.state |
当前运行状态 | state(idle/running/cancelling/cancelled) |
session.updated |
会话标题或状态变化 | title、status |
error |
错误 | code、message、retryable |
heartbeat |
保活 | 空对象 |
status 取值统一为 accepted / pending-confirmation / succeeded / failed。
7 审批时序
- 引擎请求写能力 → Core 判定需确认 → 落一条待确认动作(ADR-0003、架构设计 4.2)
- 服务端推
approval.requested,客户端渲染确认卡(影响范围、参数摘要、目标版本、有效期) - 用户点批准 →
POST /v1/approvals/{id},带revision与idempotencyKey - 服务端校验版本与有效期 → 推
approval.resolved→ 执行 → 推execution.updated - 版本过期或已执行 → 返回
expired,客户端提示重新确认;确认人是会话发起人本人
8 错误模型
| 位置 | 形状 | 说明 |
|---|---|---|
| HTTP | ErrorEnvelope |
请求本身失败(鉴权、参数、找不到) |
| SSE | error 事件 |
运行中失败;retryable 表示可重试,客户端据此给"重试"按钮 |
9 幂等与取消
| 场景 | 规则 |
|---|---|
| 发消息 | 带 idempotencyKey;同一 key 重试不产生第二个 run |
| 审批 | 带 idempotencyKey;同一 key 只生效一次 |
| 取消 | 幂等:重复取消不报错;已结束的运行返回 idle |
| 结果未知 | 执行超时返回 failed 且 errorKind = unknown,由客户端提示查执行记录,不自动重试写操作 |
10 变更规则
改协议必须同时改三处,否则 pnpm test 会失败:
contracts/schemas/*.json(规范本体)contracts/events.ts/contracts/protocol.ts(TS 镜像;第 4 节的端点表与ENDPOINT_SPECS由测试逐条比对)- 本文档(第 4、6 节的表)