Skip to content

Commit 7653fd3

Browse files
committed
feat(m3): 执行引擎统计埋点、Docker、CI、文档
补齐真实缺陷:执行引擎的 docstring 声明了统计但从未采集, 端到端验证时发现失败的聊天请求在统计里完全不出现。 统计埋点: - stream/complete 两条路径记录成功与失败,按 username 归属 - 上游 usage 缺失时字段为 None(不写 0);StreamTranslator 缓存 usage - 轮换耗尽后仍归到实际尝试过的 provider,而非占位符 - 统计写入失败只记日志,绝不影响聊天响应 部署与工程化: - Dockerfile:多阶段(Node 构建前端 + Python 运行时),uv 锁定依赖,非 root 运行 - docker-compose.yml:APP_SECRET 必填校验、数据与用户文件分别挂载 - scripts/hash_password.py:原子写回、0600 权限、区分「未提供密码」与「空密码」 - .github/workflows/ci.yml:后端(ruff + 100% 覆盖率门槛)、前端(tsc + vitest + build)、 compose 真实构建启动并健康检查 - README.md 中文完整文档 + README.en.md 英文 端到端验证(真实启动服务): - /health、SPA 首页、未登录 401、登录发 Cookie、凭证导入、凭证列表不泄漏密文 - API Key 创建、/v1/models(33 个模型)、错误 Key 401 - 假 token 聊天 → 401 上游 → 会话失效硬禁用 + 503 + 统计落库 - 统计按 provider 正确归属 后端 510 测试 / 100% 覆盖;前端 67 测试 / tsc 零错误。
1 parent bdbe5ca commit 7653fd3

12 files changed

Lines changed: 834 additions & 19 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
backend:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: astral-sh/setup-uv@v5
14+
- run: uv sync --frozen
15+
- name: Ruff
16+
run: uv run ruff check src tests scripts
17+
- name: 测试与覆盖率(门槛 100%)
18+
run: uv run pytest -q --cov=src --cov-report=term --cov-fail-under=100
19+
20+
frontend:
21+
runs-on: ubuntu-latest
22+
defaults:
23+
run:
24+
working-directory: web
25+
steps:
26+
- uses: actions/checkout@v4
27+
- uses: pnpm/action-setup@v4
28+
with:
29+
version: 10
30+
- uses: actions/setup-node@v4
31+
with:
32+
node-version: 24
33+
cache: pnpm
34+
cache-dependency-path: web/pnpm-lock.yaml
35+
- run: pnpm install --frozen-lockfile
36+
- run: pnpm exec tsc --noEmit
37+
- run: pnpm exec vitest run
38+
- run: pnpm build
39+
40+
compose:
41+
runs-on: ubuntu-latest
42+
steps:
43+
- uses: actions/checkout@v4
44+
- name: 校验 compose 语法
45+
run: docker compose config --quiet
46+
env:
47+
APP_SECRET: ci-placeholder-secret
48+
- name: 构建镜像并启动(验证 compose 可真实运行)
49+
run: |
50+
mkdir -p secrets
51+
docker build -t coding2api:ci .
52+
printf 'ci:%s\n' "$(docker run --rm coding2api:ci python -c 'from src.auth.users import create_password_hash;print(create_password_hash("cipw"))')" > secrets/users.txt
53+
APP_SECRET=ci-placeholder-secret docker compose up -d
54+
for _ in $(seq 1 30); do
55+
if curl -fsS http://127.0.0.1:8000/health >/dev/null; then break; fi
56+
sleep 2
57+
done
58+
curl -fsS http://127.0.0.1:8000/health
59+
docker compose down

‎Dockerfile‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# 多阶段构建:前端产物 + Python 运行时
2+
FROM node:24-alpine AS web
3+
WORKDIR /web
4+
COPY web/package.json web/pnpm-lock.yaml ./
5+
RUN corepack enable && pnpm install --frozen-lockfile
6+
COPY web/ ./
7+
RUN pnpm exec vite build
8+
9+
FROM python:3.12-slim AS runtime
10+
11+
ENV PYTHONUNBUFFERED=1 \
12+
PYTHONDONTWRITEBYTECODE=1 \
13+
UV_PROJECT_ENVIRONMENT=/app/.venv \
14+
PATH="/app/.venv/bin:$PATH"
15+
16+
# uv:从官方镜像复制,避免联网安装脚本
17+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
18+
19+
WORKDIR /app
20+
21+
COPY pyproject.toml uv.lock README.md ./
22+
RUN uv sync --frozen --no-dev --no-install-project
23+
24+
COPY src/ ./src/
25+
COPY scripts/ ./scripts/
26+
COPY web/dist/ ./web/dist/
27+
RUN uv sync --frozen --no-dev
28+
29+
# 运行数据与用户文件由挂载提供;非 root 运行
30+
RUN useradd --create-home --uid 1001 appuser \
31+
&& mkdir -p /app/data /app/secrets \
32+
&& chown -R appuser:appuser /app/data /app/secrets
33+
USER appuser
34+
35+
ENV DATA_DIR=/app/data \
36+
USERS_FILE=/app/secrets/users.txt \
37+
HOST=0.0.0.0 \
38+
PORT=8000
39+
40+
EXPOSE 8000
41+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
42+
CMD python -c "import urllib.request;urllib.request.urlopen('http://127.0.0.1:8000/health')"
43+
44+
CMD ["python", "-m", "uvicorn", "src.main:build_app", "--factory", "--host", "0.0.0.0", "--port", "8000"]

‎README.en.md‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# coding2api
2+
3+
Unified OpenAI-compatible gateway for the **CodeBuddy** and **TRAE SOLO** coding-agent
4+
upstream channels, with a shared credential pool, unified scheduling, and per-user usage stats.
5+
6+
> [!WARNING]
7+
> This project bridges reverse-engineered third-party endpoints and is intended for study
8+
> and research only. It has not been security-audited. Do not expose it to the public
9+
> internet without a reverse proxy, authentication, and an IP allowlist.
10+
11+
## Features
12+
13+
- **OpenAI-compatible surface**: `/v1/chat/completions` (streaming and non-streaming), `/v1/models`
14+
- **Two upstreams, one model namespace**: flat model names auto-route by health; `model@provider` pins an upstream
15+
- **Three-state health + tiered cooldowns**: quota exhausted 12h, rate limited 60s, consecutive errors 10m, dead session disabled
16+
- **Shared credential pool**: admins maintain credentials, everyone shares them; usage is tracked per user
17+
- **Encrypted credentials at rest**: Fernet (AES-128-CBC + HMAC), key from `APP_SECRET`
18+
- **Privacy-preserving stats**: never stores prompts, completions, headers, tokens, or tool arguments; 90-day detail, permanent hourly rollups
19+
20+
## Quick start
21+
22+
```bash
23+
uv sync
24+
uv run python scripts/hash_password.py admin # prompts for a password
25+
cd web && pnpm install && pnpm build && cd ..
26+
27+
APP_SECRET="pick-a-random-string" ADMIN_USERNAMES=admin \
28+
uv run python -m uvicorn src.main:build_app --factory --port 8000
29+
```
30+
31+
Open <http://127.0.0.1:8000>, sign in, add credentials, create an API key, then:
32+
33+
```bash
34+
curl http://127.0.0.1:8000/v1/chat/completions \
35+
-H "Authorization: Bearer sk-your-key" \
36+
-H "Content-Type: application/json" \
37+
-d '{"model":"glm-5.2","messages":[{"role":"user","content":"hello"}]}'
38+
```
39+
40+
Point any OpenAI-compatible client at `http://127.0.0.1:8000/v1`.
41+
42+
## Documentation
43+
44+
Detailed documentation is written in Chinese:
45+
46+
| Document | Content |
47+
|---|---|
48+
| [`README.md`](README.md) | Full setup, usage, and configuration guide |
49+
| [`PROPOSAL.md`](PROPOSAL.md) | Design decisions, scope, feasibility findings, risks |
50+
| [`TECHNICAL.md`](TECHNICAL.md) | Stack, module specs, provider protocol, request flow, testing |
51+
| [`diagrams/coding2api-architecture.html`](diagrams/coding2api-architecture.html) | Architecture diagram |
52+
53+
## License
54+
55+
MIT — see [LICENSE](LICENSE). Attribution for the upstream projects this work learned from is in [NOTICE](NOTICE).

‎README.md‎

Lines changed: 133 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,144 @@
33
把 **CodeBuddy** 与 **TRAE SOLO** 两个 coding agent 上游通道,统一封装为 OpenAI 兼容 API,
44
并提供公共凭证池、统一调度与按人用量统计。
55

6-
## 状态
6+
> [!WARNING]
7+
> 本项目接通的是第三方服务的逆向接口,仅供学习研究。未做安全审计,不建议直接暴露在公网;
8+
> 如确需公网访问,请置于反向代理之后并加鉴权与 IP 白名单。
9+
10+
## 特性
11+
12+
- **OpenAI 兼容出口**:`/v1/chat/completions`(流式 + 非流式)、`/v1/models`
13+
- **双上游统一调度**:同一扁平模型名可按健康度自动选号,`model@provider` 可强制指定
14+
- **三态健康度 + 三级冷却**:权益耗尽 12h / 限流 60s / 连续错误 10m / 会话失效硬禁用
15+
- **公共凭证池**:管理员集中维护,全员共享;按人统计用量
16+
- **凭证加密入库**:Fernet(AES-128-CBC + HMAC),密钥走 `APP_SECRET`
17+
- **脱敏统计**:不保存提示词、回答、请求头、Token、工具参数;明细 90 天、小时汇总永久
18+
- **完整凭证运维**:OAuth 设备码登录、多账号切换、额度探测、每日签到、token 预刷新
19+
20+
## 快速开始
21+
22+
### 前置要求
23+
24+
- Python 3.12+
25+
- [uv](https://docs.astral.sh/uv/)
26+
- Node.js 24+ 与 pnpm 10+(仅构建前端需要)
27+
28+
### 本地运行
29+
30+
```bash
31+
# 1. 安装后端依赖
32+
uv sync
33+
34+
# 2. 创建管理台用户(交互输入密码)
35+
uv run python scripts/hash_password.py admin
36+
37+
# 3. 构建前端
38+
cd web && pnpm install && pnpm build && cd ..
39+
40+
# 4. 启动
41+
APP_SECRET="换成你自己的随机字符串" ADMIN_USERNAMES=admin uv run python -m uvicorn src.main:build_app --factory --port 8000
42+
```
43+
44+
打开 <http://127.0.0.1:8000> 登录管理台。
45+
46+
### Docker
47+
48+
```bash
49+
cat > .env <<'EOF'
50+
APP_SECRET=换成你自己的随机字符串
51+
ADMIN_USERNAMES=admin
52+
EOF
53+
54+
mkdir -p secrets
55+
docker run --rm -it -v "$PWD/secrets:/app/secrets" \
56+
--entrypoint python coding2api:local scripts/hash_password.py admin
57+
58+
docker compose up -d
59+
```
60+
61+
`APP_SECRET` 丢失会导致已存凭证全部无法解密,只能重新录入——请备份。
62+
63+
## 使用
64+
65+
### 1. 添加凭证
66+
67+
管理台「凭证管理」页:
68+
69+
- **CodeBuddy**:点「登录 CodeBuddy」走设备码授权;也可手动粘贴 `{"token":"..."}`
70+
- **TRAE**:粘贴凭证 JSON(`accessToken` / `uid` / `refreshToken`)或回调链接
771

8-
早期开发中(M0 骨架)。当前进度见 `PROPOSAL.md` §9 里程碑。
72+
### 2. 创建 API Key
73+
74+
「API Key」页创建,明文只显示一次。
75+
76+
### 3. 调用
77+
78+
```bash
79+
curl http://127.0.0.1:8000/v1/chat/completions \
80+
-H "Authorization: Bearer sk-你的key" \
81+
-H "Content-Type: application/json" \
82+
-d '{"model":"glm-5.2","messages":[{"role":"user","content":"你好"}]}'
83+
```
84+
85+
强制走某个上游:
86+
87+
```bash
88+
-d '{"model":"glm-5.2@trae", ...}' # 只走 TRAE
89+
-d '{"model":"glm-5.2@codebuddy", ...}' # 只走 CodeBuddy
90+
```
91+
92+
### 4. 在客户端中使用
93+
94+
任何支持自定义 OpenAI 端点的客户端都可接入:
95+
96+
- Base URL:`http://127.0.0.1:8000/v1`
97+
- API Key:上一步创建的 `sk-...`
98+
- 模型名:以 `GET /v1/models` 返回为准
99+
100+
## 配置
101+
102+
全部通过环境变量(见 `docker-compose.yml`)。完整清单参考 `TECHNICAL.md` §8。
103+
104+
| 变量 | 默认 | 说明 |
105+
|---|---|---|
106+
| `APP_SECRET` | **必填** | 凭证列加密密钥;丢失等于凭证全部作废 |
107+
| `ADMIN_USERNAMES` | 空 | 逗号分隔;空则所有用户只读 |
108+
| `USERS_FILE` | `secrets/users.txt` | 用户文件路径 |
109+
| `DATA_DIR` | `./data` | SQLite 与运行数据目录 |
110+
| `PUBLIC_BASE_URL` | `http://127.0.0.1:8000` | 浏览器可达地址;TRAE 登录回调依赖它 |
111+
| `CODEBUDDY_API_ENDPOINT` | 中国站 | 上游地址,只接受白名单内地址 |
112+
| `DEFAULT_MODEL` | `glm-5.2` | 模型为空或 `auto` 时的目标 |
113+
| `CHECKIN_HOUR` | `9` | 每日签到时刻(服务器本地时区) |
114+
| `QUOTA_PROBE_MINUTES` | `60` | 额度探测周期 |
115+
116+
## 开发
117+
118+
```bash
119+
# 后端测试(行/分支覆盖门槛 100%)
120+
uv run pytest -q --cov=src --cov-report=term --cov-fail-under=100
121+
122+
# 代码检查
123+
uv run ruff check src tests scripts
124+
125+
# 前端
126+
cd web
127+
pnpm exec tsc --noEmit
128+
pnpm exec vitest run
129+
pnpm build
130+
```
131+
132+
## 文档
9133

10134
| 文档 | 内容 |
11135
|---|---|
12-
| [`PROPOSAL.md`](PROPOSAL.md) | 立项决策(Q1–Q30)、可行性核实、风险清单 |
136+
| [`PROPOSAL.md`](PROPOSAL.md) | 立项决策、目标与非目标、可行性核实、风险清单 |
13137
| [`TECHNICAL.md`](TECHNICAL.md) | 技术栈、模块规格、Provider 协议、请求时序、测试策略 |
14-
| [`diagrams/coding2api-architecture.html`](diagrams/coding2api-architecture.html) | 系统架构图(浏览器直接打开) |
138+
| [`diagrams/coding2api-architecture.html`](diagrams/coding2api-architecture.html) | 系统架构图(浏览器打开) |
15139

16-
## 开源协议
140+
## 状态
17141

18-
MIT,见 [LICENSE](LICENSE)。借鉴的上游项目署名见 [NOTICE](NOTICE)。
142+
后端完成(M0–M1.5),前端完成(M2)。当前 `main` 分支可运行。
19143

20-
> 本仓库接通的是第三方服务的逆向接口,仅供学习研究,请勿用于生产用途。
144+
## 授权协议
145+
146+
MIT,见 [LICENSE](LICENSE)。借鉴的上游项目署名见 [NOTICE](NOTICE)。

‎docker-compose.yml‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
services:
2+
coding2api:
3+
build: .
4+
image: ghcr.io/robbsluo/coding2api:latest
5+
container_name: coding2api
6+
restart: unless-stopped
7+
environment:
8+
# 必填:凭证列加密密钥。丢失后已存凭证全部不可解,只能重录。
9+
APP_SECRET: ${APP_SECRET:?set APP_SECRET in .env}
10+
# 管理台管理员用户名(逗号分隔);留空则所有用户只读
11+
ADMIN_USERNAMES: ${ADMIN_USERNAMES:-admin}
12+
# 远程部署时必须设为浏览器可达的地址,TRAE 登录回调依赖它
13+
PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-http://127.0.0.1:8000}
14+
CODEBUDDY_API_ENDPOINT: ${CODEBUDDY_API_ENDPOINT:-https://copilot.tencent.com}
15+
DEFAULT_MODEL: ${DEFAULT_MODEL:-glm-5.2}
16+
CHECKIN_HOUR: ${CHECKIN_HOUR:-9}
17+
QUOTA_PROBE_MINUTES: ${QUOTA_PROBE_MINUTES:-60}
18+
LOG_LEVEL: ${LOG_LEVEL:-INFO}
19+
ports:
20+
- "${PORT:-8000}:8000"
21+
volumes:
22+
# 凭证与统计(SQLite)与用户文件分开挂载
23+
- ./data:/app/data
24+
- ./secrets:/app/secrets:ro
25+
healthcheck:
26+
test: ["CMD", "python", "-c", "import urllib.request;urllib.request.urlopen('http://127.0.0.1:8000/health')"]
27+
interval: 30s
28+
timeout: 5s
29+
retries: 3

0 commit comments

Comments
 (0)