Files
heicode-mananger/docs/integration/heicode-desktop-client-api.md
T

407 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`,真实账号):部署→`running`→直连 `/health`/`/message/send`(带令牌任务 `completed`)→stop→delete 全通,`access_token` 为 HM 现签非空 UUID。**仅剩 AM 两项待加固**(不影响功能):① agent 端尚未真正启用令牌校验(无令牌也被放行);② 子域名目前是 `http://` 明文。详见 AM 契约 §0.1。
---
## 0. 模型总览(先读)
新模型很简单:
1. 用户在 **HM 网页控制台**绑定资源(git/vm/数据库/对象存储)、选「资源 + 模板」**部署一个常驻 agent**。
2. HM 把所选资源解出后注入 agent 的 `.env`(并**现签一把 per-agent 访问令牌**一起注入),交给 **AM** 启动;AM 返回**唯一子域名**。
3. **桌面客户端**从 HM 读 agent 列表 → 拿到**子域名 + 访问令牌**(HM 现签的 `access_token`)→ **直连 agent 子域名(SSE)对话使用**。**HM 不在对话回路里。**
4. **模型调用**:客户端自己用模型、以及 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(字节序固定,服务端按此重建,顺序不可改):**
```text
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`)。
```json
// 成功
{ "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` | 同上 | 当前用户可用模型 |
```json
// 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` | 模型目录(渲染模型选择);免登录 |
```json
// 生产实测: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` 是旧任务模式,新模型已无意义。
> ⚠️ **能力发现 vs 登录用户模型列表**:`/api/heicode/capabilities` 是**免登录**的总目录(渲染模型选择用)。**登录后客户端展示的「我能用哪些模型」必须走 §3.1 `/api/heicode/available-models`**——它按当前用户的分组/订阅服务端收口,且**只有** HM 这一个来源:客户端不得使用本地 preset,也不得从 CodeGW 渠道后台读取模型。
---
## 3.1 登录用户可用模型 🟢(模型列表收口)
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| GET | `/api/heicode/available-models` | `UserOrV2DeviceAuth`(会话/JWT 或设备签名) | 当前登录用户**实际可用**的模型列表;服务端按用户可用分组 → 分组启用模型解析 |
```json
{ "success": true, "data": {
"available_models": [
{ "model_id": "gpt-5.4", "display_name": "gpt-5.4", "default": true }
]
}}
```
- 字段仅 `model_id` / `display_name` / `default`(默认模型)。
- **禁止暴露字段**:`channel_id`、`base_url`、`api_key`、供应商类型、价格/倍率等一律不返回。
- **唯一模型来源**:客户端登录后模型列表完全来自此接口,不使用本地 preset / 不读 CodeGW 渠道后台。
- 鉴权要求登录用户上下文(`id>0`);未登录返回 `authentication required`。
---
## 4. Agent 模板列表 🟢
供客户端展示"有哪些 agent 角色可用"。模板由 HM 维护(管理员可在控制台增改),**展示用中文名 + 中文简介**。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/api/heicode/agent-templates` | 列出可部署的 agent 模板 |
```json
{ "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:**2026-06-04 复测均已通**——`stop` 返回 `status:stopped`,`delete` 返回 `runtime_cleanup:"ok"`(旧版的 404/500 已修)。`delete` 仍保留容错:即便 AM 删除失败也会清掉 HM 本地记录并以 `runtime_cleanup:"failed"` 提示。
| 方法 | 路径 | 说明 | 状态 |
|---|---|---|---|
| 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 对象:**
```json
// 生产实测响应(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`:
```json
{ "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`:
```json
{ "success": true, "data": { "agent_id":"dep_…", "status":"running", "updated_at":"2026-06-04T…" }}
```
---
## 5.1 Agent 模型用量 🟢(按部署 Agent 聚合)
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| GET | `/api/heicode/agents/{deployment_id}/usage` | `UserOrV2DeviceAuth`,且只能查**自己**的 agent | 按该 agent 的隐藏模型 token(name=`agent:<deployment_id>`)在计费 logs 中聚合用量 |
查询参数(可选):`start` / `end` = unix 秒时间窗(缺省=全窗口)。
```json
{ "success": true, "data": {
"agent_id": "dep_4bb07dc1e376",
"quota": 12345, // 消耗的额度(配额单位)
"prompt_tokens": 8000,
"completion_tokens": 4000,
"call_count": 12,
"quota_per_unit": 500000, // 额度→货币换算分母(quota/quota_per_unit=美元额度)
"budget_remaining": 1234567 // ★ 用户钱包剩余额度(预算剩余;-1=读取失败,不阻断展示)
}}
```
- **空数据语义**:无调用记录时各计数为 `0`(仍返回 `success:true`,不是 404)。
- **`budget_remaining`(#9)**:用户剩余可用额度(同 `quota_per_unit` 口径换算)。Agent 模型调用经隐藏 token 计费到 `user.Quota`,故"本任务预算剩余"= 用户钱包剩余额度。
- **与 billing logs 的关系**:用量来自统一计费 logs(`SumAgentUsage`),按 token name `agent:<deployment_id>` 过滤聚合 —— 即 agent 走 HM `/v1/*` 的真实消耗,与用户钱包/订阅扣费同源。
- **计费归集语义(#30)**:每个部署的 agent,HM 为其 mint 一个**隐藏、不展示在用户 token 列表**的模型 token(`UnlimitedQuota:true`)。`UnlimitedQuota` 的含义是「**不对该 token 自身设单独的剩余额度上限**」——它**不**绕过用户额度:agent 经此 token 调 `/v1/*` 时,HM 仍先校验 `user.Quota`,并在结算时从 `user.Quota`(钱包)或订阅项扣费、写计费 log,完全经过计费表达式。停止/删除 agent 后该 token 被撤销,旧 token 无法再调 `/v1/*`。该 token 对普通用户隐藏,但审计/管理员可追踪。
---
## 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 复测:数秒即 `running`,`/health` 200、`/message/send` 带令牌任务 `completed`,直连已通。**
>
> ⚠️ 当前 AM 侧两点(待加固,不影响调通):① **令牌校验尚未真正生效**——无 `X-Agent-Access-Token` 也被放行;客户端仍应规范地每请求都带,等 AM 开启校验即自动生效。② 子域名目前 `http://` 明文,令牌/`api_key` 会明文传输,等 AM 上 HTTPS。
- **同步**:`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):
```json
{ "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` == env `AGENT_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 统一鉴权 + 计费 + 路由。
---
## 7.1 客户端运行时配置 🟢
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| GET | `/api/heicode/config` | 无(公开,非敏感全局配置) | 客户端 runtime config / telemetry 开关;客户端轮询以便会话内即时生效(无需重登) |
```json
{ "success": true, "data": {
"telemetry": {
"enabled": false, // 默认关闭(kill switch)
"endpoint": "/api/heicode/telemetry/events",
"max_batch": 20,
"flush_interval_sec": 30,
"retention_days": 30 // 服务端保留期:超期遥测被清理(#32)
}
}}
```
- 客户端必须以 `telemetry.enabled` 为准:为 `false` 时**停止上送**(摄入端点也会回 410)。
---
## 7.2 客户端错误遥测上送 🟡(默认关闭)
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| POST | `/api/heicode/telemetry/events` | `UserOrV2DeviceAuth` + **V2 设备签名**(需配对设备) | 上送客户端错误遥测;**诊断流量,绝不计费、不进 consume log** |
- **默认关闭**:`HEICODE_TELEMETRY_ENABLED=false` 时返回 **410**(kill switch),客户端应停止上送。
- **鉴权**:需登录用户 + 已配对设备;请求头带 `X-Heicode-Device-Id`。会话-only(无设备)调用被拒(403)。
- **Body = 顶层 JSON 数组**(不是包裹对象),**1–20 条/批**,**≤256KB**。超限 413,非数组 400。
- **每条事件**字段(诊断用,无用户内容):`client_id`(须等于配对设备 id)、`schema_version`、`app_version`、`platform`、`os_version`、`arch`、`locale`、`error_category`、`error_code`、`error_message_hash`、`stack_hash`、`stack_top`(数组)、`context`(对象)、`timestamp`、`session_seq`。
- **服务端脱敏**:`stack_top` / `context` 即使客户端已脱敏,服务端仍二次 redaction(剥离 `sk-`/`Bearer`/URL token/JSON 密钥字段)。
- **context 字段白名单(#32)**:`context` 仅保留 `route` / `retryable` / `phase` / `exit_code` / `duration_ms` / `attempt`;其余键(含 email、完整文件路径、prompt、IP 原文等可识别信息)**一律丢弃**。`stack_top`/`context` 单字段脱敏后截断到 8KiB。
- **重试语义**:4xx(校验失败/超限/kill switch 410)**丢弃不重试**;5xx(持久化失败)可重试。
- **保留期(#32)**:服务端按 `HEICODE_TELEMETRY_RETENTION_DAYS`(默认 30)定期清理超期遥测。
> ⚠️ **上线前置门槛(#32)**:`device_id`/`client_id` 可关联账号,属隐私敏感。生产开启 `HEICODE_TELEMETRY_ENABLED=true` 前必须:隐私文档已如实披露「设备 ID 可关联账号的错误遥测」、产品/法务已确认、kill switch 已验证。隐私文档同步见 heicodeDocs(#34)。
```json
// 请求体(顶层数组,示意一条)
[
{ "client_id":"<paired-device-id>", "app_version":"0.5.0", "platform":"win32",
"error_category":"ui_crash", "error_code":"RENDERER_ERROR",
"error_message_hash":"9f2a7c1b4e8d", "stack_hash":"a1b2c3d4e5f6",
"stack_top":["at MessageList (MessageList.tsx:212:9)"],
"context":{"route":"chat"}, "timestamp":"2026-06-09T07:21:33.123Z", "session_seq":1 }
]
// 成功:{ "success": true, "accepted": 1 }
```
---
## 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. 客户端完整流程
```text
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/heicode/available-models` | 会话/设备 | 🟢(登录用户模型列表收口,唯一来源) |
| `GET /api/heicode/config` | 无(公开) | 🟢(runtime config / telemetry 开关) |
| `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 🔴) |
| `GET /api/heicode/agents/{id}/usage` | 会话/设备(仅自己的 agent) | 🟢(按 `agent:<id>` token 聚合计费 logs) |
| `POST /api/heicode/telemetry/events` | 会话/设备 + V2 设备签名 | 🟡 默认关闭(410 kill switch);不计费 |
| 模型 `/v1/*` | 同模型调用 | 🟢 |
| `POST /api/heicode/agent-access/verify` | 公开(令牌即凭据) | 🟢(可选兜底;接入用 ① 本地比对,默认不用此端点) |
| 直连 agent SSE(subdomain + X-Agent-Access-Token) | agent 本地比对令牌 | 🟢 直连已通 / ⚠️ 令牌校验待 AM 开启 |
> 端到端(部署→running→直连→stop→delete)已生产验证。AM 侧仅剩两项加固:**令牌校验真正生效** + **子域名 HTTPS**(见 AM 契约 §0.1)。代码隔离层见 `controller/agent_template_runtime.go`。