Files
cpa-plugin/docs/modules/api.md
T

438 lines
18 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.
# HTTP API 模块
## 1. 定位
本模块冻结 cpa-ext 的 HTTP 边界:谁可以调用、使用哪些固定路径、请求/响应如何表达、错误是否稳定,以及 UI 如何只通过公开用例访问 Core。
API 不直接暴露 Repository 表,也不允许 UI 自行拼 SQL 语义。每个写接口都对应 [core.md](core.md) 中的应用用例,每个查询都经过权限化 facade。
当前宿主基线为 CLIProxyAPI `v7.2.130`
- 插件 Management routes 位于 `/v0/management/...`,由 CPA Management Key 保护;
- plugin resource 位于 `/v0/resource/plugins/<pluginID>/...`,宿主只支持未鉴权的精确 GET
- 插件声明的路径不能包含 `:`, `*``..`
- Management 和 resource handler 都会在进入插件前被宿主完整读入内存;
- `home.enabled` 时两类插件路由均不可用;
- Management JSON 的所有字符串目前会被宿主 HTML entity 转义,resource JSON 不会。
这些是设计约束,不是实现细节。
## 2. API 表面
### 2.1 管理 API
固定基路径:
```text
/v0/management/plugins/cpa-ext/v1
```
CPA 先执行 Management 鉴权,再把请求交给插件。cpa-ext 不创建第二套管理员密码或 session。第一版 CPA 只有一把 Management Key,因此所有 Management 调用都是全管理员权限,不能在文档中虚构细粒度 RBAC。
### 2.2 Browser resources
固定基路径:
```text
/v0/resource/plugins/cpa-ext
```
用途分为:
- 静态 UI shell 和构建产物;
- 最小公开 readiness
- 由插件自行校验 downstream Key 的用户只读 JSON。
resource route 在宿主层**没有鉴权**。任何包含管理员数据、写操作、OAuth 原文或数据库备份的 handler 都不得注册为 ResourceRoute。
### 2.3 模型调用 API
模型调用仍使用 CPA 原生 Codex/OpenAI Responses 路径。cpa-ext 通过 frontend auth、interceptor、scheduler 和 response/usage callback 参与,不另建代理入口。允许路径见 [access-routing.md](access-routing.md)。
## 3. 通用 wire 约定
### 3.1 JSON
- Content-Type`application/json; charset=utf-8`
- 字段名:`snake_case`
- 未知写入字段默认拒绝;
- 空集合返回 `[]`/`{}`,不使用 `null`
- ID 是 opaque string,调用方不能解析前缀获得权限;
- 时间是 UTC RFC3339Nano
- 时长使用整数毫秒或明确命名的秒数;
- 百分比使用 `0..100` 并明确字段名,fraction 使用 `0..1`
- Token 只出现在管理员审计 DTO;
- secrets、Authorization、Prompt、Response 和 raw Auth JSON 永不进入普通 DTO。
### 3.2 金额
```json
{
"currency": "USD",
"micros": 1234567,
"display": "$1.234567"
}
```
`currency + micros` 是机器真值,`display` 由服务端统一 formatter 生成。用户看到的余额、额度、消费和账单只有金额,不返回 credits/token 余额。管理员可以在账单明细中额外看到 Token、费率和倍率。
### 3.3 成功响应
单对象:
```json
{
"data": {},
"meta": {
"request_id": "req_opaque",
"revision": 12
}
}
```
列表:
```json
{
"data": [],
"meta": {
"request_id": "req_opaque",
"next_cursor": "opaque_cursor",
"has_more": false
}
}
```
创建 Credential/轮换 secret 的第一次成功响应额外包含 `secret`,并强制 `Cache-Control: no-store`。明文只展示一次。
### 3.4 错误响应
```json
{
"error": {
"code": "billing_quota_exhausted",
"message": "Amount balance is exhausted.",
"request_id": "req_opaque",
"retryable": false,
"details": {
"field": "billing_account_id"
}
}
}
```
`message` 可本地化且不得作为程序判断依据;`code` 稳定。`details` 只包含安全字段、约束和资源 ID,不回显 secret 或原始数据库错误。
状态映射:
| HTTP | 语义 |
| ---: | --- |
| 400 | DTO/字段/游标无效 |
| 401 | 用户 Key 无效;当前 CPA frontend auth 也会把认证依赖故障折叠为 401 |
| 403 | 已认证但无权限/入口不允许 |
| 404 | 资源不存在或为防枚举而隐藏 |
| 409 | revision、状态或幂等冲突 |
| 412 | `expected_revision` 不匹配 |
| 413 | 请求体过大;真正上限必须先由反向代理/CPA HTTP 层执行 |
| 422 | 语法正确但领域规则不可满足 |
| 429 | 金额额度、并发或速率限制 |
| 500 | 未分类内部缺陷,响应脱敏 |
| 503 | 数据库、账本、价格或必需 runtime 不健康 |
### 3.5 版本与并发控制
- 路径版本为 `v1`
- 响应头返回 `X-CPA-Ext-API-Version: 1`
- 每个可写对象返回 `revision`
- PATCH/PUT/状态动作必须携带 `expected_revision`
- 版本不匹配返回 `412 revision_mismatch` 和当前 revision
- 不允许 last-write-wins 覆盖套餐、价格、路由和账户状态。
### 3.6 幂等
以下写操作必须携带 `Idempotency-Key`
- provision/签发/轮换 Credential
- 金额 adjustment
- 价格发布/回滚;
- quota 批量刷新;
- backup/rebuild 等 maintenance command。
相同 key + 相同 payload 返回同一业务结果;相同 key + 不同 payload 返回 `409 idempotency_conflict`
Credential 明文不持久化,因此签发/轮换响应重放只保证不会再创建第二个 secret:首次响应有 `secret`,后续幂等重放返回同一 Credential 但 `secret=null``secret_available=false`。如果调用方丢失首次响应,只能执行轮换。
## 4. 管理 API 路由
CPA 只允许精确路径,所以记录 ID 放 query/body,不使用 `/credentials/:id`
### 4.1 系统与诊断
| Method | Path suffix | 用途 |
| --- | --- | --- |
| GET | `/status` | 版本、runtime topology、schema/capabilities、数据库与统计状态 |
| GET | `/readiness` | 计费依赖、HMAC keyring、active price、gateway 前置条件 |
| GET | `/diagnostics` | 脱敏故障、lag、unpriced/unmeasured/persistence gap |
`readiness` 只能证明插件自身依赖。部署网关还必须验证 CPA 中插件 active、Home 关闭、exclusive/sentinel 流量探针成立,不能只信一个自报 JSON。
### 4.2 下游账户、Credential 与套餐
| Method | Path suffix | 用途 |
| --- | --- | --- |
| POST | `/provision` | 原子创建 BillingAccount、绑定 Plan/Route 并签发第一把 Key |
| GET | `/billing-accounts` | 列表或 `?id=` 详情 |
| POST | `/billing-accounts` | 创建空账户 |
| PATCH | `/billing-accounts` | 修改 label/status/并发等安全字段 |
| GET | `/credentials` | 列表或 `?id=` 详情,不返回明文/digest |
| POST | `/credentials` | 为现有账户签发 Key |
| PATCH | `/credentials` | label、有效期、模型/tier 范围 |
| POST | `/credentials/rotate` | 创建新 SecretVersion 并按策略失效旧版本 |
| POST | `/credentials/revoke` | 撤销 Credential/SecretVersion |
| GET | `/plans` | 计划列表或详情 |
| POST | `/plans` | 创建计划 |
| PATCH | `/plans` | 修改计划,必须预览影响 |
| DELETE | `/plans` | retire 未使用计划;历史引用不删除 |
| GET | `/route-policies` | 查询账户/credential 路由策略 |
| PUT | `/route-policies` | 原子替换一条策略及绑定 |
`POST /provision` 是 UI 的主路径,避免业务层出现“账户创建成功但 Key/Plan/Route 只完成一半”。其事务边界以 [core.md](core.md) 为准。
### 4.3 金额账本
| Method | Path suffix | 用途 |
| --- | --- | --- |
| GET | `/ledger` | 按 account、时间、kind 稳定分页 |
| POST | `/ledger/adjustments` | 管理员充值、扣减、refund/纠错 |
| GET | `/billing-cycles` | 周期、额度、消费、余额投影 |
Adjustment 必须包含 `billing_account_id`、Money、kind、reason、`expected_revision``Idempotency-Key`。API 不提供“直接改 balance”端点。
### 4.4 价格
| Method | Path suffix | 用途 |
| --- | --- | --- |
| GET | `/pricing/active` | active version 与模型政策 |
| GET | `/pricing/versions` | 版本历史/详情 |
| GET | `/pricing/candidates` | 候选、来源和冲突 |
| POST | `/pricing/candidates/refresh` | 拉取外部 candidate,不发布 |
| POST | `/pricing/preview` | 校验 draft、diff、golden cases |
| POST | `/pricing/publish` | 显式发布不可变版本 |
| POST | `/pricing/rollback` | 以历史内容创建并发布新版本 |
没有“直接 PUT 当前价格”的捷径。所有变更走 draft/preview/publish,详见 [pricing.md](pricing.md)。
### 4.5 上游账户和池
| Method | Path suffix | 用途 |
| --- | --- | --- |
| GET | `/upstream-accounts` | 目录、状态、priority、bindability |
| POST | `/upstream-accounts/sync` | 触发 host.auth.list reconciliation |
| POST | `/upstream-accounts/reconcile` | 人工确认 Auth replacement/merge 拒绝 |
| GET | `/upstream-quotas` | 最新 quota snapshot |
| POST | `/upstream-quotas/refresh` | 单个/有界批量按需刷新 |
| GET | `/upstream-pools` | 池列表/详情 |
| POST | `/upstream-pools` | 创建池 |
| PATCH | `/upstream-pools` | 修改名称/成员/revision |
| DELETE | `/upstream-pools` | retire 无引用池 |
Quota refresh 请求只接受内部 `upstream_account_id[]`,不接受 URL、Header、token 或任意 provider payload。
### 4.6 统计、请求与审计
| Method | Path suffix | 用途 |
| --- | --- | --- |
| GET | `/statistics/overview` | 金额/请求/健康总览 |
| GET | `/statistics/activity` | 时间序列 |
| GET | `/statistics/analysis` | 维度分析 |
| GET | `/requests` | Request 列表或 `?request_id=` 详情 |
| GET | `/executions` | Execution/重试明细 |
| GET | `/audit-events` | 管理动作与安全事件 |
图表接口返回服务端聚合。请求详情默认不含 Prompt/ResponseCPA request log 访问走单独显式授权、短期 token、审计和脱敏流程。
### 4.7 Maintenance
| Method | Path suffix | 用途 |
| --- | --- | --- |
| GET | `/maintenance/status` | checkpoint、备份、完整性和 runner 状态 |
| POST | `/maintenance/backup` | 创建 SQLite online backup |
| GET | `/maintenance/backups` | 脱敏备份清单,不直接返回任意文件路径 |
| POST | `/maintenance/integrity-check` | 启动有界检查 |
| POST | `/maintenance/rebuild` | 从指定 event_seq 重建投影 |
在线 API 不提供 restore、删除数据库或任意路径下载。恢复属于 [operations.md](operations.md) 的离线、排空流程。
## 5. 用户只读 API
固定 ResourceRoute
| Method | Path | 用途 |
| --- | --- | --- |
| GET | `/v0/resource/plugins/cpa-ext/v1/me` | 当前 Credential、共享金额账户和周期摘要 |
| GET | `/v0/resource/plugins/cpa-ext/v1/me/requests` | 当前 Credential 的逐请求金额记录 |
| GET | `/v0/resource/plugins/cpa-ext/v1/me/activity` | 当前 Credential/账户金额趋势 |
鉴权:
```http
Authorization: Bearer <downstream-key>
```
规则:
- Key 不得放 query、fragment、cookie、URL 或 localStorage
- 插件自行 HMAC lookup,并按 Credential scope 构造查询;
- 共享 BillingAccount 的余额可以显示,但默认请求列表只显示当前 Credential 产生的记录;
- 请求参数中的 account/credential ID 不能扩大 scope
- 响应 `Cache-Control: no-store``Pragma: no-cache``Vary: Authorization`
- 错误不区分不存在、撤销、过期等可枚举细节;
- 用户接口只返回 Money、模型展示名、时间和安全状态,不返回 Token、上游账户、价格规则或管理员诊断。
当前 resource 只有 GET,且没有 HttpOnly session/CSRF 模型。需要用户写操作、稳定登录或团队账号时必须增加 sidecar/public API 或扩展 CPA authenticated user plugin routes。
## 6. UI resources
至少注册:
```text
GET /v0/resource/plugins/cpa-ext/ui
GET /v0/resource/plugins/cpa-ext/assets/<每个确定文件名>
GET /v0/resource/plugins/cpa-ext/ready
```
宿主不支持 wildcard,因此每个构建产物在 `management.register` 中逐个声明。前端使用相对 base、HashRouter、不注册 service worker。
`/ready` 只返回:
```json
{"ready":true,"version":"0.1.0"}
```
它不包含数据库路径、price 内容、账户数、错误详情或 secret。外部 gateway 可以把 404/非 200 视为插件缺失,但仍需执行部署级旁路探针。
## 7. 查询、分页和导出
- 默认 `limit=50`,最大 `200`
- 使用 `(occurred_at, stable_id)` 编码的 opaque cursor,不使用大 offset
- cursor 绑定 endpoint、scope、排序和过滤条件,修改条件后 cursor 无效;
- 默认时间范围 24 小时,普通交互最大 90 天;
- 过滤字段使用 allowlist,未知字段返回 400
- 排序字段固定,禁止任意 SQL column;
- 大导出使用受控异步任务/sidecar,不能在动态库 handler 中一次加载全表;
- CSV 需要防 `=`, `+`, `-`, `@` 公式注入,并记录导出审计。
## 8. 输入与资源限制
插件 handler 内执行:
- 方法和精确路径二次校验;
- Content-Type 校验;
- strict JSON + 单文档 EOF
- 字段长度、集合数量、时间范围和分页上限;
- 响应行数/字节预算;
- context cancellation
- 不持锁执行 SQL、host callback 或网络。
但 CPA 当前在调用 handler 前执行 `io.ReadAll`,所以这些校验无法保护宿主免受超大 body。生产反向代理/CPA HTTP 层必须先限制:
- Management body 建议不超过 1 MiB
- Resource GET 不接收 body
- Header/query 总大小由入口代理限制;
- 请求读取超时和并发连接数在入口层限制。
## 9. 当前 CPA Management JSON 转义限制
`internal/pluginhost/management.go` 会对看起来像 JSON 的响应递归执行 `html.EscapeString`。因此 `A & B <x>` 在 wire 上会变成 HTML entity;这不是标准 JSON API 应有的语义。
过渡规则:
- cpa-ext Management JSON 响应头标记 `X-CPA-Ext-String-Encoding: html-entity-v1`
- 内置 UI 对**响应字符串**最多兼容 decode 一次,再通过 React text node 渲染;
- 禁止 `dangerouslySetInnerHTML`
- IDs、error codes、cursor 只使用不受影响的安全字符;
- 写 DTO 不能盲目回传未 decode 的读 DTO
- 测试覆盖 `A & B <x>` 往返和已含 entity 文本;
- 在 CPA 停止改写 JSON 或 API 移到 sidecar 前,不将 Management API 宣称为无损的通用第三方 API。
Resource JSON 不经过该 escape 路径,仍然必须使用普通 JSON encoder 和文本渲染防 XSS。
## 10. 安全头与浏览器策略
HTML
- `Content-Security-Policy`,默认 `default-src 'self'`,按实际构建最小放开;
- `X-Content-Type-Options: nosniff`
- `Referrer-Policy: no-referrer`
- `Cache-Control: no-store`HTML shell);
- 不使用 inline script,或固定构建 hash
- 不信任 iframe parent 消息,除非校验明确 origin。
敏感 JSON
- `Cache-Control: no-store`
- 默认不启用 CORS
- 不在错误响应反射 Origin/Header/Body
- 不把 Management Key 或 downstream Key 写入前端持久存储。
完整要求见 [security.md](security.md)。
## 11. 审计
以下写操作必须形成审计事件:
- provision、签发、轮换、撤销;
- 账户/套餐/路由/池修改;
- 金额 adjustment/refund
- 价格发布/回滚;
- 上游 Auth reconciliation
- quota reset credits 等有副作用 provider 操作;
- backup、rebuild、导出;
- 安全设置和热配置拒绝。
CPA 当前只有共享 Management KeyActor 至少记录 management principal fingerprint、来源 IP 的受信代理结果、request ID、reason 和变更前后 revision。调用方自报 `actor` 只能作为 label,除非受信 gateway 已验证并覆盖该 header。
## 12. 如何参考现有项目
### 12.1 CLIProxyAPI
- `sdk/pluginapi/types.go`ManagementRequest/Response、Route/Resource
- `internal/pluginhost/management.go`:精确路径、未鉴权 resource、body ReadAll、JSON escape
- `internal/api/server_management.go`Management middleware、Home 限制和 route mounting
- `examples/plugin/management-api/`ABI/RPC 形状。
### 12.2 `cpa-plugin-key-billing`
- `internal/plugin/management.go`:精确 route 表、query/body 传 ID、严格 DTO
- `internal/plugin/admin.go`:价格、计划、Key 操作用例;
- `internal/plugin/ui.html`Management 页面调用方式。
吸收其固定路径和 handler 分派;不要继承错误响应泄露内部文本、浏览器持久化 Management Key 或 UI 直接承担业务原子性的做法。
### 12.3 `cpa-usage-keeper`
- `internal/api/router.go`:管理员/Key viewer 分离、no-store 与静态资源;
- `internal/api/usage_*`:分页、范围和统计投影;
- `internal/api/pricing*.go``quota.go`:领域 API
- `internal/api/request_limits.go``errors.go`:入口限制和脱敏错误;
- API security testssession、rate limit、secret redaction、CSP、request log token。
Keeper 是独立 HTTP 服务,Gin wildcard、middleware session 和后台任务接口不能假设在 CPA plugin route 中同样可用。
## 13. 验收标准
- 所有注册 route 都是宿主允许的精确路径且无冲突;
- Management 数据必须通过 CPA Management 鉴权;
- resource 静态页不含敏感数据,用户 JSON 独立校验 downstream Key
- 用户不能通过 ID/query 越权读取其他 Credential/账户;
- 所有金额 DTO 使用 currency + micros + display
- 用户 DTO 不出现 Token、价格规则和上游账户;
- 写接口 strict decode、revision check、审计和必要幂等全部生效;
- secret 只在首次签发/轮换响应显示,重放不创建第二把 Key;
- 超大 body 在反向代理层被拒绝,不能依赖插件收到后再拒绝;
- Management JSON entity 转义兼容测试通过,且限制被明确标记;
- 列表 cursor 在并发插入下无重复/漏页;
- panic/DB 失败返回脱敏 5xx,不泄露 SQL、路径或 secret
- Home 模式返回明确不支持,UI 不显示空数据假象;
- API、Core、UI 的字段和错误码保持同一份实现定义。