forked from xiaohei/taiji-AI-PAD
== Heicode integration (~41 endpoints across 5 modules) ==
- §2 ResourceBinding (5 endpoints) — resources.py / resource_grants.py
- §4 NewAPI metadata proxy (4 endpoints) — heicode_proxy.py + heicode_client.py
- §5 Agnet platform stub (12 endpoints, in-memory mock) — agnet_stub.py
- §6 Task orchestration (5 endpoints + 3 extension endpoints) — heicode_tasks.py
6.1-6.5: intent / list / get / answer / messages
6.6-6.8: execution / delivery / audit?tab=... (Slice 8/9/10)
- §7 SSE single channel + approvals (4 endpoints + 5 event types) —
heicode_events.py + event_bus.py
- §7.8.1 internal billing-provider PUT endpoint — auth.py (routes)
== Schema changes ==
- migrations/026 heicode_tasks (orchestration state)
- migrations/027 users.billing_provider (litellm | newapi switch)
- migrations/028 heicode_approvals (high-risk approval queue)
== Register transaction hardening (P0 + P1 + P2) ==
routes/auth.py register():
- Pre-existing P0: failed register returned IntegrityError str verbatim
(leaking SQL params + ~50 plaintext LiteLLM keys per attempt).
Now logs exc_info, returns {code: REGISTER_FAILED, message: ...}.
- Pre-existing P0: model dedupe — two ModelProvider rows with overlapping
supported_models (e.g. taiji/gpt-4o-mini in both taiji and azure providers)
collide on uq_tenant_model. seen_models set deduplicates within the loop.
- New P1: track created_litellm_keys; on any failure call delete_key() for
each — prevents remote orphan keys when DB rollback fires.
- New P1: replace verify_code with peek_verification_code at the start;
only call verify_code (which consumes) after commit succeeds. Failed
registrations no longer burn the user's one-shot code.
- New P2: narrow inner `except (LiteLLMClientError, Exception)` to just
LiteLLMClientError so SQLAlchemy errors bubble to the outer rollback
instead of being silently swallowed into a half-allocated 200 response.
- New P2: same narrowing on outer `except (AgentManagerError, Exception)`.
== Auth middleware ==
- app/auth.py: allow /api/auth/internal/billing-provider and
/api/auth/internal/approvals to bypass user JWT (service-token auth
via HEICODE_INTERNAL_SERVICE_TOKEN, validated in-route).
== Docs ==
- Heicode-接口契约文档.md v2.2 (41 endpoints + SSE schema + 6.6-6.8)
- Heicode-对接进度与待办.md (through §7.14 SSE + 7.8.2 delivery回执)
- Heicode-完整调用流程图.md (sequence + routing diagrams)
- Agent-Manager-Heicode对接需求文档.md
- HEICODE_API_INTEGRATION.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1578 lines
49 KiB
Markdown
1578 lines
49 KiB
Markdown
# Heicode ↔ mcp-server 接口契约文档
|
||
|
||
**版本**: v2.2
|
||
**生效日期**: 2026-05-08
|
||
**状态**: 已上线生产,可直接对接
|
||
**当前镜像**: `taiji.azurecr.io/mcp-server:heicode-7.8.2-tasks-ext-20260508`
|
||
|
||
> 本文档列出 Heicode 客户端可调用的全部 mcp-server 接口、入参与出参。
|
||
> 所有接口已在生产环境通过端到端测试。
|
||
|
||
**v2.2 修订**(2026-05-08,本次):
|
||
- 新增 §6.6 / §6.7 / §6.8(execution / delivery / audit)— Slice 8/9/10 配套,3 端点全过 13/13 smoke
|
||
- **接口总数:38 → 41**
|
||
|
||
**v2.1 修订**(2026-05-08):
|
||
- 新增 §7 Server-Sent Events(5 类 event + 配套 REST)— §7.8.3 SSE 单通道全过 12/12 smoke
|
||
- **接口总数:34 → 38**
|
||
|
||
**v2.0 修订**(2026-05-08):
|
||
- 新增 §4 NewAPI 元数据透传(4 接口)
|
||
- 新增 §5 Agnet 平台本地 stub(12 接口,mock 数据,前端可立即联调)
|
||
- 新增 §6 任务编排(5 接口,cc-haha 任务驾驶舱 / 工作台 配套)
|
||
- **接口总数:13 → 34**
|
||
|
||
> 📍 调用关系全景见 [`Heicode-完整调用流程图.md`](./Heicode-完整调用流程图.md)。
|
||
> 📍 进度与待办见 [`Heicode-对接进度与待办.md`](./Heicode-对接进度与待办.md)。
|
||
|
||
---
|
||
|
||
## 0. 接入信息
|
||
|
||
### Base URL
|
||
|
||
```
|
||
https://apimtaiji.azure-api.net/api/mcp
|
||
```
|
||
|
||
完整路径示例:
|
||
|
||
```
|
||
POST https://apimtaiji.azure-api.net/api/mcp/api/auth/login
|
||
GET https://apimtaiji.azure-api.net/api/mcp/api/resources
|
||
```
|
||
|
||
### ⚠️ 重要:登录接口被两类调用方共用
|
||
|
||
经过代码核实(2026-05-05),mcp-server 的登录接口实际被以下两个调用方依赖:
|
||
|
||
| 调用方 | 调用方式 | 关键依赖字段 |
|
||
|---|---|---|
|
||
| **heicode 前端** (`web/default/src/features/auth/api.ts`) | 浏览器跨域,`Authorization: Bearer` | `data.token`, `data.refreshToken`, `data.user.{id,name,email,role,channelId}` |
|
||
| **heicode 后端** (`controller/heicode_agnet_session.go`) | 服务端调 `/me` `/refresh`,验证前端给的 token | `data.{id,email,name,role,channelId,status}` |
|
||
|
||
字段形态**任何变更都会破坏对接**。增加字段安全;删/改字段名/字段类型 = breaking change。
|
||
|
||
### 通用请求头
|
||
|
||
| Header | 必填 | 说明 |
|
||
|---|---|---|
|
||
| `Content-Type: application/json` | POST/PUT 时是 | 请求体 JSON 格式 |
|
||
| `Authorization: Bearer <accessToken>` | 受保护接口必填 | JWT access token |
|
||
| `X-Request-Id: <uuid>` | 建议 | 客户端生成,便于排障反查日志 |
|
||
|
||
### 通用响应封装
|
||
|
||
**成功**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": { ... }
|
||
}
|
||
```
|
||
|
||
部分接口含 `message`:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": { ... },
|
||
"message": "操作成功"
|
||
}
|
||
```
|
||
|
||
**失败(FastAPI 默认)**:
|
||
```json
|
||
{
|
||
"detail": "邮箱或密码错误"
|
||
}
|
||
```
|
||
|
||
**失败(结构化错误)**:
|
||
```json
|
||
{
|
||
"detail": {
|
||
"code": "RESOURCE_GRANT_INVALID",
|
||
"message": "allowed_actions 超出对应 binding.permission_scope: ['repo:admin']"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Token 模型
|
||
|
||
| Token | TTL | 用途 |
|
||
|---|---|---|
|
||
| Access Token | 24 小时 | 调业务接口(含 /me、/logout) |
|
||
| Refresh Token | 7 天 | 仅用于 `/refresh` 换新 access |
|
||
|
||
JWT claims:`sub`(user_id)、`email`、`role`、`channelId`、`type`(access/refresh)、`iat`、`exp`。
|
||
|
||
### Role 字段值与 heicode 前端解析
|
||
|
||
mcp-server 现有 role 取值(按用户类型):`user`, `channel_admin`, `billing_admin`, `operations_admin`, `admin`, `super_admin`, `provider_admin`。
|
||
|
||
heicode 前端 (`api.ts:259-261`) **硬编码**解析为 3 档:
|
||
|
||
| mcp-server role 值 | heicode 前端解析 | 数值 |
|
||
|---|---|---|
|
||
| `root` | root | 100 |
|
||
| `admin` | admin | 10 |
|
||
| `user` | user | 1 |
|
||
| 其他(channel_admin, super_admin, provider_admin 等) | 默认 user | 1 |
|
||
|
||
**对 Heicode 客户端的实际影响**:
|
||
- Heicode 客户端预期登录的角色 = `user`,前端解析为 1 ✓
|
||
- 高权限角色(admin/root)由 **heicode 后端独立白名单** (`HEICODE_ROOT_EMAILS` / `HEICODE_ADMIN_EMAILS` 环境变量) 决定,**不信任** mcp-server 返回的 role 字段
|
||
- mcp-server 这边**无需调整** role 取值,保持现有取值即可
|
||
|
||
### CORS 当前状态
|
||
|
||
mcp-server 生产环境 `CORS_ORIGINS` 未显式配置 → fallback 到 `["*"]`。代码同时设置 `allow_credentials=True`。
|
||
|
||
| 调用方式 | 当前是否能用 |
|
||
|---|---|
|
||
| `Authorization: Bearer <token>` | ✅ 能用(heicode 前端走这条) |
|
||
| 浏览器 cookie + credentials | ❌ 浏览器规范禁止 `*` + credentials 同用 |
|
||
|
||
**Heicode 前端用 Bearer,不受影响**。如需后续硬化(去掉 `*`),需 heicode 团队提供前端实际部署域名清单。
|
||
|
||
---
|
||
|
||
## 1. 登录认证(4 接口)
|
||
|
||
### 1.1 POST /api/auth/login
|
||
|
||
**用途**: 账号密码登录,换取 token 对。
|
||
|
||
**请求**
|
||
|
||
```http
|
||
POST /api/auth/login
|
||
Content-Type: application/json
|
||
```
|
||
|
||
```json
|
||
{
|
||
"email": "user@example.com",
|
||
"password": "YourPassword123",
|
||
"role": "user"
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|---|---|---|---|
|
||
| `email` | string | 是 | 用户邮箱 |
|
||
| `password` | string | 是 | 密码 |
|
||
| `role` | string | 是 | Heicode 客户端固定传 `"user"` |
|
||
|
||
**成功响应 200**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"token": "eyJhbGc...",
|
||
"refreshToken": "eyJhbGc...",
|
||
"user": {
|
||
"id": "b00a7b8e-9e8b-463d-9593-a3b4d0006778",
|
||
"name": "张三",
|
||
"email": "user@example.com",
|
||
"role": "user",
|
||
"channelId": "6e6fc470-76f8-4bb1-8ea4-625dc5b12bc6"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**错误响应**
|
||
|
||
| HTTP | 含义 |
|
||
|---|---|
|
||
| 401 | `邮箱或密码错误` |
|
||
| 403 | `账户已被禁用` / `权限不足` |
|
||
| 422 | 请求体 Pydantic 校验失败(邮箱格式不合法等) |
|
||
| 429 | 登录尝试过于频繁;响应头含 `Retry-After: 60` |
|
||
|
||
**限流**: 每 IP 每 60 秒最多 5 次(不区分成功失败)。
|
||
|
||
---
|
||
|
||
### 1.2 GET /api/auth/me
|
||
|
||
**用途**: 校验 access token 有效性,返回当前用户资料。
|
||
|
||
> ⚠️ **高频调用 + 双调用方依赖**:
|
||
> - heicode 前端:客户端启动 + token 续期后立即调一次
|
||
> - heicode 后端:每次走 `from-agnet` 流程时调一次(即每次"建立 Manager 会话")
|
||
> - 字段形态被 hardcode 解析(id/email/name/role/channelId/status),**绝不能改**
|
||
> - 性能敏感:响应时间影响登录用户体验
|
||
|
||
**请求**
|
||
|
||
```http
|
||
GET /api/auth/me
|
||
Authorization: Bearer <accessToken>
|
||
```
|
||
|
||
**成功响应 200**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": "b00a7b8e-9e8b-463d-9593-a3b4d0006778",
|
||
"email": "user@example.com",
|
||
"name": "张三",
|
||
"role": "user",
|
||
"channelId": "6e6fc470-76f8-4bb1-8ea4-625dc5b12bc6",
|
||
"status": "active",
|
||
"subscriptionTier": "free",
|
||
"lastLoginAt": "2026-05-05T06:01:23.456789"
|
||
}
|
||
}
|
||
```
|
||
|
||
**错误响应**
|
||
|
||
| HTTP | 含义 |
|
||
|---|---|
|
||
| 401 | token 无效 / 过期 / 已登出 / 用户不存在 |
|
||
| 403 | 账户已被禁用 |
|
||
|
||
---
|
||
|
||
### 1.3 POST /api/auth/refresh
|
||
|
||
**用途**: 用 refresh token 换取新的 access + refresh token 对。
|
||
|
||
**请求**
|
||
|
||
```http
|
||
POST /api/auth/refresh
|
||
Authorization: Bearer <refreshToken>
|
||
```
|
||
|
||
> ⚠️ 必须传 **refresh token**,传 access token 会被拒绝。
|
||
|
||
**成功响应 200**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"token": "eyJhbGc...",
|
||
"refreshToken": "eyJhbGc..."
|
||
}
|
||
}
|
||
```
|
||
|
||
客户端收到新 token 对后**应替换本地缓存**(旧的也作废)。
|
||
|
||
**错误响应**
|
||
|
||
| HTTP | 含义 |
|
||
|---|---|
|
||
| 401 | refresh token 无效 / 过期 / 错传了 access token(`必须使用 refresh token 调用此接口`) |
|
||
|
||
---
|
||
|
||
### 1.4 POST /api/auth/logout
|
||
|
||
**用途**: 登出,把当前 access token 加入黑名单。
|
||
|
||
**请求**
|
||
|
||
```http
|
||
POST /api/auth/logout
|
||
Authorization: Bearer <accessToken>
|
||
```
|
||
|
||
**成功响应 200**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": null,
|
||
"message": "登出成功"
|
||
}
|
||
```
|
||
|
||
**说明**: 容错性强,token 黑名单写入失败也会返回 200。客户端总是清理本地 token + 跳登录页。
|
||
|
||
> ⚠️ **登出双调用约定**:heicode 前端登出时会**同时**调用:
|
||
> 1. `GET /api/user/logout`(heicode 后端,清 heicode session cookie)
|
||
> 2. `POST /api/auth/logout`(mcp-server,本接口;token 加黑名单)
|
||
>
|
||
> 两次调用都做容错处理(不阻断登出体验)。前端最后总是清 localStorage 跳登录页。
|
||
|
||
---
|
||
|
||
## 2. 资源绑定 ResourceBinding(5 接口)
|
||
|
||
### 2.0 公共说明
|
||
|
||
**对象语义**: 用户绑定到 Heicode 的外部资源(Git 仓库 / SK / 项目文档 / 云账号 / 云资源)。
|
||
|
||
**安全红线**:
|
||
- DB **只保存元数据** + `secret_ref`,**不保存明文密钥**
|
||
- `metadata` / `constraints` / `permission_scope` 中**不得**出现 key 包含以下关键词的字段(不区分大小写):
|
||
```
|
||
password, token, secret, private_key, access_key, credential
|
||
```
|
||
违反返回 **422 `RESOURCE_GRANT_SECRET_REJECTED`**
|
||
- 例外:key 名为 `secret_ref` 视为引用(如 `vault://...`),允许通过
|
||
|
||
**枚举值**:
|
||
|
||
| 字段 | 允许值 |
|
||
|---|---|
|
||
| `type` | `git` / `sk` / `project_doc` / `cloud_account` / `cloud_resource` |
|
||
| `status` | `pending` / `active` / `disabled` / `revoked` |
|
||
|
||
---
|
||
|
||
### 2.1 POST /api/resources
|
||
|
||
**用途**: 创建资源绑定。
|
||
|
||
**请求**
|
||
|
||
```http
|
||
POST /api/resources
|
||
Authorization: Bearer <accessToken>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
```json
|
||
{
|
||
"type": "git",
|
||
"name": "heicode-main-repo",
|
||
"external_ref": "https://example.com/org/heicode.git",
|
||
"metadata": {
|
||
"provider": "gitee",
|
||
"default_branch": "main"
|
||
},
|
||
"permission_scope": ["repo:read", "repo:write:current-branch"],
|
||
"constraints": {
|
||
"ref": "main",
|
||
"allowed_paths": "services/**,docs/**"
|
||
},
|
||
"secret_ref": "vault://secret/users/{user_id}/bindings/heicode-main-repo",
|
||
"status": "active"
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|---|---|---|---|
|
||
| `type` | string (enum) | 是 | 资源类型,见 §2.0 |
|
||
| `name` | string (≤255) | 是 | 用户可见名称 |
|
||
| `external_ref` | string | 否 | 外部资源定位(repo URL、subscription ID 等) |
|
||
| `metadata` | object | 否,默认 `{}` | 元数据;不得含敏感 key |
|
||
| `permission_scope` | string[] | 否,默认 `[]` | 该资源允许的最大动作集合 |
|
||
| `constraints` | object | 否,默认 `{}` | 路径/分支/网络等限制 |
|
||
| `secret_ref` | string (≤500) | 否 | 凭证引用(`vault://...`),不是密钥本身 |
|
||
| `status` | string (enum) | 否,默认 `pending` | 见 §2.0 |
|
||
|
||
**成功响应 200**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": "88963d56-5a95-4863-8a0b-91a83e456028",
|
||
"user_id": "b00a7b8e-9e8b-463d-9593-a3b4d0006778",
|
||
"type": "git",
|
||
"name": "heicode-main-repo",
|
||
"external_ref": "https://example.com/org/heicode.git",
|
||
"metadata": { "provider": "gitee", "default_branch": "main" },
|
||
"permission_scope": ["repo:read", "repo:write:current-branch"],
|
||
"constraints": { "ref": "main", "allowed_paths": "services/**,docs/**" },
|
||
"secret_ref": "vault://secret/users/.../bindings/heicode-main-repo",
|
||
"status": "active",
|
||
"created_by": "b00a7b8e-9e8b-463d-9593-a3b4d0006778",
|
||
"updated_by": "b00a7b8e-9e8b-463d-9593-a3b4d0006778",
|
||
"created_at": "2026-05-05T06:10:00.123456",
|
||
"updated_at": "2026-05-05T06:10:00.123456"
|
||
}
|
||
}
|
||
```
|
||
|
||
**错误响应**
|
||
|
||
| HTTP | code | 含义 |
|
||
|---|---|---|
|
||
| 401 | — | 未登录 |
|
||
| 422 | — | type/status 枚举不合法 |
|
||
| 422 | `RESOURCE_GRANT_SECRET_REJECTED` | metadata/constraints/permission_scope 含敏感字段 |
|
||
|
||
---
|
||
|
||
### 2.2 GET /api/resources
|
||
|
||
**用途**: 列出当前登录用户的资源绑定。
|
||
|
||
**请求**
|
||
|
||
```http
|
||
GET /api/resources?type=git&status=active&limit=100&offset=0
|
||
Authorization: Bearer <accessToken>
|
||
```
|
||
|
||
| Query | 类型 | 默认 | 说明 |
|
||
|---|---|---|---|
|
||
| `type` | string | — | 按类型过滤 |
|
||
| `status` | string | — | 按状态过滤 |
|
||
| `limit` | int (1-500) | 100 | 返回上限 |
|
||
| `offset` | int (≥0) | 0 | 偏移 |
|
||
|
||
**成功响应 200**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"items": [
|
||
{ "id": "...", "type": "git", "name": "...", ... }
|
||
],
|
||
"total": 1,
|
||
"offset": 0,
|
||
"limit": 100
|
||
}
|
||
}
|
||
```
|
||
|
||
> `items` 元素结构同 §2.1 的成功响应 `data` 字段。
|
||
|
||
---
|
||
|
||
### 2.3 GET /api/resources/{binding_id}
|
||
|
||
**用途**: 获取单个资源绑定详情。
|
||
|
||
**请求**
|
||
|
||
```http
|
||
GET /api/resources/88963d56-5a95-4863-8a0b-91a83e456028
|
||
Authorization: Bearer <accessToken>
|
||
```
|
||
|
||
**成功响应 200**: 同 §2.1 的 `data` 字段。
|
||
|
||
**错误响应**
|
||
|
||
| HTTP | 含义 |
|
||
|---|---|
|
||
| 400 | binding_id 不是有效 UUID |
|
||
| 401 | 未登录 |
|
||
| 403 | 该资源不属于当前用户 |
|
||
| 404 | 资源不存在 |
|
||
|
||
---
|
||
|
||
### 2.4 PUT /api/resources/{binding_id}
|
||
|
||
**用途**: 更新资源绑定(部分字段)。
|
||
|
||
**请求**
|
||
|
||
```http
|
||
PUT /api/resources/88963d56-5a95-4863-8a0b-91a83e456028
|
||
Authorization: Bearer <accessToken>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
```json
|
||
{
|
||
"name": "heicode-main-repo-renamed",
|
||
"permission_scope": ["repo:read"],
|
||
"status": "active"
|
||
}
|
||
```
|
||
|
||
可更新字段(全部可选):`name` / `external_ref` / `metadata` / `permission_scope` / `constraints` / `secret_ref` / `status`。
|
||
|
||
未传的字段保持原值。
|
||
|
||
**成功响应 200**: 同 §2.1(含更新后字段)。
|
||
|
||
**错误响应**: 同 §2.3 + §2.1 的 422。
|
||
|
||
---
|
||
|
||
### 2.5 DELETE /api/resources/{binding_id}
|
||
|
||
**用途**: 软删除资源绑定(设 `status=revoked`,保留历史)。
|
||
|
||
**请求**
|
||
|
||
```http
|
||
DELETE /api/resources/88963d56-5a95-4863-8a0b-91a83e456028
|
||
Authorization: Bearer <accessToken>
|
||
```
|
||
|
||
**成功响应 200**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": "88963d56-5a95-4863-8a0b-91a83e456028",
|
||
"status": "revoked"
|
||
}
|
||
}
|
||
```
|
||
|
||
**说明**: 软删除后 `GET /api/resources/{id}` 仍可读到(status=revoked)。基于该 binding 已存在的 grant 不会自动撤销 — 业务层自行判断或调 `DELETE /api/resource-grants/{id}`。
|
||
|
||
---
|
||
|
||
## 3. 资源授权 ResourceGrant(4 接口)
|
||
|
||
### 3.0 公共说明
|
||
|
||
**对象语义**: 把某个 ResourceBinding 授给某个角色 / 子 Agent 使用。
|
||
|
||
**核心约束**:
|
||
- `allowed_actions` 必须是对应 binding 的 `permission_scope` 的**子集**
|
||
- 违反 → **400 `RESOURCE_GRANT_INVALID`**
|
||
- `constraints` 不得放宽对应 binding 的 `constraints`(应用层校验)
|
||
- 仅能基于**自己的** binding 创建 grant
|
||
- 同一类敏感字段拒绝规则适用于 `constraints` / `allowed_actions`
|
||
|
||
**枚举值**:
|
||
|
||
| 字段 | 允许值 |
|
||
|---|---|
|
||
| `status` | `active` / `suspended` / `revoked` / `expired` |
|
||
| `role`(建议值) | `product` / `frontend` / `backend` / `reviewer` / `ops` / 自定义字符串 |
|
||
|
||
---
|
||
|
||
### 3.1 POST /api/resource-grants
|
||
|
||
**用途**: 创建资源授权。
|
||
|
||
**请求**
|
||
|
||
```http
|
||
POST /api/resource-grants
|
||
Authorization: Bearer <accessToken>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
```json
|
||
{
|
||
"resource_id": "88963d56-5a95-4863-8a0b-91a83e456028",
|
||
"binding_scope": "main",
|
||
"role": "backend",
|
||
"agent_id": null,
|
||
"allowed_actions": ["repo:read"],
|
||
"constraints": {
|
||
"ref": "main",
|
||
"allowed_paths": "services/api/**"
|
||
},
|
||
"expires_at": "2026-06-05T00:00:00",
|
||
"status": "active"
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|---|---|---|---|
|
||
| `resource_id` | string (UUID) | 是 | 对应 ResourceBinding ID |
|
||
| `binding_scope` | string (≤255) | 是 | repo/ref/path 或云资源引用 |
|
||
| `role` | string | 否 | 子 Agent 角色 |
|
||
| `agent_id` | string (UUID) | 否 | 子 Agent ID;空表示授下次该角色部署 |
|
||
| `allowed_actions` | string[] | 否 | 必须是 binding.permission_scope 的子集 |
|
||
| `constraints` | object | 否 | 限制条件 |
|
||
| `expires_at` | string (ISO 8601) | 否 | 过期时间;高危资源建议必填 |
|
||
| `status` | string (enum) | 否,默认 `active` | 见 §3.0 |
|
||
|
||
**成功响应 200**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": "1e8d7a4d-3a2c-4d5e-9b1f-12c3d4e5f6a7",
|
||
"user_id": "b00a7b8e-9e8b-463d-9593-a3b4d0006778",
|
||
"binding_scope": "main",
|
||
"resource_id": "88963d56-5a95-4863-8a0b-91a83e456028",
|
||
"role": "backend",
|
||
"agent_id": null,
|
||
"allowed_actions": ["repo:read"],
|
||
"constraints": { "ref": "main", "allowed_paths": "services/api/**" },
|
||
"status": "active",
|
||
"expires_at": "2026-06-05T00:00:00",
|
||
"created_by": "b00a7b8e-9e8b-463d-9593-a3b4d0006778",
|
||
"revoked_by": null,
|
||
"created_at": "2026-05-05T06:15:00.123456",
|
||
"revoked_at": null
|
||
}
|
||
}
|
||
```
|
||
|
||
**错误响应**
|
||
|
||
| HTTP | code | 含义 |
|
||
|---|---|---|
|
||
| 400 | — | resource_id / agent_id 非合法 UUID |
|
||
| 400 | `RESOURCE_GRANT_INVALID` | binding 状态不允许新建授权(如 disabled/revoked),或 `allowed_actions` 超出 binding.permission_scope |
|
||
| 401 | — | 未登录 |
|
||
| 403 | `FORBIDDEN_SCOPE` | 不能基于他人的 binding 创建授权 |
|
||
| 404 | `NOT_FOUND` | 对应 binding 不存在 |
|
||
| 422 | — | status 枚举不合法 |
|
||
| 422 | `RESOURCE_GRANT_SECRET_REJECTED` | constraints/allowed_actions 含敏感字段 |
|
||
|
||
---
|
||
|
||
### 3.2 GET /api/resource-grants
|
||
|
||
**用途**: 列出当前用户的资源授权。
|
||
|
||
**请求**
|
||
|
||
```http
|
||
GET /api/resource-grants?resource_id=...&role=backend&binding_scope=main&status=active&limit=100&offset=0
|
||
Authorization: Bearer <accessToken>
|
||
```
|
||
|
||
| Query | 类型 | 默认 | 说明 |
|
||
|---|---|---|---|
|
||
| `resource_id` | string (UUID) | — | 按 binding 过滤 |
|
||
| `role` | string | — | 按角色过滤 |
|
||
| `binding_scope` | string | — | 按作用域过滤 |
|
||
| `status` | string | — | 按状态过滤 |
|
||
| `limit` | int (1-500) | 100 | 返回上限 |
|
||
| `offset` | int (≥0) | 0 | 偏移 |
|
||
|
||
**成功响应 200**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"items": [ { "id": "...", ... } ],
|
||
"total": 1,
|
||
"offset": 0,
|
||
"limit": 100
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3.3 GET /api/resource-grants/{grant_id}
|
||
|
||
**用途**: 获取单个授权详情。
|
||
|
||
**请求**
|
||
|
||
```http
|
||
GET /api/resource-grants/1e8d7a4d-3a2c-4d5e-9b1f-12c3d4e5f6a7
|
||
Authorization: Bearer <accessToken>
|
||
```
|
||
|
||
**成功响应 200**: 同 §3.1 的 `data` 字段。
|
||
|
||
**错误响应**:
|
||
|
||
| HTTP | code | 含义 |
|
||
|---|---|---|
|
||
| 400 | — | grant_id 非合法 UUID |
|
||
| 401 | — | 未登录 |
|
||
| 403 | `FORBIDDEN_SCOPE` | 不属于当前用户 |
|
||
| 404 | `NOT_FOUND` | 授权不存在 |
|
||
|
||
---
|
||
|
||
### 3.4 DELETE /api/resource-grants/{grant_id}
|
||
|
||
**用途**: 撤销授权(软删除:`status=revoked` + `revoked_at` + `revoked_by`)。**幂等**:重复撤销返回 200。
|
||
|
||
**请求**
|
||
|
||
```http
|
||
DELETE /api/resource-grants/1e8d7a4d-3a2c-4d5e-9b1f-12c3d4e5f6a7
|
||
Authorization: Bearer <accessToken>
|
||
```
|
||
|
||
**成功响应 200**: 同 §3.1 的 `data` 字段(含 revoked_at / revoked_by)。
|
||
|
||
**错误响应**: 同 §3.3。
|
||
|
||
---
|
||
|
||
## 4. 完整使用流程示例
|
||
|
||
### 4.1 客户端启动
|
||
|
||
```
|
||
1. 读取本地缓存的 access token
|
||
2. GET /api/auth/me
|
||
- 200 → 进入主界面,刷新本地用户资料
|
||
- 401 → 看是否有 refresh token
|
||
├─ 有 → POST /api/auth/refresh
|
||
│ ├─ 200 → 替换 token 对,重试 /me
|
||
│ └─ 401 → 清理本地 token,跳登录页
|
||
└─ 无 → 跳登录页
|
||
```
|
||
|
||
### 4.2 用户登录
|
||
|
||
```
|
||
POST /api/auth/login {email, password, role: "user"}
|
||
├─ 200 → 存 token 对 + user 资料,进主界面
|
||
├─ 401 → 显示"邮箱或密码错误"
|
||
├─ 429 → 显示"尝试过于频繁,请 N 秒后再试"(N = Retry-After)
|
||
└─ 其他 → 通用错误提示
|
||
```
|
||
|
||
### 4.3 绑定 Git 资源
|
||
|
||
```
|
||
POST /api/resources
|
||
{
|
||
"type": "git",
|
||
"name": "my-repo",
|
||
"external_ref": "https://example.com/org/my-repo.git",
|
||
"permission_scope": ["repo:read", "repo:write:current-branch"],
|
||
"constraints": { "ref": "main" },
|
||
"secret_ref": "vault://secret/users/{user_id}/bindings/my-repo",
|
||
"status": "active"
|
||
}
|
||
→ 200, data.id = <binding_id>
|
||
```
|
||
|
||
### 4.4 把绑定授给一个 backend 子 Agent 角色
|
||
|
||
```
|
||
POST /api/resource-grants
|
||
{
|
||
"resource_id": "<binding_id>",
|
||
"binding_scope": "main",
|
||
"role": "backend",
|
||
"allowed_actions": ["repo:read"]
|
||
}
|
||
→ 200, data.id = <grant_id>
|
||
```
|
||
|
||
### 4.5 撤销与清理
|
||
|
||
```
|
||
DELETE /api/resource-grants/{grant_id} # 撤销授权
|
||
DELETE /api/resources/{binding_id} # 软删除 binding
|
||
```
|
||
|
||
### 4.6 业务请求中 token 过期
|
||
|
||
```
|
||
任意业务接口返回 401
|
||
└→ POST /api/auth/refresh (用 refresh token)
|
||
├─ 200 → 替换 token,重试原请求
|
||
└─ 401 → 清理 token,跳登录页
|
||
```
|
||
|
||
### 4.7 用户登出
|
||
|
||
```
|
||
POST /api/auth/logout
|
||
→ 不论结果都清理本地 token + 跳登录页
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 错误码索引
|
||
|
||
| code | HTTP | 接口 | 含义 |
|
||
|---|---|---|---|
|
||
| `RESOURCE_GRANT_INVALID` | 400 | resource-grants | binding 状态非法 / `allowed_actions` 超出 binding.permission_scope |
|
||
| `RESOURCE_GRANT_SECRET_REJECTED` | 422 | resources / resource-grants | metadata/constraints/permission_scope/allowed_actions 含敏感字段 |
|
||
| `FORBIDDEN_SCOPE` | 403 | resources / resource-grants | 跨用户访问别人的资源/授权 |
|
||
| `NOT_FOUND` | 404 | resource-grants | 引用的 binding 或 grant 不存在 |
|
||
| —(detail 字符串) | 401 | 全部 | 未登录 / token 失效 / 已登出 |
|
||
| —(detail 字符串) | 403 | 登录 / /me | 账户已禁用 / 权限不足 |
|
||
| —(detail 字符串) | 429 | login | 限流(每 IP 5 次/60s) |
|
||
| —(detail 数组) | 422 | 全部 | Pydantic 校验失败(邮箱格式、枚举值等) |
|
||
|
||
---
|
||
|
||
## 6. 字段速查
|
||
|
||
### 资源类型枚举
|
||
|
||
| `type` | 子 Agent 可见内容 | `permission_scope` 示例 |
|
||
|---|---|---|
|
||
| `git` | repo URL、ref、允许路径、读写范围 | `repo:read`, `repo:write:current-branch` |
|
||
| `sk` | SK 来源、版本、允许/禁止策略 | `sk:read`, `sk:list` |
|
||
| `project_doc` | 文档引用、版本、可读范围 | `doc:read`, `doc:list` |
|
||
| `cloud_account` | account/project/subscription 元数据、允许动作 | `cloud:list`, `cloud:read` |
|
||
| `cloud_resource` | 资源 ID、环境、网络边界、允许动作 | `vm:ssh:approved-window`, `service:restart` |
|
||
|
||
### 状态机
|
||
|
||
**ResourceBinding**:
|
||
```
|
||
pending ─→ active ─┬→ disabled ─→ active ...
|
||
└→ revoked (终态)
|
||
```
|
||
|
||
**ResourceGrant**:
|
||
```
|
||
active ─┬→ suspended ─→ active ...
|
||
├→ revoked (终态,由 DELETE 设置)
|
||
└→ expired (终态,由 expires_at 触发,当前由调用方判断)
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 安全与配额
|
||
|
||
| 项 | 数值/规则 |
|
||
|---|---|
|
||
| 登录限流 | 每 IP 每 60 秒 ≤ 5 次(不区分成功失败) |
|
||
| Access token TTL | 24 小时 |
|
||
| Refresh token TTL | 7 天 |
|
||
| API 列表分页上限 | `limit ≤ 500` |
|
||
| `name` 长度上限 | 255 字符 |
|
||
| `secret_ref` 长度上限 | 500 字符 |
|
||
| 敏感字段拒绝模式 | key 含 `password\|token\|secret\|private_key\|access_key\|credential`(不区分大小写)→ 422 |
|
||
| 例外 key | `secret_ref` 不算敏感(视为引用) |
|
||
|
||
---
|
||
|
||
## 8. 测试账号(仅联调)
|
||
|
||
| 角色 | 邮箱 | 密码 |
|
||
|---|---|---|
|
||
| 普通用户 | `55@55.com` | `By@123456.` |
|
||
|
||
> Heicode 客户端固定使用 `role="user"`。其他角色(channel/admin)由 mcp-server 现有业务使用,不开放给 Heicode。
|
||
|
||
---
|
||
|
||
## 9. 排障
|
||
|
||
如发现接口行为与本文档不一致:
|
||
|
||
1. 客户端**应在所有请求带 `X-Request-Id` 头**(UUID 格式)
|
||
2. 报告内容:
|
||
- 完整请求 URL + Method + Headers + Body
|
||
- 完整响应 HTTP 状态 + Body
|
||
- `X-Request-Id` 值
|
||
- 时间戳(精确到秒)
|
||
3. 后端按 `X-Request-Id` 反查日志和审计
|
||
|
||
---
|
||
|
||
## 4. NewAPI 元数据透传(4 接口)
|
||
|
||
### 4.0 公共说明
|
||
|
||
Heicode 决策 ④(透传方案):mcp-server 包一层 `/api/user/heicode/*`,背后调 NewAPI(`code.xinghanlab.com`)admin token,按当前登录用户的 email 反查 NewAPI 本地 user_id 后透传。
|
||
|
||
**前端只对接 mcp-server 一个域**,不直连 `code.xinghanlab.com`,零 CORS 问题。
|
||
|
||
**当前状态**:代码已上线 + 4 端点全注册;**未配置 admin token 时返回 503 `HEICODE_NEWAPI_NOT_CONFIGURED`**。Heicode 配齐 token 后立即生效,**无需重新构建/部署**。
|
||
|
||
### 4.1 GET /api/user/heicode/balance
|
||
|
||
**响应 200**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"heicodeUserId": 123,
|
||
"email": "user@example.com",
|
||
"username": "user",
|
||
"displayName": "张三",
|
||
"group": "default",
|
||
"status": 1,
|
||
"quota": 100000,
|
||
"usedQuota": 12345,
|
||
"requestCount": 78
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.2 GET /api/user/heicode/models
|
||
|
||
**响应 200**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"heicodeUserId": 123,
|
||
"email": "...",
|
||
"items": [/* NewAPI 全部模型清单(admin 视角)*/],
|
||
"count": 28
|
||
}
|
||
}
|
||
```
|
||
|
||
> 客户端结合 `/balance` 的 `group` 字段做用户可见性过滤。
|
||
|
||
### 4.3 GET /api/user/heicode/usage
|
||
|
||
Query: `days` (1-90,默认 30)
|
||
|
||
**响应 200**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"heicodeUserId": 123,
|
||
"email": "...",
|
||
"days": 30,
|
||
"items": [/* NewAPI 按日 quota 统计 */],
|
||
"count": 30
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.4 GET /api/user/heicode/logs
|
||
|
||
Query: `limit` (1-200,默认 50),`page` (默认 1)
|
||
|
||
**响应 200**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"heicodeUserId": 123,
|
||
"email": "...",
|
||
"page": 1,
|
||
"limit": 50,
|
||
"items": [/* NewAPI 调用日志 */],
|
||
"count": 50
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.x 共通错误码
|
||
|
||
| HTTP | code | 含义 |
|
||
|---|---|---|
|
||
| 401 | — | 未登录 |
|
||
| 404 | `HEICODE_USER_NOT_FOUND` | 在 NewAPI 找不到 email 的用户(用户需先在 Heicode 完成首次登录建账) |
|
||
| 502 | `HEICODE_NEWAPI_UPSTREAM_ERROR` | NewAPI 调用失败 |
|
||
| 503 | `HEICODE_NEWAPI_NOT_CONFIGURED` | mcp-server 未配置 admin token(见 §B-1) |
|
||
|
||
---
|
||
|
||
## 5. Agnet 平台本地 stub(12 接口)
|
||
|
||
### 5.0 公共说明
|
||
|
||
按 Heicode 仓库 `docs/integration/agnet-platform-request-contract.md` 字段形态返回 mock 数据。**用于前端联调**,真实 agent-manager 12 接口落地后切换为出站调用,**字段 / 路径 / 错误码 0 改动**。
|
||
|
||
存储:内存 dict,重启清空(仅联调用)。
|
||
|
||
**通用 Header**:
|
||
| Header | 必填 | 说明 |
|
||
|---|---|---|
|
||
| `Authorization: Bearer <accessToken>` | 是 | 复用登录 token |
|
||
| `X-Correlation-Id` | 建议 | mcp-server 生成;全链路追踪 ID |
|
||
| `X-Request-Id` | 建议 | 单次 HTTP 请求追踪 |
|
||
| `Idempotency-Key` | 创建类建议 | 同 key 返回原响应(避免重复创建) |
|
||
|
||
**通用错误码**:
|
||
|
||
| HTTP | business code |
|
||
|---|---|
|
||
| 400 | `POLICY_REJECTED` (字段缺/非法 / 高危无审批) |
|
||
| 403 | `MODEL_NOT_ALLOWED` / `FORBIDDEN_SCOPE` / `POLICY_REJECTED` (高危) |
|
||
| 404 | `NOT_FOUND` |
|
||
| 409 | `DEPLOYMENT_CONFLICT` |
|
||
| 422 | `RESOURCE_GRANT_INVALID` / `RESOURCE_GRANT_SECRET_REJECTED` / `RESOURCE_GRANT_SECRET_REF_REQUIRED` |
|
||
|
||
### 5.1 POST /api/agnet/deployments
|
||
|
||
**请求体**(详见 heicode 仓库 `agnet-platform-request-contract.md §2`,stub 严格按其字段校验):
|
||
|
||
```json
|
||
{
|
||
"orchestration_plan": {
|
||
"intent_id": "intent_xxx",
|
||
"template_hint": "manager-resource-binding",
|
||
"objective": "...",
|
||
"risk_level": "low",
|
||
"budget": {"max_tokens": 100000, "max_cost_usd": 30, "max_duration_sec": 7200},
|
||
"user_context": {"user_id": "...", "channel_id": "..."},
|
||
"billing_context": {"provider": "newapi"},
|
||
"agent_runtime": {"platform": "agnet", "agents": [...]},
|
||
"metadata": {"correlation_id": "corr_xxx"},
|
||
"agents": [{
|
||
"role_template": "builder",
|
||
"goal": "...",
|
||
"default_model_id": "...",
|
||
"resource_grants": [{
|
||
"grant_id": "...", "resource_id": "...", "resource_type": "git",
|
||
"user_id": "...", "binding_scope": "main",
|
||
"target_role": "builder", "target_agent_ref": "...",
|
||
"permission_scope": ["repo:read"], "status": "active",
|
||
"secret_ref": "vault://...",
|
||
"constraints": {}, "metadata": {}
|
||
}]
|
||
}]
|
||
}
|
||
}
|
||
```
|
||
|
||
**响应 200**:
|
||
```json
|
||
{"success": true, "data": {
|
||
"deployment_id": "dep_xxx",
|
||
"status": "accepted",
|
||
"agent_instances": [{"instance_id": "agi_xxx", "role": "builder", "phase": "pending"}]
|
||
}}
|
||
```
|
||
|
||
**校验规则(stub 已实现)**:
|
||
- `risk_level=high` 必须有 `resource_grants[].constraints.approval_id` 或 `audit.approval_id`
|
||
- `billing_context.provider` ∈ {`newapi`, `litellm`}
|
||
- 凭据型资源(git/sk/cloud_*)必须有 `secret_ref`
|
||
- `metadata` / `constraints` / `audit` key 不得含敏感词(password/token/secret/private_key/access_key/credential)→ 422 `RESOURCE_GRANT_SECRET_REJECTED`
|
||
- `target_role` 必须等于所属 agent 的 `role_template`
|
||
|
||
### 5.2 GET /api/agnet/deployments
|
||
|
||
Query: `user_id`, `binding_scope`, `status`, `limit` (1-500)
|
||
**响应 200**: `{success, data: {items: [...], total: N}}`
|
||
|
||
### 5.3 GET /api/agnet/deployments/{id}
|
||
|
||
**响应 200**: 完整 deployment 详情(含 agent_instances / resource_grants_summary / budget_consumed)
|
||
|
||
### 5.4 POST /api/agnet/deployments/{id}/stop
|
||
|
||
请求体可空。**幂等**:已 stopped 重复调返 200。
|
||
|
||
### 5.5 GET /api/agnet/deployments/{id}/logs
|
||
|
||
Query: `agent_instance_id`, `stream` (stdout/stderr/system/audit), `since`, `limit` (1-1000), `cursor`
|
||
**响应**: `{items: [{log_id, deployment_id, agent_instance_id, stream, level, message, redacted, occurred_at}], next_cursor, total}`
|
||
|
||
### 5.6 GET /api/agnet/deployments/{id}/logs/stream (SSE)
|
||
|
||
返回 `text/event-stream`,事件类型:`log` / `heartbeat` / `done`。
|
||
|
||
### 5.7 GET /api/agnet/projects/{binding_scope}/dashboard-snapshot
|
||
|
||
> 路径名 `projects` 是契约保留旧名,参数实际是 `binding_scope`。
|
||
|
||
**响应**: `{project_id, binding_scope, active_instances, phase_distribution, failure_rate_1h, avg_task_duration, budget, resource_usage, updated_at}`
|
||
|
||
### 5.8 GET /api/agnet/deployments/{id}/metrics
|
||
|
||
Query: `window` (默认 15m), `step` (默认 60s)
|
||
**响应**: `{deployment_id, window, step, series: [{metric, unit, points: [[time, value], ...]}, ...]}`
|
||
|
||
返回 9 个 metric: `tokens_used` / `cost_usd` / `duration_sec` / `cpu_millicores` / `memory_mb` / `restart_count` / `tool_call_count` / `error_count` / `queue_latency_ms`
|
||
|
||
### 5.9 GET /api/agnet/deployments/{id}/events
|
||
|
||
事件名(mock 已 seed): `deployment.accepted` / `instance.phase_changed` / `sk_snapshot_refreshed` / `resource_grant.attached` / `resource_grant.revoked` / `budget.threshold_reached` / `deployment.failed` / `deployment.stopped`
|
||
|
||
**响应**: `{items: [{event_id, event, schema_version, user_id, channel_id, binding_scope, deployment_id, correlation_id, occurred_at}], next_cursor, total}`
|
||
|
||
### 5.10 GET /api/agnet/audit-logs
|
||
|
||
Query: `user_id`, `binding_scope`, `actor`, `action`, `since`, `limit` (1-1000), `cursor`
|
||
|
||
### 5.11 POST /api/agnet/sk-snapshots/resolve
|
||
|
||
**请求体**: `{"deployment_id": "dep_xxx"}`
|
||
**响应**: `{deployment_id, items: [snapshot], total: 1}`
|
||
|
||
snapshot 形态: `{snapshot_id, deployment_id, user_id, binding_scope, source_type, source_ref, artifact_ref, checksum, status, resolved_at}`
|
||
|
||
### 5.12 GET /api/agnet/deployments/{id}/sk-snapshots
|
||
|
||
Query: `source_type`, `limit`, `cursor`
|
||
|
||
---
|
||
|
||
## 6. 任务编排(5 接口) — cc-haha 任务驾驶舱 / 工作台 配套
|
||
|
||
字段形态严格对齐 `cc-haha/desktop/src/stores/heicodeTaskStore.ts`。
|
||
|
||
**对象语义**:用户输入想法(intent)→ Heicode 追问(followups)→ 全部答完 → 生成任务卡(TaskCard)→ 进入"准备资源 / 部署"阶段。
|
||
|
||
**状态机**:
|
||
```
|
||
draft → configuring → running → (后续 awaiting_approval / completed / failed / paused)
|
||
```
|
||
|
||
### 6.1 POST /api/user/tasks/intent
|
||
|
||
**请求体**:
|
||
```json
|
||
{ "intent": "做一个面向小团队的任务协作 SaaS", "name": "可选" }
|
||
```
|
||
|
||
**响应 200** (`HeicodeTask`):
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": "uuid",
|
||
"user_id": "uuid",
|
||
"name": "做一个面向小团队的任务协作 SaaS",
|
||
"status": "configuring",
|
||
"status_caption": "等待你回答几个问题",
|
||
"intent": "做一个面向小团队的任务协作 SaaS",
|
||
"thread": [
|
||
{"kind": "user", "text": "...", "at": 1715168400000},
|
||
{"kind": "heicode", "text": "好的。我先问你两个问题...", "at": 1715168400001,
|
||
"followups": [
|
||
{"id": "scope", "question": "你希望第一阶段交付到什么程度?",
|
||
"options": [
|
||
{"id": "mvp", "label": "MVP:能跑通主流程"},
|
||
{"id": "polish", "label": "完整功能 + UI 细节打磨"},
|
||
{"id": "prod", "label": "直接上生产", "risk": "high-risk"}
|
||
]},
|
||
{"id": "tech", "question": "技术栈倾向?",
|
||
"options": [
|
||
{"id": "modern_web", "label": "现代 Web(React + Node/Python)"},
|
||
{"id": "py_backend", "label": "Python 后端为主"},
|
||
{"id": "let_heicode", "label": "让 Heicode 决定"}
|
||
]}
|
||
]}
|
||
],
|
||
"card": null,
|
||
"created_at": 1715168400000,
|
||
"updated_at": 1715168400000
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.2 GET /api/user/tasks
|
||
|
||
Query: `status`, `limit` (1-200), `offset`
|
||
**响应**: `{items: [HeicodeTask], total, offset, limit}`
|
||
|
||
### 6.3 GET /api/user/tasks/{id}
|
||
|
||
**响应**: 单个 `HeicodeTask`
|
||
**错误**: 404 `NOT_FOUND` / 403 `FORBIDDEN_SCOPE`(跨用户)
|
||
|
||
### 6.4 POST /api/user/tasks/{id}/answer
|
||
|
||
**请求体**: `{"question_id": "scope", "option_id": "mvp"}`
|
||
|
||
**响应 200**:返回更新后的 `HeicodeTask`。
|
||
- 还有未答的 followup → `status` 仍 `configuring`,`card` 仍 null
|
||
- 所有 followup 都答完 → `status` → `running`,**`card` 自动生成**:
|
||
|
||
```json
|
||
{
|
||
"card": {
|
||
"goal": "做一个面向小团队的任务协作 SaaS",
|
||
"scope": ["MVP 范围:核心功能跑通", "技术债务可后续清理", "暂不做高级 UI/性能优化"],
|
||
"generated_artifacts": ["产品说明", "原型描述", "技术方案", "代码骨架(React + Node/Python 后端)"],
|
||
"manager_actions": [
|
||
{"label": "去 Manager 准备资源", "deeplink": "/manager/resources?from=task"},
|
||
{"label": "查看团队建议", "deeplink": "/manager/team?from=task"}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**错误**:
|
||
- 400 `INVALID_OPTION` — option_id 不在该 followup 的 options 列表里
|
||
- 404 `QUESTION_NOT_FOUND` — question_id 不存在
|
||
- 400 `TASK_STATE_INVALID` — task 当前 status 不允许再答
|
||
|
||
### 6.5 POST /api/user/tasks/{id}/messages
|
||
|
||
**请求体**: `{"text": "再补充:希望支持飞书集成"}`
|
||
**响应 200**:thread 追加 user 消息 + heicode ack("收到。我会把这条要求纳入任务上下文。")。MVP 不再触发新 followups。
|
||
|
||
### 6.6 GET /api/user/tasks/{id}/execution(Slice 8 — 执行反馈)
|
||
|
||
**请求**: 仅需 `Authorization: Bearer <jwt>`,无 query。
|
||
**响应 200**:`ExecutionState`(字段对齐 `cc-haha/desktop/src/stores/heicodeTaskStore.ts:ExecutionState`)
|
||
|
||
```jsonc
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"sub_steps": [
|
||
{"id":"step_1","title":"需求分解","status":"done","caption":"已完成","at": 1778237397000}
|
||
// status ∈ done|running|waiting|failed|skipped
|
||
],
|
||
"sk_tool_calls": [
|
||
{"id":"tc_1","name":"read_codebase","status":"done","summary":"...","at": 1778237397200}
|
||
],
|
||
"events": [
|
||
{"id":"ev_1","level":"info","message":"任务启动...","at": 1778237397100}
|
||
// level ∈ info|warn|error
|
||
],
|
||
"artifacts": [
|
||
{"id":"art_1","kind":"doc","label":"产品说明(PRD)","url":"/manager/artifacts/<task_id>/prd.md"}
|
||
// kind ∈ doc|api|diff|report
|
||
],
|
||
"spend_today": "¥12.30" // 可空
|
||
}
|
||
}
|
||
```
|
||
|
||
**生命周期约束**:仅 `task.status ∈ {running, awaiting_approval, completed}` 才有数据;其他状态返回**空 ExecutionState**(5 个字段都是空数组 / spend_today=null)。前端可据此显示"等待启动"占位。
|
||
|
||
**错误**: 404 `NOT_FOUND` / 403 `FORBIDDEN_SCOPE` / 401。
|
||
|
||
### 6.7 GET /api/user/tasks/{id}/delivery(Slice 9 — 交付结果)
|
||
|
||
**响应 200**:`DeliveryResult`
|
||
|
||
```jsonc
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"summary": "任务「...」已交付。包含产品说明、代码骨架、测试报告 3 大类产物...",
|
||
"deliverables": [
|
||
{
|
||
"id":"deliv_1","kind":"product-spec","title":"产品说明 PRD v1",
|
||
"primary_action": {"label":"查看文档","deeplink":"/manager/artifacts/<task_id>/prd.md"},
|
||
"secondary_action": {"label":"...","deeplink":"..."} // 可选
|
||
}
|
||
// kind ∈ product-spec|code-diff|test-env|prod-env
|
||
],
|
||
"quality": [
|
||
{"id":"q_1","label":"单元测试覆盖率","status":"pass","detail":"82%"}
|
||
// status ∈ pass|warn|fail
|
||
],
|
||
"next_actions": [
|
||
{"label":"去 Manager 准备资源","intent":"manager.resources"}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**生命周期约束**:仅 `task.status == "completed"` 时返回完整数据;其他状态所有字段都为空字符串 / 空数组。前端可据此显示"任务尚未完成"占位。
|
||
|
||
**错误**: 404 / 403 / 401。
|
||
|
||
### 6.8 GET /api/user/tasks/{id}/audit(Slice 10 — 任务详情抽屉)
|
||
|
||
**Query**: `tab` 可选,∈ `usage | resources | approvals | security`。**不传**则一次性返回 4 个 tab 全量;**传**则只填该 tab,其他 tab 字段返回空数组(schema 始终稳定)。
|
||
|
||
**响应 200**:`TaskAudit`
|
||
|
||
```jsonc
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"usage": [{"id":"u_1","model":"gpt-4o-mini","input_tokens":12400,"output_tokens":3200,"cost":"¥4.20","at_iso":"2026-05-08T..."}],
|
||
"resources": [{"id":"r_1","resource":"git/heicode-frontend","scope":"read","last_used_iso":"2026-05-08T..."}],
|
||
"approvals": [{"id":"<approval_uuid>","operation":"deploy","target_resource":"prod-cluster","risk_level":"high","decision":"approve","enqueued_iso":"...","resolved_iso":"..."}],
|
||
"security": [{"id":"s_1","event":"task_created","level":"info","at_iso":"..."}]
|
||
}
|
||
}
|
||
```
|
||
|
||
**`approvals` tab 是真表查询**(heicode_approvals where task_id=...,按 enqueued_at DESC,limit 50),其他 3 tab 当前是 deterministic mock,后续可接入真实数据源(usage 接 NewAPI logs / resources 接 resource_grants 使用日志 / security 接 audit_logs)。
|
||
|
||
**错误**:
|
||
- 400 `INVALID_TAB` — tab 不在允许枚举内
|
||
- 404 / 403 / 401
|
||
|
||
---
|
||
|
||
## 7. Server-Sent Events(单通道事件推送)
|
||
|
||
> **状态**:契约定义(mcp-server 端实现待发,预计 Day 2.5 上线 — 见 [`Heicode-对接进度与待办.md §7.11.1`](./Heicode-对接进度与待办.md))。本节字段形态**已冻结**,cc-haha 可立即按此搭 Slice 15 SSE 订阅骨架。
|
||
|
||
### 7.0 公共说明
|
||
|
||
mcp-server 提供**唯一一条 SSE 长连接**给前端订阅所有用户级实时事件(高危审批、任务状态、执行进度、保活)。
|
||
|
||
合并成单通道的好处:
|
||
- 客户端只维护 1 个 EventSource(节省资源)
|
||
- 后端只管 1 个推送泵
|
||
- 不同事件类型按 SSE `event:` 字段路由,互不冲突
|
||
|
||
### 7.1 GET /api/user/events/stream
|
||
|
||
**用途**:当前登录用户的实时事件流。
|
||
|
||
**请求**:
|
||
```http
|
||
GET /api/user/events/stream
|
||
Accept: text/event-stream
|
||
Authorization: Bearer <accessToken>
|
||
```
|
||
|
||
> ⚠️ 浏览器 `EventSource` 不支持自定义 header。前端建议用 `fetch` + `ReadableStream` 手动解析 SSE,或使用支持 header 的 polyfill(如 `eventsource-polyfill`)。
|
||
|
||
**响应**: `200 OK`,`Content-Type: text/event-stream`,长连接。
|
||
|
||
**事件路由**(按 SSE `event:` 字段):
|
||
|
||
| event 类型 | 用途 |
|
||
|---|---|
|
||
| `approval.requested` | 高危审批请求 — 弹审批弹窗 |
|
||
| `approval.resolved` | 审批已被响应(其他设备处理 / 过期)— 关闭对应弹窗 |
|
||
| `task.status_changed` | 任务 status 变化 — 切右侧面板 |
|
||
| `task.execution_progress` | sub_step 状态变化 — 刷新执行反馈面板 |
|
||
| `heartbeat` | 保活(每 25s)|
|
||
|
||
### 7.2 event: approval.requested
|
||
|
||
字段对齐 cc-haha `desktop/src/stores/approvalStore.ts:ApprovalRequest`。
|
||
|
||
**SSE 帧**:
|
||
```text
|
||
event: approval.requested
|
||
data: {"approval_id":"apr_xxx","task_id":"task_xxx","task_name":"…","operation":"在生产环境部署服务","target_resource":"vm://newapi-prod","requesting_role":"operator","risk_level":"high","impact_summary":["重启服务","回滚预案已备好"],"heicode_suggestion":"approve","derives_short_lived_credential":true,"ttl_minutes":60,"enqueued_at":"2026-05-08T10:00:00Z"}
|
||
|
||
```
|
||
|
||
**字段**:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `approval_id` | string | 审批唯一 ID |
|
||
| `task_id` | string | 关联任务 ID(可空,如系统级审批)|
|
||
| `task_name` | string | 任务名(弹窗显示)|
|
||
| `operation` | string | 待批准的操作描述 |
|
||
| `target_resource` | string | 受影响资源(如 `vm://newapi-prod` / `repo://org/foo`)|
|
||
| `requesting_role` | string | 发起请求的子 Agent 角色(builder / operator / reviewer 等)|
|
||
| `risk_level` | enum | `low` / `medium` / `high` |
|
||
| `impact_summary` | string[] | 影响摘要 bullet 列表 |
|
||
| `heicode_suggestion` | enum | Heicode 给出的建议:`approve` / `reject` / `delegate` |
|
||
| `derives_short_lived_credential` | bool | 批准后是否会生成短期凭据注入 Pod |
|
||
| `ttl_minutes` | int | 审批有效时长(minutes),过期自动 expired |
|
||
| `enqueued_at` | string (ISO 8601) | 入队时间 |
|
||
|
||
### 7.3 event: approval.resolved
|
||
|
||
**SSE 帧**:
|
||
```text
|
||
event: approval.resolved
|
||
data: {"approval_id":"apr_xxx","decision":"approve","resolved_by":"user_55","resolved_at":"2026-05-08T10:01:23Z"}
|
||
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `approval_id` | string | 与 `approval.requested` 对应 |
|
||
| `decision` | enum | `approve` / `reject` / `expired` |
|
||
| `resolved_by` | string \| null | 谁做的决定(其他设备同步时使用;`expired` 时为 null)|
|
||
| `resolved_at` | string (ISO 8601) | 决定时间 |
|
||
|
||
客户端收到该事件应**关闭对应 approval_id 的弹窗**(避免多设备登录时弹窗鬼影)。
|
||
|
||
### 7.4 event: task.status_changed
|
||
|
||
**SSE 帧**:
|
||
```text
|
||
event: task.status_changed
|
||
data: {"task_id":"task_xxx","old_status":"running","new_status":"completed","status_caption":"已交付","at":"2026-05-08T10:05:00Z"}
|
||
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `task_id` | string | 任务 ID |
|
||
| `old_status` | string | 之前的 status(参见 §6.1 task.status 枚举)|
|
||
| `new_status` | string | 当前 status |
|
||
| `status_caption` | string \| null | 副标题(如"等待生产部署审批")|
|
||
| `at` | string (ISO 8601) | 变化时间 |
|
||
|
||
客户端应据此切换右侧面板(running/awaiting_approval → ExecutionFeedbackPanel;completed → DeliveryResultPanel)。
|
||
|
||
### 7.5 event: task.execution_progress
|
||
|
||
**SSE 帧**:
|
||
```text
|
||
event: task.execution_progress
|
||
data: {"task_id":"task_xxx","sub_step_id":"step_compile","status":"done","caption":"编译通过","at":"2026-05-08T10:03:12Z"}
|
||
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `task_id` | string | 任务 ID |
|
||
| `sub_step_id` | string | 步骤 ID(与 `/api/user/tasks/{id}/execution` 返回的 `sub_steps[].id` 对应)|
|
||
| `status` | enum | `waiting` / `running` / `done` / `failed` / `skipped` |
|
||
| `caption` | string \| null | 步骤当前描述 |
|
||
| `at` | string (ISO 8601) | 变化时间 |
|
||
|
||
客户端应据此**就地刷新** ExecutionFeedbackPanel 中对应 sub_step 行(不需要重拉整个 execution)。
|
||
|
||
### 7.6 event: heartbeat
|
||
|
||
**SSE 帧**:
|
||
```text
|
||
event: heartbeat
|
||
data: {"server_time":"2026-05-08T10:00:25Z"}
|
||
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `server_time` | string (ISO 8601) | 服务端时间,便于客户端检测时钟漂移 |
|
||
|
||
发送频率:每 **25 秒**一次。客户端 60 秒未收到 heartbeat 应主动重连。
|
||
|
||
### 7.7 配套 REST 端点
|
||
|
||
实时通道之外,前端启动时需要拉一次"当前未处理审批",并提供"用户响应审批"入口:
|
||
|
||
#### 7.7.1 GET /api/user/approvals
|
||
|
||
**用途**:拉取当前用户**待响应**的审批列表(启动时调一次,后续靠 SSE 增量)。
|
||
|
||
**响应 200**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"items": [/* ApprovalRequest 对象数组,字段同 §7.2 */],
|
||
"total": 0
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 7.7.2 POST /api/user/approvals/{approval_id}/decision
|
||
|
||
**用途**:用户在弹窗里选择批准/拒绝。
|
||
|
||
**请求体**:
|
||
```json
|
||
{ "decision": "approve" }
|
||
```
|
||
`decision` ∈ `approve` / `reject`(不能传 `expired`,那是服务端自动设置)。
|
||
|
||
**响应 200**: 返回更新后的 `ApprovalRequest` 对象(含 `decision` / `resolved_by` / `resolved_at`)。
|
||
|
||
mcp-server 此时会**广播** `approval.resolved` 事件到该用户所有 SSE 连接(其他设备的弹窗会自动关闭)。
|
||
|
||
**错误响应**:
|
||
|
||
| HTTP | code | 含义 |
|
||
|---|---|---|
|
||
| 404 | `NOT_FOUND` | approval_id 不存在 |
|
||
| 403 | `FORBIDDEN_SCOPE` | 该审批不属于当前用户 |
|
||
| 410 | `APPROVAL_EXPIRED` | 已超时,决定无效 |
|
||
| 409 | `ALREADY_RESOLVED` | 已被响应(其他设备处理)|
|
||
|
||
### 7.8 错误处理(SSE)
|
||
|
||
| 场景 | mcp-server 行为 | 客户端建议 |
|
||
|---|---|---|
|
||
| 401(token 过期) | 立即关闭流 | 调 `/api/auth/refresh` 后重连 |
|
||
| 503 / 网络抖动 | — | 指数退避重连(1s / 2s / 5s / 10s)|
|
||
| 60s 无 heartbeat | — | 主动重连 |
|
||
|
||
### 7.9 客户端订阅最简实现(参考)
|
||
|
||
```ts
|
||
// cc-haha/desktop/src/lib/heicodeEventStream.ts
|
||
async function subscribe(token: string, handlers: { [evt: string]: (data: any) => void }) {
|
||
const res = await fetch(`${BASE}/api/user/events/stream`, {
|
||
headers: { Authorization: `Bearer ${token}`, Accept: 'text/event-stream' },
|
||
})
|
||
const reader = res.body!.getReader()
|
||
const decoder = new TextDecoder()
|
||
let buf = ''
|
||
while (true) {
|
||
const { value, done } = await reader.read()
|
||
if (done) break
|
||
buf += decoder.decode(value, { stream: true })
|
||
let idx
|
||
while ((idx = buf.indexOf('\n\n')) !== -1) {
|
||
const frame = buf.slice(0, idx); buf = buf.slice(idx + 2)
|
||
const evtLine = frame.split('\n').find(l => l.startsWith('event:'))
|
||
const dataLine = frame.split('\n').find(l => l.startsWith('data:'))
|
||
if (!evtLine || !dataLine) continue
|
||
const event = evtLine.slice(6).trim()
|
||
const data = JSON.parse(dataLine.slice(5).trim())
|
||
handlers[event]?.(data)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 版本历史
|
||
|
||
| 版本 | 日期 | 变更 |
|
||
|---|---|---|
|
||
| v1.0 | 2026-05-05 | 初版:登录 4 接口 + P1 资源模型 9 接口 |
|
||
| v2.0 | 2026-05-08 | + NewAPI 元数据透传 4 接口(§4) + Agnet stub 12 接口(§5) + 任务编排 5 接口(§6)。**总计 34 接口** |
|
||
| v2.1 | 2026-05-08 | + SSE 单通道事件推送契约(§7)+ 配套审批 REST 2 接口(§7.7)。**冻结字段形态供 cc-haha Slice 15 起骨架;mcp-server 实现 Day 2.5 上线** |
|
||
|
||
待开发(参考 [Heicode-对接进度与待办.md](./Heicode-对接进度与待办.md)):
|
||
- 高危审批 SSE 推送(cc-haha 客户端 ApprovalDialog 配套)
|
||
- P2 Secret Broker(等 Vault 部署)
|
||
- P5 出站调用真实 Agnet 平台(等 agent-manager 12 接口)
|
||
|
||
---
|
||
|
||
## B. Heicode 团队待办清单
|
||
|
||
> 按 mcp-server 当前已交付的能力,Heicode 这边需要做以下 5 件事让全链路真跑起来。括号内是优先级与阻塞影响。
|
||
|
||
### B-1 配置 NewAPI service token(🔴 高优 — 阻塞 P4 真跑)
|
||
|
||
**Heicode 端要做**:
|
||
1. 在 NewAPI 创建一个专用账号 `mcp-server-service`,角色 = admin
|
||
2. 生成永久 access token
|
||
3. 把 token + admin user_id 给 mcp-server 团队(**通过安全通道,绝不写 Git/聊天/邮件**)
|
||
|
||
**mcp-server 端要做**(拿到后 ~30 分钟):
|
||
- 通过 K8s secret 注入 env:`HEICODE_NEWAPI_SERVICE_TOKEN` + `HEICODE_NEWAPI_ADMIN_USER_ID`
|
||
- `kubectl rollout restart` 即可生效(**无需重新构建镜像**)
|
||
|
||
### B-2 答复 P4 2 个细节(🟡 中优 — 不阻塞但影响实现细节)
|
||
|
||
详见 [`Heicode-对接进度与待办.md §2.3.2`](./Heicode-对接进度与待办.md)。两个问题:
|
||
- 细节①:mcp-server 调 NewAPI 时 `New-Api-User` 怎么映射到 heicode 本地 user_id?(mcp-server 当前用 admin search by email,工作但有 1 次往返开销)
|
||
- 细节②:子 Agent 部署 `billing_context.provider` 怎么决定?(mcp-server 默认 `litellm`,需 Heicode 用户场景默认 `newapi` 时给规则)
|
||
|
||
### B-3 协调 heicode web 团队对接 P1 资源绑定 UI(🟡 中优)
|
||
|
||
cc-haha 桌面客户端切片 5 已下线 P1 UI(按 Heicode 产品包 08-client-guide.md 边界)。资源绑定 UI 现在归 **heicode web (`code.xinghanlab.com`)**。需要:
|
||
- heicode web 团队接 mcp-server 的 9 个 P1 接口(§2 / §3)
|
||
- 否则用户从客户端任务卡点"去 Manager 准备"会 404
|
||
|
||
### B-4 推动 agent-manager 团队实现 12 真实接口(🟢 中优 — 阻塞 P5 出站联调)
|
||
|
||
agent-manager 12 接口契约见:
|
||
- Heicode 仓库 `docs/integration/agnet-platform-request-contract.md`
|
||
- mcp-server 仓库 `Docs/Agent-Manager-Heicode对接需求文档.md`
|
||
|
||
mcp-server 已上线 stub(§5)字段形态完全一致,agent-manager 实装时可直接对照。
|
||
|
||
**Heicode 这边要给 mcp-server**:
|
||
- agent-manager 团队对接负责人
|
||
- staging 联调环境地址(建议 `staging-agnet.xinghanlab.com`)
|
||
- 服务令牌方案确认(建议 A pre-shared bearer 起步)
|
||
|
||
### B-5 联调测试(🟢 低优 — 可并行)
|
||
|
||
cc-haha 客户端 / heicode web 拿到本文档后即可开始联调:
|
||
- 登录链路(§1)已实测,无需 mcp-server 配合
|
||
- P1 资源(§2 / §3)已实测,无需 mcp-server 配合
|
||
- Agnet stub(§5)已实测,**前端可立即开始任务执行联调**
|
||
- 任务编排(§6)已实测,**cc-haha 可立即把任务驾驶舱 / 工作台从 mock 切真**
|
||
- NewAPI 透传(§4)等 B-1 完成后真跑
|
||
|
||
如发现接口问题,按 §9 排障流程提交。
|
||
|
||
---
|
||
|
||
## C. 总账速查
|
||
|
||
| 模块 | 章节 | 端点数 | 状态 |
|
||
|---|---|---|---|
|
||
| 登录认证 | §1 | 4 | ✅ 上线 + Heicode 已对接 |
|
||
| P1 资源绑定 | §2 | 5 | ✅ 上线 |
|
||
| P1 资源授权 | §3 | 4 | ✅ 上线 |
|
||
| P4 NewAPI 透传 | §4 | 4 | ✅ 上线(待 B-1 token 真跑)|
|
||
| P5 Agnet stub | §5 | 12 | ✅ 上线(mock 数据) |
|
||
| 任务编排 | §6 | 5 | ✅ 上线 |
|
||
| **合计** | — | **34** | **全部上线** |
|