Per the chosen design, the agent authorizes callers by comparing the request header X-Agent-Access-Token against its env AGENT_ACCESS_TOKEN (constant-time), no HM round-trip. AM contract §3.1 now states ① as the agreed integration with Python pseudo-code; the /agent-access/verify endpoint is demoted to an optional fallback. Client API §6 spells out the client's job: send X-Agent-Access-Token on every direct-connect request. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
17 KiB
Heicode 桌面客户端 对接接口文档(模板 Agent 模型 · v1)
更新时间:2026-06-04 适用:Heicode Desktop 对接 Heicode Manager(HM)的模板 Agent 模型。 生产地址:
https://code.xinghanlab.com状态图例:🟢 已实现并生产验证 · 🔴 待建(依赖 AM)
本文取代旧的桌面 sub 任务编排接口文档(描述 tasks/workflow/display_status/git_ref/产物下载的那套已下线,相关 md 已删除)。新客户端一律按本文对接。
2026-06-04 客户端流程已在生产逐接口实测(
code.xinghanlab.com,真实账号):capabilities / user-self / agent-templates / 部署 / 列表 / 状态 / 注销 / 删除 均返回真实数据。唯一未通是「直连 agent」——agent 暂停在Pending、子域名外网未就绪(AM 侧在排查),HM 侧数据均真实。各响应示例下方均标注「生产实测」。
0. 模型总览(先读)
新模型很简单:
- 用户在 HM 网页控制台绑定资源(git/vm/数据库/对象存储)、选「资源 + 模板」部署一个常驻 agent。
- HM 把所选资源解出后注入 agent 的
.env,交给 AM 启动;AM 返回唯一子域名 + 访问 token。 - 桌面客户端从 HM 读 agent 列表 → 拿到子域名 + token → 直连 agent 子域名(SSE)对话使用。HM 不在对话回路里。
- 模型调用:客户端自己用模型、以及 agent 用模型,都走 HM
/v1/*(鉴权 + 计费)。
客户端在新模型里的核心动作 = 列出我的 agent → 直连用。部署/管理主要在网页台完成(客户端也可调同一接口)。
总原则
- 客户端调 HM 用 V2 设备签名(与模型调用同一套,见 §1)。
- 列表里的
access_token是连 agent 用的专属访问令牌(HM 给每个 agent 现签,只有部署它的你拿得到)。直连时带上它(X-Agent-Access-Token头);agent 校验「是不是该 agent 的所有者」。它与模型api_key职责分离:access_token管「谁能调这个 agent」,api_key管「用谁的额度跑模型」。 - 模型永远走 HM
/v1/*,不要直连上游。
1. 认证与加密 🟢
桌面客户端用设备绑定密钥对每个请求做 Ed25519 签名;有 body 的写请求额外用 X25519-ECDH + HKDF + ChaCha20-Poly1305 加密 body。与调模型 /v1/* 用的是同一套(cc-haha/src/services/device/signRequest.ts),无需新增协议。本节自包含,照此即可实现。
每个请求都带的头:
| 头 | 含义 |
|---|---|
X-Heicode-Device-Id |
设备 id(配对时获得) |
X-Heicode-Timestamp |
unix 毫秒(服务端校验时钟偏移窗口) |
X-Heicode-Nonce |
每次随机,防重放 |
X-Heicode-Fingerprint |
设备指纹 |
X-Heicode-Eph-Pubkey |
本次临时 X25519 公钥(base64),每请求新生成 |
X-Heicode-Signature |
下面 canonical 的签名(base64) |
Content-Encoding: heicode-aead-v1 |
仅有加密 body 的写请求带;GET 不带 |
签名 canonical(字节序固定,服务端按此重建,顺序不可改):
canonical = method + "\n" # "GET" / "POST" / "DELETE"
+ path_with_query + "\n" # 服务端实际收到的 path(含 query,不含 origin)
+ timestamp_ms + "\n"
+ nonce + "\n"
+ fingerprint + "\n"
+ eph_pubkey_b64 + "\n"
+ hex( sha256(body明文) ) # 无 body(GET) 填 hex(sha256("")) = e3b0c442…b855
signature = base64( ed25519_sign( device_priv, sha256(canonical) ) )
- GET / 无 body:不发
Content-Encoding、body 不加密,canonical 末项填空串哈希。 - POST / DELETE 有 body:发
Content-Encoding: heicode-aead-v1;body = 用eph_priv × 服务器公钥做 X25519-ECDH → HKDF → ChaCha20-Poly1305 加密;canonical 末项是加密前明文 body 的 sha256(hex)。 - 服务端中间件
UserOrV2DeviceAuth解密 + 验签,从设备绑定 token 解析用户身份。
鉴权失败时的响应头(客户端据此给用户准确提示,而非笼统"token 失效"):
X-Heicode-Auth-Error:timestamp_drift(时钟偏移)/nonce_replay/revoked/fingerprint_mismatch/signature_invalid/device_not_found/eph_pubkey_missing…X-Heicode-Server-Time:服务器 unix 毫秒(用来校正本地时钟,解决timestamp_drift)。
设备如何拿到
device_id+ 设备密钥:走与模型调用相同的设备配对/登录流程(cc-haha/src/services/device、/api/heicode-auth/*),本文不重复。
1.1 注销登录(logout)🟢
桌面客户端用设备绑定 token 登录,注销 = 吊销当前设备的 token(不是清会话 cookie):
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| POST | /api/devices/logout |
V2 设备签名 / 会话 | 吊销当前设备的 token |
- 设备客户端:用 V2 设备签名调用即可(空 body 也行)——服务端按签名头
X-Heicode-Device-Id找到本设备的 token 并吊销。只能注销自己,动不了用户的其它设备。 - 会话/JWT 调用方:可在 body 传
{"device_id":"..."}注销该用户名下某台设备。 - 幂等:设备已不存在也返回
{"success":true}(注销已达成)。 - 调用成功后客户端应同时删除本地设备密钥;要再用需重新配对(
POST /api/devices/pair)。
// 成功
{ "success": true }
其它设备管理(会话/JWT):
GET /api/devices/(列我的设备)、PATCH /api/devices/:id(改名)、DELETE /api/devices/:id(按 id 吊销某设备)。这些走UserAuth,网页台「设备」页用;客户端自助注销用上面的/api/devices/logout。
2. 账户与余额 🟢(可选;用用户会话/JWT,非设备签名)
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| GET | /api/user/self |
UserAuth(会话 cookie / 用户 JWT) |
当前用户:quota(剩余)、used_quota(累计已用)、request_count、分组 |
| GET | /api/user/self/models |
同上 | 当前用户可用模型 |
// data 摘要
{ "id":22, "username":"chenchen", "quota":..., "used_quota":..., "request_count":... }
⚠️ 注意鉴权不同:
/api/user/self*走UserAuth,不接受设备签名——客户端要用登录拿到的用户会话/JWT(Authorization: Bearer <jwt>或会话 cookie)调用。/api/heicode/*(§3–§5)走UserOrV2DeviceAuth,设备签名即可。 余额仅用于展示用量,非客户端核心流程(部署在网页台);拿不到也不影响"列 agent → 直连用"。
3. 能力发现 🟢
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/heicode/capabilities |
模型目录(渲染模型选择);免登录 |
// 生产实测:models = [{"id":"gpt-5.4",...}](modes 仍返回 sub_agile/swarm,客户端忽略即可)
{ "success": true, "data": { "models": [{"id":"gpt-5.4","name":"gpt-5.4","available":true}] }}
客户端只需
models(当前可用gpt-5.4);modes是旧任务模式,新模型已无意义。
4. Agent 模板列表 🟢
供客户端展示"有哪些 agent 角色可用"。模板由 HM 维护(管理员可在控制台增改),展示用中文名 + 中文简介。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/heicode/agent-templates |
列出可部署的 agent 模板 |
{ "success": true, "data": {
"total": 19,
"items": [
{"template_id":"architect","name":"架构顾问","description":"只读地分析代码、定位缺陷、给出架构与调试建议","model":"opus","status":"active"},
{"template_id":"code-reviewer","name":"代码审查员","description":"审查代码改动,找出 bug 与质量问题","model":"...","status":"active"}
]
}}
template_id是模板的稳定 key(如architect),部署时回传。name/description为中文,直接展示。model是模板的角色层级提示(如opus),不是实际跑的模型——agent 实际用网关模型gpt-5.4(部署时 HM 注入)。客户端不用管这个字段。
5. 我的 Agent 🟢(已对接 AM,生产验证)
2026-06-04 生产实测:部署 / 列表 / 详情 / 状态 / 删除均真实可用。
subdomain由 AM 分配(如dep-xxxx.taijiagnet.com);新建后status为Pending,需等 agent 起来变running(直连前先确认,见 §6)。access_token是 HM 现签的 per-agent 专属访问令牌(非空,UUID)——直连 agent 时带上(X-Agent-Access-Token头),agent 据此判定「是不是本 agent 的所有者」(见 §6)。stop/delete调 AM:目前 AM 的stop端点缺失、delete有已知 bug,所以delete会先清掉 HM 本地记录并返回runtime_cleanup:"failed"(AM 侧可能残留);stop暂时会失败。
| 方法 | 路径 | 说明 | 状态 |
|---|---|---|---|
| GET | /api/heicode/agents |
列出我部署的 agent | 🟢 |
| GET | /api/heicode/agents/{agent_id} |
agent 详情(读取时刷新状态) | 🟢 |
| GET | /api/heicode/agents/{agent_id}/status |
主动拉 agent 实时状态(是否运行/挂了) | 🟢 |
| POST | /api/heicode/agents |
部署:{template_id, binding_ids:[...]} |
🟢(调 AM 🔴) |
| POST | /api/heicode/agents/{agent_id}/stop |
停止 | 🟢 |
| DELETE | /api/heicode/agents/{agent_id} |
删除 | 🟢 |
列表 / 详情 / 部署成功 返回的 agent 对象:
// 生产实测响应(GET /api/heicode/agents 的一项 / 部署成功返回)
{ "success": true, "data": {
"items": [
{
"agent_id": "dep_4bb07dc1e376", // HM 侧 agent 记录 id(部署/停删都用它)
"template_id": "architect", // 用了哪个模板
"subdomain": "dep-4bb07dc1e376.taijiagnet.com", // ★ 直连地址(AM 分配,主机名)
"access_token": "550e8400-e29b-41d4-a716-446655440000", // ★ per-agent 访问令牌(直连带上)
"binding_ids": [], // 挂了哪些资源绑定(网页台绑的)
"status": "Pending", // Pending | running | failed | stopped …
"runtime_id": "dep-4bb07dc1e376", // AM 侧运行时 id
"created_at": "2026-06-04T09:04:15Z",
"updated_at": "2026-06-04T09:04:15Z"
}
],
"total": 1
}}
部署请求 POST /api/heicode/agents:
{ "template_id": "architect", "binding_ids": [17] }
binding_ids是网页台「资源绑定」里已绑资源的 id(可空数组,表示不挂资源)。- HM 处理:加载模板
.md→ 把所选资源按类型解成 env(非密配置 + KV 解出密钥)+ 注入模型环境(OPENAI_BASE_URL/OPENAI_API_KEY/MODEL_NAME=gpt-5.4)→ 交 AM 启动 → 存子域名返回。
状态查询 GET /api/heicode/agents/{id}/status:
{ "success": true, "data": { "agent_id":"dep_…", "status":"running", "updated_at":"2026-06-04T…" }}
6. 直连 Agent(客户端 ↔ agent,A2A 协议)
客户端直接连 agent 子域名(§5 的
subdomain),不经过 HM。agent 是 AM 的coding_a2a_agent,走 A2A 协议。
⚠️ 连之前先确认 agent 就绪:新建后
status=Pending(还在拉起)。等GET /api/heicode/agents/{id}/status变running、或GET {subdomain}/health返 200 再连。2026-06-04 实测:agent 暂时停在 Pending、子域名外网未就绪(AM 侧在排查),所以直连还连不上——HM 侧的列表/地址都是真实的。
- 同步:
POST {subdomain}/message/send - 流式:
POST {subdomain}/message/stream(返回text/event-stream) - 发现/健康:
GET {subdomain}/.well-known/agent.json、GET {subdomain}/health
请求头:X-Agent-Access-Token: <§5 列表里的 access_token> —— 访问鉴权(证明你是该 agent 的所有者)。
请求体(JSON-RPC,A2A):
{ "jsonrpc":"2.0", "id":"task-1", "method":"message/send",
"params": {
"api_key": "sk-…", // ★ 模型 key,agent 用它调 HM /v1 计费(客户端自带)
"model": "gpt-5.4",
"message": { "role":"user", "parts":[ {"kind":"text","text":"…"} ] },
"configuration": { "workspace": { "root_dir":"/workspace", "allowed_paths":["a.py"] } }
}
}
两层鉴权,职责分离:
- 访问鉴权 =
access_token(放X-Agent-Access-Token头,值取 §5 列表里那把 per-agent 令牌):证明「你是这个 agent 的所有者,有权调它」。每个直连请求都要带(message/send、message/stream都带)。agent 端把它和自己 env 里的AGENT_ACCESS_TOKEN做常量时间比对,不符返回 401/403。 - 模型鉴权 =
params.api_key(模型 sk-,客户端自带的网关 key):agent 凭它调 HM/v1/*,计费到你账上。 - agent 用
.env里注入的资源(git/mysql/postgres/azure-blob)自行干活;工具:read_file/write_file/edit_file/run_command/git_*/run_database_query/list_blob_objects等。缺配置的资源工具调用会返回resource not configured,不阻塞。 - 可在
params.configuration.resources里按需覆盖资源(请求级覆盖启动级)。
ℹ️ 安全:HM 为「客户端↔agent」提供按用户隔离 —— per-agent 访问令牌(
access_token,部署时现签、注入 agent env、随列表下发),只有部署该 agent 的你拿得到 ⇒ 只有你能调。接入方式 = agent 本地比对(X-Agent-Access-Token== envAGENT_ACCESS_TOKEN),不回 HM、零往返。客户端职责很简单:直连每个请求都带上X-Agent-Access-Token头。通道加密走 agent 子域名的 HTTPS/TLS。详见 AM 契约 §3.1。
7. 模型调用 🟢
- 客户端自己用模型:
POST /v1/chat/completions等,base_url = HM,鉴权同模型调用。 - agent 用模型:agent 内部也打 HM
/v1/*(用户身份计费)。 - 两边共用同一套模型接口,HM 统一鉴权 + 计费 + 路由。
8. 响应 envelope 与错误码
成功:{ "success": true, "data": {…} }
失败:{ "success": false, "message":"…", "error": { "code":"…", "message":"…", "retryable": false, "request_id":"…" } }
⚠️ 两个易踩的点:
- 失败也是 HTTP 200(
success:false)——客户端必须读success字段判成败,不能只看 HTTP 状态码(否则失败会被当成功)。error.retryable当前一律为false,不要据它判重试;按下表的code判。request_id便于排查。
| code | 场景 | 处理 |
|---|---|---|
POLICY_REJECTED |
参数非法 / 未认证 / 未知 template_id |
改参数,不重试 |
RESOURCE_BINDING_INVALID |
binding_ids 里有不存在/非本人的绑定 |
检查资源 |
RUNTIME_UNAVAILABLE |
调 AM 失败(含 AM 接口未就绪) | AM 侧问题,可稍后重试 |
DEPLOYMENT_CONFLICT |
agent 不存在 | — |
DEPLOYMENT_PERSIST_FAILED |
HM 落库失败 | — |
实时刷新:运行中轮询 GET /agents 或 /agents/{id}/status(3–5s);终态降频。
9. 客户端完整流程
0. (设备绑定/登录) — 复用模型调用的 V2 设备签名
1. (可选) GET /api/heicode/capabilities 取模型目录
2. (可选) GET /api/user/self 取余额
3. GET /api/heicode/agents 列出我的 agent
- 想看有哪些角色: GET /api/heicode/agent-templates (中文名/简介)
- 部署(也可在网页台做): POST /api/heicode/agents {template_id, binding_ids}
4. 选一个 status=running 的 agent → 取 subdomain + access_token
5. 直连 subdomain (SSE) → 与 agent 对话/干活 ← 不经 HM
- 头带 X-Agent-Access-Token: <access_token> (访问鉴权,证明是该 agent 所有者)
- body params.api_key: <模型 sk> (模型鉴权,计费到你账上)
6. 期间用模型 → HM /v1/* (客户端与 agent 两端都是)
7. 管理: GET /agents/{id}/status | POST /agents/{id}/stop | DELETE /agents/{id}
10. 接口清单(覆盖核查)
| 接口 | 鉴权 | 状态 |
|---|---|---|
GET /api/heicode/capabilities |
无(公开) | 🟢 |
GET /api/user/self、/self/models |
UserAuth(会话/JWT) | 🟢 |
GET /api/heicode/agent-templates |
会话/设备 | 🟢(生产已验证 19 中文模板) |
GET /api/heicode/agents /{id} /{id}/status |
会话/设备 | 🟢 |
POST /api/heicode/agents |
会话/设备 | 🟢(调 AM,AM 接口 🔴) |
POST /api/heicode/agents/{id}/stop、DELETE /{id} |
会话/设备 | 🟢(调 AM 🔴) |
模型 /v1/* |
同模型调用 | 🟢 |
POST /api/heicode/agent-access/verify |
公开(令牌即凭据) | 🟢(可选兜底;接入用 ① 本地比对,默认不用此端点) |
| 直连 agent SSE(subdomain + X-Agent-Access-Token) | agent 本地比对令牌 | 🔴(待 AM agent 端) |
🔴 项不阻塞客户端集成:HM 侧字段/接口已定型并生产验证,等 AM 实现 agent 启动/状态/直连即全线打通。AM 契约见
heicode-hm-template-agent-model.md§7 与代码controller/agent_template_runtime.go(隔离层)。