docs(client-api): available-models + config + telemetry + agent usage + #30 billing semantics (#35)
448 lines
26 KiB
Markdown
448 lines
26 KiB
Markdown
# 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 注入)。客户端不用管这个字段。
|
||
|
||
---
|
||
|
||
## 4.1 启动前预检 / 执行摘要 🟢(preflight)
|
||
|
||
部署 agent 前,给用户看一份「执行摘要」:用哪些资源、还缺什么、有哪些高危操作、预算上限。用户**只确认摘要**,不必面对完整参数。
|
||
|
||
| 方法 | 路径 | 鉴权 | 说明 |
|
||
|---|---|---|---|
|
||
| GET | `/api/heicode/preflight?template_id=&binding_ids=1,2,3` | `UserOrV2DeviceAuth` | 返回缺失项 + 可读执行摘要 |
|
||
|
||
- `binding_ids` 同部署入参(逗号分隔或重复 key,可空)。
|
||
|
||
```json
|
||
{ "success": true, "data": {
|
||
"template_id": "architect",
|
||
"agent_role": { "template_id":"architect", "name":"架构顾问", "model":"opus" },
|
||
"resources": [
|
||
{ "binding_id":17, "type":"git", "provider":"github", "name":"my-repo", "status":"active", "has_secret":true }
|
||
],
|
||
"invalid_bindings": [],
|
||
"missing": [
|
||
{ "kind":"sk", "reason":"未绑定SK 资源包" },
|
||
{ "kind":"budget", "reason":"账户可用额度不足,请充值或开通订阅" }
|
||
],
|
||
"high_risk_ops": [
|
||
{ "op":"production_deploy", "label":"生产部署 / 代码改动", "requires_approval":true },
|
||
{ "op":"large_budget", "label":"大额预算消耗", "requires_approval":true }
|
||
],
|
||
"budget": { "remaining_quota":1234567, "quota_per_unit":500000, "tier_max_agents":5, "current_agents":1 },
|
||
"approval_policy": { "mode":"per_high_risk_op" },
|
||
"ready": false
|
||
}}
|
||
```
|
||
|
||
- **`missing`**:必需类别(`git`/`sk`/`project_document`/`cloud_account`)未绑定、`budget`(余额≤0)、`agent_slot`(在跑数已达 tier 上限)。`ready=true` 当且仅当 `missing` 为空。
|
||
- **`high_risk_ops`**:固定 enum —— `production_deploy` / `db_write` / `cloud_resource_delete` / `production_secret` / `large_budget`;由已绑资源类型推导,均 `requires_approval`。
|
||
- **红线**:`resources` 只暴露 `type/provider/name/status/has_secret`(布尔),**绝不返回 `secret_ref`/`channelId`/`base_url`/价格**。
|
||
- **`invalid_bindings`**:请求里无效 / 非本人 / 非 active 的绑定 id(不阻断,供前端提示)。
|
||
|
||
> confirm + 审计 + 防篡改版本校验(#41)将作为 `POST /api/heicode/preflight/confirm` 后续补充;当前 preflight 为只读。
|
||
|
||
---
|
||
|
||
## 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`。
|