docs(client-api): add available-models, config, telemetry, agent usage + hidden-token billing semantics (#35, #30)

Bring docs/integration/heicode-desktop-client-api.md in lock-step with merged
endpoints:

- §3.1 /api/heicode/available-models: logged-in user model list is the single
  source of truth (no local presets / no CodeGW channel backend); exposes only
  model_id/display_name/default; forbidden fields enumerated.
- §5.1 /api/heicode/agents/{id}/usage: token-name aggregation, time window,
  empty-data semantics, relation to billing logs. Includes the #30 hidden agent
  model token billing-collection semantics: UnlimitedQuota only waives the
  token's own remaining-quota cap, it does NOT bypass user.Quota — HM still
  checks user quota and settles to wallet/subscription via the billing
  expression; token revoked on stop/delete; hidden from user UI but auditable.
- §7.1 /api/heicode/config: telemetry enabled/endpoint/max_batch/flush/retention_days.
- §7.2 /api/heicode/telemetry/events: default-off 410 kill switch, V2 device
  signature, top-level array 1-20 / <=256KB, server redaction, context field
  whitelist (#32), 4xx-drop/5xx-retry, retention; production enable-gate note.
- Updated §10 endpoint inventory.

Docs-only; no code change. heicodeDocs org-level sync tracked in #34.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 00:45:29 +08:00
co-authored by Claude Opus 4.8
parent 818f1412d5
commit d2d9a22ae4
@@ -120,6 +120,29 @@ signature = base64( ed25519_sign( device_priv, sha256(canonical) ) )
> 客户端只需 `models`(当前可用 `gpt-5.4`);`modes` 是旧任务模式,新模型已无意义。 > 客户端只需 `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 模板列表 🟢 ## 4. Agent 模板列表 🟢
@@ -198,6 +221,31 @@ signature = base64( ed25519_sign( device_priv, sha256(canonical) ) )
--- ---
## 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=美元额度)
}}
```
- **空数据语义**:无调用记录时各计数为 `0`(仍返回 `success:true`,不是 404)。
- **与 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 协议) ## 6. 直连 Agent(客户端 ↔ agent,A2A 协议)
> 客户端**直接连 agent 子域名**(§5 的 `subdomain`),不经过 HM。agent 是 AM 的 `coding_a2a_agent`,走 **A2A 协议**。 > 客户端**直接连 agent 子域名**(§5 的 `subdomain`),不经过 HM。agent 是 AM 的 `coding_a2a_agent`,走 **A2A 协议**。
@@ -242,6 +290,59 @@ signature = base64( ed25519_sign( device_priv, sha256(canonical) ) )
--- ---
## 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 与错误码 ## 8. 响应 envelope 与错误码
**成功**:`{ "success": true, "data": {…} }` **成功**:`{ "success": true, "data": {…} }`
@@ -287,11 +388,15 @@ signature = base64( ed25519_sign( device_priv, sha256(canonical) ) )
| 接口 | 鉴权 | 状态 | | 接口 | 鉴权 | 状态 |
|---|---|---| |---|---|---|
| `GET /api/heicode/capabilities` | 无(公开) | 🟢 | | `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/user/self`、`/self/models` | **UserAuth(会话/JWT)** | 🟢 |
| `GET /api/heicode/agent-templates` | 会话/设备 | 🟢(生产已验证 19 中文模板) | | `GET /api/heicode/agent-templates` | 会话/设备 | 🟢(生产已验证 19 中文模板) |
| `GET /api/heicode/agents` `/{id}` `/{id}/status` | 会话/设备 | 🟢 | | `GET /api/heicode/agents` `/{id}` `/{id}/status` | 会话/设备 | 🟢 |
| `POST /api/heicode/agents` | 会话/设备 | 🟢(调 AM,AM 接口 🔴) | | `POST /api/heicode/agents` | 会话/设备 | 🟢(调 AM,AM 接口 🔴) |
| `POST /api/heicode/agents/{id}/stop`、`DELETE /{id}` | 会话/设备 | 🟢(调 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/*` | 同模型调用 | 🟢 | | 模型 `/v1/*` | 同模型调用 | 🟢 |
| `POST /api/heicode/agent-access/verify` | 公开(令牌即凭据) | 🟢(可选兜底;接入用 ① 本地比对,默认不用此端点) | | `POST /api/heicode/agent-access/verify` | 公开(令牌即凭据) | 🟢(可选兜底;接入用 ① 本地比对,默认不用此端点) |
| 直连 agent SSE(subdomain + X-Agent-Access-Token) | agent 本地比对令牌 | 🟢 直连已通 / ⚠️ 令牌校验待 AM 开启 | | 直连 agent SSE(subdomain + X-Agent-Access-Token) | agent 本地比对令牌 | 🟢 直连已通 / ⚠️ 令牌校验待 AM 开启 |